3DMLoader.js 39 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835
  1. import {
  2. BufferGeometryLoader,
  3. CanvasTexture,
  4. ClampToEdgeWrapping,
  5. Color,
  6. DirectionalLight,
  7. DoubleSide,
  8. FileLoader,
  9. LinearFilter,
  10. Line,
  11. LineBasicMaterial,
  12. Loader,
  13. Matrix4,
  14. Mesh,
  15. MeshPhysicalMaterial,
  16. MeshStandardMaterial,
  17. Object3D,
  18. PointLight,
  19. Points,
  20. PointsMaterial,
  21. RectAreaLight,
  22. RepeatWrapping,
  23. SpotLight,
  24. Sprite,
  25. SpriteMaterial,
  26. TextureLoader
  27. } from 'three';
  28. import { EXRLoader } from '../loaders/EXRLoader.js';
  29. const _taskCache = new WeakMap();
  30. /**
  31. * A loader for Rhinoceros 3D files and objects.
  32. *
  33. * Rhinoceros is a 3D modeler used to create, edit, analyze, document, render,
  34. * animate, and translate NURBS curves, surfaces, breps, extrusions, point clouds,
  35. * as well as polygon meshes and SubD objects. `rhino3dm.js` is compiled to WebAssembly
  36. * from the open source geometry library `openNURBS`. The loader currently uses
  37. * `rhino3dm.js 8.4.0`.
  38. *
  39. * ```js
  40. * const loader = new Rhino3dmLoader();
  41. * loader.setLibraryPath( 'https://cdn.jsdelivr.net/npm/rhino3dm@8.0.1' );
  42. *
  43. * const object = await loader.loadAsync( 'models/3dm/Rhino_Logo.3dm' );
  44. * scene.add( object );
  45. * ```
  46. *
  47. * @augments Loader
  48. * @three_import import { Rhino3dmLoader } from 'three/addons/loaders/3DMLoader.js';
  49. */
  50. class Rhino3dmLoader extends Loader {
  51. /**
  52. * Constructs a new Rhino 3DM loader.
  53. *
  54. * @param {LoadingManager} [manager] - The loading manager.
  55. */
  56. constructor( manager ) {
  57. super( manager );
  58. // internals
  59. this.libraryPath = '';
  60. this.libraryPending = null;
  61. this.libraryBinary = null;
  62. this.libraryConfig = {};
  63. this.url = '';
  64. this.workerLimit = 4;
  65. this.workerPool = [];
  66. this.workerNextTaskID = 1;
  67. this.workerSourceURL = '';
  68. this.workerConfig = {};
  69. this.materials = [];
  70. this.warnings = [];
  71. }
  72. /**
  73. * Path to a folder containing the JS and WASM libraries.
  74. *
  75. * @param {string} path - The library path to set.
  76. * @return {Rhino3dmLoader} A reference to this loader.
  77. */
  78. setLibraryPath( path ) {
  79. this.libraryPath = path;
  80. return this;
  81. }
  82. /**
  83. * Sets the maximum number of Web Workers to be used during decoding.
  84. * A lower limit may be preferable if workers are also for other
  85. * tasks in the application.
  86. *
  87. * @param {number} workerLimit - The worker limit.
  88. * @return {Rhino3dmLoader} A reference to this loader.
  89. */
  90. setWorkerLimit( workerLimit ) {
  91. this.workerLimit = workerLimit;
  92. return this;
  93. }
  94. /**
  95. * Starts loading from the given URL and passes the loaded 3DM asset
  96. * to the `onLoad()` callback.
  97. *
  98. * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI.
  99. * @param {function(Object3D)} onLoad - Executed when the loading process has been finished.
  100. * @param {onProgressCallback} onProgress - Executed while the loading is in progress.
  101. * @param {onErrorCallback} onError - Executed when errors occur.
  102. */
  103. load( url, onLoad, onProgress, onError ) {
  104. const loader = new FileLoader( this.manager );
  105. loader.setPath( this.path );
  106. loader.setResponseType( 'arraybuffer' );
  107. loader.setRequestHeader( this.requestHeader );
  108. this.url = url;
  109. loader.load( url, ( buffer ) => {
  110. // Check for an existing task using this buffer. A transferred buffer cannot be transferred
  111. // again from this thread.
  112. if ( _taskCache.has( buffer ) ) {
  113. const cachedTask = _taskCache.get( buffer );
  114. return cachedTask.promise.then( onLoad ).catch( onError );
  115. }
  116. this.decodeObjects( buffer, url )
  117. .then( result => {
  118. result.userData.warnings = this.warnings;
  119. onLoad( result );
  120. } )
  121. .catch( e => onError( e ) );
  122. }, onProgress, onError );
  123. }
  124. /**
  125. * Prints debug messages to the browser console.
  126. */
  127. debug() {
  128. console.log( 'Task load: ', this.workerPool.map( ( worker ) => worker._taskLoad ) );
  129. }
  130. /**
  131. * Decodes the 3DM asset data with a Web Worker.
  132. *
  133. * @param {ArrayBuffer} buffer - The raw 3DM asset data as an array buffer.
  134. * @param {string} url - The asset URL.
  135. * @return {Promise<Object3D>} A Promise that resolved with the decoded 3D object.
  136. */
  137. decodeObjects( buffer, url ) {
  138. let worker;
  139. let taskID;
  140. const taskCost = buffer.byteLength;
  141. const objectPending = this._getWorker( taskCost )
  142. .then( ( _worker ) => {
  143. worker = _worker;
  144. taskID = this.workerNextTaskID ++;
  145. return new Promise( ( resolve, reject ) => {
  146. worker._callbacks[ taskID ] = { resolve, reject };
  147. worker.postMessage( { type: 'decode', id: taskID, buffer }, [ buffer ] );
  148. // this.debug();
  149. } );
  150. } )
  151. .then( ( message ) => this._createGeometry( message.data ) )
  152. .catch( e => {
  153. throw e;
  154. } );
  155. // Remove task from the task list.
  156. // Note: replaced '.finally()' with '.catch().then()' block - iOS 11 support (#19416)
  157. objectPending
  158. .catch( () => true )
  159. .then( () => {
  160. if ( worker && taskID ) {
  161. this._releaseTask( worker, taskID );
  162. //this.debug();
  163. }
  164. } );
  165. // Cache the task result.
  166. _taskCache.set( buffer, {
  167. url: url,
  168. promise: objectPending
  169. } );
  170. return objectPending;
  171. }
  172. /**
  173. * Parses the given 3DM data and passes the loaded 3DM asset
  174. * to the `onLoad()` callback.
  175. *
  176. * @param {ArrayBuffer} data - The raw 3DM asset data as an array buffer.
  177. * @param {function(Object3D)} onLoad - Executed when the loading process has been finished.
  178. * @param {onErrorCallback} onError - Executed when errors occur.
  179. */
  180. parse( data, onLoad, onError ) {
  181. this.decodeObjects( data, '' )
  182. .then( result => {
  183. result.userData.warnings = this.warnings;
  184. onLoad( result );
  185. } )
  186. .catch( e => onError( e ) );
  187. }
  188. _compareMaterials( material ) {
  189. const mat = {};
  190. mat.name = material.name;
  191. mat.color = {};
  192. mat.color.r = material.color.r;
  193. mat.color.g = material.color.g;
  194. mat.color.b = material.color.b;
  195. mat.type = material.type;
  196. mat.vertexColors = material.vertexColors;
  197. const json = JSON.stringify( mat );
  198. for ( let i = 0; i < this.materials.length; i ++ ) {
  199. const m = this.materials[ i ];
  200. const _mat = {};
  201. _mat.name = m.name;
  202. _mat.color = {};
  203. _mat.color.r = m.color.r;
  204. _mat.color.g = m.color.g;
  205. _mat.color.b = m.color.b;
  206. _mat.type = m.type;
  207. _mat.vertexColors = m.vertexColors;
  208. if ( JSON.stringify( _mat ) === json ) {
  209. return m;
  210. }
  211. }
  212. this.materials.push( material );
  213. return material;
  214. }
  215. _createMaterial( material, renderEnvironment ) {
  216. if ( material === undefined ) {
  217. return new MeshStandardMaterial( {
  218. color: new Color( 1, 1, 1 ),
  219. metalness: 0.8,
  220. name: Loader.DEFAULT_MATERIAL_NAME,
  221. side: DoubleSide
  222. } );
  223. }
  224. //console.log(material)
  225. const mat = new MeshPhysicalMaterial( {
  226. color: new Color( material.diffuseColor.r / 255.0, material.diffuseColor.g / 255.0, material.diffuseColor.b / 255.0 ),
  227. emissive: new Color( material.emissionColor.r, material.emissionColor.g, material.emissionColor.b ),
  228. flatShading: material.disableLighting,
  229. ior: material.indexOfRefraction,
  230. name: material.name,
  231. reflectivity: material.reflectivity,
  232. opacity: 1.0 - material.transparency,
  233. side: DoubleSide,
  234. specularColor: material.specularColor,
  235. transparent: material.transparency > 0 ? true : false
  236. } );
  237. mat.userData.id = material.id;
  238. if ( material.pbrSupported ) {
  239. const pbr = material.pbr;
  240. mat.anisotropy = pbr.anisotropic;
  241. mat.anisotropyRotation = pbr.anisotropicRotation;
  242. mat.color = new Color( pbr.baseColor.r, pbr.baseColor.g, pbr.baseColor.b );
  243. mat.clearcoat = pbr.clearcoat;
  244. mat.clearcoatRoughness = pbr.clearcoatRoughness;
  245. mat.metalness = pbr.metallic;
  246. mat.transmission = 1 - pbr.opacity;
  247. mat.roughness = pbr.roughness;
  248. mat.sheen = pbr.sheen;
  249. mat.specularIntensity = pbr.specular;
  250. mat.thickness = pbr.subsurface;
  251. }
  252. if ( material.pbrSupported && material.pbr.opacity === 0 && material.transparency === 1 ) {
  253. //some compromises
  254. mat.opacity = 0.2;
  255. mat.transmission = 1.00;
  256. }
  257. const textureLoader = new TextureLoader();
  258. for ( let i = 0; i < material.textures.length; i ++ ) {
  259. const texture = material.textures[ i ];
  260. if ( texture.image !== null ) {
  261. const map = textureLoader.load( texture.image );
  262. //console.log(texture.type )
  263. switch ( texture.type ) {
  264. case 'Bump':
  265. mat.bumpMap = map;
  266. break;
  267. case 'Diffuse':
  268. mat.map = map;
  269. break;
  270. case 'Emap':
  271. mat.envMap = map;
  272. break;
  273. case 'Opacity':
  274. mat.transmissionMap = map;
  275. break;
  276. case 'Transparency':
  277. mat.alphaMap = map;
  278. mat.transparent = true;
  279. break;
  280. case 'PBR_Alpha':
  281. mat.alphaMap = map;
  282. mat.transparent = true;
  283. break;
  284. case 'PBR_AmbientOcclusion':
  285. mat.aoMap = map;
  286. break;
  287. case 'PBR_Anisotropic':
  288. mat.anisotropyMap = map;
  289. break;
  290. case 'PBR_BaseColor':
  291. mat.map = map;
  292. break;
  293. case 'PBR_Clearcoat':
  294. mat.clearcoatMap = map;
  295. break;
  296. case 'PBR_ClearcoatBump':
  297. mat.clearcoatNormalMap = map;
  298. break;
  299. case 'PBR_ClearcoatRoughness':
  300. mat.clearcoatRoughnessMap = map;
  301. break;
  302. case 'PBR_Displacement':
  303. mat.displacementMap = map;
  304. break;
  305. case 'PBR_Emission':
  306. mat.emissiveMap = map;
  307. break;
  308. case 'PBR_Metallic':
  309. mat.metalnessMap = map;
  310. break;
  311. case 'PBR_Roughness':
  312. mat.roughnessMap = map;
  313. break;
  314. case 'PBR_Sheen':
  315. mat.sheenColorMap = map;
  316. break;
  317. case 'PBR_Specular':
  318. mat.specularColorMap = map;
  319. break;
  320. case 'PBR_Subsurface':
  321. mat.thicknessMap = map;
  322. break;
  323. default:
  324. this.warnings.push( {
  325. message: `THREE.3DMLoader: No conversion exists for 3dm ${texture.type}.`,
  326. type: 'no conversion'
  327. } );
  328. break;
  329. }
  330. map.wrapS = texture.wrapU === 0 ? RepeatWrapping : ClampToEdgeWrapping;
  331. map.wrapT = texture.wrapV === 0 ? RepeatWrapping : ClampToEdgeWrapping;
  332. if ( texture.repeat ) {
  333. map.repeat.set( texture.repeat[ 0 ], texture.repeat[ 1 ] );
  334. }
  335. }
  336. }
  337. if ( renderEnvironment ) {
  338. new EXRLoader().load( renderEnvironment.image, function ( texture ) {
  339. texture.mapping = THREE.EquirectangularReflectionMapping;
  340. mat.envMap = texture;
  341. } );
  342. }
  343. return mat;
  344. }
  345. _createGeometry( data ) {
  346. const object = new Object3D();
  347. const instanceDefinitionObjects = [];
  348. const instanceDefinitions = [];
  349. const instanceReferences = [];
  350. object.userData[ 'layers' ] = data.layers;
  351. object.userData[ 'groups' ] = data.groups;
  352. object.userData[ 'settings' ] = data.settings;
  353. object.userData.settings[ 'renderSettings' ] = data.renderSettings;
  354. object.userData[ 'objectType' ] = 'File3dm';
  355. object.userData[ 'materials' ] = null;
  356. object.name = this.url;
  357. let objects = data.objects;
  358. const materials = data.materials;
  359. for ( let i = 0; i < objects.length; i ++ ) {
  360. const obj = objects[ i ];
  361. const attributes = obj.attributes;
  362. switch ( obj.objectType ) {
  363. case 'InstanceDefinition':
  364. instanceDefinitions.push( obj );
  365. break;
  366. case 'InstanceReference':
  367. instanceReferences.push( obj );
  368. break;
  369. default:
  370. let matId = null;
  371. switch ( attributes.materialSource.name ) {
  372. case 'ObjectMaterialSource_MaterialFromLayer':
  373. //check layer index
  374. if ( attributes.layerIndex >= 0 ) {
  375. matId = data.layers[ attributes.layerIndex ].renderMaterialIndex;
  376. }
  377. break;
  378. case 'ObjectMaterialSource_MaterialFromObject':
  379. if ( attributes.materialIndex >= 0 ) {
  380. matId = attributes.materialIndex;
  381. }
  382. break;
  383. }
  384. let material = null;
  385. if ( matId >= 0 ) {
  386. const rMaterial = materials[ matId ];
  387. material = this._createMaterial( rMaterial, data.renderEnvironment );
  388. }
  389. const _object = this._createObject( obj, material );
  390. if ( _object === undefined ) {
  391. continue;
  392. }
  393. const layer = data.layers[ attributes.layerIndex ];
  394. _object.visible = layer ? data.layers[ attributes.layerIndex ].visible : true;
  395. if ( attributes.isInstanceDefinitionObject ) {
  396. instanceDefinitionObjects.push( _object );
  397. } else {
  398. object.add( _object );
  399. }
  400. break;
  401. }
  402. }
  403. for ( let i = 0; i < instanceDefinitions.length; i ++ ) {
  404. const iDef = instanceDefinitions[ i ];
  405. objects = [];
  406. for ( let j = 0; j < iDef.attributes.objectIds.length; j ++ ) {
  407. const objId = iDef.attributes.objectIds[ j ];
  408. for ( let p = 0; p < instanceDefinitionObjects.length; p ++ ) {
  409. const idoId = instanceDefinitionObjects[ p ].userData.attributes.id;
  410. if ( objId === idoId ) {
  411. objects.push( instanceDefinitionObjects[ p ] );
  412. }
  413. }
  414. }
  415. // Currently clones geometry and does not take advantage of instancing
  416. for ( let j = 0; j < instanceReferences.length; j ++ ) {
  417. const iRef = instanceReferences[ j ];
  418. if ( iRef.geometry.parentIdefId === iDef.attributes.id ) {
  419. const iRefObject = new Object3D();
  420. const xf = iRef.geometry.xform.array;
  421. const matrix = new Matrix4();
  422. matrix.set( ...xf );
  423. iRefObject.applyMatrix4( matrix );
  424. for ( let p = 0; p < objects.length; p ++ ) {
  425. iRefObject.add( objects[ p ].clone( true ) );
  426. }
  427. object.add( iRefObject );
  428. }
  429. }
  430. }
  431. object.userData[ 'materials' ] = this.materials;
  432. object.name = '';
  433. return object;
  434. }
  435. _createObject( obj, mat ) {
  436. const loader = new BufferGeometryLoader();
  437. const attributes = obj.attributes;
  438. let geometry, material, _color, color;
  439. switch ( obj.objectType ) {
  440. case 'Point':
  441. case 'PointSet':
  442. geometry = loader.parse( obj.geometry );
  443. if ( geometry.attributes.hasOwnProperty( 'color' ) ) {
  444. material = new PointsMaterial( { vertexColors: true, sizeAttenuation: false, size: 2 } );
  445. } else {
  446. _color = attributes.drawColor;
  447. color = new Color( _color.r / 255.0, _color.g / 255.0, _color.b / 255.0 );
  448. material = new PointsMaterial( { color: color, sizeAttenuation: false, size: 2 } );
  449. }
  450. material = this._compareMaterials( material );
  451. const points = new Points( geometry, material );
  452. points.userData[ 'attributes' ] = attributes;
  453. points.userData[ 'objectType' ] = obj.objectType;
  454. if ( attributes.name ) {
  455. points.name = attributes.name;
  456. }
  457. return points;
  458. case 'Mesh':
  459. case 'Extrusion':
  460. case 'SubD':
  461. case 'Brep':
  462. if ( obj.geometry === null ) return;
  463. geometry = loader.parse( obj.geometry );
  464. if ( mat === null ) {
  465. mat = this._createMaterial();
  466. }
  467. if ( geometry.attributes.hasOwnProperty( 'color' ) ) {
  468. mat.vertexColors = true;
  469. }
  470. mat = this._compareMaterials( mat );
  471. const mesh = new Mesh( geometry, mat );
  472. mesh.castShadow = attributes.castsShadows;
  473. mesh.receiveShadow = attributes.receivesShadows;
  474. mesh.userData[ 'attributes' ] = attributes;
  475. mesh.userData[ 'objectType' ] = obj.objectType;
  476. if ( attributes.name ) {
  477. mesh.name = attributes.name;
  478. }
  479. return mesh;
  480. case 'Curve':
  481. geometry = loader.parse( obj.geometry );
  482. _color = attributes.drawColor;
  483. color = new Color( _color.r / 255.0, _color.g / 255.0, _color.b / 255.0 );
  484. material = new LineBasicMaterial( { color: color } );
  485. material = this._compareMaterials( material );
  486. const lines = new Line( geometry, material );
  487. lines.userData[ 'attributes' ] = attributes;
  488. lines.userData[ 'objectType' ] = obj.objectType;
  489. if ( attributes.name ) {
  490. lines.name = attributes.name;
  491. }
  492. return lines;
  493. case 'TextDot':
  494. geometry = obj.geometry;
  495. const ctx = document.createElement( 'canvas' ).getContext( '2d' );
  496. const font = `${geometry.fontHeight}px ${geometry.fontFace}`;
  497. ctx.font = font;
  498. const width = ctx.measureText( geometry.text ).width + 10;
  499. const height = geometry.fontHeight + 10;
  500. const r = window.devicePixelRatio;
  501. ctx.canvas.width = width * r;
  502. ctx.canvas.height = height * r;
  503. ctx.canvas.style.width = width + 'px';
  504. ctx.canvas.style.height = height + 'px';
  505. ctx.setTransform( r, 0, 0, r, 0, 0 );
  506. ctx.font = font;
  507. ctx.textBaseline = 'middle';
  508. ctx.textAlign = 'center';
  509. color = attributes.drawColor;
  510. ctx.fillStyle = `rgba(${color.r},${color.g},${color.b},${color.a})`;
  511. ctx.fillRect( 0, 0, width, height );
  512. ctx.fillStyle = 'white';
  513. ctx.fillText( geometry.text, width / 2, height / 2 );
  514. const texture = new CanvasTexture( ctx.canvas );
  515. texture.minFilter = LinearFilter;
  516. texture.generateMipmaps = false;
  517. texture.wrapS = ClampToEdgeWrapping;
  518. texture.wrapT = ClampToEdgeWrapping;
  519. material = new SpriteMaterial( { map: texture, depthTest: false } );
  520. const sprite = new Sprite( material );
  521. sprite.position.set( geometry.point[ 0 ], geometry.point[ 1 ], geometry.point[ 2 ] );
  522. sprite.scale.set( width / 10, height / 10, 1.0 );
  523. sprite.userData[ 'attributes' ] = attributes;
  524. sprite.userData[ 'objectType' ] = obj.objectType;
  525. if ( attributes.name ) {
  526. sprite.name = attributes.name;
  527. }
  528. return sprite;
  529. case 'Light':
  530. geometry = obj.geometry;
  531. let light;
  532. switch ( geometry.lightStyle.name ) {
  533. case 'LightStyle_WorldPoint':
  534. light = new PointLight();
  535. light.castShadow = attributes.castsShadows;
  536. light.position.set( geometry.location[ 0 ], geometry.location[ 1 ], geometry.location[ 2 ] );
  537. light.shadow.normalBias = 0.1;
  538. break;
  539. case 'LightStyle_WorldSpot':
  540. light = new SpotLight();
  541. light.castShadow = attributes.castsShadows;
  542. light.position.set( geometry.location[ 0 ], geometry.location[ 1 ], geometry.location[ 2 ] );
  543. light.target.position.set( geometry.direction[ 0 ], geometry.direction[ 1 ], geometry.direction[ 2 ] );
  544. light.angle = geometry.spotAngleRadians;
  545. light.shadow.normalBias = 0.1;
  546. break;
  547. case 'LightStyle_WorldRectangular':
  548. light = new RectAreaLight();
  549. const width = Math.abs( geometry.width[ 2 ] );
  550. const height = Math.abs( geometry.length[ 0 ] );
  551. light.position.set( geometry.location[ 0 ] - ( height / 2 ), geometry.location[ 1 ], geometry.location[ 2 ] - ( width / 2 ) );
  552. light.height = height;
  553. light.width = width;
  554. light.lookAt( geometry.direction[ 0 ], geometry.direction[ 1 ], geometry.direction[ 2 ] );
  555. break;
  556. case 'LightStyle_WorldDirectional':
  557. light = new DirectionalLight();
  558. light.castShadow = attributes.castsShadows;
  559. light.position.set( geometry.location[ 0 ], geometry.location[ 1 ], geometry.location[ 2 ] );
  560. light.target.position.set( geometry.direction[ 0 ], geometry.direction[ 1 ], geometry.direction[ 2 ] );
  561. light.shadow.normalBias = 0.1;
  562. break;
  563. case 'LightStyle_WorldLinear':
  564. // no conversion exists, warning has already been printed to the console
  565. break;
  566. default:
  567. break;
  568. }
  569. if ( light ) {
  570. light.intensity = geometry.intensity;
  571. _color = geometry.diffuse;
  572. color = new Color( _color.r / 255.0, _color.g / 255.0, _color.b / 255.0 );
  573. light.color = color;
  574. light.userData[ 'attributes' ] = attributes;
  575. light.userData[ 'objectType' ] = obj.objectType;
  576. }
  577. return light;
  578. }
  579. }
  580. _initLibrary() {
  581. if ( ! this.libraryPending ) {
  582. // Load rhino3dm wrapper.
  583. const jsLoader = new FileLoader( this.manager );
  584. jsLoader.setPath( this.libraryPath );
  585. const jsContent = new Promise( ( resolve, reject ) => {
  586. jsLoader.load( 'rhino3dm.js', resolve, undefined, reject );
  587. } );
  588. // Load rhino3dm WASM binary.
  589. const binaryLoader = new FileLoader( this.manager );
  590. binaryLoader.setPath( this.libraryPath );
  591. binaryLoader.setResponseType( 'arraybuffer' );
  592. const binaryContent = new Promise( ( resolve, reject ) => {
  593. binaryLoader.load( 'rhino3dm.wasm', resolve, undefined, reject );
  594. } );
  595. this.libraryPending = Promise.all( [ jsContent, binaryContent ] )
  596. .then( ( [ jsContent, binaryContent ] ) => {
  597. //this.libraryBinary = binaryContent;
  598. this.libraryConfig.wasmBinary = binaryContent;
  599. const fn = Rhino3dmWorker.toString();
  600. const body = [
  601. '/* rhino3dm.js */',
  602. jsContent,
  603. '/* worker */',
  604. fn.substring( fn.indexOf( '{' ) + 1, fn.lastIndexOf( '}' ) )
  605. ].join( '\n' );
  606. this.workerSourceURL = URL.createObjectURL( new Blob( [ body ] ) );
  607. } );
  608. }
  609. return this.libraryPending;
  610. }
  611. _getWorker( taskCost ) {
  612. return this._initLibrary().then( () => {
  613. if ( this.workerPool.length < this.workerLimit ) {
  614. const worker = new Worker( this.workerSourceURL );
  615. worker._callbacks = {};
  616. worker._taskCosts = {};
  617. worker._taskLoad = 0;
  618. worker.postMessage( {
  619. type: 'init',
  620. libraryConfig: this.libraryConfig
  621. } );
  622. worker.onmessage = e => {
  623. const message = e.data;
  624. switch ( message.type ) {
  625. case 'warning':
  626. this.warnings.push( message.data );
  627. console.warn( message.data );
  628. break;
  629. case 'decode':
  630. worker._callbacks[ message.id ].resolve( message );
  631. break;
  632. case 'error':
  633. worker._callbacks[ message.id ].reject( message );
  634. break;
  635. default:
  636. console.error( 'THREE.Rhino3dmLoader: Unexpected message, "' + message.type + '"' );
  637. }
  638. };
  639. this.workerPool.push( worker );
  640. } else {
  641. this.workerPool.sort( function ( a, b ) {
  642. return a._taskLoad > b._taskLoad ? - 1 : 1;
  643. } );
  644. }
  645. const worker = this.workerPool[ this.workerPool.length - 1 ];
  646. worker._taskLoad += taskCost;
  647. return worker;
  648. } );
  649. }
  650. _releaseTask( worker, taskID ) {
  651. worker._taskLoad -= worker._taskCosts[ taskID ];
  652. delete worker._callbacks[ taskID ];
  653. delete worker._taskCosts[ taskID ];
  654. }
  655. /**
  656. * Frees internal resources. This method should be called
  657. * when the loader is no longer required.
  658. */
  659. dispose() {
  660. for ( let i = 0; i < this.workerPool.length; ++ i ) {
  661. this.workerPool[ i ].terminate();
  662. }
  663. this.workerPool.length = 0;
  664. }
  665. }
  666. /* WEB WORKER */
  667. function Rhino3dmWorker() {
  668. let libraryPending;
  669. let libraryConfig;
  670. let rhino;
  671. let taskID;
  672. onmessage = function ( e ) {
  673. const message = e.data;
  674. switch ( message.type ) {
  675. case 'init':
  676. libraryConfig = message.libraryConfig;
  677. const wasmBinary = libraryConfig.wasmBinary;
  678. let RhinoModule;
  679. libraryPending = new Promise( function ( resolve ) {
  680. /* Like Basis Loader */
  681. RhinoModule = { wasmBinary, onRuntimeInitialized: resolve };
  682. rhino3dm( RhinoModule ); // eslint-disable-line no-undef
  683. } ).then( () => {
  684. rhino = RhinoModule;
  685. } );
  686. break;
  687. case 'decode':
  688. taskID = message.id;
  689. const buffer = message.buffer;
  690. libraryPending.then( () => {
  691. try {
  692. const data = decodeObjects( rhino, buffer );
  693. self.postMessage( { type: 'decode', id: message.id, data } );
  694. } catch ( error ) {
  695. self.postMessage( { type: 'error', id: message.id, error } );
  696. }
  697. } );
  698. break;
  699. }
  700. };
  701. function decodeObjects( rhino, buffer ) {
  702. const arr = new Uint8Array( buffer );
  703. const doc = rhino.File3dm.fromByteArray( arr );
  704. const objects = [];
  705. const materials = [];
  706. const layers = [];
  707. const views = [];
  708. const namedViews = [];
  709. const groups = [];
  710. const strings = [];
  711. //Handle objects
  712. const objs = doc.objects();
  713. const cnt = objs.count;
  714. for ( let i = 0; i < cnt; i ++ ) {
  715. const _object = objs.get( i );
  716. const object = extractObjectData( _object, doc );
  717. _object.delete();
  718. if ( object ) {
  719. objects.push( object );
  720. }
  721. }
  722. // Handle instance definitions
  723. // console.log( `Instance Definitions Count: ${doc.instanceDefinitions().count()}` );
  724. for ( let i = 0; i < doc.instanceDefinitions().count; i ++ ) {
  725. const idef = doc.instanceDefinitions().get( i );
  726. const idefAttributes = extractProperties( idef );
  727. idefAttributes.objectIds = idef.getObjectIds();
  728. objects.push( { geometry: null, attributes: idefAttributes, objectType: 'InstanceDefinition' } );
  729. }
  730. // Handle materials
  731. const textureTypes = [
  732. // rhino.TextureType.Bitmap,
  733. rhino.TextureType.Diffuse,
  734. rhino.TextureType.Bump,
  735. rhino.TextureType.Transparency,
  736. rhino.TextureType.Opacity,
  737. rhino.TextureType.Emap
  738. ];
  739. const pbrTextureTypes = [
  740. rhino.TextureType.PBR_BaseColor,
  741. rhino.TextureType.PBR_Subsurface,
  742. rhino.TextureType.PBR_SubsurfaceScattering,
  743. rhino.TextureType.PBR_SubsurfaceScatteringRadius,
  744. rhino.TextureType.PBR_Metallic,
  745. rhino.TextureType.PBR_Specular,
  746. rhino.TextureType.PBR_SpecularTint,
  747. rhino.TextureType.PBR_Roughness,
  748. rhino.TextureType.PBR_Anisotropic,
  749. rhino.TextureType.PBR_Anisotropic_Rotation,
  750. rhino.TextureType.PBR_Sheen,
  751. rhino.TextureType.PBR_SheenTint,
  752. rhino.TextureType.PBR_Clearcoat,
  753. rhino.TextureType.PBR_ClearcoatBump,
  754. rhino.TextureType.PBR_ClearcoatRoughness,
  755. rhino.TextureType.PBR_OpacityIor,
  756. rhino.TextureType.PBR_OpacityRoughness,
  757. rhino.TextureType.PBR_Emission,
  758. rhino.TextureType.PBR_AmbientOcclusion,
  759. rhino.TextureType.PBR_Displacement
  760. ];
  761. for ( let i = 0; i < doc.materials().count; i ++ ) {
  762. const _material = doc.materials().get( i );
  763. const material = extractProperties( _material );
  764. const textures = [];
  765. textures.push( ...extractTextures( _material, textureTypes, doc ) );
  766. material.pbrSupported = _material.physicallyBased().supported;
  767. if ( material.pbrSupported ) {
  768. textures.push( ...extractTextures( _material, pbrTextureTypes, doc ) );
  769. material.pbr = extractProperties( _material.physicallyBased() );
  770. }
  771. material.textures = textures;
  772. materials.push( material );
  773. _material.delete();
  774. }
  775. // Handle layers
  776. for ( let i = 0; i < doc.layers().count; i ++ ) {
  777. const _layer = doc.layers().get( i );
  778. const layer = extractProperties( _layer );
  779. layers.push( layer );
  780. _layer.delete();
  781. }
  782. // Handle views
  783. for ( let i = 0; i < doc.views().count; i ++ ) {
  784. const _view = doc.views().get( i );
  785. const view = extractProperties( _view );
  786. views.push( view );
  787. _view.delete();
  788. }
  789. // Handle named views
  790. for ( let i = 0; i < doc.namedViews().count; i ++ ) {
  791. const _namedView = doc.namedViews().get( i );
  792. const namedView = extractProperties( _namedView );
  793. namedViews.push( namedView );
  794. _namedView.delete();
  795. }
  796. // Handle groups
  797. for ( let i = 0; i < doc.groups().count; i ++ ) {
  798. const _group = doc.groups().get( i );
  799. const group = extractProperties( _group );
  800. groups.push( group );
  801. _group.delete();
  802. }
  803. // Handle settings
  804. const settings = extractProperties( doc.settings() );
  805. //TODO: Handle other document stuff like dimstyles, instance definitions, bitmaps etc.
  806. // Handle dimstyles
  807. // console.log( `Dimstyle Count: ${doc.dimstyles().count()}` );
  808. // Handle bitmaps
  809. // console.log( `Bitmap Count: ${doc.bitmaps().count()}` );
  810. // Handle strings
  811. // console.log( `Document Strings Count: ${doc.strings().count()}` );
  812. // Note: doc.strings().documentUserTextCount() counts any doc.strings defined in a section
  813. // console.log( `Document User Text Count: ${doc.strings().documentUserTextCount()}` );
  814. const strings_count = doc.strings().count;
  815. for ( let i = 0; i < strings_count; i ++ ) {
  816. strings.push( doc.strings().get( i ) );
  817. }
  818. // Handle Render Environments for Material Environment
  819. // get the id of the active render environment skylight, which we'll use for environment texture
  820. const reflectionId = doc.settings().renderSettings().renderEnvironments.reflectionId;
  821. const rc = doc.renderContent();
  822. let renderEnvironment = null;
  823. for ( let i = 0; i < rc.count; i ++ ) {
  824. const content = rc.get( i );
  825. switch ( content.kind ) {
  826. case 'environment':
  827. const id = content.id;
  828. // there could be multiple render environments in a 3dm file
  829. if ( id !== reflectionId ) break;
  830. const renderTexture = content.findChild( 'texture' );
  831. const fileName = renderTexture.fileName;
  832. for ( let j = 0; j < doc.embeddedFiles().count; j ++ ) {
  833. const _fileName = doc.embeddedFiles().get( j ).fileName;
  834. if ( fileName === _fileName ) {
  835. const background = doc.getEmbeddedFileAsBase64( fileName );
  836. const backgroundImage = 'data:image/png;base64,' + background;
  837. renderEnvironment = { type: 'renderEnvironment', image: backgroundImage, name: fileName };
  838. }
  839. }
  840. break;
  841. }
  842. }
  843. // Handle Render Settings
  844. const renderSettings = {
  845. ambientLight: doc.settings().renderSettings().ambientLight,
  846. backgroundColorTop: doc.settings().renderSettings().backgroundColorTop,
  847. backgroundColorBottom: doc.settings().renderSettings().backgroundColorBottom,
  848. useHiddenLights: doc.settings().renderSettings().useHiddenLights,
  849. depthCue: doc.settings().renderSettings().depthCue,
  850. flatShade: doc.settings().renderSettings().flatShade,
  851. renderBackFaces: doc.settings().renderSettings().renderBackFaces,
  852. renderPoints: doc.settings().renderSettings().renderPoints,
  853. renderCurves: doc.settings().renderSettings().renderCurves,
  854. renderIsoParams: doc.settings().renderSettings().renderIsoParams,
  855. renderMeshEdges: doc.settings().renderSettings().renderMeshEdges,
  856. renderAnnotations: doc.settings().renderSettings().renderAnnotations,
  857. useViewportSize: doc.settings().renderSettings().useViewportSize,
  858. scaleBackgroundToFit: doc.settings().renderSettings().scaleBackgroundToFit,
  859. transparentBackground: doc.settings().renderSettings().transparentBackground,
  860. imageDpi: doc.settings().renderSettings().imageDpi,
  861. shadowMapLevel: doc.settings().renderSettings().shadowMapLevel,
  862. namedView: doc.settings().renderSettings().namedView,
  863. snapShot: doc.settings().renderSettings().snapShot,
  864. specificViewport: doc.settings().renderSettings().specificViewport,
  865. groundPlane: extractProperties( doc.settings().renderSettings().groundPlane ),
  866. safeFrame: extractProperties( doc.settings().renderSettings().safeFrame ),
  867. dithering: extractProperties( doc.settings().renderSettings().dithering ),
  868. skylight: extractProperties( doc.settings().renderSettings().skylight ),
  869. linearWorkflow: extractProperties( doc.settings().renderSettings().linearWorkflow ),
  870. renderChannels: extractProperties( doc.settings().renderSettings().renderChannels ),
  871. sun: extractProperties( doc.settings().renderSettings().sun ),
  872. renderEnvironments: extractProperties( doc.settings().renderSettings().renderEnvironments ),
  873. postEffects: extractProperties( doc.settings().renderSettings().postEffects ),
  874. };
  875. doc.delete();
  876. return { objects, materials, layers, views, namedViews, groups, strings, settings, renderSettings, renderEnvironment };
  877. }
  878. function extractTextures( m, tTypes, d ) {
  879. const textures = [];
  880. for ( let i = 0; i < tTypes.length; i ++ ) {
  881. const _texture = m.getTexture( tTypes[ i ] );
  882. if ( _texture ) {
  883. let textureType = tTypes[ i ].constructor.name;
  884. textureType = textureType.substring( 12, textureType.length );
  885. const texture = extractTextureData( _texture, textureType, d );
  886. textures.push( texture );
  887. _texture.delete();
  888. }
  889. }
  890. return textures;
  891. }
  892. function extractTextureData( t, tType, d ) {
  893. const texture = { type: tType };
  894. const image = d.getEmbeddedFileAsBase64( t.fileName );
  895. texture.wrapU = t.wrapU;
  896. texture.wrapV = t.wrapV;
  897. texture.wrapW = t.wrapW;
  898. const uvw = t.uvwTransform.toFloatArray( true );
  899. texture.repeat = [ uvw[ 0 ], uvw[ 5 ] ];
  900. if ( image ) {
  901. texture.image = 'data:image/png;base64,' + image;
  902. } else {
  903. self.postMessage( { type: 'warning', id: taskID, data: {
  904. message: `THREE.3DMLoader: Image for ${tType} texture not embedded in file.`,
  905. type: 'missing resource'
  906. }
  907. } );
  908. texture.image = null;
  909. }
  910. return texture;
  911. }
  912. function extractObjectData( object, doc ) {
  913. const _geometry = object.geometry();
  914. const _attributes = object.attributes();
  915. let objectType = _geometry.objectType;
  916. let geometry, attributes, position, data, mesh;
  917. // skip instance definition objects
  918. //if( _attributes.isInstanceDefinitionObject ) { continue; }
  919. // TODO: handle other geometry types
  920. switch ( objectType ) {
  921. case rhino.ObjectType.Curve:
  922. const pts = curveToPoints( _geometry, 100 );
  923. position = {};
  924. attributes = {};
  925. data = {};
  926. position.itemSize = 3;
  927. position.type = 'Float32Array';
  928. position.array = [];
  929. for ( let j = 0; j < pts.length; j ++ ) {
  930. position.array.push( pts[ j ][ 0 ] );
  931. position.array.push( pts[ j ][ 1 ] );
  932. position.array.push( pts[ j ][ 2 ] );
  933. }
  934. attributes.position = position;
  935. data.attributes = attributes;
  936. geometry = { data };
  937. break;
  938. case rhino.ObjectType.Point:
  939. const pt = _geometry.location;
  940. position = {};
  941. const color = {};
  942. attributes = {};
  943. data = {};
  944. position.itemSize = 3;
  945. position.type = 'Float32Array';
  946. position.array = [ pt[ 0 ], pt[ 1 ], pt[ 2 ] ];
  947. const _color = _attributes.drawColor( doc );
  948. color.itemSize = 3;
  949. color.type = 'Float32Array';
  950. color.array = [ _color.r / 255.0, _color.g / 255.0, _color.b / 255.0 ];
  951. attributes.position = position;
  952. attributes.color = color;
  953. data.attributes = attributes;
  954. geometry = { data };
  955. break;
  956. case rhino.ObjectType.PointSet:
  957. case rhino.ObjectType.Mesh:
  958. geometry = _geometry.toThreejsJSON();
  959. break;
  960. case rhino.ObjectType.Brep:
  961. const faces = _geometry.faces();
  962. mesh = new rhino.Mesh();
  963. for ( let faceIndex = 0; faceIndex < faces.count; faceIndex ++ ) {
  964. const face = faces.get( faceIndex );
  965. const _mesh = face.getMesh( rhino.MeshType.Any );
  966. if ( _mesh ) {
  967. mesh.append( _mesh );
  968. _mesh.delete();
  969. }
  970. face.delete();
  971. }
  972. if ( mesh.faces().count > 0 ) {
  973. mesh.compact();
  974. geometry = mesh.toThreejsJSON();
  975. faces.delete();
  976. }
  977. mesh.delete();
  978. break;
  979. case rhino.ObjectType.Extrusion:
  980. mesh = _geometry.getMesh( rhino.MeshType.Any );
  981. if ( mesh ) {
  982. geometry = mesh.toThreejsJSON();
  983. mesh.delete();
  984. }
  985. break;
  986. case rhino.ObjectType.TextDot:
  987. geometry = extractProperties( _geometry );
  988. break;
  989. case rhino.ObjectType.Light:
  990. geometry = extractProperties( _geometry );
  991. if ( geometry.lightStyle.name === 'LightStyle_WorldLinear' ) {
  992. self.postMessage( { type: 'warning', id: taskID, data: {
  993. message: `THREE.3DMLoader: No conversion exists for ${objectType.constructor.name} ${geometry.lightStyle.name}`,
  994. type: 'no conversion',
  995. guid: _attributes.id
  996. }
  997. } );
  998. }
  999. break;
  1000. case rhino.ObjectType.InstanceReference:
  1001. geometry = extractProperties( _geometry );
  1002. geometry.xform = extractProperties( _geometry.xform );
  1003. geometry.xform.array = _geometry.xform.toFloatArray( true );
  1004. break;
  1005. case rhino.ObjectType.SubD:
  1006. // TODO: precalculate resulting vertices and faces and warn on excessive results
  1007. _geometry.subdivide( 3 );
  1008. mesh = rhino.Mesh.createFromSubDControlNet( _geometry, false );
  1009. if ( mesh ) {
  1010. geometry = mesh.toThreejsJSON();
  1011. mesh.delete();
  1012. }
  1013. break;
  1014. /*
  1015. case rhino.ObjectType.Annotation:
  1016. case rhino.ObjectType.Hatch:
  1017. case rhino.ObjectType.ClipPlane:
  1018. */
  1019. default:
  1020. self.postMessage( { type: 'warning', id: taskID, data: {
  1021. message: `THREE.3DMLoader: Conversion not implemented for ${objectType.constructor.name}`,
  1022. type: 'not implemented',
  1023. guid: _attributes.id
  1024. }
  1025. } );
  1026. break;
  1027. }
  1028. if ( geometry ) {
  1029. attributes = extractProperties( _attributes );
  1030. attributes.geometry = extractProperties( _geometry );
  1031. if ( _attributes.groupCount > 0 ) {
  1032. attributes.groupIds = _attributes.getGroupList();
  1033. }
  1034. if ( _attributes.userStringCount > 0 ) {
  1035. attributes.userStrings = _attributes.getUserStrings();
  1036. }
  1037. if ( _geometry.userStringCount > 0 ) {
  1038. attributes.geometry.userStrings = _geometry.getUserStrings();
  1039. }
  1040. if ( _attributes.decals().count > 0 ) {
  1041. self.postMessage( { type: 'warning', id: taskID, data: {
  1042. message: 'THREE.3DMLoader: No conversion exists for the decals associated with this object.',
  1043. type: 'no conversion',
  1044. guid: _attributes.id
  1045. }
  1046. } );
  1047. }
  1048. attributes.drawColor = _attributes.drawColor( doc );
  1049. objectType = objectType.constructor.name;
  1050. objectType = objectType.substring( 11, objectType.length );
  1051. return { geometry, attributes, objectType };
  1052. } else {
  1053. self.postMessage( { type: 'warning', id: taskID, data: {
  1054. message: `THREE.3DMLoader: ${objectType.constructor.name} has no associated mesh geometry.`,
  1055. type: 'missing mesh',
  1056. guid: _attributes.id
  1057. }
  1058. } );
  1059. }
  1060. }
  1061. function extractProperties( object ) {
  1062. const result = {};
  1063. for ( const property in object ) {
  1064. const value = object[ property ];
  1065. if ( typeof value !== 'function' ) {
  1066. if ( typeof value === 'object' && value !== null && value.hasOwnProperty( 'constructor' ) ) {
  1067. result[ property ] = { name: value.constructor.name, value: value.value };
  1068. } else if ( typeof value === 'object' && value !== null ) {
  1069. result[ property ] = extractProperties( value );
  1070. } else {
  1071. result[ property ] = value;
  1072. }
  1073. } else {
  1074. // these are functions that could be called to extract more data.
  1075. //console.log( `${property}: ${object[ property ].constructor.name}` );
  1076. }
  1077. }
  1078. return result;
  1079. }
  1080. function curveToPoints( curve, pointLimit ) {
  1081. let pointCount = pointLimit;
  1082. let rc = [];
  1083. const ts = [];
  1084. if ( curve instanceof rhino.LineCurve ) {
  1085. return [ curve.pointAtStart, curve.pointAtEnd ];
  1086. }
  1087. if ( curve instanceof rhino.PolylineCurve ) {
  1088. pointCount = curve.pointCount;
  1089. for ( let i = 0; i < pointCount; i ++ ) {
  1090. rc.push( curve.point( i ) );
  1091. }
  1092. return rc;
  1093. }
  1094. if ( curve instanceof rhino.PolyCurve ) {
  1095. const segmentCount = curve.segmentCount;
  1096. for ( let i = 0; i < segmentCount; i ++ ) {
  1097. const segment = curve.segmentCurve( i );
  1098. const segmentArray = curveToPoints( segment, pointCount );
  1099. rc = rc.concat( segmentArray );
  1100. segment.delete();
  1101. }
  1102. return rc;
  1103. }
  1104. if ( curve instanceof rhino.ArcCurve ) {
  1105. pointCount = Math.floor( curve.angleDegrees / 5 );
  1106. pointCount = pointCount < 2 ? 2 : pointCount;
  1107. // alternative to this hardcoded version: https://stackoverflow.com/a/18499923/2179399
  1108. }
  1109. if ( curve instanceof rhino.NurbsCurve && curve.degree === 1 ) {
  1110. const pLine = curve.tryGetPolyline();
  1111. for ( let i = 0; i < pLine.count; i ++ ) {
  1112. rc.push( pLine.get( i ) );
  1113. }
  1114. pLine.delete();
  1115. return rc;
  1116. }
  1117. const domain = curve.domain;
  1118. const divisions = pointCount - 1.0;
  1119. for ( let j = 0; j < pointCount; j ++ ) {
  1120. const t = domain[ 0 ] + ( j / divisions ) * ( domain[ 1 ] - domain[ 0 ] );
  1121. if ( t === domain[ 0 ] || t === domain[ 1 ] ) {
  1122. ts.push( t );
  1123. continue;
  1124. }
  1125. const tan = curve.tangentAt( t );
  1126. const prevTan = curve.tangentAt( ts.slice( - 1 )[ 0 ] );
  1127. // Duplicated from THREE.Vector3
  1128. // How to pass imports to worker?
  1129. const tS = tan[ 0 ] * tan[ 0 ] + tan[ 1 ] * tan[ 1 ] + tan[ 2 ] * tan[ 2 ];
  1130. const ptS = prevTan[ 0 ] * prevTan[ 0 ] + prevTan[ 1 ] * prevTan[ 1 ] + prevTan[ 2 ] * prevTan[ 2 ];
  1131. const denominator = Math.sqrt( tS * ptS );
  1132. let angle;
  1133. if ( denominator === 0 ) {
  1134. angle = Math.PI / 2;
  1135. } else {
  1136. const theta = ( tan.x * prevTan.x + tan.y * prevTan.y + tan.z * prevTan.z ) / denominator;
  1137. angle = Math.acos( Math.max( - 1, Math.min( 1, theta ) ) );
  1138. }
  1139. if ( angle < 0.1 ) continue;
  1140. ts.push( t );
  1141. }
  1142. rc = ts.map( t => curve.pointAt( t ) );
  1143. return rc;
  1144. }
  1145. }
  1146. export { Rhino3dmLoader };