123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481 |
- 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, GreasedLineRibbonAutoDirectionMode, GreasedLineRibbonFacesMode, GreasedLineRibbonPointsMode } 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 === 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;
- 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 === GreasedLineRibbonPointsMode.POINTS_MODE_PATHS) {
- indiceOffset = this._preprocess(GreasedLineTools.ToVector3Array(subPoints), indiceOffset, pathOptions);
- }
- else {
- if (pathOptions.ribbonOptions?.directionsAutoMode === 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);
- }
- subPoints.forEach((p, 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.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 === GreasedLineRibbonFacesMode.FACES_MODE_DOUBLE_SIDED ?? false;
- const closePath = options.ribbonOptions?.pointsMode === 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 === 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 === 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 === 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 === GreasedLineRibbonPointsMode.POINTS_MODE_POINTS) {
- const width = ribbonInfo.width / 2;
- const pointVectors = GreasedLineTools.ToVector3Array(points);
- let direction = null;
- let fatDirection = null;
- if (ribbonInfo.directionsAutoMode === 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 === 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 === 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 === 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
|