123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399 |
- 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 { CompatibilityOptions } 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, CompatibilityOptions.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, CompatibilityOptions.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] = CompatibilityOptions.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
|