UNPKG

@babylonjs/core

Version:

Getting started? Play directly with the Babylon.js API using our [playground](https://playground.babylonjs.com/). It also contains a lot of samples to learn how to use it.

379 lines 14.7 kB
import { Vector3, Vector2 } from "../../Maths/math.vector.js"; import { Mesh } from "../mesh.js"; import { VertexData } from "../mesh.vertexData.js"; import { useOpenGLOrientationForUV } 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, // v0-3 0, -1, -t, 0, 1, -t, 0, -1, t, 0, 1, t, // v4-7 t, 0, 1, t, 0, -1, -t, 0, 1, -t, 0, -1, // v8-11 ]; // index of 3 vertex makes a face of icopshere const icoIndices = [ 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 verticesUnaliasId = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // vertex alias 0, // 12: 0 + 12 2, // 13: 2 + 11 3, // 14: 3 + 11 3, // 15: 3 + 12 3, // 16: 3 + 13 4, // 17: 4 + 13 7, // 18: 7 + 11 8, // 19: 8 + 11 9, // 20: 9 + 11 9, // 21: 9 + 12 10, // 22: A + 12 11, // 23: B + 12 ]; // uv as integer step (not pixels !) const icoVertexuv = [ 5, 1, 3, 1, 6, 4, 0, 0, // v0-3 5, 3, 4, 2, 2, 2, 4, 0, // v4-7 2, 0, 1, 1, 6, 0, 6, 2, // v8-11 // vertex alias (for same vertex on different faces) 0, 4, // 12: 0 + 12 3, 3, // 13: 2 + 11 4, 4, // 14: 3 + 11 3, 1, // 15: 3 + 12 4, 2, // 16: 3 + 13 4, 4, // 17: 4 + 13 0, 2, // 18: 7 + 11 1, 1, // 19: 8 + 11 2, 2, // 20: 9 + 11 3, 3, // 21: 9 + 12 1, 3, // 22: A + 12 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 islandUoffset = -40 / 1024; const islandVoffset = +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 - 4 0, 0, 1, 1, 0, // 5 - 9 0, 0, 1, 1, 0, // 10 - 14 0, 1, 1, 1, 0, // 15 - 19 ]; const indices = []; const positions = []; const normals = []; const uvs = []; let currentIndice = 0; // prepare array of 3 vector (empty) (to be worked in place, shared for each face) const faceVertexPos = new Array(3); const faceVertexUv = new Array(3); let v012; for (v012 = 0; v012 < 3; v012++) { faceVertexPos[v012] = Vector3.Zero(); faceVertexUv[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 vId = icoIndices[3 * face + v012]; // vertex have 3D position (x,y,z) faceVertexPos[v012].copyFromFloats(icoVertices[3 * verticesUnaliasId[vId]], icoVertices[3 * verticesUnaliasId[vId] + 1], icoVertices[3 * verticesUnaliasId[vId] + 2]); // Normalize to get normal faceVertexPos[v012].normalize(); // uv Coordinates from vertex ID faceVertexUv[v012].copyFromFloats(icoVertexuv[2 * vId] * ustep + uoffset + island[face] * islandUoffset, icoVertexuv[2 * vId + 1] * vstep + voffset + island[face] * islandVoffset); } // 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 interpVertex = (i1, i2, c1, c2) => { // vertex is interpolated from // - face_vertex_pos[0..2] // - face_vertex_uv[0..2] const posX0 = Vector3.Lerp(faceVertexPos[0], faceVertexPos[2], i2 / subdivisions); const posX1 = Vector3.Lerp(faceVertexPos[1], faceVertexPos[2], i2 / subdivisions); const posInterp = subdivisions === i2 ? faceVertexPos[2] : Vector3.Lerp(posX0, posX1, i1 / (subdivisions - i2)); posInterp.normalize(); let vertexNormal; if (flat) { // in flat mode, recalculate normal as face centroid normal const centroidX0 = Vector3.Lerp(faceVertexPos[0], faceVertexPos[2], c2 / subdivisions); const centroidX1 = Vector3.Lerp(faceVertexPos[1], faceVertexPos[2], c2 / subdivisions); vertexNormal = Vector3.Lerp(centroidX0, centroidX1, c1 / (subdivisions - c2)); } else { // in smooth mode, recalculate normal from each single vertex position vertexNormal = new Vector3(posInterp.x, posInterp.y, posInterp.z); } // Vertex normal need correction due to X,Y,Z radius scaling vertexNormal.x /= radiusX; vertexNormal.y /= radiusY; vertexNormal.z /= radiusZ; vertexNormal.normalize(); const uvX0 = Vector2.Lerp(faceVertexUv[0], faceVertexUv[2], i2 / subdivisions); const uvX1 = Vector2.Lerp(faceVertexUv[1], faceVertexUv[2], i2 / subdivisions); const uvInterp = subdivisions === i2 ? faceVertexUv[2] : Vector2.Lerp(uvX0, uvX1, i1 / (subdivisions - i2)); positions.push(posInterp.x * radiusX, posInterp.y * radiusY, posInterp.z * radiusZ); normals.push(vertexNormal.x, vertexNormal.y, vertexNormal.z); uvs.push(uvInterp.x, useOpenGLOrientationForUV ? 1.0 - uvInterp.y : uvInterp.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(currentIndice); currentIndice++; }; 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) interpVertex(i1, i2, i1 + 1.0 / 3, i2 + 1.0 / 3); interpVertex(i1 + 1, i2, i1 + 1.0 / 3, i2 + 1.0 / 3); interpVertex(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) interpVertex(i1 + 1, i2, i1 + 2.0 / 3, i2 + 2.0 / 3); interpVertex(i1 + 1, i2 + 1, i1 + 2.0 / 3, i2 + 2.0 / 3); interpVertex(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