@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.
1,059 lines • 45.3 kB
JavaScript
import { Clamp, WithinEpsilon } from "./math.scalar.functions.js";
import { Vector2, Vector3, Quaternion, Matrix } from "./math.vector.js";
import { Epsilon } from "./math.constants.js";
/**
* Defines potential orientation for back face culling
*/
export var Orientation;
(function (Orientation) {
/**
* Clockwise
*/
Orientation[Orientation["CW"] = 0] = "CW";
/** Counter clockwise */
Orientation[Orientation["CCW"] = 1] = "CCW";
})(Orientation || (Orientation = {}));
/** Class used to represent a Bezier curve */
export class BezierCurve {
/**
* Returns the cubic Bezier interpolated value (float) at "t" (float) from the given x1, y1, x2, y2 floats
* @param t defines the time
* @param x1 defines the left coordinate on X axis
* @param y1 defines the left coordinate on Y axis
* @param x2 defines the right coordinate on X axis
* @param y2 defines the right coordinate on Y axis
* @returns the interpolated value
*/
static Interpolate(t, x1, y1, x2, y2) {
if (t === 0) {
return 0;
}
// Extract X (which is equal to time here)
const f0 = 1 - 3 * x2 + 3 * x1;
const f1 = 3 * x2 - 6 * x1;
const f2 = 3 * x1;
let refinedT = t;
for (let i = 0; i < 5; i++) {
const refinedT2 = refinedT * refinedT;
const refinedT3 = refinedT2 * refinedT;
const x = f0 * refinedT3 + f1 * refinedT2 + f2 * refinedT;
const slope = 1.0 / (3.0 * f0 * refinedT2 + 2.0 * f1 * refinedT + f2);
refinedT -= (x - t) * slope;
refinedT = Math.min(1, Math.max(0, refinedT));
}
// Resolve cubic bezier for the given x
return 3 * Math.pow(1 - refinedT, 2) * refinedT * y1 + 3 * (1 - refinedT) * Math.pow(refinedT, 2) * y2 + Math.pow(refinedT, 3);
}
}
/**
* Defines angle representation
*/
export class Angle {
/**
* Creates an Angle object of "radians" radians (float).
* @param radians the angle in radians
*/
constructor(radians) {
this._radians = radians;
if (this._radians < 0.0) {
this._radians += 2.0 * Math.PI;
}
}
/**
* Get value in degrees
* @returns the Angle value in degrees (float)
*/
degrees() {
return (this._radians * 180.0) / Math.PI;
}
/**
* Get value in radians
* @returns the Angle value in radians (float)
*/
radians() {
return this._radians;
}
/**
* Gets a new Angle object with a value of the angle (in radians) between the line connecting the two points and the x-axis
* @param a defines first point as the origin
* @param b defines point
* @returns a new Angle
*/
static BetweenTwoPoints(a, b) {
const delta = b.subtract(a);
const theta = Math.atan2(delta.y, delta.x);
return new Angle(theta);
}
/**
* Gets the angle between the two vectors
* @param a defines first vector
* @param b defines vector
* @returns Returns an new Angle between 0 and PI
*/
static BetweenTwoVectors(a, b) {
let product = a.lengthSquared() * b.lengthSquared();
if (product === 0) {
return new Angle(Math.PI / 2);
}
product = Math.sqrt(product);
let cosVal = a.dot(b) / product;
cosVal = Clamp(cosVal, -1, 1);
const angle = Math.acos(cosVal);
return new Angle(angle);
}
/**
* Gets a new Angle object from the given float in radians
* @param radians defines the angle value in radians
* @returns a new Angle
*/
static FromRadians(radians) {
return new Angle(radians);
}
/**
* Gets a new Angle object from the given float in degrees
* @param degrees defines the angle value in degrees
* @returns a new Angle
*/
static FromDegrees(degrees) {
return new Angle((degrees * Math.PI) / 180.0);
}
}
/**
* This represents an arc in a 2d space.
*/
export class Arc2 {
/**
* Creates an Arc object from the three given points : start, middle and end.
* @param startPoint Defines the start point of the arc
* @param midPoint Defines the middle point of the arc
* @param endPoint Defines the end point of the arc
*/
constructor(
/** Defines the start point of the arc */
startPoint,
/** Defines the mid point of the arc */
midPoint,
/** Defines the end point of the arc */
endPoint) {
this.startPoint = startPoint;
this.midPoint = midPoint;
this.endPoint = endPoint;
const temp = Math.pow(midPoint.x, 2) + Math.pow(midPoint.y, 2);
const startToMid = (Math.pow(startPoint.x, 2) + Math.pow(startPoint.y, 2) - temp) / 2;
const midToEnd = (temp - Math.pow(endPoint.x, 2) - Math.pow(endPoint.y, 2)) / 2;
const det = (startPoint.x - midPoint.x) * (midPoint.y - endPoint.y) - (midPoint.x - endPoint.x) * (startPoint.y - midPoint.y);
this.centerPoint = new Vector2((startToMid * (midPoint.y - endPoint.y) - midToEnd * (startPoint.y - midPoint.y)) / det, ((startPoint.x - midPoint.x) * midToEnd - (midPoint.x - endPoint.x) * startToMid) / det);
this.radius = this.centerPoint.subtract(this.startPoint).length();
this.startAngle = Angle.BetweenTwoPoints(this.centerPoint, this.startPoint);
const a1 = this.startAngle.degrees();
let a2 = Angle.BetweenTwoPoints(this.centerPoint, this.midPoint).degrees();
let a3 = Angle.BetweenTwoPoints(this.centerPoint, this.endPoint).degrees();
// angles correction
if (a2 - a1 > +180.0) {
a2 -= 360.0;
}
if (a2 - a1 < -180.0) {
a2 += 360.0;
}
if (a3 - a2 > +180.0) {
a3 -= 360.0;
}
if (a3 - a2 < -180.0) {
a3 += 360.0;
}
this.orientation = a2 - a1 < 0 ? 0 /* Orientation.CW */ : 1 /* Orientation.CCW */;
this.angle = Angle.FromDegrees(this.orientation === 0 /* Orientation.CW */ ? a1 - a3 : a3 - a1);
}
}
/**
* Represents a 2D path made up of multiple 2D points
*/
export class Path2 {
/**
* Creates a Path2 object from the starting 2D coordinates x and y.
* @param x the starting points x value
* @param y the starting points y value
*/
constructor(x, y) {
this._points = new Array();
this._length = 0.0;
/**
* If the path start and end point are the same
*/
this.closed = false;
this._points.push(new Vector2(x, y));
}
/**
* Adds a new segment until the given coordinates (x, y) to the current Path2.
* @param x the added points x value
* @param y the added points y value
* @returns the updated Path2.
*/
addLineTo(x, y) {
if (this.closed) {
return this;
}
const newPoint = new Vector2(x, y);
const previousPoint = this._points[this._points.length - 1];
this._points.push(newPoint);
this._length += newPoint.subtract(previousPoint).length();
return this;
}
/**
* Adds _numberOfSegments_ segments according to the arc definition (middle point coordinates, end point coordinates, the arc start point being the current Path2 last point) to the current Path2.
* @param midX middle point x value
* @param midY middle point y value
* @param endX end point x value
* @param endY end point y value
* @param numberOfSegments (default: 36)
* @returns the updated Path2.
*/
addArcTo(midX, midY, endX, endY, numberOfSegments = 36) {
if (this.closed) {
return this;
}
const startPoint = this._points[this._points.length - 1];
const midPoint = new Vector2(midX, midY);
const endPoint = new Vector2(endX, endY);
const arc = new Arc2(startPoint, midPoint, endPoint);
let increment = arc.angle.radians() / numberOfSegments;
if (arc.orientation === 0 /* Orientation.CW */) {
increment *= -1;
}
let currentAngle = arc.startAngle.radians() + increment;
for (let i = 0; i < numberOfSegments; i++) {
const x = Math.cos(currentAngle) * arc.radius + arc.centerPoint.x;
const y = Math.sin(currentAngle) * arc.radius + arc.centerPoint.y;
this.addLineTo(x, y);
currentAngle += increment;
}
return this;
}
/**
* Adds _numberOfSegments_ segments according to the quadratic curve definition to the current Path2.
* @param controlX control point x value
* @param controlY control point y value
* @param endX end point x value
* @param endY end point y value
* @param numberOfSegments (default: 36)
* @returns the updated Path2.
*/
addQuadraticCurveTo(controlX, controlY, endX, endY, numberOfSegments = 36) {
if (this.closed) {
return this;
}
const equation = (t, val0, val1, val2) => {
const res = (1.0 - t) * (1.0 - t) * val0 + 2.0 * t * (1.0 - t) * val1 + t * t * val2;
return res;
};
const startPoint = this._points[this._points.length - 1];
for (let i = 0; i <= numberOfSegments; i++) {
const step = i / numberOfSegments;
const x = equation(step, startPoint.x, controlX, endX);
const y = equation(step, startPoint.y, controlY, endY);
this.addLineTo(x, y);
}
return this;
}
/**
* Adds _numberOfSegments_ segments according to the bezier curve definition to the current Path2.
* @param originTangentX tangent vector at the origin point x value
* @param originTangentY tangent vector at the origin point y value
* @param destinationTangentX tangent vector at the destination point x value
* @param destinationTangentY tangent vector at the destination point y value
* @param endX end point x value
* @param endY end point y value
* @param numberOfSegments (default: 36)
* @returns the updated Path2.
*/
addBezierCurveTo(originTangentX, originTangentY, destinationTangentX, destinationTangentY, endX, endY, numberOfSegments = 36) {
if (this.closed) {
return this;
}
const equation = (t, val0, val1, val2, val3) => {
const res = (1.0 - t) * (1.0 - t) * (1.0 - t) * val0 + 3.0 * t * (1.0 - t) * (1.0 - t) * val1 + 3.0 * t * t * (1.0 - t) * val2 + t * t * t * val3;
return res;
};
const startPoint = this._points[this._points.length - 1];
for (let i = 0; i <= numberOfSegments; i++) {
const step = i / numberOfSegments;
const x = equation(step, startPoint.x, originTangentX, destinationTangentX, endX);
const y = equation(step, startPoint.y, originTangentY, destinationTangentY, endY);
this.addLineTo(x, y);
}
return this;
}
/**
* Defines if a given point is inside the polygon defines by the path
* @param point defines the point to test
* @returns true if the point is inside
*/
isPointInside(point) {
let isInside = false;
const count = this._points.length;
for (let p = count - 1, q = 0; q < count; p = q++) {
let edgeLow = this._points[p];
let edgeHigh = this._points[q];
let edgeDx = edgeHigh.x - edgeLow.x;
let edgeDy = edgeHigh.y - edgeLow.y;
if (Math.abs(edgeDy) > Number.EPSILON) {
// Not parallel
if (edgeDy < 0) {
edgeLow = this._points[q];
edgeDx = -edgeDx;
edgeHigh = this._points[p];
edgeDy = -edgeDy;
}
if (point.y < edgeLow.y || point.y > edgeHigh.y) {
continue;
}
if (point.y === edgeLow.y && point.x === edgeLow.x) {
return true;
}
else {
const perpEdge = edgeDy * (point.x - edgeLow.x) - edgeDx * (point.y - edgeLow.y);
if (perpEdge === 0) {
return true;
}
if (perpEdge < 0) {
continue;
}
isInside = !isInside;
}
}
else {
// parallel or collinear
if (point.y !== edgeLow.y) {
continue;
}
if ((edgeHigh.x <= point.x && point.x <= edgeLow.x) || (edgeLow.x <= point.x && point.x <= edgeHigh.x)) {
return true;
}
}
}
return isInside;
}
/**
* Closes the Path2.
* @returns the Path2.
*/
close() {
this.closed = true;
return this;
}
/**
* Gets the sum of the distance between each sequential point in the path
* @returns the Path2 total length (float).
*/
length() {
let result = this._length;
if (this.closed) {
const lastPoint = this._points[this._points.length - 1];
const firstPoint = this._points[0];
result += firstPoint.subtract(lastPoint).length();
}
return result;
}
/**
* Gets the area of the polygon defined by the path
* @returns area value
*/
area() {
const n = this._points.length;
let value = 0.0;
for (let p = n - 1, q = 0; q < n; p = q++) {
value += this._points[p].x * this._points[q].y - this._points[q].x * this._points[p].y;
}
return value * 0.5;
}
/**
* Gets the points which construct the path
* @returns the Path2 internal array of points.
*/
getPoints() {
return this._points;
}
/**
* Retrieves the point at the distance aways from the starting point
* @param normalizedLengthPosition the length along the path to retrieve the point from
* @returns a new Vector2 located at a percentage of the Path2 total length on this path.
*/
getPointAtLengthPosition(normalizedLengthPosition) {
if (normalizedLengthPosition < 0 || normalizedLengthPosition > 1) {
return Vector2.Zero();
}
const lengthPosition = normalizedLengthPosition * this.length();
let previousOffset = 0;
for (let i = 0; i < this._points.length; i++) {
const j = (i + 1) % this._points.length;
const a = this._points[i];
const b = this._points[j];
const bToA = b.subtract(a);
const nextOffset = bToA.length() + previousOffset;
if (lengthPosition >= previousOffset && lengthPosition <= nextOffset) {
const dir = bToA.normalize();
const localOffset = lengthPosition - previousOffset;
return new Vector2(a.x + dir.x * localOffset, a.y + dir.y * localOffset);
}
previousOffset = nextOffset;
}
return Vector2.Zero();
}
/**
* Creates a new path starting from an x and y position
* @param x starting x value
* @param y starting y value
* @returns a new Path2 starting at the coordinates (x, y).
*/
static StartingAt(x, y) {
return new Path2(x, y);
}
}
/**
* Represents a 3D path made up of multiple 3D points
* @see https://doc.babylonjs.com/features/featuresDeepDive/mesh/path3D
*/
export class Path3D {
/**
* new Path3D(path, normal, raw)
* Creates a Path3D. A Path3D is a logical math object, so not a mesh.
* please read the description in the tutorial : https://doc.babylonjs.com/features/featuresDeepDive/mesh/path3D
* @param path an array of Vector3, the curve axis of the Path3D
* @param firstNormal (options) Vector3, the first wanted normal to the curve. Ex (0, 1, 0) for a vertical normal.
* @param raw (optional, default false) : boolean, if true the returned Path3D isn't normalized. Useful to depict path acceleration or speed.
* @param alignTangentsWithPath (optional, default false) : boolean, if true the tangents will be aligned with the path.
*/
constructor(
/**
* an array of Vector3, the curve axis of the Path3D
*/
path, firstNormal = null, raw, alignTangentsWithPath = false) {
this.path = path;
this._curve = new Array();
this._distances = new Array();
this._tangents = new Array();
this._normals = new Array();
this._binormals = new Array();
// holds interpolated point data
this._pointAtData = {
id: 0,
point: Vector3.Zero(),
previousPointArrayIndex: 0,
position: 0,
subPosition: 0,
interpolateReady: false,
interpolationMatrix: Matrix.Identity(),
};
for (let p = 0; p < path.length; p++) {
this._curve[p] = path[p].clone(); // hard copy
}
this._raw = raw || false;
this._alignTangentsWithPath = alignTangentsWithPath;
this._compute(firstNormal, alignTangentsWithPath);
}
/**
* Returns the Path3D array of successive Vector3 designing its curve.
* @returns the Path3D array of successive Vector3 designing its curve.
*/
getCurve() {
return this._curve;
}
/**
* Returns the Path3D array of successive Vector3 designing its curve.
* @returns the Path3D array of successive Vector3 designing its curve.
*/
getPoints() {
return this._curve;
}
/**
* @returns the computed length (float) of the path.
*/
length() {
return this._distances[this._distances.length - 1];
}
/**
* Returns an array populated with tangent vectors on each Path3D curve point.
* @returns an array populated with tangent vectors on each Path3D curve point.
*/
getTangents() {
return this._tangents;
}
/**
* Returns an array populated with normal vectors on each Path3D curve point.
* @returns an array populated with normal vectors on each Path3D curve point.
*/
getNormals() {
return this._normals;
}
/**
* Returns an array populated with binormal vectors on each Path3D curve point.
* @returns an array populated with binormal vectors on each Path3D curve point.
*/
getBinormals() {
return this._binormals;
}
/**
* Returns an array populated with distances (float) of the i-th point from the first curve point.
* @returns an array populated with distances (float) of the i-th point from the first curve point.
*/
getDistances() {
return this._distances;
}
/**
* Returns an interpolated point along this path
* @param position the position of the point along this path, from 0.0 to 1.0
* @returns a new Vector3 as the point
*/
getPointAt(position) {
return this._updatePointAtData(position).point;
}
/**
* Returns the tangent vector of an interpolated Path3D curve point at the specified position along this path.
* @param position the position of the point along this path, from 0.0 to 1.0
* @param interpolated (optional, default false) : boolean, if true returns an interpolated tangent instead of the tangent of the previous path point.
* @returns a tangent vector corresponding to the interpolated Path3D curve point, if not interpolated, the tangent is taken from the precomputed tangents array.
*/
getTangentAt(position, interpolated = false) {
this._updatePointAtData(position, interpolated);
return interpolated ? Vector3.TransformCoordinates(Vector3.Forward(), this._pointAtData.interpolationMatrix) : this._tangents[this._pointAtData.previousPointArrayIndex];
}
/**
* Returns the tangent vector of an interpolated Path3D curve point at the specified position along this path.
* @param position the position of the point along this path, from 0.0 to 1.0
* @param interpolated (optional, default false) : boolean, if true returns an interpolated normal instead of the normal of the previous path point.
* @returns a normal vector corresponding to the interpolated Path3D curve point, if not interpolated, the normal is taken from the precomputed normals array.
*/
getNormalAt(position, interpolated = false) {
this._updatePointAtData(position, interpolated);
return interpolated ? Vector3.TransformCoordinates(Vector3.Right(), this._pointAtData.interpolationMatrix) : this._normals[this._pointAtData.previousPointArrayIndex];
}
/**
* Returns the binormal vector of an interpolated Path3D curve point at the specified position along this path.
* @param position the position of the point along this path, from 0.0 to 1.0
* @param interpolated (optional, default false) : boolean, if true returns an interpolated binormal instead of the binormal of the previous path point.
* @returns a binormal vector corresponding to the interpolated Path3D curve point, if not interpolated, the binormal is taken from the precomputed binormals array.
*/
getBinormalAt(position, interpolated = false) {
this._updatePointAtData(position, interpolated);
return interpolated ? Vector3.TransformCoordinates(Vector3.UpReadOnly, this._pointAtData.interpolationMatrix) : this._binormals[this._pointAtData.previousPointArrayIndex];
}
/**
* Returns the distance (float) of an interpolated Path3D curve point at the specified position along this path.
* @param position the position of the point along this path, from 0.0 to 1.0
* @returns the distance of the interpolated Path3D curve point at the specified position along this path.
*/
getDistanceAt(position) {
return this.length() * position;
}
/**
* Returns the array index of the previous point of an interpolated point along this path
* @param position the position of the point to interpolate along this path, from 0.0 to 1.0
* @returns the array index
*/
getPreviousPointIndexAt(position) {
this._updatePointAtData(position);
return this._pointAtData.previousPointArrayIndex;
}
/**
* Returns the position of an interpolated point relative to the two path points it lies between, from 0.0 (point A) to 1.0 (point B)
* @param position the position of the point to interpolate along this path, from 0.0 to 1.0
* @returns the sub position
*/
getSubPositionAt(position) {
this._updatePointAtData(position);
return this._pointAtData.subPosition;
}
/**
* Returns the position of the closest virtual point on this path to an arbitrary Vector3, from 0.0 to 1.0
* @param target the vector of which to get the closest position to
* @returns the position of the closest virtual point on this path to the target vector
*/
getClosestPositionTo(target) {
let smallestDistance = Number.MAX_VALUE;
let closestPosition = 0.0;
for (let i = 0; i < this._curve.length - 1; i++) {
const point = this._curve[i + 0];
const tangent = this._curve[i + 1].subtract(point).normalize();
const subLength = this._distances[i + 1] - this._distances[i + 0];
const subPosition = Math.min((Math.max(Vector3.Dot(tangent, target.subtract(point).normalize()), 0.0) * Vector3.Distance(point, target)) / subLength, 1.0);
const distance = Vector3.Distance(point.add(tangent.scale(subPosition * subLength)), target);
if (distance < smallestDistance) {
smallestDistance = distance;
closestPosition = (this._distances[i + 0] + subLength * subPosition) / this.length();
}
}
return closestPosition;
}
/**
* Returns a sub path (slice) of this path
* @param start the position of the fist path point, from 0.0 to 1.0, or a negative value, which will get wrapped around from the end of the path to 0.0 to 1.0 values
* @param end the position of the last path point, from 0.0 to 1.0, or a negative value, which will get wrapped around from the end of the path to 0.0 to 1.0 values
* @returns a sub path (slice) of this path
*/
slice(start = 0.0, end = 1.0) {
if (start < 0.0) {
start = 1 - ((start * -1.0) % 1.0);
}
if (end < 0.0) {
end = 1 - ((end * -1.0) % 1.0);
}
if (start > end) {
const _start = start;
start = end;
end = _start;
}
const curvePoints = this.getCurve();
const startPoint = this.getPointAt(start);
let startIndex = this.getPreviousPointIndexAt(start);
const endPoint = this.getPointAt(end);
const endIndex = this.getPreviousPointIndexAt(end) + 1;
const slicePoints = [];
if (start !== 0.0) {
startIndex++;
slicePoints.push(startPoint);
}
slicePoints.push(...curvePoints.slice(startIndex, endIndex));
if (end !== 1.0 || start === 1.0) {
slicePoints.push(endPoint);
}
return new Path3D(slicePoints, this.getNormalAt(start), this._raw, this._alignTangentsWithPath);
}
/**
* Forces the Path3D tangent, normal, binormal and distance recomputation.
* @param path path which all values are copied into the curves points
* @param firstNormal which should be projected onto the curve
* @param alignTangentsWithPath (optional, default false) : boolean, if true the tangents will be aligned with the path
* @returns the same object updated.
*/
update(path, firstNormal = null, alignTangentsWithPath = false) {
for (let p = 0; p < path.length; p++) {
this._curve[p].x = path[p].x;
this._curve[p].y = path[p].y;
this._curve[p].z = path[p].z;
}
this._compute(firstNormal, alignTangentsWithPath);
return this;
}
// private function compute() : computes tangents, normals and binormals
_compute(firstNormal, alignTangentsWithPath = false) {
const l = this._curve.length;
if (l < 2) {
return;
}
// first and last tangents
this._tangents[0] = this._getFirstNonNullVector(0);
if (!this._raw) {
this._tangents[0].normalize();
}
this._tangents[l - 1] = this._curve[l - 1].subtract(this._curve[l - 2]);
if (!this._raw) {
this._tangents[l - 1].normalize();
}
// normals and binormals at first point : arbitrary vector with _normalVector()
const tg0 = this._tangents[0];
const pp0 = this._normalVector(tg0, firstNormal);
this._normals[0] = pp0;
if (!this._raw) {
this._normals[0].normalize();
}
this._binormals[0] = Vector3.Cross(tg0, this._normals[0]);
if (!this._raw) {
this._binormals[0].normalize();
}
this._distances[0] = 0.0;
// normals and binormals : next points
let prev; // previous vector (segment)
let cur; // current vector (segment)
let curTang; // current tangent
// previous normal
let prevNor; // previous normal
let prevBinor; // previous binormal
for (let i = 1; i < l; i++) {
// tangents
prev = this._getLastNonNullVector(i);
if (i < l - 1) {
cur = this._getFirstNonNullVector(i);
this._tangents[i] = alignTangentsWithPath ? cur : prev.add(cur);
this._tangents[i].normalize();
}
this._distances[i] = this._distances[i - 1] + this._curve[i].subtract(this._curve[i - 1]).length();
// normals and binormals
// http://www.cs.cmu.edu/afs/andrew/scs/cs/15-462/web/old/asst2camera.html
curTang = this._tangents[i];
prevBinor = this._binormals[i - 1];
this._normals[i] = Vector3.Cross(prevBinor, curTang);
if (!this._raw) {
if (this._normals[i].length() === 0) {
prevNor = this._normals[i - 1];
this._normals[i] = prevNor.clone();
}
else {
this._normals[i].normalize();
}
}
this._binormals[i] = Vector3.Cross(curTang, this._normals[i]);
if (!this._raw) {
this._binormals[i].normalize();
}
}
this._pointAtData.id = NaN;
}
// private function getFirstNonNullVector(index)
// returns the first non null vector from index : curve[index + N].subtract(curve[index])
_getFirstNonNullVector(index) {
let i = 1;
let nNVector = this._curve[index + i].subtract(this._curve[index]);
while (nNVector.length() === 0 && index + i + 1 < this._curve.length) {
i++;
nNVector = this._curve[index + i].subtract(this._curve[index]);
}
return nNVector;
}
// private function getLastNonNullVector(index)
// returns the last non null vector from index : curve[index].subtract(curve[index - N])
_getLastNonNullVector(index) {
let i = 1;
let nLVector = this._curve[index].subtract(this._curve[index - i]);
while (nLVector.length() === 0 && index > i + 1) {
i++;
nLVector = this._curve[index].subtract(this._curve[index - i]);
}
return nLVector;
}
// private function normalVector(v0, vt, va) :
// returns an arbitrary point in the plane defined by the point v0 and the vector vt orthogonal to this plane
// if va is passed, it returns the va projection on the plane orthogonal to vt at the point v0
_normalVector(vt, va) {
let normal0;
let tgl = vt.length();
if (tgl === 0.0) {
tgl = 1.0;
}
if (va === undefined || va === null) {
let point;
if (!WithinEpsilon(Math.abs(vt.y) / tgl, 1.0, Epsilon)) {
// search for a point in the plane
point = new Vector3(0.0, -1.0, 0.0);
}
else if (!WithinEpsilon(Math.abs(vt.x) / tgl, 1.0, Epsilon)) {
point = new Vector3(1.0, 0.0, 0.0);
}
else if (!WithinEpsilon(Math.abs(vt.z) / tgl, 1.0, Epsilon)) {
point = new Vector3(0.0, 0.0, 1.0);
}
else {
point = Vector3.Zero();
}
normal0 = Vector3.Cross(vt, point);
}
else {
normal0 = Vector3.Cross(vt, va);
Vector3.CrossToRef(normal0, vt, normal0);
}
normal0.normalize();
return normal0;
}
/**
* Updates the point at data for an interpolated point along this curve
* @param position the position of the point along this curve, from 0.0 to 1.0
* @param interpolateTNB
* @interpolateTNB whether to compute the interpolated tangent, normal and binormal
* @returns the (updated) point at data
*/
_updatePointAtData(position, interpolateTNB = false) {
// set an id for caching the result
if (this._pointAtData.id === position) {
if (!this._pointAtData.interpolateReady) {
this._updateInterpolationMatrix();
}
return this._pointAtData;
}
else {
this._pointAtData.id = position;
}
const curvePoints = this.getPoints();
// clamp position between 0.0 and 1.0
if (position <= 0.0) {
return this._setPointAtData(0.0, 0.0, curvePoints[0], 0, interpolateTNB);
}
else if (position >= 1.0) {
return this._setPointAtData(1.0, 1.0, curvePoints[curvePoints.length - 1], curvePoints.length - 1, interpolateTNB);
}
let previousPoint = curvePoints[0];
let currentPoint;
let currentLength = 0.0;
const targetLength = position * this.length();
for (let i = 1; i < curvePoints.length; i++) {
currentPoint = curvePoints[i];
const distance = Vector3.Distance(previousPoint, currentPoint);
currentLength += distance;
if (currentLength === targetLength) {
return this._setPointAtData(position, 1.0, currentPoint, i, interpolateTNB);
}
else if (currentLength > targetLength) {
const toLength = currentLength - targetLength;
const diff = toLength / distance;
const dir = previousPoint.subtract(currentPoint);
const point = currentPoint.add(dir.scaleInPlace(diff));
return this._setPointAtData(position, 1 - diff, point, i - 1, interpolateTNB);
}
previousPoint = currentPoint;
}
return this._pointAtData;
}
/**
* Updates the point at data from the specified parameters
* @param position where along the path the interpolated point is, from 0.0 to 1.0
* @param subPosition
* @param point the interpolated point
* @param parentIndex the index of an existing curve point that is on, or else positionally the first behind, the interpolated point
* @param interpolateTNB whether to compute the interpolated tangent, normal and binormal
* @returns the (updated) point at data
*/
_setPointAtData(position, subPosition, point, parentIndex, interpolateTNB) {
this._pointAtData.point = point;
this._pointAtData.position = position;
this._pointAtData.subPosition = subPosition;
this._pointAtData.previousPointArrayIndex = parentIndex;
this._pointAtData.interpolateReady = interpolateTNB;
if (interpolateTNB) {
this._updateInterpolationMatrix();
}
return this._pointAtData;
}
/**
* Updates the point at interpolation matrix for the tangents, normals and binormals
*/
_updateInterpolationMatrix() {
this._pointAtData.interpolationMatrix = Matrix.Identity();
const parentIndex = this._pointAtData.previousPointArrayIndex;
if (parentIndex !== this._tangents.length - 1) {
const index = parentIndex + 1;
const tangentFrom = this._tangents[parentIndex].clone();
const normalFrom = this._normals[parentIndex].clone();
const binormalFrom = this._binormals[parentIndex].clone();
const tangentTo = this._tangents[index].clone();
const normalTo = this._normals[index].clone();
const binormalTo = this._binormals[index].clone();
const quatFrom = Quaternion.RotationQuaternionFromAxis(normalFrom, binormalFrom, tangentFrom);
const quatTo = Quaternion.RotationQuaternionFromAxis(normalTo, binormalTo, tangentTo);
const quatAt = Quaternion.Slerp(quatFrom, quatTo, this._pointAtData.subPosition);
quatAt.toRotationMatrix(this._pointAtData.interpolationMatrix);
}
}
}
/**
* A Curve3 object is a logical object, so not a mesh, to handle curves in the 3D geometric space.
* A Curve3 is designed from a series of successive Vector3.
* @see https://doc.babylonjs.com/features/featuresDeepDive/mesh/drawCurves
*/
export class Curve3 {
/**
* Returns a Curve3 object along a Quadratic Bezier curve : https://doc.babylonjs.com/features/featuresDeepDive/mesh/drawCurves#quadratic-bezier-curve
* @param v0 (Vector3) the origin point of the Quadratic Bezier
* @param v1 (Vector3) the control point
* @param v2 (Vector3) the end point of the Quadratic Bezier
* @param nbPoints (integer) the wanted number of points in the curve
* @returns the created Curve3
*/
static CreateQuadraticBezier(v0, v1, v2, nbPoints) {
nbPoints = nbPoints > 2 ? nbPoints : 3;
const bez = [];
const equation = (t, val0, val1, val2) => {
const res = (1.0 - t) * (1.0 - t) * val0 + 2.0 * t * (1.0 - t) * val1 + t * t * val2;
return res;
};
for (let i = 0; i <= nbPoints; i++) {
bez.push(new Vector3(equation(i / nbPoints, v0.x, v1.x, v2.x), equation(i / nbPoints, v0.y, v1.y, v2.y), equation(i / nbPoints, v0.z, v1.z, v2.z)));
}
return new Curve3(bez);
}
/**
* Returns a Curve3 object along a Cubic Bezier curve : https://doc.babylonjs.com/features/featuresDeepDive/mesh/drawCurves#cubic-bezier-curve
* @param v0 (Vector3) the origin point of the Cubic Bezier
* @param v1 (Vector3) the first control point
* @param v2 (Vector3) the second control point
* @param v3 (Vector3) the end point of the Cubic Bezier
* @param nbPoints (integer) the wanted number of points in the curve
* @returns the created Curve3
*/
static CreateCubicBezier(v0, v1, v2, v3, nbPoints) {
nbPoints = nbPoints > 3 ? nbPoints : 4;
const bez = [];
const equation = (t, val0, val1, val2, val3) => {
const res = (1.0 - t) * (1.0 - t) * (1.0 - t) * val0 + 3.0 * t * (1.0 - t) * (1.0 - t) * val1 + 3.0 * t * t * (1.0 - t) * val2 + t * t * t * val3;
return res;
};
for (let i = 0; i <= nbPoints; i++) {
bez.push(new Vector3(equation(i / nbPoints, v0.x, v1.x, v2.x, v3.x), equation(i / nbPoints, v0.y, v1.y, v2.y, v3.y), equation(i / nbPoints, v0.z, v1.z, v2.z, v3.z)));
}
return new Curve3(bez);
}
/**
* Returns a Curve3 object along a Hermite Spline curve : https://doc.babylonjs.com/features/featuresDeepDive/mesh/drawCurves#hermite-spline
* @param p1 (Vector3) the origin point of the Hermite Spline
* @param t1 (Vector3) the tangent vector at the origin point
* @param p2 (Vector3) the end point of the Hermite Spline
* @param t2 (Vector3) the tangent vector at the end point
* @param nSeg (integer) the number of curve segments or nSeg + 1 points in the array
* @returns the created Curve3
*/
static CreateHermiteSpline(p1, t1, p2, t2, nSeg) {
const hermite = [];
const step = 1.0 / nSeg;
for (let i = 0; i <= nSeg; i++) {
hermite.push(Vector3.Hermite(p1, t1, p2, t2, i * step));
}
return new Curve3(hermite);
}
/**
* Returns a Curve3 object along a CatmullRom Spline curve :
* @param points (array of Vector3) the points the spline must pass through. At least, four points required
* @param nbPoints (integer) the wanted number of points between each curve control points
* @param closed (boolean) optional with default false, when true forms a closed loop from the points
* @returns the created Curve3
*/
static CreateCatmullRomSpline(points, nbPoints, closed) {
const catmullRom = [];
const step = 1.0 / nbPoints;
let amount = 0.0;
if (closed) {
const pointsCount = points.length;
for (let i = 0; i < pointsCount; i++) {
amount = 0;
for (let c = 0; c < nbPoints; c++) {
catmullRom.push(Vector3.CatmullRom(points[i % pointsCount], points[(i + 1) % pointsCount], points[(i + 2) % pointsCount], points[(i + 3) % pointsCount], amount));
amount += step;
}
}
catmullRom.push(catmullRom[0]);
}
else {
const totalPoints = [];
totalPoints.push(points[0].clone());
Array.prototype.push.apply(totalPoints, points);
totalPoints.push(points[points.length - 1].clone());
let i = 0;
for (; i < totalPoints.length - 3; i++) {
amount = 0;
for (let c = 0; c < nbPoints; c++) {
catmullRom.push(Vector3.CatmullRom(totalPoints[i], totalPoints[i + 1], totalPoints[i + 2], totalPoints[i + 3], amount));
amount += step;
}
}
i--;
catmullRom.push(Vector3.CatmullRom(totalPoints[i], totalPoints[i + 1], totalPoints[i + 2], totalPoints[i + 3], amount));
}
return new Curve3(catmullRom);
}
/**
* Returns a Curve3 object along an arc through three vector3 points:
* The three points should not be colinear. When they are the Curve3 is empty.
* @param first (Vector3) the first point the arc must pass through.
* @param second (Vector3) the second point the arc must pass through.
* @param third (Vector3) the third point the arc must pass through.
* @param steps (number) the larger the number of steps the more detailed the arc.
* @param closed (boolean) optional with default false, when true forms the chord from the first and third point
* @param fullCircle Circle (boolean) optional with default false, when true forms the complete circle through the three points
* @returns the created Curve3
*/
static ArcThru3Points(first, second, third, steps = 32, closed = false, fullCircle = false) {
const arc = [];
const vec1 = second.subtract(first);
const vec2 = third.subtract(second);
const vec3 = first.subtract(third);
const zAxis = Vector3.Cross(vec1, vec2);
const len4 = zAxis.length();
if (len4 < Math.pow(10, -8)) {
return new Curve3(arc); // colinear points arc is empty
}
const len1Sq = vec1.lengthSquared();
const len2Sq = vec2.lengthSquared();
const len3Sq = vec3.lengthSquared();
const len4Sq = zAxis.lengthSquared();
const len1 = vec1.length();
const len2 = vec2.length();
const len3 = vec3.length();
const radius = (0.5 * len1 * len2 * len3) / len4;
const dot1 = Vector3.Dot(vec1, vec3);
const dot2 = Vector3.Dot(vec1, vec2);
const dot3 = Vector3.Dot(vec2, vec3);
const a = (-0.5 * len2Sq * dot1) / len4Sq;
const b = (-0.5 * len3Sq * dot2) / len4Sq;
const c = (-0.5 * len1Sq * dot3) / len4Sq;
const center = first.scale(a).add(second.scale(b)).add(third.scale(c));
const radiusVec = first.subtract(center);
const xAxis = radiusVec.normalize();
const yAxis = Vector3.Cross(zAxis, xAxis).normalize();
if (fullCircle) {
const dStep = (2 * Math.PI) / steps;
for (let theta = 0; theta <= 2 * Math.PI; theta += dStep) {
arc.push(center.add(xAxis.scale(radius * Math.cos(theta)).add(yAxis.scale(radius * Math.sin(theta)))));
}
arc.push(first);
}
else {
const dStep = 1 / steps;
let theta = 0;
let point = Vector3.Zero();
do {
point = center.add(xAxis.scale(radius * Math.cos(theta)).add(yAxis.scale(radius * Math.sin(theta))));
arc.push(point);
theta += dStep;
} while (!point.equalsWithEpsilon(third, radius * dStep * 1.1));
arc.push(third);
if (closed) {
arc.push(first);
}
}
return new Curve3(arc);
}
/**
* A Curve3 object is a logical object, so not a mesh, to handle curves in the 3D geometric space.
* A Curve3 is designed from a series of successive Vector3.
* Tuto : https://doc.babylonjs.com/features/featuresDeepDive/mesh/drawCurves#curve3-object
* @param points points which make up the curve
*/
constructor(points) {
this._length = 0.0;
this._points = points;
this._length = this._computeLength(points);
}
/**
* @returns the Curve3 stored array of successive Vector3
*/
getPoints() {
return this._points;
}
/**
* @returns the computed length (float) of the curve.
*/
length() {
return this._length;
}
/**
* Returns a new instance of Curve3 object : var curve = curveA.continue(curveB);
* This new Curve3 is built by translating and sticking the curveB at the end of the curveA.
* curveA and curveB keep unchanged.
* @param curve the curve to continue from this curve
* @returns the newly constructed curve
*/
continue(curve) {
const lastPoint = this._points[this._points.length - 1];
const continuedPoints = this._points.slice();
const curvePoints = curve.getPoints();
for (let i = 1; i < curvePoints.length; i++) {
continuedPoints.push(curvePoints[i].subtract(curvePoints[0]).add(lastPoint));
}
const continuedCurve = new Curve3(continuedPoints);
return continuedCurve;
}
_computeLength(path) {
let l = 0;
for (let i = 1; i < path.length; i++) {
l += path[i].subtract(path[i - 1]).length();
}
return l;
}
}
//# sourceMappingURL=math.path.js.map