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.

482 lines 22.5 kB
import { Quaternion, TmpVectors, Vector3 } from "../../Maths/math.vector.js"; import { Mesh } from "../mesh.js"; import { Buffer } from "../../Buffers/buffer.js"; import { DeepCopier } from "../../Misc/deepCopier.js"; import { GreasedLineTools } from "../../Misc/greasedLineTools.js"; import { GreasedLineBaseMesh } from "./greasedLineBaseMesh.js"; Mesh._GreasedLineRibbonMeshParser = (parsedMesh, scene) => { return GreasedLineRibbonMesh.Parse(parsedMesh, scene); }; /** * GreasedLineRibbonMesh * Use the GreasedLineBuilder.CreateGreasedLine function to create an instance of this class. */ export class GreasedLineRibbonMesh extends GreasedLineBaseMesh { /** * GreasedLineRibbonMesh * @param name name of the mesh * @param scene the scene * @param _options mesh options * @param _pathOptions used internaly when parsing a serialized GreasedLineRibbonMesh */ constructor(name, scene, _options, _pathOptions) { super(name, scene, _options); this.name = name; if (!_options.ribbonOptions) { // eslint-disable-next-line no-throw-literal throw "'GreasedLineMeshOptions.ribbonOptions' is not set."; } this._paths = []; this._counters = []; this._slopes = []; this._widths = _options.widths ?? []; this._ribbonWidths = []; this._pathsOptions = _pathOptions ?? []; if (_options.points) { this.addPoints(GreasedLineTools.ConvertPoints(_options.points), _options, !!_pathOptions); } } /** * Adds new points to the line. It doesn't rerenders the line if in lazy mode. * @param points points table * @param options mesh options * @param hasPathOptions defaults to false */ addPoints(points, options, hasPathOptions = false) { if (!options.ribbonOptions) { // eslint-disable-next-line no-throw-literal throw "addPoints() on GreasedLineRibbonMesh instance requires 'GreasedLineMeshOptions.ribbonOptions'."; } if (!hasPathOptions) { this._pathsOptions.push({ options, pathCount: points.length }); } super.addPoints(points, options); } /** * "GreasedLineRibbonMesh" * @returns "GreasedLineRibbonMesh" */ getClassName() { return "GreasedLineRibbonMesh"; } /** * Return true if the line was created from two edge paths or one points path. * In this case the line is always flat. */ get isFlatLine() { return this._paths.length < 3; } /** * Returns the slopes of the line at each point relative to the center of the line */ get slopes() { return this._slopes; } /** * Set the slopes of the line at each point relative to the center of the line */ set slopes(slopes) { this._slopes = slopes; } _updateColorPointers() { if (this._options.colorPointers) { return; } let colorPointer = 0; this._colorPointers = []; for (let i = 0; i < this._pathsOptions.length; i++) { const { options: pathOptions, pathCount } = this._pathsOptions[i]; const points = this._points[i]; if (pathOptions.ribbonOptions.pointsMode === 0 /* GreasedLineRibbonPointsMode.POINTS_MODE_POINTS */) { for (let k = 0; k < pathCount; k++) { for (let j = 0; j < points.length; j += 3) { this._colorPointers.push(colorPointer); this._colorPointers.push(colorPointer++); } } } else { for (let j = 0; j < points.length; j += 3) { for (let k = 0; k < pathCount; k++) { this._colorPointers.push(colorPointer); } colorPointer++; } } } } _updateWidths() { super._updateWidthsWithValue(1); } _setPoints(points, _options) { if (!this._options.ribbonOptions) { // eslint-disable-next-line no-throw-literal throw "No 'GreasedLineMeshOptions.ribbonOptions' provided."; } this._points = points; this._options.points = points; this._initGreasedLine(); let indiceOffset = 0; let directionPlanes = undefined; for (let i = 0, c = 0; i < this._pathsOptions.length; i++) { const { options: pathOptions, pathCount } = this._pathsOptions[i]; const subPoints = points.slice(c, c + pathCount); c += pathCount; if (pathOptions.ribbonOptions?.pointsMode === 1 /* GreasedLineRibbonPointsMode.POINTS_MODE_PATHS */) { indiceOffset = this._preprocess(GreasedLineTools.ToVector3Array(subPoints), indiceOffset, pathOptions); } else { if (pathOptions.ribbonOptions?.directionsAutoMode === 99 /* GreasedLineRibbonAutoDirectionMode.AUTO_DIRECTIONS_NONE */) { if (!pathOptions.ribbonOptions.directions) { // eslint-disable-next-line no-throw-literal throw "In GreasedLineRibbonAutoDirectionMode.AUTO_DIRECTIONS_NONE 'GreasedLineMeshOptions.ribbonOptions.directions' must be defined."; } directionPlanes = GreasedLineRibbonMesh._GetDirectionPlanesFromDirectionsOption(subPoints.length, pathOptions.ribbonOptions.directions); } for (let idx = 0; idx < subPoints.length; idx++) { const p = subPoints[idx]; const pathArray = GreasedLineRibbonMesh._ConvertToRibbonPath(p, pathOptions.ribbonOptions, this._scene.useRightHandedSystem, directionPlanes ? directionPlanes[idx] : directionPlanes); indiceOffset = this._preprocess(pathArray, indiceOffset, pathOptions); } } } if (!this._lazy) { this._createVertexBuffers(); !this.doNotSyncBoundingInfo && this.refreshBoundingInfo(); } } static _GetDirectionPlanesFromDirectionsOption(count, directions) { if (Array.isArray(directions)) { return directions; } return new Array(count).fill(directions); } static _CreateRibbonVertexData(pathArray, options) { const numOfPaths = pathArray.length; if (numOfPaths < 2) { // eslint-disable-next-line no-throw-literal throw "Minimum of two paths are required to create a GreasedLineRibbonMesh."; } const positions = []; const indices = []; const path = pathArray[0]; for (let i = 0; i < path.length; i++) { for (let pi = 0; pi < pathArray.length; pi++) { const v = pathArray[pi][i]; positions.push(v.x, v.y, v.z); } } const v = [1, 0, numOfPaths]; const doubleSided = options.ribbonOptions?.facesMode === 2 /* GreasedLineRibbonFacesMode.FACES_MODE_DOUBLE_SIDED */; const closePath = options.ribbonOptions?.pointsMode === 1 /* GreasedLineRibbonPointsMode.POINTS_MODE_PATHS */ && options.ribbonOptions.closePath; if (numOfPaths > 2) { for (let i = 0; i < path.length - 1; i++) { v[0] = 1 + numOfPaths * i; v[1] = numOfPaths * i; v[2] = (i + 1) * numOfPaths; for (let pi = 0; pi < (numOfPaths - 1) * 2; pi++) { if (pi % 2 !== 0) { v[2] += 1; } if (pi % 2 === 0 && pi > 0) { v[0] += 1; v[1] += 1; } indices.push(v[1] + (pi % 2 !== 0 ? numOfPaths : 0), v[0], v[2]); if (doubleSided) { indices.push(v[0], v[1] + (pi % 2 !== 0 ? numOfPaths : 0), v[2]); } } } } else { for (let i = 0; i < positions.length / 3 - 3; i += 2) { indices.push(i, i + 1, i + 2); indices.push(i + 2, i + 1, i + 3); if (doubleSided) { indices.push(i + 1, i, i + 2); indices.push(i + 1, i + 2, i + 3); } } } if (closePath) { let lastIndice = numOfPaths * (path.length - 1); for (let pi = 0; pi < numOfPaths - 1; pi++) { indices.push(lastIndice, pi + 1, pi); indices.push(lastIndice + 1, pi + 1, lastIndice); if (doubleSided) { indices.push(pi, pi + 1, lastIndice); indices.push(lastIndice, pi + 1, lastIndice + 1); } lastIndice++; } } return { positions, indices, }; } _preprocess(pathArray, indiceOffset, options) { this._paths = pathArray; const ribbonVertexData = GreasedLineRibbonMesh._CreateRibbonVertexData(pathArray, options); const positions = ribbonVertexData.positions; if (!this._options.widths) { // eslint-disable-next-line no-throw-literal throw "No 'GreasedLineMeshOptions.widths' table is specified."; } const vertexPositions = Array.isArray(this._vertexPositions) ? this._vertexPositions : Array.from(this._vertexPositions); this._vertexPositions = vertexPositions; const uvs = Array.isArray(this._uvs) ? this._uvs : Array.from(this._uvs); this._uvs = uvs; const indices = Array.isArray(this._indices) ? this._indices : Array.from(this._indices); this._indices = indices; for (const p of positions) { vertexPositions.push(p); } let pathArrayCopy = pathArray; if (options.ribbonOptions?.pointsMode === 1 /* GreasedLineRibbonPointsMode.POINTS_MODE_PATHS */ && options.ribbonOptions.closePath) { pathArrayCopy = []; for (let i = 0; i < pathArray.length; i++) { const pathCopy = pathArray[i].slice(); pathCopy.push(pathArray[i][0].clone()); pathArrayCopy.push(pathCopy); } } this._calculateSegmentLengths(pathArrayCopy); const pathArrayLength = pathArrayCopy.length; const previousCounters = new Array(pathArrayLength).fill(0); for (let i = 0; i < pathArrayCopy[0].length; i++) { let v = 0; for (let pi = 0; pi < pathArrayLength; pi++) { const counter = previousCounters[pi] + this._vSegmentLengths[pi][i] / this._vTotalLengths[pi]; this._counters.push(counter); uvs.push(counter, v); previousCounters[pi] = counter; v += this._uSegmentLengths[i][pi] / this._uTotalLengths[i]; } } for (let i = 0, c = 0; i < pathArrayCopy[0].length; i++) { const widthLower = this._uSegmentLengths[i][0] / 2; const widthUpper = this._uSegmentLengths[i][pathArrayLength - 1] / 2; this._ribbonWidths.push(((this._widths[c++] ?? 1) - 1) * widthLower); for (let pi = 0; pi < pathArrayLength - 2; pi++) { this._ribbonWidths.push(0); } this._ribbonWidths.push(((this._widths[c++] ?? 1) - 1) * widthUpper); } const slopes = options.ribbonOptions?.pointsMode === 1 /* GreasedLineRibbonPointsMode.POINTS_MODE_PATHS */ ? new Array(pathArrayCopy[0].length * pathArrayCopy.length * 6).fill(0) : GreasedLineRibbonMesh._CalculateSlopes(pathArrayCopy); for (const s of slopes) { this._slopes.push(s); } if (ribbonVertexData.indices) { for (let i = 0; i < ribbonVertexData.indices.length; i++) { indices.push(ribbonVertexData.indices[i] + indiceOffset); } } indiceOffset += positions.length / 3; return indiceOffset; } static _ConvertToRibbonPath(points, ribbonInfo, rightHandedSystem, directionPlane) { if (ribbonInfo.pointsMode === 0 /* GreasedLineRibbonPointsMode.POINTS_MODE_POINTS */ && !ribbonInfo.width) { // eslint-disable-next-line no-throw-literal throw "'GreasedLineMeshOptions.ribbonOptiosn.width' must be specified in GreasedLineRibbonPointsMode.POINTS_MODE_POINTS."; } const path1 = []; const path2 = []; if (ribbonInfo.pointsMode === 0 /* GreasedLineRibbonPointsMode.POINTS_MODE_POINTS */) { const width = ribbonInfo.width / 2; const pointVectors = GreasedLineTools.ToVector3Array(points); let direction = null; let fatDirection = null; if (ribbonInfo.directionsAutoMode === 0 /* GreasedLineRibbonAutoDirectionMode.AUTO_DIRECTIONS_FROM_FIRST_SEGMENT */) { // set the direction plane from the first line segment for the whole line directionPlane = GreasedLineRibbonMesh._GetDirectionFromPoints(pointVectors[0], pointVectors[1], null); } if (ribbonInfo.directionsAutoMode === 3 /* GreasedLineRibbonAutoDirectionMode.AUTO_DIRECTIONS_FACE_TO */ && !(ribbonInfo.directions instanceof Vector3)) { // eslint-disable-next-line no-throw-literal throw "In GreasedLineRibbonAutoDirectionMode.AUTO_DIRECTIONS_FACE_TO 'GreasedLineMeshOptions.ribbonOptions.directions' must be a Vector3."; } TmpVectors.Vector3[1] = ribbonInfo.directions instanceof Vector3 ? ribbonInfo.directions : GreasedLineRibbonMesh.DIRECTION_XZ; for (let i = 0; i < pointVectors.length - (directionPlane ? 0 : 1); i++) { const p1 = pointVectors[i]; const p2 = pointVectors[i + 1]; if (directionPlane) { direction = directionPlane; } else if (ribbonInfo.directionsAutoMode === 3 /* GreasedLineRibbonAutoDirectionMode.AUTO_DIRECTIONS_FACE_TO */) { p2.subtractToRef(p1, TmpVectors.Vector3[0]); direction = Vector3.CrossToRef(TmpVectors.Vector3[0], TmpVectors.Vector3[1], TmpVectors.Vector3[2]).normalize(); } else if (ribbonInfo.directionsAutoMode === 1 /* GreasedLineRibbonAutoDirectionMode.AUTO_DIRECTIONS_FROM_ALL_SEGMENTS */) { direction = GreasedLineRibbonMesh._GetDirectionFromPoints(p1, p2, direction); } else { // GreasedLineRibbonAutoDirectionMode.DIRECTION_ENHANCED const directionTemp = p2.subtract(p1); directionTemp.applyRotationQuaternionInPlace(directionTemp.x > directionTemp.y && directionTemp.x > directionTemp.z ? rightHandedSystem ? GreasedLineRibbonMesh._RightHandedForwardReadOnlyQuaternion : GreasedLineRibbonMesh._LeftHandedForwardReadOnlyQuaternion : GreasedLineRibbonMesh._LeftReadOnlyQuaternion); direction = directionTemp.normalize(); } fatDirection = direction.multiplyByFloats(width, width, width); path1.push(p1.add(fatDirection)); path2.push(p1.subtract(fatDirection)); } if (!directionPlane) { path1.push(pointVectors[pointVectors.length - 1].add(fatDirection)); path2.push(pointVectors[pointVectors.length - 1].subtract(fatDirection)); } } return [path1, path2]; } static _GetDirectionFromPoints(p1, p2, previousDirection) { // handle straight lines if (p1.x === p2.x && (!previousDirection || previousDirection?.x === 1)) { return GreasedLineRibbonMesh.DIRECTION_YZ; } if (p1.y === p2.y) { return GreasedLineRibbonMesh.DIRECTION_XZ; } if (p1.z === p2.z) { return GreasedLineRibbonMesh.DIRECTION_XY; } return GreasedLineRibbonMesh.DIRECTION_XZ; } /** * Clones the GreasedLineRibbonMesh. * @param name new line name * @param newParent new parent node * @returns cloned line */ clone(name = `${this.name}-cloned`, newParent) { const lineOptions = this._createLineOptions(); const deepCopiedLineOptions = {}; const pathOptionsCloned = []; DeepCopier.DeepCopy(this._pathsOptions, pathOptionsCloned, undefined, undefined, true); DeepCopier.DeepCopy(lineOptions, deepCopiedLineOptions, ["instance"], undefined, true); const cloned = new GreasedLineRibbonMesh(name, this._scene, deepCopiedLineOptions, pathOptionsCloned); if (newParent) { cloned.parent = newParent; } cloned.material = this.material; return cloned; } /** * Serializes this GreasedLineRibbonMesh * @param serializationObject object to write serialization to */ serialize(serializationObject) { super.serialize(serializationObject); serializationObject.type = this.getClassName(); serializationObject.lineOptions = this._createLineOptions(); serializationObject.pathsOptions = this._pathsOptions; } /** * Parses a serialized GreasedLineRibbonMesh * @param parsedMesh the serialized GreasedLineRibbonMesh * @param scene the scene to create the GreasedLineRibbonMesh in * @returns the created GreasedLineRibbonMesh */ static Parse(parsedMesh, scene) { const lineOptions = parsedMesh.lineOptions; const name = parsedMesh.name; const pathOptions = parsedMesh.pathOptions; const result = new GreasedLineRibbonMesh(name, scene, lineOptions, pathOptions); return result; } _initGreasedLine() { super._initGreasedLine(); this._paths = []; this._counters = []; this._slopes = []; this._ribbonWidths = []; } _calculateSegmentLengths(pathArray) { const pathArrayLength = pathArray.length; this._vSegmentLengths = new Array(pathArrayLength); this._vTotalLengths = new Array(pathArrayLength); let length = 0; for (let pi = 0; pi < pathArrayLength; pi++) { const points = pathArray[pi]; this._vSegmentLengths[pi] = [0]; // first point has 0 distance length = 0; for (let i = 0; i < points.length - 1; i++) { const l = Math.abs(points[i].subtract(points[i + 1]).lengthSquared()); // it's ok to have lengthSquared() here length += l; this._vSegmentLengths[pi].push(l); } this._vTotalLengths[pi] = length; } const positionsLength = pathArray[0].length; this._uSegmentLengths = new Array(positionsLength).fill([]); this._uTotalLengths = new Array(positionsLength).fill([]); const uLength = new Vector3(); for (let i = 0; i < positionsLength; i++) { length = 0; for (let pi = 1; pi < pathArrayLength; pi++) { pathArray[pi][i].subtractToRef(pathArray[pi - 1][i], uLength); const l = uLength.length(); // must be length() length += l; this._uSegmentLengths[i].push(l); } this._uTotalLengths[i] = length; } } static _CalculateSlopes(paths) { const points1 = paths[0]; const points2 = paths.length === 2 ? paths[1] : paths[paths.length - 1]; const slopes = []; const slope = new Vector3(); for (let i = 0; i < points1.length; i++) { for (let pi = 0; pi < paths.length; pi++) { if (pi === 0 || pi === paths.length - 1) { points1[i].subtract(points2[i]).normalizeToRef(slope); slopes.push(slope.x, slope.y, slope.z); slopes.push(-slope.x, -slope.y, -slope.z); } else { slopes.push(0, 0, 0, 0, 0, 0); } } } return slopes; } _createVertexBuffers() { this._uvs = this._options.uvs ?? this._uvs; const vertexData = super._createVertexBuffers(this._options.ribbonOptions?.smoothShading); const countersBuffer = new Buffer(this._engine, this._counters, this._updatable, 1); this.setVerticesBuffer(countersBuffer.createVertexBuffer("grl_counters", 0, 1)); const colorPointersBuffer = new Buffer(this._engine, this._colorPointers, this._updatable, 1); this.setVerticesBuffer(colorPointersBuffer.createVertexBuffer("grl_colorPointers", 0, 1)); const slopesBuffer = new Buffer(this._engine, this._slopes, this._updatable, 3); this.setVerticesBuffer(slopesBuffer.createVertexBuffer("grl_slopes", 0, 3)); const widthsBuffer = new Buffer(this._engine, this._ribbonWidths, this._updatable, 1); this.setVerticesBuffer(widthsBuffer.createVertexBuffer("grl_widths", 0, 1)); this._widthsBuffer = widthsBuffer; return vertexData; } } /** * Default line width */ GreasedLineRibbonMesh.DEFAULT_WIDTH = 0.1; GreasedLineRibbonMesh._RightHandedForwardReadOnlyQuaternion = Quaternion.RotationAxis(Vector3.RightHandedForwardReadOnly, Math.PI / 2); GreasedLineRibbonMesh._LeftHandedForwardReadOnlyQuaternion = Quaternion.RotationAxis(Vector3.LeftHandedForwardReadOnly, Math.PI / 2); GreasedLineRibbonMesh._LeftReadOnlyQuaternion = Quaternion.RotationAxis(Vector3.LeftReadOnly, Math.PI / 2); /** * Direction which the line segment will be thickened if drawn on the XY plane */ GreasedLineRibbonMesh.DIRECTION_XY = Vector3.LeftHandedForwardReadOnly; // doesn't matter in which handed system the scene operates /** * Direction which the line segment will be thickened if drawn on the XZ plane */ GreasedLineRibbonMesh.DIRECTION_XZ = Vector3.UpReadOnly; /** * Direction which the line segment will be thickened if drawn on the YZ plane */ GreasedLineRibbonMesh.DIRECTION_YZ = Vector3.LeftReadOnly; //# sourceMappingURL=greasedLineRibbonMesh.js.map