capsuleBuilder.js 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. import { VertexData } from "../mesh.vertexData.js";
  2. import { Vector2, Vector3, Matrix } from "../../Maths/math.vector.js";
  3. import { Mesh } from "../mesh.js";
  4. import { CompatibilityOptions } from "../../Compat/compatibilityOptions.js";
  5. /**
  6. * Scripts based off of https://github.com/maximeq/three-js-capsule-geometry/blob/master/src/CapsuleBufferGeometry.js
  7. * @param options the constructors options used to shape the mesh.
  8. * @returns the capsule VertexData
  9. * @see https://doc.babylonjs.com/features/featuresDeepDive/mesh/creation/set/capsule
  10. */
  11. // eslint-disable-next-line @typescript-eslint/naming-convention
  12. export function CreateCapsuleVertexData(options = {
  13. subdivisions: 2,
  14. tessellation: 16,
  15. height: 1,
  16. radius: 0.25,
  17. capSubdivisions: 6,
  18. }) {
  19. const subdivisions = Math.max(options.subdivisions ? options.subdivisions : 2, 1) | 0;
  20. const tessellation = Math.max(options.tessellation ? options.tessellation : 16, 3) | 0;
  21. const height = Math.max(options.height ? options.height : 1, 0);
  22. const radius = Math.max(options.radius ? options.radius : 0.25, 0);
  23. const capDetail = Math.max(options.capSubdivisions ? options.capSubdivisions : 6, 1) | 0;
  24. const radialSegments = tessellation;
  25. const heightSegments = subdivisions;
  26. const radiusTop = Math.max(options.radiusTop ? options.radiusTop : radius, 0);
  27. const radiusBottom = Math.max(options.radiusBottom ? options.radiusBottom : radius, 0);
  28. const heightMinusCaps = height - (radiusTop + radiusBottom);
  29. const thetaStart = 0.0;
  30. const thetaLength = 2.0 * Math.PI;
  31. const capsTopSegments = Math.max(options.topCapSubdivisions ? options.topCapSubdivisions : capDetail, 1);
  32. const capsBottomSegments = Math.max(options.bottomCapSubdivisions ? options.bottomCapSubdivisions : capDetail, 1);
  33. const alpha = Math.acos((radiusBottom - radiusTop) / height);
  34. let indices = [];
  35. const vertices = [];
  36. const normals = [];
  37. const uvs = [];
  38. let index = 0;
  39. const indexArray = [], halfHeight = heightMinusCaps * 0.5;
  40. const pi2 = Math.PI * 0.5;
  41. let x, y;
  42. const normal = Vector3.Zero();
  43. const vertex = Vector3.Zero();
  44. const cosAlpha = Math.cos(alpha);
  45. const sinAlpha = Math.sin(alpha);
  46. const coneLength = new Vector2(radiusTop * sinAlpha, halfHeight + radiusTop * cosAlpha)
  47. .subtract(new Vector2(radiusBottom * sinAlpha, -halfHeight + radiusBottom * cosAlpha))
  48. .length();
  49. // Total length for v texture coord
  50. const vl = radiusTop * alpha + coneLength + radiusBottom * (pi2 - alpha);
  51. let v = 0;
  52. for (y = 0; y <= capsTopSegments; y++) {
  53. const indexRow = [];
  54. const a = pi2 - alpha * (y / capsTopSegments);
  55. v += (radiusTop * alpha) / capsTopSegments;
  56. const cosA = Math.cos(a);
  57. const sinA = Math.sin(a);
  58. // calculate the radius of the current row
  59. const _radius = cosA * radiusTop;
  60. for (x = 0; x <= radialSegments; x++) {
  61. const u = x / radialSegments;
  62. const theta = u * thetaLength + thetaStart;
  63. const sinTheta = Math.sin(theta);
  64. const cosTheta = Math.cos(theta);
  65. // vertex
  66. vertex.x = _radius * sinTheta;
  67. vertex.y = halfHeight + sinA * radiusTop;
  68. vertex.z = _radius * cosTheta;
  69. vertices.push(vertex.x, vertex.y, vertex.z);
  70. // normal
  71. normal.set(cosA * sinTheta, sinA, cosA * cosTheta);
  72. normals.push(normal.x, normal.y, normal.z);
  73. // uv
  74. uvs.push(u, CompatibilityOptions.UseOpenGLOrientationForUV ? v / vl : 1 - v / vl);
  75. // save index of vertex in respective row
  76. indexRow.push(index);
  77. // increase index
  78. index++;
  79. }
  80. // now save vertices of the row in our index array
  81. indexArray.push(indexRow);
  82. }
  83. const coneHeight = height - radiusTop - radiusBottom + cosAlpha * radiusTop - cosAlpha * radiusBottom;
  84. const slope = (sinAlpha * (radiusBottom - radiusTop)) / coneHeight;
  85. for (y = 1; y <= heightSegments; y++) {
  86. const indexRow = [];
  87. v += coneLength / heightSegments;
  88. // calculate the radius of the current row
  89. const _radius = sinAlpha * ((y * (radiusBottom - radiusTop)) / heightSegments + radiusTop);
  90. for (x = 0; x <= radialSegments; x++) {
  91. const u = x / radialSegments;
  92. const theta = u * thetaLength + thetaStart;
  93. const sinTheta = Math.sin(theta);
  94. const cosTheta = Math.cos(theta);
  95. // vertex
  96. vertex.x = _radius * sinTheta;
  97. vertex.y = halfHeight + cosAlpha * radiusTop - (y * coneHeight) / heightSegments;
  98. vertex.z = _radius * cosTheta;
  99. vertices.push(vertex.x, vertex.y, vertex.z);
  100. // normal
  101. normal.set(sinTheta, slope, cosTheta).normalize();
  102. normals.push(normal.x, normal.y, normal.z);
  103. // uv
  104. uvs.push(u, CompatibilityOptions.UseOpenGLOrientationForUV ? v / vl : 1 - v / vl);
  105. // save index of vertex in respective row
  106. indexRow.push(index);
  107. // increase index
  108. index++;
  109. }
  110. // now save vertices of the row in our index array
  111. indexArray.push(indexRow);
  112. }
  113. for (y = 1; y <= capsBottomSegments; y++) {
  114. const indexRow = [];
  115. const a = pi2 - alpha - (Math.PI - alpha) * (y / capsBottomSegments);
  116. v += (radiusBottom * alpha) / capsBottomSegments;
  117. const cosA = Math.cos(a);
  118. const sinA = Math.sin(a);
  119. // calculate the radius of the current row
  120. const _radius = cosA * radiusBottom;
  121. for (x = 0; x <= radialSegments; x++) {
  122. const u = x / radialSegments;
  123. const theta = u * thetaLength + thetaStart;
  124. const sinTheta = Math.sin(theta);
  125. const cosTheta = Math.cos(theta);
  126. // vertex
  127. vertex.x = _radius * sinTheta;
  128. vertex.y = -halfHeight + sinA * radiusBottom;
  129. vertex.z = _radius * cosTheta;
  130. vertices.push(vertex.x, vertex.y, vertex.z);
  131. // normal
  132. normal.set(cosA * sinTheta, sinA, cosA * cosTheta);
  133. normals.push(normal.x, normal.y, normal.z);
  134. // uv
  135. uvs.push(u, CompatibilityOptions.UseOpenGLOrientationForUV ? v / vl : 1 - v / vl);
  136. // save index of vertex in respective row
  137. indexRow.push(index);
  138. // increase index
  139. index++;
  140. }
  141. // now save vertices of the row in our index array
  142. indexArray.push(indexRow);
  143. }
  144. // generate indices
  145. for (x = 0; x < radialSegments; x++) {
  146. for (y = 0; y < capsTopSegments + heightSegments + capsBottomSegments; y++) {
  147. // we use the index array to access the correct indices
  148. const i1 = indexArray[y][x];
  149. const i2 = indexArray[y + 1][x];
  150. const i3 = indexArray[y + 1][x + 1];
  151. const i4 = indexArray[y][x + 1];
  152. // face one
  153. indices.push(i1);
  154. indices.push(i2);
  155. indices.push(i4);
  156. // face two
  157. indices.push(i2);
  158. indices.push(i3);
  159. indices.push(i4);
  160. }
  161. }
  162. indices = indices.reverse();
  163. if (options.orientation && !options.orientation.equals(Vector3.Up())) {
  164. const m = new Matrix();
  165. options.orientation
  166. .clone()
  167. .scale(Math.PI * 0.5)
  168. .cross(Vector3.Up())
  169. .toQuaternion()
  170. .toRotationMatrix(m);
  171. const v = Vector3.Zero();
  172. for (let i = 0; i < vertices.length; i += 3) {
  173. v.set(vertices[i], vertices[i + 1], vertices[i + 2]);
  174. Vector3.TransformCoordinatesToRef(v.clone(), m, v);
  175. vertices[i] = v.x;
  176. vertices[i + 1] = v.y;
  177. vertices[i + 2] = v.z;
  178. }
  179. }
  180. const vDat = new VertexData();
  181. vDat.positions = vertices;
  182. vDat.normals = normals;
  183. vDat.uvs = uvs;
  184. vDat.indices = indices;
  185. return vDat;
  186. }
  187. /**
  188. * Creates a capsule or a pill mesh
  189. * @param name defines the name of the mesh
  190. * @param options The constructors options.
  191. * @param scene The scene the mesh is scoped to.
  192. * @returns Capsule Mesh
  193. */
  194. // eslint-disable-next-line @typescript-eslint/naming-convention
  195. export function CreateCapsule(name, options = {
  196. orientation: Vector3.Up(),
  197. subdivisions: 2,
  198. tessellation: 16,
  199. height: 1,
  200. radius: 0.25,
  201. capSubdivisions: 6,
  202. updatable: false,
  203. }, scene = null) {
  204. const capsule = new Mesh(name, scene);
  205. const vertexData = CreateCapsuleVertexData(options);
  206. vertexData.applyToMesh(capsule, options.updatable);
  207. return capsule;
  208. }
  209. /**
  210. * Class containing static functions to help procedurally build meshes
  211. * @deprecated please use CreateCapsule directly
  212. */
  213. // eslint-disable-next-line @typescript-eslint/naming-convention
  214. export const CapsuleBuilder = {
  215. // eslint-disable-next-line @typescript-eslint/naming-convention
  216. CreateCapsule,
  217. };
  218. /**
  219. * Creates a capsule or a pill mesh
  220. * @param name defines the name of the mesh.
  221. * @param options the constructors options used to shape the mesh.
  222. * @param scene defines the scene the mesh is scoped to.
  223. * @returns the capsule mesh
  224. * @see https://doc.babylonjs.com/how_to/capsule_shape
  225. */
  226. Mesh.CreateCapsule = (name, options, scene) => {
  227. return CreateCapsule(name, options, scene);
  228. };
  229. VertexData.CreateCapsule = CreateCapsuleVertexData;
  230. //# sourceMappingURL=capsuleBuilder.js.map