RoundedBoxGeometry.js 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. import {
  2. BoxGeometry,
  3. Vector3
  4. } from 'three';
  5. const _tempNormal = new Vector3();
  6. function getUv( faceDirVector, normal, uvAxis, projectionAxis, radius, sideLength ) {
  7. const totArcLength = 2 * Math.PI * radius / 4;
  8. // length of the planes between the arcs on each axis
  9. const centerLength = Math.max( sideLength - 2 * radius, 0 );
  10. const halfArc = Math.PI / 4;
  11. // Get the vector projected onto the Y plane
  12. _tempNormal.copy( normal );
  13. _tempNormal[ projectionAxis ] = 0;
  14. _tempNormal.normalize();
  15. // total amount of UV space alloted to a single arc
  16. const arcUvRatio = 0.5 * totArcLength / ( totArcLength + centerLength );
  17. // the distance along one arc the point is at
  18. const arcAngleRatio = 1.0 - ( _tempNormal.angleTo( faceDirVector ) / halfArc );
  19. if ( Math.sign( _tempNormal[ uvAxis ] ) === 1 ) {
  20. return arcAngleRatio * arcUvRatio;
  21. } else {
  22. // total amount of UV space alloted to the plane between the arcs
  23. const lenUv = centerLength / ( totArcLength + centerLength );
  24. return lenUv + arcUvRatio + arcUvRatio * ( 1.0 - arcAngleRatio );
  25. }
  26. }
  27. /**
  28. * A special type of box geometry with rounded corners and edges.
  29. *
  30. * ```js
  31. * const geometry = new THREE.RoundedBoxGeometry();
  32. * const material = new THREE.MeshStandardMaterial( { color: 0x00ff00 } );
  33. * const cube = new THREE.Mesh( geometry, material );
  34. * scene.add( cube );
  35. * ```
  36. *
  37. * @augments BoxGeometry
  38. * @three_import import { RoundedBoxGeometry } from 'three/addons/geometries/RoundedBoxGeometry.js';
  39. */
  40. class RoundedBoxGeometry extends BoxGeometry {
  41. /**
  42. * Constructs a new rounded box geometry.
  43. *
  44. * @param {number} [width=1] - The width. That is, the length of the edges parallel to the X axis.
  45. * @param {number} [height=1] - The height. That is, the length of the edges parallel to the Y axis.
  46. * @param {number} [depth=1] - The depth. That is, the length of the edges parallel to the Z axis.
  47. * @param {number} [segments=2] - Number of segments that form the rounded corners.
  48. * @param {number} [radius=0.1] - The radius of the rounded corners.
  49. */
  50. constructor( width = 1, height = 1, depth = 1, segments = 2, radius = 0.1 ) {
  51. // calculate total segments needed &
  52. // ensure it's odd so that we have a plane connecting the rounded corners
  53. const totalSegments = segments * 2 + 1;
  54. // ensure radius isn't bigger than shortest side
  55. radius = Math.min( width / 2, height / 2, depth / 2, radius );
  56. // start with a unit box geometry, its vertices will be modified to form the rounded box
  57. super( 1, 1, 1, totalSegments, totalSegments, totalSegments );
  58. this.type = 'RoundedBoxGeometry';
  59. /**
  60. * Holds the constructor parameters that have been
  61. * used to generate the geometry. Any modification
  62. * after instantiation does not change the geometry.
  63. *
  64. * @type {Object}
  65. */
  66. this.parameters = {
  67. width: width,
  68. height: height,
  69. depth: depth,
  70. segments: segments,
  71. radius: radius,
  72. };
  73. // if totalSegments is 1, no rounding is needed - return regular box
  74. if ( totalSegments === 1 ) return;
  75. const geometry2 = this.toNonIndexed();
  76. this.index = null;
  77. this.attributes.position = geometry2.attributes.position;
  78. this.attributes.normal = geometry2.attributes.normal;
  79. this.attributes.uv = geometry2.attributes.uv;
  80. //
  81. const position = new Vector3();
  82. const normal = new Vector3();
  83. const box = new Vector3( width, height, depth ).divideScalar( 2 ).subScalar( radius );
  84. const positions = this.attributes.position.array;
  85. const normals = this.attributes.normal.array;
  86. const uvs = this.attributes.uv.array;
  87. const faceTris = positions.length / 6;
  88. const faceDirVector = new Vector3();
  89. const halfSegmentSize = 0.5 / totalSegments;
  90. for ( let i = 0, j = 0; i < positions.length; i += 3, j += 2 ) {
  91. position.fromArray( positions, i );
  92. normal.copy( position );
  93. normal.x -= Math.sign( normal.x ) * halfSegmentSize;
  94. normal.y -= Math.sign( normal.y ) * halfSegmentSize;
  95. normal.z -= Math.sign( normal.z ) * halfSegmentSize;
  96. normal.normalize();
  97. positions[ i + 0 ] = box.x * Math.sign( position.x ) + normal.x * radius;
  98. positions[ i + 1 ] = box.y * Math.sign( position.y ) + normal.y * radius;
  99. positions[ i + 2 ] = box.z * Math.sign( position.z ) + normal.z * radius;
  100. normals[ i + 0 ] = normal.x;
  101. normals[ i + 1 ] = normal.y;
  102. normals[ i + 2 ] = normal.z;
  103. const side = Math.floor( i / faceTris );
  104. switch ( side ) {
  105. case 0: // right
  106. // generate UVs along Z then Y
  107. faceDirVector.set( 1, 0, 0 );
  108. uvs[ j + 0 ] = getUv( faceDirVector, normal, 'z', 'y', radius, depth );
  109. uvs[ j + 1 ] = 1.0 - getUv( faceDirVector, normal, 'y', 'z', radius, height );
  110. break;
  111. case 1: // left
  112. // generate UVs along Z then Y
  113. faceDirVector.set( - 1, 0, 0 );
  114. uvs[ j + 0 ] = 1.0 - getUv( faceDirVector, normal, 'z', 'y', radius, depth );
  115. uvs[ j + 1 ] = 1.0 - getUv( faceDirVector, normal, 'y', 'z', radius, height );
  116. break;
  117. case 2: // top
  118. // generate UVs along X then Z
  119. faceDirVector.set( 0, 1, 0 );
  120. uvs[ j + 0 ] = 1.0 - getUv( faceDirVector, normal, 'x', 'z', radius, width );
  121. uvs[ j + 1 ] = getUv( faceDirVector, normal, 'z', 'x', radius, depth );
  122. break;
  123. case 3: // bottom
  124. // generate UVs along X then Z
  125. faceDirVector.set( 0, - 1, 0 );
  126. uvs[ j + 0 ] = 1.0 - getUv( faceDirVector, normal, 'x', 'z', radius, width );
  127. uvs[ j + 1 ] = 1.0 - getUv( faceDirVector, normal, 'z', 'x', radius, depth );
  128. break;
  129. case 4: // front
  130. // generate UVs along X then Y
  131. faceDirVector.set( 0, 0, 1 );
  132. uvs[ j + 0 ] = 1.0 - getUv( faceDirVector, normal, 'x', 'y', radius, width );
  133. uvs[ j + 1 ] = 1.0 - getUv( faceDirVector, normal, 'y', 'x', radius, height );
  134. break;
  135. case 5: // back
  136. // generate UVs along X then Y
  137. faceDirVector.set( 0, 0, - 1 );
  138. uvs[ j + 0 ] = getUv( faceDirVector, normal, 'x', 'y', radius, width );
  139. uvs[ j + 1 ] = 1.0 - getUv( faceDirVector, normal, 'y', 'x', radius, height );
  140. break;
  141. }
  142. }
  143. }
  144. /**
  145. * Factory method for creating an instance of this class from the given
  146. * JSON object.
  147. *
  148. * @param {Object} data - A JSON object representing the serialized geometry.
  149. * @returns {RoundedBoxGeometry} A new instance.
  150. */
  151. static fromJSON( data ) {
  152. return new RoundedBoxGeometry(
  153. data.width,
  154. data.height,
  155. data.depth,
  156. data.segments,
  157. data.radius
  158. );
  159. }
  160. }
  161. export { RoundedBoxGeometry };
粤ICP备19079148号