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.

399 lines 20.2 kB
import { TmpVectors } from "../../Maths/math.vector.js"; import { Mesh, _CreationDataStorage } from "../mesh.js"; import { VertexBuffer } from "../../Buffers/buffer.js"; import { VertexData } from "../mesh.vertexData.js"; import { useOpenGLOrientationForUV } from "../../Compat/compatibilityOptions.js"; /** * Creates the VertexData for a Ribbon * @param options an object used to set the following optional parameters for the ribbon, required but can be empty * * pathArray array of paths, each of which an array of successive Vector3 * * closeArray creates a seam between the first and the last paths of the pathArray, optional, default false * * closePath creates a seam between the first and the last points of each path of the path array, optional, default false * * offset a positive integer, only used when pathArray contains a single path (offset = 10 means the point 1 is joined to the point 11), default rounded half size of the pathArray length * * 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) * * invertUV swaps in the U and V coordinates when applying a texture, optional, default false * * uvs a linear array, of length 2 * number of vertices, of custom UV values, optional * * colors a linear array, of length 4 * number of vertices, of custom color values, optional * @returns the VertexData of the ribbon */ export function CreateRibbonVertexData(options) { let pathArray = options.pathArray; const closeArray = options.closeArray || false; const closePath = options.closePath || false; const invertUV = options.invertUV || false; const defaultOffset = Math.floor(pathArray[0].length / 2); let offset = options.offset || defaultOffset; offset = offset > defaultOffset ? defaultOffset : Math.floor(offset); // offset max allowed : defaultOffset const sideOrientation = options.sideOrientation === 0 ? 0 : options.sideOrientation || VertexData.DEFAULTSIDE; const customUV = options.uvs; const customColors = options.colors; const positions = []; const indices = []; const normals = []; const uvs = []; const us = []; // us[path_id] = [uDist1, uDist2, uDist3 ... ] distances between points on path path_id const vs = []; // vs[i] = [vDist1, vDist2, vDist3, ... ] distances between points i of consecutive paths from pathArray const uTotalDistance = []; // uTotalDistance[p] : total distance of path p const vTotalDistance = []; // vTotalDistance[i] : total distance between points i of first and last path from pathArray let minlg; // minimal length among all paths from pathArray const lg = []; // array of path lengths : nb of vertex per path const idx = []; // array of path indexes : index of each path (first vertex) in the total vertex number let p; // path iterator let i; // point iterator let j; // point iterator // if single path in pathArray if (pathArray.length < 2) { const ar1 = []; const ar2 = []; for (i = 0; i < pathArray[0].length - offset; i++) { ar1.push(pathArray[0][i]); ar2.push(pathArray[0][i + offset]); } pathArray = [ar1, ar2]; } // positions and horizontal distances (u) let idc = 0; const closePathCorr = closePath ? 1 : 0; // the final index will be +1 if closePath const closeArrayCorr = closeArray ? 1 : 0; let path; let l; minlg = pathArray[0].length; let vectlg; let dist; for (p = 0; p < pathArray.length + closeArrayCorr; p++) { uTotalDistance[p] = 0; us[p] = [0]; path = p === pathArray.length ? pathArray[0] : pathArray[p]; l = path.length; minlg = minlg < l ? minlg : l; j = 0; while (j < l) { positions.push(path[j].x, path[j].y, path[j].z); if (j > 0) { vectlg = path[j].subtract(path[j - 1]).length(); dist = vectlg + uTotalDistance[p]; us[p].push(dist); uTotalDistance[p] = dist; } j++; } if (closePath) { // an extra hidden vertex is added in the "positions" array j--; positions.push(path[0].x, path[0].y, path[0].z); vectlg = path[j].subtract(path[0]).length(); dist = vectlg + uTotalDistance[p]; us[p].push(dist); uTotalDistance[p] = dist; } lg[p] = l + closePathCorr; idx[p] = idc; idc += l + closePathCorr; } // vertical distances (v) let path1; let path2; let vertex1 = null; let vertex2 = null; for (i = 0; i < minlg + closePathCorr; i++) { vTotalDistance[i] = 0; vs[i] = [0]; for (p = 0; p < pathArray.length - 1 + closeArrayCorr; p++) { path1 = pathArray[p]; path2 = p === pathArray.length - 1 ? pathArray[0] : pathArray[p + 1]; if (i === minlg) { // closePath vertex1 = path1[0]; vertex2 = path2[0]; } else { vertex1 = path1[i]; vertex2 = path2[i]; } vectlg = vertex2.subtract(vertex1).length(); dist = vectlg + vTotalDistance[i]; vs[i].push(dist); vTotalDistance[i] = dist; } } // uvs let u; let v; if (customUV) { for (p = 0; p < customUV.length; p++) { uvs.push(customUV[p].x, useOpenGLOrientationForUV ? 1.0 - customUV[p].y : customUV[p].y); } } else { for (p = 0; p < pathArray.length + closeArrayCorr; p++) { for (i = 0; i < minlg + closePathCorr; i++) { u = uTotalDistance[p] != 0.0 ? us[p][i] / uTotalDistance[p] : 0.0; v = vTotalDistance[i] != 0.0 ? vs[i][p] / vTotalDistance[i] : 0.0; if (invertUV) { uvs.push(v, u); } else { uvs.push(u, useOpenGLOrientationForUV ? 1.0 - v : v); } } } } // indices p = 0; // path index let pi = 0; // positions array index let l1 = lg[p] - 1; // path1 length let l2 = lg[p + 1] - 1; // path2 length let min = l1 < l2 ? l1 : l2; // current path stop index let shft = idx[1] - idx[0]; // shift const path1nb = lg.length - 1; // number of path1 to iterate on while (pi <= min && p < path1nb) { // stay under min and don't go over next to last path // draw two triangles between path1 (p1) and path2 (p2) : (p1.pi, p2.pi, p1.pi+1) and (p2.pi+1, p1.pi+1, p2.pi) clockwise indices.push(pi, pi + shft, pi + 1); indices.push(pi + shft + 1, pi + 1, pi + shft); pi += 1; if (pi === min) { // if end of one of two consecutive paths reached, go to next existing path p++; shft = idx[p + 1] - idx[p]; l1 = lg[p] - 1; l2 = lg[p + 1] - 1; pi = idx[p]; min = l1 < l2 ? l1 + pi : l2 + pi; } } // normals VertexData.ComputeNormals(positions, indices, normals); if (closePath) { // update both the first and last vertex normals to their average value let indexFirst = 0; let indexLast = 0; for (p = 0; p < pathArray.length; p++) { indexFirst = idx[p] * 3; if (p + 1 < pathArray.length) { indexLast = (idx[p + 1] - 1) * 3; } else { indexLast = normals.length - 3; } normals[indexFirst] = (normals[indexFirst] + normals[indexLast]) * 0.5; normals[indexFirst + 1] = (normals[indexFirst + 1] + normals[indexLast + 1]) * 0.5; normals[indexFirst + 2] = (normals[indexFirst + 2] + normals[indexLast + 2]) * 0.5; const l = Math.sqrt(normals[indexFirst] * normals[indexFirst] + normals[indexFirst + 1] * normals[indexFirst + 1] + normals[indexFirst + 2] * normals[indexFirst + 2]); normals[indexFirst] /= l; normals[indexFirst + 1] /= l; normals[indexFirst + 2] /= l; normals[indexLast] = normals[indexFirst]; normals[indexLast + 1] = normals[indexFirst + 1]; normals[indexLast + 2] = normals[indexFirst + 2]; } } if (closeArray) { let indexFirst = idx[0] * 3; let indexLast = idx[pathArray.length] * 3; for (i = 0; i < minlg + closePathCorr; i++) { normals[indexFirst] = (normals[indexFirst] + normals[indexLast]) * 0.5; normals[indexFirst + 1] = (normals[indexFirst + 1] + normals[indexLast + 1]) * 0.5; normals[indexFirst + 2] = (normals[indexFirst + 2] + normals[indexLast + 2]) * 0.5; const l = Math.sqrt(normals[indexFirst] * normals[indexFirst] + normals[indexFirst + 1] * normals[indexFirst + 1] + normals[indexFirst + 2] * normals[indexFirst + 2]); normals[indexFirst] /= l; normals[indexFirst + 1] /= l; normals[indexFirst + 2] /= l; normals[indexLast] = normals[indexFirst]; normals[indexLast + 1] = normals[indexFirst + 1]; normals[indexLast + 2] = normals[indexFirst + 2]; indexFirst += 3; indexLast += 3; } } // sides VertexData._ComputeSides(sideOrientation, positions, indices, normals, uvs, options.frontUVs, options.backUVs); // Colors let colors = null; if (customColors) { colors = new Float32Array(customColors.length * 4); for (let c = 0; c < customColors.length; c++) { colors[c * 4] = customColors[c].r; colors[c * 4 + 1] = customColors[c].g; colors[c * 4 + 2] = customColors[c].b; colors[c * 4 + 3] = customColors[c].a; } } // Result const vertexData = new VertexData(); const positions32 = new Float32Array(positions); const normals32 = new Float32Array(normals); const uvs32 = new Float32Array(uvs); vertexData.indices = indices; vertexData.positions = positions32; vertexData.normals = normals32; vertexData.uvs = uvs32; if (colors) { vertexData.set(colors, VertexBuffer.ColorKind); } if (closePath) { vertexData._idx = idx; } return vertexData; } /** * Creates a ribbon mesh. The ribbon is a parametric shape. It has no predefined shape. Its final shape will depend on the input parameters * * The parameter `pathArray` is a required array of paths, what are each an array of successive Vector3. The pathArray parameter depicts the ribbon geometry * * The parameter `closeArray` (boolean, default false) creates a seam between the first and the last paths of the path array * * The parameter `closePath` (boolean, default false) creates a seam between the first and the last points of each path of the path array * * The parameter `offset` (positive integer, default : rounded half size of the pathArray length), is taken in account only if the `pathArray` is containing a single path * * It's the offset to join the points from the same path. Ex : offset = 10 means the point 1 is joined to the point 11 * * The optional parameter `instance` is an instance of an existing Ribbon object to be updated with the passed `pathArray` parameter : https://doc.babylonjs.com/features/featuresDeepDive/mesh/dynamicMeshMorph#ribbon * * 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 optional parameter `invertUV` (boolean, default false) swaps in the geometry the U and V coordinates to apply a texture * * The parameter `uvs` is an optional flat array of `Vector2` to update/set each ribbon vertex with its own custom UV values instead of the computed ones * * The parameters `colors` is an optional flat array of `Color4` to set/update each ribbon vertex with its own custom color values * * Note that if you use the parameters `uvs` or `colors`, the passed arrays must be populated with the right number of elements, it is to say the number of ribbon vertices. Remember that if you set `closePath` to `true`, there's one extra vertex per path in the geometry * * Moreover, you can use the parameter `color` with `instance` (to update the ribbon), only if you previously used it at creation time * * 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 ribbon mesh * @see https://doc.babylonjs.com/features/featuresDeepDive/mesh/creation/param/ribbon_extra * @see https://doc.babylonjs.com/features/featuresDeepDive/mesh/creation/param */ export function CreateRibbon(name, options, scene = null) { const pathArray = options.pathArray; const closeArray = options.closeArray; const closePath = options.closePath; const sideOrientation = Mesh._GetDefaultSideOrientation(options.sideOrientation); const instance = options.instance; const updatable = options.updatable; if (instance) { // existing ribbon instance update // positionFunction : ribbon case // only pathArray and sideOrientation parameters are taken into account for positions update const minimum = TmpVectors.Vector3[0].setAll(Number.MAX_VALUE); const maximum = TmpVectors.Vector3[1].setAll(-Number.MAX_VALUE); const positionFunction = (positions) => { let minlg = pathArray[0].length; const mesh = instance; let i = 0; const ns = mesh._originalBuilderSideOrientation === Mesh.DOUBLESIDE ? 2 : 1; for (let si = 1; si <= ns; ++si) { for (let p = 0; p < pathArray.length; ++p) { const path = pathArray[p]; const l = path.length; minlg = minlg < l ? minlg : l; for (let j = 0; j < minlg; ++j) { const pathPoint = path[j]; positions[i] = pathPoint.x; positions[i + 1] = pathPoint.y; positions[i + 2] = pathPoint.z; minimum.minimizeInPlaceFromFloats(pathPoint.x, pathPoint.y, pathPoint.z); maximum.maximizeInPlaceFromFloats(pathPoint.x, pathPoint.y, pathPoint.z); i += 3; } if (mesh._creationDataStorage && mesh._creationDataStorage.closePath) { const pathPoint = path[0]; positions[i] = pathPoint.x; positions[i + 1] = pathPoint.y; positions[i + 2] = pathPoint.z; i += 3; } } } }; const positions = instance.getVerticesData(VertexBuffer.PositionKind); positionFunction(positions); if (instance.hasBoundingInfo) { instance.getBoundingInfo().reConstruct(minimum, maximum, instance._worldMatrix); } else { instance.buildBoundingInfo(minimum, maximum, instance._worldMatrix); } instance.updateVerticesData(VertexBuffer.PositionKind, positions, false, false); if (options.colors) { const colors = instance.getVerticesData(VertexBuffer.ColorKind); for (let c = 0, colorIndex = 0; c < options.colors.length; c++, colorIndex += 4) { const color = options.colors[c]; colors[colorIndex] = color.r; colors[colorIndex + 1] = color.g; colors[colorIndex + 2] = color.b; colors[colorIndex + 3] = color.a; } instance.updateVerticesData(VertexBuffer.ColorKind, colors, false, false); } if (options.uvs) { const uvs = instance.getVerticesData(VertexBuffer.UVKind); for (let i = 0; i < options.uvs.length; i++) { uvs[i * 2] = options.uvs[i].x; uvs[i * 2 + 1] = useOpenGLOrientationForUV ? 1.0 - options.uvs[i].y : options.uvs[i].y; } instance.updateVerticesData(VertexBuffer.UVKind, uvs, false, false); } if (!instance.areNormalsFrozen || instance.isFacetDataEnabled) { const indices = instance.getIndices(); const normals = instance.getVerticesData(VertexBuffer.NormalKind); const params = instance.isFacetDataEnabled ? instance.getFacetDataParameters() : null; VertexData.ComputeNormals(positions, indices, normals, params); if (instance._creationDataStorage && instance._creationDataStorage.closePath) { let indexFirst = 0; let indexLast = 0; for (let p = 0; p < pathArray.length; p++) { indexFirst = instance._creationDataStorage.idx[p] * 3; if (p + 1 < pathArray.length) { indexLast = (instance._creationDataStorage.idx[p + 1] - 1) * 3; } else { indexLast = normals.length - 3; } normals[indexFirst] = (normals[indexFirst] + normals[indexLast]) * 0.5; normals[indexFirst + 1] = (normals[indexFirst + 1] + normals[indexLast + 1]) * 0.5; normals[indexFirst + 2] = (normals[indexFirst + 2] + normals[indexLast + 2]) * 0.5; normals[indexLast] = normals[indexFirst]; normals[indexLast + 1] = normals[indexFirst + 1]; normals[indexLast + 2] = normals[indexFirst + 2]; } } if (!instance.areNormalsFrozen) { instance.updateVerticesData(VertexBuffer.NormalKind, normals, false, false); } } return instance; } else { // new ribbon creation const ribbon = new Mesh(name, scene); ribbon._originalBuilderSideOrientation = sideOrientation; ribbon._creationDataStorage = new _CreationDataStorage(); const vertexData = CreateRibbonVertexData(options); if (closePath) { ribbon._creationDataStorage.idx = vertexData._idx; } ribbon._creationDataStorage.closePath = closePath; ribbon._creationDataStorage.closeArray = closeArray; vertexData.applyToMesh(ribbon, updatable); return ribbon; } } /** * Class containing static functions to help procedurally build meshes * @deprecated use CreateRibbon directly */ export const RibbonBuilder = { // eslint-disable-next-line @typescript-eslint/naming-convention CreateRibbon, }; VertexData.CreateRibbon = CreateRibbonVertexData; Mesh.CreateRibbon = (name, pathArray, closeArray = false, closePath, offset, scene, updatable = false, sideOrientation, instance) => { return CreateRibbon(name, { pathArray: pathArray, closeArray: closeArray, closePath: closePath, offset: offset, updatable: updatable, sideOrientation: sideOrientation, instance: instance, }, scene); }; //# sourceMappingURL=ribbonBuilder.js.map