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