@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
JavaScript
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