12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055 |
- import { Scalar } from "./math.scalar.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) {
- // 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 = Scalar.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 ? Orientation.CW : Orientation.CCW;
- this.angle = Angle.FromDegrees(this.orientation === 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 === 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 (!Scalar.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 (!Scalar.WithinEpsilon(Math.abs(vt.x) / tgl, 1.0, Epsilon)) {
- point = new Vector3(1.0, 0.0, 0.0);
- }
- else if (!Scalar.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 len1_sq = vec1.lengthSquared();
- const len2_sq = vec2.lengthSquared();
- const len3_sq = vec3.lengthSquared();
- const len4_sq = 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 * len2_sq * dot1) / len4_sq;
- const b = (-0.5 * len3_sq * dot2) / len4_sq;
- const c = (-0.5 * len1_sq * dot3) / len4_sq;
- 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
|