LDrawUtils.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. import {
  2. BufferAttribute,
  3. BufferGeometry,
  4. Group,
  5. LineSegments,
  6. Matrix3,
  7. Mesh
  8. } from 'three';
  9. import { mergeGeometries } from './BufferGeometryUtils.js';
  10. /**
  11. * Utility class for LDraw models.
  12. *
  13. * @three_import import { LDrawUtils } from 'three/addons/utils/LDrawUtils.js';
  14. */
  15. class LDrawUtils {
  16. /**
  17. * Merges geometries in the given object by materials and returns a new group object.
  18. * Use on not indexed geometries. The object buffers reference the old object ones.
  19. * Special treatment is done to the conditional lines generated by LDrawLoader.
  20. *
  21. * @param {Object3D} object - The object to merge.
  22. * @returns {Group} The merged object.
  23. */
  24. static mergeObject( object ) {
  25. function extractGroup( geometry, group, elementSize, isConditionalLine ) {
  26. // Extracts a group from a geometry as a new geometry (with attribute buffers referencing original buffers)
  27. const newGeometry = new BufferGeometry();
  28. const originalPositions = geometry.getAttribute( 'position' ).array;
  29. const originalNormals = elementSize === 3 ? geometry.getAttribute( 'normal' ).array : null;
  30. const numVertsGroup = Math.min( group.count, Math.floor( originalPositions.length / 3 ) - group.start );
  31. const vertStart = group.start * 3;
  32. const vertEnd = ( group.start + numVertsGroup ) * 3;
  33. const positions = originalPositions.subarray( vertStart, vertEnd );
  34. const normals = originalNormals !== null ? originalNormals.subarray( vertStart, vertEnd ) : null;
  35. newGeometry.setAttribute( 'position', new BufferAttribute( positions, 3 ) );
  36. if ( normals !== null ) newGeometry.setAttribute( 'normal', new BufferAttribute( normals, 3 ) );
  37. if ( isConditionalLine ) {
  38. const controlArray0 = geometry.getAttribute( 'control0' ).array.subarray( vertStart, vertEnd );
  39. const controlArray1 = geometry.getAttribute( 'control1' ).array.subarray( vertStart, vertEnd );
  40. const directionArray = geometry.getAttribute( 'direction' ).array.subarray( vertStart, vertEnd );
  41. newGeometry.setAttribute( 'control0', new BufferAttribute( controlArray0, 3, false ) );
  42. newGeometry.setAttribute( 'control1', new BufferAttribute( controlArray1, 3, false ) );
  43. newGeometry.setAttribute( 'direction', new BufferAttribute( directionArray, 3, false ) );
  44. }
  45. return newGeometry;
  46. }
  47. function addGeometry( mat, geometry, geometries ) {
  48. const geoms = geometries[ mat.uuid ];
  49. if ( ! geoms ) {
  50. geometries[ mat.uuid ] = {
  51. mat: mat,
  52. arr: [ geometry ]
  53. };
  54. } else {
  55. geoms.arr.push( geometry );
  56. }
  57. }
  58. function permuteAttribute( attribute, elemSize ) {
  59. // Permutes first two vertices of each attribute element
  60. if ( ! attribute ) return;
  61. const verts = attribute.array;
  62. const numVerts = Math.floor( verts.length / 3 );
  63. let offset = 0;
  64. for ( let i = 0; i < numVerts; i ++ ) {
  65. const x = verts[ offset ];
  66. const y = verts[ offset + 1 ];
  67. const z = verts[ offset + 2 ];
  68. verts[ offset ] = verts[ offset + 3 ];
  69. verts[ offset + 1 ] = verts[ offset + 4 ];
  70. verts[ offset + 2 ] = verts[ offset + 5 ];
  71. verts[ offset + 3 ] = x;
  72. verts[ offset + 4 ] = y;
  73. verts[ offset + 5 ] = z;
  74. offset += elemSize * 3;
  75. }
  76. }
  77. // Traverse the object hierarchy collecting geometries and transforming them to world space
  78. const meshGeometries = {};
  79. const linesGeometries = {};
  80. const condLinesGeometries = {};
  81. object.updateMatrixWorld( true );
  82. const normalMatrix = new Matrix3();
  83. object.traverse( c => {
  84. if ( c.isMesh | c.isLineSegments ) {
  85. const elemSize = c.isMesh ? 3 : 2;
  86. const geometry = c.geometry.clone();
  87. const matrixIsInverted = c.matrixWorld.determinant() < 0;
  88. if ( matrixIsInverted ) {
  89. permuteAttribute( geometry.attributes.position, elemSize );
  90. permuteAttribute( geometry.attributes.normal, elemSize );
  91. }
  92. geometry.applyMatrix4( c.matrixWorld );
  93. if ( c.isConditionalLine ) {
  94. geometry.attributes.control0.applyMatrix4( c.matrixWorld );
  95. geometry.attributes.control1.applyMatrix4( c.matrixWorld );
  96. normalMatrix.getNormalMatrix( c.matrixWorld );
  97. geometry.attributes.direction.applyNormalMatrix( normalMatrix );
  98. }
  99. const geometries = c.isMesh ? meshGeometries : ( c.isConditionalLine ? condLinesGeometries : linesGeometries );
  100. if ( Array.isArray( c.material ) ) {
  101. for ( const groupIndex in geometry.groups ) {
  102. const group = geometry.groups[ groupIndex ];
  103. const mat = c.material[ group.materialIndex ];
  104. const newGeometry = extractGroup( geometry, group, elemSize, c.isConditionalLine );
  105. addGeometry( mat, newGeometry, geometries );
  106. }
  107. } else {
  108. addGeometry( c.material, geometry, geometries );
  109. }
  110. }
  111. } );
  112. // Create object with merged geometries
  113. const mergedObject = new Group();
  114. const meshMaterialsIds = Object.keys( meshGeometries );
  115. for ( const meshMaterialsId of meshMaterialsIds ) {
  116. const meshGeometry = meshGeometries[ meshMaterialsId ];
  117. const mergedGeometry = mergeGeometries( meshGeometry.arr );
  118. mergedObject.add( new Mesh( mergedGeometry, meshGeometry.mat ) );
  119. }
  120. const linesMaterialsIds = Object.keys( linesGeometries );
  121. for ( const linesMaterialsId of linesMaterialsIds ) {
  122. const lineGeometry = linesGeometries[ linesMaterialsId ];
  123. const mergedGeometry = mergeGeometries( lineGeometry.arr );
  124. mergedObject.add( new LineSegments( mergedGeometry, lineGeometry.mat ) );
  125. }
  126. const condLinesMaterialsIds = Object.keys( condLinesGeometries );
  127. for ( const condLinesMaterialsId of condLinesMaterialsIds ) {
  128. const condLineGeometry = condLinesGeometries[ condLinesMaterialsId ];
  129. const mergedGeometry = mergeGeometries( condLineGeometry.arr );
  130. const condLines = new LineSegments( mergedGeometry, condLineGeometry.mat );
  131. condLines.isConditionalLine = true;
  132. mergedObject.add( condLines );
  133. }
  134. mergedObject.userData.constructionStep = 0;
  135. mergedObject.userData.numConstructionSteps = 1;
  136. return mergedObject;
  137. }
  138. }
  139. export { LDrawUtils };