3DMLoader.js 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836
  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 };