icoSphereBuilder.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. import { Vector3, Vector2 } from "../../Maths/math.vector.js";
  2. import { Mesh } from "../mesh.js";
  3. import { VertexData } from "../mesh.vertexData.js";
  4. import { CompatibilityOptions } from "../../Compat/compatibilityOptions.js";
  5. /**
  6. * Creates the VertexData of the IcoSphere
  7. * @param options an object used to set the following optional parameters for the IcoSphere, required but can be empty
  8. * * radius the radius of the IcoSphere, optional default 1
  9. * * radiusX allows stretching in the x direction, optional, default radius
  10. * * radiusY allows stretching in the y direction, optional, default radius
  11. * * radiusZ allows stretching in the z direction, optional, default radius
  12. * * flat when true creates a flat shaded mesh, optional, default true
  13. * * subdivisions increasing the subdivisions increases the number of faces, optional, default 4
  14. * * sideOrientation optional and takes the values : Mesh.FRONTSIDE (default), Mesh.BACKSIDE or Mesh.DOUBLESIDE
  15. * * frontUvs only usable when you create a double-sided mesh, used to choose what parts of the texture image to crop and apply on the front side, optional, default vector4 (0, 0, 1, 1)
  16. * * backUVs only usable when you create a double-sided mesh, used to choose what parts of the texture image to crop and apply on the back side, optional, default vector4 (0, 0, 1, 1)
  17. * @returns the VertexData of the IcoSphere
  18. */
  19. export function CreateIcoSphereVertexData(options) {
  20. const sideOrientation = options.sideOrientation || VertexData.DEFAULTSIDE;
  21. const radius = options.radius || 1;
  22. const flat = options.flat === undefined ? true : options.flat;
  23. const subdivisions = (options.subdivisions || 4) | 0;
  24. const radiusX = options.radiusX || radius;
  25. const radiusY = options.radiusY || radius;
  26. const radiusZ = options.radiusZ || radius;
  27. const t = (1 + Math.sqrt(5)) / 2;
  28. // 12 vertex x,y,z
  29. const icoVertices = [
  30. -1,
  31. t,
  32. -0,
  33. 1,
  34. t,
  35. 0,
  36. -1,
  37. -t,
  38. 0,
  39. 1,
  40. -t,
  41. 0,
  42. 0,
  43. -1,
  44. -t,
  45. 0,
  46. 1,
  47. -t,
  48. 0,
  49. -1,
  50. t,
  51. 0,
  52. 1,
  53. t,
  54. t,
  55. 0,
  56. 1,
  57. t,
  58. 0,
  59. -1,
  60. -t,
  61. 0,
  62. 1,
  63. -t,
  64. 0,
  65. -1, // v8-11
  66. ];
  67. // index of 3 vertex makes a face of icopshere
  68. const ico_indices = [
  69. 0, 11, 5, 0, 5, 1, 0, 1, 7, 0, 7, 10, 12, 22, 23, 1, 5, 20, 5, 11, 4, 23, 22, 13, 22, 18, 6, 7, 1, 8, 14, 21, 4, 14, 4, 2, 16, 13, 6, 15, 6, 19, 3, 8, 9, 4, 21, 5, 13, 17,
  70. 23, 6, 13, 22, 19, 6, 18, 9, 8, 1,
  71. ];
  72. // vertex for uv have aliased position, not for UV
  73. const vertices_unalias_id = [
  74. 0,
  75. 1,
  76. 2,
  77. 3,
  78. 4,
  79. 5,
  80. 6,
  81. 7,
  82. 8,
  83. 9,
  84. 10,
  85. 11,
  86. // vertex alias
  87. 0,
  88. 2,
  89. 3,
  90. 3,
  91. 3,
  92. 4,
  93. 7,
  94. 8,
  95. 9,
  96. 9,
  97. 10,
  98. 11, // 23: B + 12
  99. ];
  100. // uv as integer step (not pixels !)
  101. const ico_vertexuv = [
  102. 5,
  103. 1,
  104. 3,
  105. 1,
  106. 6,
  107. 4,
  108. 0,
  109. 0,
  110. 5,
  111. 3,
  112. 4,
  113. 2,
  114. 2,
  115. 2,
  116. 4,
  117. 0,
  118. 2,
  119. 0,
  120. 1,
  121. 1,
  122. 6,
  123. 0,
  124. 6,
  125. 2,
  126. // vertex alias (for same vertex on different faces)
  127. 0,
  128. 4,
  129. 3,
  130. 3,
  131. 4,
  132. 4,
  133. 3,
  134. 1,
  135. 4,
  136. 2,
  137. 4,
  138. 4,
  139. 0,
  140. 2,
  141. 1,
  142. 1,
  143. 2,
  144. 2,
  145. 3,
  146. 3,
  147. 1,
  148. 3,
  149. 2,
  150. 4, // 23: B + 12
  151. ];
  152. // Vertices[0, 1, ...9, A, B] : position on UV plane
  153. // '+' indicate duplicate position to be fixed (3,9:0,2,3,4,7,8,A,B)
  154. // First island of uv mapping
  155. // v = 4h 3+ 2
  156. // v = 3h 9+ 4
  157. // v = 2h 9+ 5 B
  158. // v = 1h 9 1 0
  159. // v = 0h 3 8 7 A
  160. // u = 0 1 2 3 4 5 6 *a
  161. // Second island of uv mapping
  162. // v = 4h 0+ B+ 4+
  163. // v = 3h A+ 2+
  164. // v = 2h 7+ 6 3+
  165. // v = 1h 8+ 3+
  166. // v = 0h
  167. // u = 0 1 2 3 4 5 6 *a
  168. // Face layout on texture UV mapping
  169. // ============
  170. // \ 4 /\ 16 / ======
  171. // \ / \ / /\ 11 /
  172. // \/ 7 \/ / \ /
  173. // ======= / 10 \/
  174. // /\ 17 /\ =======
  175. // / \ / \ \ 15 /\
  176. // / 8 \/ 12 \ \ / \
  177. // ============ \/ 6 \
  178. // \ 18 /\ ============
  179. // \ / \ \ 5 /\ 0 /
  180. // \/ 13 \ \ / \ /
  181. // ======= \/ 1 \/
  182. // =============
  183. // /\ 19 /\ 2 /\
  184. // / \ / \ / \
  185. // / 14 \/ 9 \/ 3 \
  186. // ===================
  187. // uv step is u:1 or 0.5, v:cos(30)=sqrt(3)/2, ratio approx is 84/97
  188. const ustep = 138 / 1024;
  189. const vstep = 239 / 1024;
  190. const uoffset = 60 / 1024;
  191. const voffset = 26 / 1024;
  192. // Second island should have margin, not to touch the first island
  193. // avoid any borderline artefact in pixel rounding
  194. const island_u_offset = -40 / 1024;
  195. const island_v_offset = +20 / 1024;
  196. // face is either island 0 or 1 :
  197. // second island is for faces : [4, 7, 8, 12, 13, 16, 17, 18]
  198. const island = [
  199. 0,
  200. 0,
  201. 0,
  202. 0,
  203. 1,
  204. 0,
  205. 0,
  206. 1,
  207. 1,
  208. 0,
  209. 0,
  210. 0,
  211. 1,
  212. 1,
  213. 0,
  214. 0,
  215. 1,
  216. 1,
  217. 1,
  218. 0, // 15 - 19
  219. ];
  220. const indices = [];
  221. const positions = [];
  222. const normals = [];
  223. const uvs = [];
  224. let current_indice = 0;
  225. // prepare array of 3 vector (empty) (to be worked in place, shared for each face)
  226. const face_vertex_pos = new Array(3);
  227. const face_vertex_uv = new Array(3);
  228. let v012;
  229. for (v012 = 0; v012 < 3; v012++) {
  230. face_vertex_pos[v012] = Vector3.Zero();
  231. face_vertex_uv[v012] = Vector2.Zero();
  232. }
  233. // create all with normals
  234. for (let face = 0; face < 20; face++) {
  235. // 3 vertex per face
  236. for (v012 = 0; v012 < 3; v012++) {
  237. // look up vertex 0,1,2 to its index in 0 to 11 (or 23 including alias)
  238. const v_id = ico_indices[3 * face + v012];
  239. // vertex have 3D position (x,y,z)
  240. face_vertex_pos[v012].copyFromFloats(icoVertices[3 * vertices_unalias_id[v_id]], icoVertices[3 * vertices_unalias_id[v_id] + 1], icoVertices[3 * vertices_unalias_id[v_id] + 2]);
  241. // Normalize to get normal
  242. face_vertex_pos[v012].normalize();
  243. // uv Coordinates from vertex ID
  244. face_vertex_uv[v012].copyFromFloats(ico_vertexuv[2 * v_id] * ustep + uoffset + island[face] * island_u_offset, ico_vertexuv[2 * v_id + 1] * vstep + voffset + island[face] * island_v_offset);
  245. }
  246. // Subdivide the face (interpolate pos, norm, uv)
  247. // - pos is linear interpolation, then projected to sphere (converge polyhedron to sphere)
  248. // - norm is linear interpolation of vertex corner normal
  249. // (to be checked if better to re-calc from face vertex, or if approximation is OK ??? )
  250. // - uv is linear interpolation
  251. //
  252. // Topology is as below for sub-divide by 2
  253. // vertex shown as v0,v1,v2
  254. // interp index is i1 to progress in range [v0,v1[
  255. // interp index is i2 to progress in range [v0,v2[
  256. // face index as (i1,i2) for /\ : (i1,i2),(i1+1,i2),(i1,i2+1)
  257. // and (i1,i2)' for \/ : (i1+1,i2),(i1+1,i2+1),(i1,i2+1)
  258. //
  259. //
  260. // i2 v2
  261. // ^ ^
  262. // / / \
  263. // / / \
  264. // / / \
  265. // / / (0,1) \
  266. // / #---------\
  267. // / / \ (0,0)'/ \
  268. // / / \ / \
  269. // / / \ / \
  270. // / / (0,0) \ / (1,0) \
  271. // / #---------#---------\
  272. // v0 v1
  273. //
  274. // --------------------> i1
  275. //
  276. // interp of (i1,i2):
  277. // along i2 : x0=lerp(v0,v2, i2/S) <---> x1=lerp(v1,v2, i2/S)
  278. // along i1 : lerp(x0,x1, i1/(S-i2))
  279. //
  280. // centroid of triangle is needed to get help normal computation
  281. // (c1,c2) are used for centroid location
  282. const interp_vertex = (i1, i2, c1, c2) => {
  283. // vertex is interpolated from
  284. // - face_vertex_pos[0..2]
  285. // - face_vertex_uv[0..2]
  286. const pos_x0 = Vector3.Lerp(face_vertex_pos[0], face_vertex_pos[2], i2 / subdivisions);
  287. const pos_x1 = Vector3.Lerp(face_vertex_pos[1], face_vertex_pos[2], i2 / subdivisions);
  288. const pos_interp = subdivisions === i2 ? face_vertex_pos[2] : Vector3.Lerp(pos_x0, pos_x1, i1 / (subdivisions - i2));
  289. pos_interp.normalize();
  290. let vertex_normal;
  291. if (flat) {
  292. // in flat mode, recalculate normal as face centroid normal
  293. const centroid_x0 = Vector3.Lerp(face_vertex_pos[0], face_vertex_pos[2], c2 / subdivisions);
  294. const centroid_x1 = Vector3.Lerp(face_vertex_pos[1], face_vertex_pos[2], c2 / subdivisions);
  295. vertex_normal = Vector3.Lerp(centroid_x0, centroid_x1, c1 / (subdivisions - c2));
  296. }
  297. else {
  298. // in smooth mode, recalculate normal from each single vertex position
  299. vertex_normal = new Vector3(pos_interp.x, pos_interp.y, pos_interp.z);
  300. }
  301. // Vertex normal need correction due to X,Y,Z radius scaling
  302. vertex_normal.x /= radiusX;
  303. vertex_normal.y /= radiusY;
  304. vertex_normal.z /= radiusZ;
  305. vertex_normal.normalize();
  306. const uv_x0 = Vector2.Lerp(face_vertex_uv[0], face_vertex_uv[2], i2 / subdivisions);
  307. const uv_x1 = Vector2.Lerp(face_vertex_uv[1], face_vertex_uv[2], i2 / subdivisions);
  308. const uv_interp = subdivisions === i2 ? face_vertex_uv[2] : Vector2.Lerp(uv_x0, uv_x1, i1 / (subdivisions - i2));
  309. positions.push(pos_interp.x * radiusX, pos_interp.y * radiusY, pos_interp.z * radiusZ);
  310. normals.push(vertex_normal.x, vertex_normal.y, vertex_normal.z);
  311. uvs.push(uv_interp.x, CompatibilityOptions.UseOpenGLOrientationForUV ? 1.0 - uv_interp.y : uv_interp.y);
  312. // push each vertex has member of a face
  313. // Same vertex can belong to multiple face, it is pushed multiple time (duplicate vertex are present)
  314. indices.push(current_indice);
  315. current_indice++;
  316. };
  317. for (let i2 = 0; i2 < subdivisions; i2++) {
  318. for (let i1 = 0; i1 + i2 < subdivisions; i1++) {
  319. // face : (i1,i2) for /\ :
  320. // interp for : (i1,i2),(i1+1,i2),(i1,i2+1)
  321. interp_vertex(i1, i2, i1 + 1.0 / 3, i2 + 1.0 / 3);
  322. interp_vertex(i1 + 1, i2, i1 + 1.0 / 3, i2 + 1.0 / 3);
  323. interp_vertex(i1, i2 + 1, i1 + 1.0 / 3, i2 + 1.0 / 3);
  324. if (i1 + i2 + 1 < subdivisions) {
  325. // face : (i1,i2)' for \/ :
  326. // interp for (i1+1,i2),(i1+1,i2+1),(i1,i2+1)
  327. interp_vertex(i1 + 1, i2, i1 + 2.0 / 3, i2 + 2.0 / 3);
  328. interp_vertex(i1 + 1, i2 + 1, i1 + 2.0 / 3, i2 + 2.0 / 3);
  329. interp_vertex(i1, i2 + 1, i1 + 2.0 / 3, i2 + 2.0 / 3);
  330. }
  331. }
  332. }
  333. }
  334. // Sides
  335. VertexData._ComputeSides(sideOrientation, positions, indices, normals, uvs, options.frontUVs, options.backUVs);
  336. // Result
  337. const vertexData = new VertexData();
  338. vertexData.indices = indices;
  339. vertexData.positions = positions;
  340. vertexData.normals = normals;
  341. vertexData.uvs = uvs;
  342. return vertexData;
  343. }
  344. /**
  345. * Creates a sphere based upon an icosahedron with 20 triangular faces which can be subdivided
  346. * * The parameter `radius` sets the radius size (float) of the icosphere (default 1)
  347. * * You can set some different icosphere dimensions, for instance to build an ellipsoid, by using the parameters `radiusX`, `radiusY` and `radiusZ` (all by default have the same value of `radius`)
  348. * * The parameter `subdivisions` sets the number of subdivisions (positive integer, default 4). The more subdivisions, the more faces on the icosphere whatever its size
  349. * * The parameter `flat` (boolean, default true) gives each side its own normals. Set it to false to get a smooth continuous light reflection on the surface
  350. * * You can also set the mesh side orientation with the values : BABYLON.Mesh.FRONTSIDE (default), BABYLON.Mesh.BACKSIDE or BABYLON.Mesh.DOUBLESIDE
  351. * * If you create a double-sided mesh, you can choose what parts of the texture image to crop and stick respectively on the front and the back sides with the parameters `frontUVs` and `backUVs` (Vector4). Detail here : https://doc.babylonjs.com/features/featuresDeepDive/mesh/creation/set#side-orientation
  352. * * The mesh can be set to updatable with the boolean parameter `updatable` (default false) if its internal geometry is supposed to change once created
  353. * @param name defines the name of the mesh
  354. * @param options defines the options used to create the mesh
  355. * @param scene defines the hosting scene
  356. * @returns the icosahedron mesh
  357. * @see https://doc.babylonjs.com/features/featuresDeepDive/mesh/creation/polyhedra#icosphere
  358. */
  359. export function CreateIcoSphere(name, options = {}, scene = null) {
  360. const sphere = new Mesh(name, scene);
  361. options.sideOrientation = Mesh._GetDefaultSideOrientation(options.sideOrientation);
  362. sphere._originalBuilderSideOrientation = options.sideOrientation;
  363. const vertexData = CreateIcoSphereVertexData(options);
  364. vertexData.applyToMesh(sphere, options.updatable);
  365. return sphere;
  366. }
  367. /**
  368. * Class containing static functions to help procedurally build meshes
  369. * @deprecated use the function directly from the module
  370. */
  371. export const IcoSphereBuilder = {
  372. // eslint-disable-next-line @typescript-eslint/naming-convention
  373. CreateIcoSphere,
  374. };
  375. VertexData.CreateIcoSphere = CreateIcoSphereVertexData;
  376. Mesh.CreateIcoSphere = (name, options, scene) => {
  377. return CreateIcoSphere(name, options, scene);
  378. };
  379. //# sourceMappingURL=icoSphereBuilder.js.map