123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379 |
- import { Vector3, Vector2 } from "../../Maths/math.vector.js";
- import { Mesh } from "../mesh.js";
- import { VertexData } from "../mesh.vertexData.js";
- import { CompatibilityOptions } from "../../Compat/compatibilityOptions.js";
- /**
- * Creates the VertexData of the IcoSphere
- * @param options an object used to set the following optional parameters for the IcoSphere, required but can be empty
- * * radius the radius of the IcoSphere, optional default 1
- * * radiusX allows stretching in the x direction, optional, default radius
- * * radiusY allows stretching in the y direction, optional, default radius
- * * radiusZ allows stretching in the z direction, optional, default radius
- * * flat when true creates a flat shaded mesh, optional, default true
- * * subdivisions increasing the subdivisions increases the number of faces, optional, default 4
- * * sideOrientation optional and takes the values : Mesh.FRONTSIDE (default), Mesh.BACKSIDE or Mesh.DOUBLESIDE
- * * 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)
- * * 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)
- * @returns the VertexData of the IcoSphere
- */
- export function CreateIcoSphereVertexData(options) {
- const sideOrientation = options.sideOrientation || VertexData.DEFAULTSIDE;
- const radius = options.radius || 1;
- const flat = options.flat === undefined ? true : options.flat;
- const subdivisions = (options.subdivisions || 4) | 0;
- const radiusX = options.radiusX || radius;
- const radiusY = options.radiusY || radius;
- const radiusZ = options.radiusZ || radius;
- const t = (1 + Math.sqrt(5)) / 2;
- // 12 vertex x,y,z
- const icoVertices = [
- -1,
- t,
- -0,
- 1,
- t,
- 0,
- -1,
- -t,
- 0,
- 1,
- -t,
- 0,
- 0,
- -1,
- -t,
- 0,
- 1,
- -t,
- 0,
- -1,
- t,
- 0,
- 1,
- t,
- t,
- 0,
- 1,
- t,
- 0,
- -1,
- -t,
- 0,
- 1,
- -t,
- 0,
- -1, // v8-11
- ];
- // index of 3 vertex makes a face of icopshere
- const ico_indices = [
- 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,
- 23, 6, 13, 22, 19, 6, 18, 9, 8, 1,
- ];
- // vertex for uv have aliased position, not for UV
- const vertices_unalias_id = [
- 0,
- 1,
- 2,
- 3,
- 4,
- 5,
- 6,
- 7,
- 8,
- 9,
- 10,
- 11,
- // vertex alias
- 0,
- 2,
- 3,
- 3,
- 3,
- 4,
- 7,
- 8,
- 9,
- 9,
- 10,
- 11, // 23: B + 12
- ];
- // uv as integer step (not pixels !)
- const ico_vertexuv = [
- 5,
- 1,
- 3,
- 1,
- 6,
- 4,
- 0,
- 0,
- 5,
- 3,
- 4,
- 2,
- 2,
- 2,
- 4,
- 0,
- 2,
- 0,
- 1,
- 1,
- 6,
- 0,
- 6,
- 2,
- // vertex alias (for same vertex on different faces)
- 0,
- 4,
- 3,
- 3,
- 4,
- 4,
- 3,
- 1,
- 4,
- 2,
- 4,
- 4,
- 0,
- 2,
- 1,
- 1,
- 2,
- 2,
- 3,
- 3,
- 1,
- 3,
- 2,
- 4, // 23: B + 12
- ];
- // Vertices[0, 1, ...9, A, B] : position on UV plane
- // '+' indicate duplicate position to be fixed (3,9:0,2,3,4,7,8,A,B)
- // First island of uv mapping
- // v = 4h 3+ 2
- // v = 3h 9+ 4
- // v = 2h 9+ 5 B
- // v = 1h 9 1 0
- // v = 0h 3 8 7 A
- // u = 0 1 2 3 4 5 6 *a
- // Second island of uv mapping
- // v = 4h 0+ B+ 4+
- // v = 3h A+ 2+
- // v = 2h 7+ 6 3+
- // v = 1h 8+ 3+
- // v = 0h
- // u = 0 1 2 3 4 5 6 *a
- // Face layout on texture UV mapping
- // ============
- // \ 4 /\ 16 / ======
- // \ / \ / /\ 11 /
- // \/ 7 \/ / \ /
- // ======= / 10 \/
- // /\ 17 /\ =======
- // / \ / \ \ 15 /\
- // / 8 \/ 12 \ \ / \
- // ============ \/ 6 \
- // \ 18 /\ ============
- // \ / \ \ 5 /\ 0 /
- // \/ 13 \ \ / \ /
- // ======= \/ 1 \/
- // =============
- // /\ 19 /\ 2 /\
- // / \ / \ / \
- // / 14 \/ 9 \/ 3 \
- // ===================
- // uv step is u:1 or 0.5, v:cos(30)=sqrt(3)/2, ratio approx is 84/97
- const ustep = 138 / 1024;
- const vstep = 239 / 1024;
- const uoffset = 60 / 1024;
- const voffset = 26 / 1024;
- // Second island should have margin, not to touch the first island
- // avoid any borderline artefact in pixel rounding
- const island_u_offset = -40 / 1024;
- const island_v_offset = +20 / 1024;
- // face is either island 0 or 1 :
- // second island is for faces : [4, 7, 8, 12, 13, 16, 17, 18]
- const island = [
- 0,
- 0,
- 0,
- 0,
- 1,
- 0,
- 0,
- 1,
- 1,
- 0,
- 0,
- 0,
- 1,
- 1,
- 0,
- 0,
- 1,
- 1,
- 1,
- 0, // 15 - 19
- ];
- const indices = [];
- const positions = [];
- const normals = [];
- const uvs = [];
- let current_indice = 0;
- // prepare array of 3 vector (empty) (to be worked in place, shared for each face)
- const face_vertex_pos = new Array(3);
- const face_vertex_uv = new Array(3);
- let v012;
- for (v012 = 0; v012 < 3; v012++) {
- face_vertex_pos[v012] = Vector3.Zero();
- face_vertex_uv[v012] = Vector2.Zero();
- }
- // create all with normals
- for (let face = 0; face < 20; face++) {
- // 3 vertex per face
- for (v012 = 0; v012 < 3; v012++) {
- // look up vertex 0,1,2 to its index in 0 to 11 (or 23 including alias)
- const v_id = ico_indices[3 * face + v012];
- // vertex have 3D position (x,y,z)
- 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]);
- // Normalize to get normal
- face_vertex_pos[v012].normalize();
- // uv Coordinates from vertex ID
- 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);
- }
- // Subdivide the face (interpolate pos, norm, uv)
- // - pos is linear interpolation, then projected to sphere (converge polyhedron to sphere)
- // - norm is linear interpolation of vertex corner normal
- // (to be checked if better to re-calc from face vertex, or if approximation is OK ??? )
- // - uv is linear interpolation
- //
- // Topology is as below for sub-divide by 2
- // vertex shown as v0,v1,v2
- // interp index is i1 to progress in range [v0,v1[
- // interp index is i2 to progress in range [v0,v2[
- // face index as (i1,i2) for /\ : (i1,i2),(i1+1,i2),(i1,i2+1)
- // and (i1,i2)' for \/ : (i1+1,i2),(i1+1,i2+1),(i1,i2+1)
- //
- //
- // i2 v2
- // ^ ^
- // / / \
- // / / \
- // / / \
- // / / (0,1) \
- // / #---------\
- // / / \ (0,0)'/ \
- // / / \ / \
- // / / \ / \
- // / / (0,0) \ / (1,0) \
- // / #---------#---------\
- // v0 v1
- //
- // --------------------> i1
- //
- // interp of (i1,i2):
- // along i2 : x0=lerp(v0,v2, i2/S) <---> x1=lerp(v1,v2, i2/S)
- // along i1 : lerp(x0,x1, i1/(S-i2))
- //
- // centroid of triangle is needed to get help normal computation
- // (c1,c2) are used for centroid location
- const interp_vertex = (i1, i2, c1, c2) => {
- // vertex is interpolated from
- // - face_vertex_pos[0..2]
- // - face_vertex_uv[0..2]
- const pos_x0 = Vector3.Lerp(face_vertex_pos[0], face_vertex_pos[2], i2 / subdivisions);
- const pos_x1 = Vector3.Lerp(face_vertex_pos[1], face_vertex_pos[2], i2 / subdivisions);
- const pos_interp = subdivisions === i2 ? face_vertex_pos[2] : Vector3.Lerp(pos_x0, pos_x1, i1 / (subdivisions - i2));
- pos_interp.normalize();
- let vertex_normal;
- if (flat) {
- // in flat mode, recalculate normal as face centroid normal
- const centroid_x0 = Vector3.Lerp(face_vertex_pos[0], face_vertex_pos[2], c2 / subdivisions);
- const centroid_x1 = Vector3.Lerp(face_vertex_pos[1], face_vertex_pos[2], c2 / subdivisions);
- vertex_normal = Vector3.Lerp(centroid_x0, centroid_x1, c1 / (subdivisions - c2));
- }
- else {
- // in smooth mode, recalculate normal from each single vertex position
- vertex_normal = new Vector3(pos_interp.x, pos_interp.y, pos_interp.z);
- }
- // Vertex normal need correction due to X,Y,Z radius scaling
- vertex_normal.x /= radiusX;
- vertex_normal.y /= radiusY;
- vertex_normal.z /= radiusZ;
- vertex_normal.normalize();
- const uv_x0 = Vector2.Lerp(face_vertex_uv[0], face_vertex_uv[2], i2 / subdivisions);
- const uv_x1 = Vector2.Lerp(face_vertex_uv[1], face_vertex_uv[2], i2 / subdivisions);
- const uv_interp = subdivisions === i2 ? face_vertex_uv[2] : Vector2.Lerp(uv_x0, uv_x1, i1 / (subdivisions - i2));
- positions.push(pos_interp.x * radiusX, pos_interp.y * radiusY, pos_interp.z * radiusZ);
- normals.push(vertex_normal.x, vertex_normal.y, vertex_normal.z);
- uvs.push(uv_interp.x, CompatibilityOptions.UseOpenGLOrientationForUV ? 1.0 - uv_interp.y : uv_interp.y);
- // push each vertex has member of a face
- // Same vertex can belong to multiple face, it is pushed multiple time (duplicate vertex are present)
- indices.push(current_indice);
- current_indice++;
- };
- for (let i2 = 0; i2 < subdivisions; i2++) {
- for (let i1 = 0; i1 + i2 < subdivisions; i1++) {
- // face : (i1,i2) for /\ :
- // interp for : (i1,i2),(i1+1,i2),(i1,i2+1)
- interp_vertex(i1, i2, i1 + 1.0 / 3, i2 + 1.0 / 3);
- interp_vertex(i1 + 1, i2, i1 + 1.0 / 3, i2 + 1.0 / 3);
- interp_vertex(i1, i2 + 1, i1 + 1.0 / 3, i2 + 1.0 / 3);
- if (i1 + i2 + 1 < subdivisions) {
- // face : (i1,i2)' for \/ :
- // interp for (i1+1,i2),(i1+1,i2+1),(i1,i2+1)
- interp_vertex(i1 + 1, i2, i1 + 2.0 / 3, i2 + 2.0 / 3);
- interp_vertex(i1 + 1, i2 + 1, i1 + 2.0 / 3, i2 + 2.0 / 3);
- interp_vertex(i1, i2 + 1, i1 + 2.0 / 3, i2 + 2.0 / 3);
- }
- }
- }
- }
- // Sides
- VertexData._ComputeSides(sideOrientation, positions, indices, normals, uvs, options.frontUVs, options.backUVs);
- // Result
- const vertexData = new VertexData();
- vertexData.indices = indices;
- vertexData.positions = positions;
- vertexData.normals = normals;
- vertexData.uvs = uvs;
- return vertexData;
- }
- /**
- * Creates a sphere based upon an icosahedron with 20 triangular faces which can be subdivided
- * * The parameter `radius` sets the radius size (float) of the icosphere (default 1)
- * * 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`)
- * * The parameter `subdivisions` sets the number of subdivisions (positive integer, default 4). The more subdivisions, the more faces on the icosphere whatever its size
- * * 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
- * * You can also set the mesh side orientation with the values : BABYLON.Mesh.FRONTSIDE (default), BABYLON.Mesh.BACKSIDE or BABYLON.Mesh.DOUBLESIDE
- * * 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
- * * The mesh can be set to updatable with the boolean parameter `updatable` (default false) if its internal geometry is supposed to change once created
- * @param name defines the name of the mesh
- * @param options defines the options used to create the mesh
- * @param scene defines the hosting scene
- * @returns the icosahedron mesh
- * @see https://doc.babylonjs.com/features/featuresDeepDive/mesh/creation/polyhedra#icosphere
- */
- export function CreateIcoSphere(name, options = {}, scene = null) {
- const sphere = new Mesh(name, scene);
- options.sideOrientation = Mesh._GetDefaultSideOrientation(options.sideOrientation);
- sphere._originalBuilderSideOrientation = options.sideOrientation;
- const vertexData = CreateIcoSphereVertexData(options);
- vertexData.applyToMesh(sphere, options.updatable);
- return sphere;
- }
- /**
- * Class containing static functions to help procedurally build meshes
- * @deprecated use the function directly from the module
- */
- export const IcoSphereBuilder = {
- // eslint-disable-next-line @typescript-eslint/naming-convention
- CreateIcoSphere,
- };
- VertexData.CreateIcoSphere = CreateIcoSphereVertexData;
- Mesh.CreateIcoSphere = (name, options, scene) => {
- return CreateIcoSphere(name, options, scene);
- };
- //# sourceMappingURL=icoSphereBuilder.js.map
|