math.path.ts 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965
  1. import { DeepImmutable, Nullable } from '../types';
  2. import { Scalar } from './math.scalar';
  3. import { Vector2, Vector3, Quaternion, Matrix } from './math.vector';
  4. import { Epsilon } from './math.constants';
  5. /**
  6. * Defines potential orientation for back face culling
  7. */
  8. export enum Orientation {
  9. /**
  10. * Clockwise
  11. */
  12. CW = 0,
  13. /** Counter clockwise */
  14. CCW = 1
  15. }
  16. /** Class used to represent a Bezier curve */
  17. export class BezierCurve {
  18. /**
  19. * Returns the cubic Bezier interpolated value (float) at "t" (float) from the given x1, y1, x2, y2 floats
  20. * @param t defines the time
  21. * @param x1 defines the left coordinate on X axis
  22. * @param y1 defines the left coordinate on Y axis
  23. * @param x2 defines the right coordinate on X axis
  24. * @param y2 defines the right coordinate on Y axis
  25. * @returns the interpolated value
  26. */
  27. public static Interpolate(t: number, x1: number, y1: number, x2: number, y2: number): number {
  28. // Extract X (which is equal to time here)
  29. var f0 = 1 - 3 * x2 + 3 * x1;
  30. var f1 = 3 * x2 - 6 * x1;
  31. var f2 = 3 * x1;
  32. var refinedT = t;
  33. for (var i = 0; i < 5; i++) {
  34. var refinedT2 = refinedT * refinedT;
  35. var refinedT3 = refinedT2 * refinedT;
  36. var x = f0 * refinedT3 + f1 * refinedT2 + f2 * refinedT;
  37. var slope = 1.0 / (3.0 * f0 * refinedT2 + 2.0 * f1 * refinedT + f2);
  38. refinedT -= (x - t) * slope;
  39. refinedT = Math.min(1, Math.max(0, refinedT));
  40. }
  41. // Resolve cubic bezier for the given x
  42. return 3 * Math.pow(1 - refinedT, 2) * refinedT * y1 +
  43. 3 * (1 - refinedT) * Math.pow(refinedT, 2) * y2 +
  44. Math.pow(refinedT, 3);
  45. }
  46. }
  47. /**
  48. * Defines angle representation
  49. */
  50. export class Angle {
  51. private _radians: number;
  52. /**
  53. * Creates an Angle object of "radians" radians (float).
  54. * @param radians the angle in radians
  55. */
  56. constructor(radians: number) {
  57. this._radians = radians;
  58. if (this._radians < 0.0) { this._radians += (2.0 * Math.PI); }
  59. }
  60. /**
  61. * Get value in degrees
  62. * @returns the Angle value in degrees (float)
  63. */
  64. public degrees() {
  65. return this._radians * 180.0 / Math.PI;
  66. }
  67. /**
  68. * Get value in radians
  69. * @returns the Angle value in radians (float)
  70. */
  71. public radians() {
  72. return this._radians;
  73. }
  74. /**
  75. * Gets a new Angle object valued with the gradient angle, in radians, of the line joining two points
  76. * @param a defines first point as the origin
  77. * @param b defines point
  78. * @returns a new Angle
  79. */
  80. public static BetweenTwoPoints(a: DeepImmutable<Vector2>, b: DeepImmutable<Vector2>): Angle {
  81. var delta = b.subtract(a);
  82. var theta = Math.atan2(delta.y, delta.x);
  83. return new Angle(theta);
  84. }
  85. /**
  86. * Gets a new Angle object from the given float in radians
  87. * @param radians defines the angle value in radians
  88. * @returns a new Angle
  89. */
  90. public static FromRadians(radians: number): Angle {
  91. return new Angle(radians);
  92. }
  93. /**
  94. * Gets a new Angle object from the given float in degrees
  95. * @param degrees defines the angle value in degrees
  96. * @returns a new Angle
  97. */
  98. public static FromDegrees(degrees: number): Angle {
  99. return new Angle(degrees * Math.PI / 180.0);
  100. }
  101. }
  102. /**
  103. * This represents an arc in a 2d space.
  104. */
  105. export class Arc2 {
  106. /**
  107. * Defines the center point of the arc.
  108. */
  109. public centerPoint: Vector2;
  110. /**
  111. * Defines the radius of the arc.
  112. */
  113. public radius: number;
  114. /**
  115. * Defines the angle of the arc (from mid point to end point).
  116. */
  117. public angle: Angle;
  118. /**
  119. * Defines the start angle of the arc (from start point to middle point).
  120. */
  121. public startAngle: Angle;
  122. /**
  123. * Defines the orientation of the arc (clock wise/counter clock wise).
  124. */
  125. public orientation: Orientation;
  126. /**
  127. * Creates an Arc object from the three given points : start, middle and end.
  128. * @param startPoint Defines the start point of the arc
  129. * @param midPoint Defines the midlle point of the arc
  130. * @param endPoint Defines the end point of the arc
  131. */
  132. constructor(
  133. /** Defines the start point of the arc */
  134. public startPoint: Vector2,
  135. /** Defines the mid point of the arc */
  136. public midPoint: Vector2,
  137. /** Defines the end point of the arc */
  138. public endPoint: Vector2) {
  139. var temp = Math.pow(midPoint.x, 2) + Math.pow(midPoint.y, 2);
  140. var startToMid = (Math.pow(startPoint.x, 2) + Math.pow(startPoint.y, 2) - temp) / 2.;
  141. var midToEnd = (temp - Math.pow(endPoint.x, 2) - Math.pow(endPoint.y, 2)) / 2.;
  142. var det = (startPoint.x - midPoint.x) * (midPoint.y - endPoint.y) - (midPoint.x - endPoint.x) * (startPoint.y - midPoint.y);
  143. this.centerPoint = new Vector2(
  144. (startToMid * (midPoint.y - endPoint.y) - midToEnd * (startPoint.y - midPoint.y)) / det,
  145. ((startPoint.x - midPoint.x) * midToEnd - (midPoint.x - endPoint.x) * startToMid) / det
  146. );
  147. this.radius = this.centerPoint.subtract(this.startPoint).length();
  148. this.startAngle = Angle.BetweenTwoPoints(this.centerPoint, this.startPoint);
  149. var a1 = this.startAngle.degrees();
  150. var a2 = Angle.BetweenTwoPoints(this.centerPoint, this.midPoint).degrees();
  151. var a3 = Angle.BetweenTwoPoints(this.centerPoint, this.endPoint).degrees();
  152. // angles correction
  153. if (a2 - a1 > +180.0) { a2 -= 360.0; }
  154. if (a2 - a1 < -180.0) { a2 += 360.0; }
  155. if (a3 - a2 > +180.0) { a3 -= 360.0; }
  156. if (a3 - a2 < -180.0) { a3 += 360.0; }
  157. this.orientation = (a2 - a1) < 0 ? Orientation.CW : Orientation.CCW;
  158. this.angle = Angle.FromDegrees(this.orientation === Orientation.CW ? a1 - a3 : a3 - a1);
  159. }
  160. }
  161. /**
  162. * Represents a 2D path made up of multiple 2D points
  163. */
  164. export class Path2 {
  165. private _points = new Array<Vector2>();
  166. private _length = 0.0;
  167. /**
  168. * If the path start and end point are the same
  169. */
  170. public closed = false;
  171. /**
  172. * Creates a Path2 object from the starting 2D coordinates x and y.
  173. * @param x the starting points x value
  174. * @param y the starting points y value
  175. */
  176. constructor(x: number, y: number) {
  177. this._points.push(new Vector2(x, y));
  178. }
  179. /**
  180. * Adds a new segment until the given coordinates (x, y) to the current Path2.
  181. * @param x the added points x value
  182. * @param y the added points y value
  183. * @returns the updated Path2.
  184. */
  185. public addLineTo(x: number, y: number): Path2 {
  186. if (this.closed) {
  187. return this;
  188. }
  189. var newPoint = new Vector2(x, y);
  190. var previousPoint = this._points[this._points.length - 1];
  191. this._points.push(newPoint);
  192. this._length += newPoint.subtract(previousPoint).length();
  193. return this;
  194. }
  195. /**
  196. * 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.
  197. * @param midX middle point x value
  198. * @param midY middle point y value
  199. * @param endX end point x value
  200. * @param endY end point y value
  201. * @param numberOfSegments (default: 36)
  202. * @returns the updated Path2.
  203. */
  204. public addArcTo(midX: number, midY: number, endX: number, endY: number, numberOfSegments = 36): Path2 {
  205. if (this.closed) {
  206. return this;
  207. }
  208. var startPoint = this._points[this._points.length - 1];
  209. var midPoint = new Vector2(midX, midY);
  210. var endPoint = new Vector2(endX, endY);
  211. var arc = new Arc2(startPoint, midPoint, endPoint);
  212. var increment = arc.angle.radians() / numberOfSegments;
  213. if (arc.orientation === Orientation.CW) { increment *= -1; }
  214. var currentAngle = arc.startAngle.radians() + increment;
  215. for (var i = 0; i < numberOfSegments; i++) {
  216. var x = Math.cos(currentAngle) * arc.radius + arc.centerPoint.x;
  217. var y = Math.sin(currentAngle) * arc.radius + arc.centerPoint.y;
  218. this.addLineTo(x, y);
  219. currentAngle += increment;
  220. }
  221. return this;
  222. }
  223. /**
  224. * Closes the Path2.
  225. * @returns the Path2.
  226. */
  227. public close(): Path2 {
  228. this.closed = true;
  229. return this;
  230. }
  231. /**
  232. * Gets the sum of the distance between each sequential point in the path
  233. * @returns the Path2 total length (float).
  234. */
  235. public length(): number {
  236. var result = this._length;
  237. if (this.closed) {
  238. var lastPoint = this._points[this._points.length - 1];
  239. var firstPoint = this._points[0];
  240. result += (firstPoint.subtract(lastPoint).length());
  241. }
  242. return result;
  243. }
  244. /**
  245. * Gets the points which construct the path
  246. * @returns the Path2 internal array of points.
  247. */
  248. public getPoints(): Vector2[] {
  249. return this._points;
  250. }
  251. /**
  252. * Retreives the point at the distance aways from the starting point
  253. * @param normalizedLengthPosition the length along the path to retreive the point from
  254. * @returns a new Vector2 located at a percentage of the Path2 total length on this path.
  255. */
  256. public getPointAtLengthPosition(normalizedLengthPosition: number): Vector2 {
  257. if (normalizedLengthPosition < 0 || normalizedLengthPosition > 1) {
  258. return Vector2.Zero();
  259. }
  260. var lengthPosition = normalizedLengthPosition * this.length();
  261. var previousOffset = 0;
  262. for (var i = 0; i < this._points.length; i++) {
  263. var j = (i + 1) % this._points.length;
  264. var a = this._points[i];
  265. var b = this._points[j];
  266. var bToA = b.subtract(a);
  267. var nextOffset = (bToA.length() + previousOffset);
  268. if (lengthPosition >= previousOffset && lengthPosition <= nextOffset) {
  269. var dir = bToA.normalize();
  270. var localOffset = lengthPosition - previousOffset;
  271. return new Vector2(
  272. a.x + (dir.x * localOffset),
  273. a.y + (dir.y * localOffset)
  274. );
  275. }
  276. previousOffset = nextOffset;
  277. }
  278. return Vector2.Zero();
  279. }
  280. /**
  281. * Creates a new path starting from an x and y position
  282. * @param x starting x value
  283. * @param y starting y value
  284. * @returns a new Path2 starting at the coordinates (x, y).
  285. */
  286. public static StartingAt(x: number, y: number): Path2 {
  287. return new Path2(x, y);
  288. }
  289. }
  290. /**
  291. * Represents a 3D path made up of multiple 3D points
  292. */
  293. export class Path3D {
  294. private _curve = new Array<Vector3>();
  295. private _distances = new Array<number>();
  296. private _tangents = new Array<Vector3>();
  297. private _normals = new Array<Vector3>();
  298. private _binormals = new Array<Vector3>();
  299. private _raw: boolean;
  300. private _alignTangentsWithPath: boolean;
  301. // holds interpolated point data
  302. private readonly _pointAtData = {
  303. id: 0,
  304. point: Vector3.Zero(),
  305. previousPointArrayIndex: 0,
  306. position: 0,
  307. subPosition: 0,
  308. interpolateReady: false,
  309. interpolationMatrix: Matrix.Identity(),
  310. };
  311. /**
  312. * new Path3D(path, normal, raw)
  313. * Creates a Path3D. A Path3D is a logical math object, so not a mesh.
  314. * please read the description in the tutorial : https://doc.babylonjs.com/how_to/how_to_use_path3d
  315. * @param path an array of Vector3, the curve axis of the Path3D
  316. * @param firstNormal (options) Vector3, the first wanted normal to the curve. Ex (0, 1, 0) for a vertical normal.
  317. * @param raw (optional, default false) : boolean, if true the returned Path3D isn't normalized. Useful to depict path acceleration or speed.
  318. * @param alignTangentsWithPath (optional, default false) : boolean, if true the tangents will be aligned with the path.
  319. */
  320. constructor(
  321. /**
  322. * an array of Vector3, the curve axis of the Path3D
  323. */
  324. public path: Vector3[],
  325. firstNormal: Nullable<Vector3> = null,
  326. raw?: boolean,
  327. alignTangentsWithPath = false
  328. ) {
  329. for (var p = 0; p < path.length; p++) {
  330. this._curve[p] = path[p].clone(); // hard copy
  331. }
  332. this._raw = raw || false;
  333. this._alignTangentsWithPath = alignTangentsWithPath;
  334. this._compute(firstNormal, alignTangentsWithPath);
  335. }
  336. /**
  337. * Returns the Path3D array of successive Vector3 designing its curve.
  338. * @returns the Path3D array of successive Vector3 designing its curve.
  339. */
  340. public getCurve(): Vector3[] {
  341. return this._curve;
  342. }
  343. /**
  344. * Returns the Path3D array of successive Vector3 designing its curve.
  345. * @returns the Path3D array of successive Vector3 designing its curve.
  346. */
  347. public getPoints(): Vector3[] {
  348. return this._curve;
  349. }
  350. /**
  351. * @returns the computed length (float) of the path.
  352. */
  353. public length() {
  354. return this._distances[this._distances.length - 1];
  355. }
  356. /**
  357. * Returns an array populated with tangent vectors on each Path3D curve point.
  358. * @returns an array populated with tangent vectors on each Path3D curve point.
  359. */
  360. public getTangents(): Vector3[] {
  361. return this._tangents;
  362. }
  363. /**
  364. * Returns an array populated with normal vectors on each Path3D curve point.
  365. * @returns an array populated with normal vectors on each Path3D curve point.
  366. */
  367. public getNormals(): Vector3[] {
  368. return this._normals;
  369. }
  370. /**
  371. * Returns an array populated with binormal vectors on each Path3D curve point.
  372. * @returns an array populated with binormal vectors on each Path3D curve point.
  373. */
  374. public getBinormals(): Vector3[] {
  375. return this._binormals;
  376. }
  377. /**
  378. * Returns an array populated with distances (float) of the i-th point from the first curve point.
  379. * @returns an array populated with distances (float) of the i-th point from the first curve point.
  380. */
  381. public getDistances(): number[] {
  382. return this._distances;
  383. }
  384. /**
  385. * Returns an interpolated point along this path
  386. * @param position the position of the point along this path, from 0.0 to 1.0
  387. * @returns a new Vector3 as the point
  388. */
  389. public getPointAt(position: number): Vector3 {
  390. return this._updatePointAtData(position).point;
  391. }
  392. /**
  393. * Returns the tangent vector of an interpolated Path3D curve point at the specified position along this path.
  394. * @param position the position of the point along this path, from 0.0 to 1.0
  395. * @param interpolated (optional, default false) : boolean, if true returns an interpolated tangent instead of the tangent of the previous path point.
  396. * @returns a tangent vector corresponding to the interpolated Path3D curve point, if not interpolated, the tangent is taken from the precomputed tangents array.
  397. */
  398. public getTangentAt(position: number, interpolated = false): Vector3 {
  399. this._updatePointAtData(position, interpolated);
  400. return interpolated ? Vector3.TransformCoordinates(Vector3.Forward(), this._pointAtData.interpolationMatrix) : this._tangents[this._pointAtData.previousPointArrayIndex];
  401. }
  402. /**
  403. * Returns the tangent vector of an interpolated Path3D curve point at the specified position along this path.
  404. * @param position the position of the point along this path, from 0.0 to 1.0
  405. * @param interpolated (optional, default false) : boolean, if true returns an interpolated normal instead of the normal of the previous path point.
  406. * @returns a normal vector corresponding to the interpolated Path3D curve point, if not interpolated, the normal is taken from the precomputed normals array.
  407. */
  408. public getNormalAt(position: number, interpolated = false): Vector3 {
  409. this._updatePointAtData(position, interpolated);
  410. return interpolated ? Vector3.TransformCoordinates(Vector3.Right(), this._pointAtData.interpolationMatrix) : this._normals[this._pointAtData.previousPointArrayIndex];
  411. }
  412. /**
  413. * Returns the binormal vector of an interpolated Path3D curve point at the specified position along this path.
  414. * @param position the position of the point along this path, from 0.0 to 1.0
  415. * @param interpolated (optional, default false) : boolean, if true returns an interpolated binormal instead of the binormal of the previous path point.
  416. * @returns a binormal vector corresponding to the interpolated Path3D curve point, if not interpolated, the binormal is taken from the precomputed binormals array.
  417. */
  418. public getBinormalAt(position: number, interpolated = false): Vector3 {
  419. this._updatePointAtData(position, interpolated);
  420. return interpolated ? Vector3.TransformCoordinates(Vector3.UpReadOnly, this._pointAtData.interpolationMatrix) : this._binormals[this._pointAtData.previousPointArrayIndex];
  421. }
  422. /**
  423. * Returns the distance (float) of an interpolated Path3D curve point at the specified position along this path.
  424. * @param position the position of the point along this path, from 0.0 to 1.0
  425. * @returns the distance of the interpolated Path3D curve point at the specified position along this path.
  426. */
  427. public getDistanceAt(position: number): number {
  428. return this.length() * position;
  429. }
  430. /**
  431. * Returns the array index of the previous point of an interpolated point along this path
  432. * @param position the position of the point to interpolate along this path, from 0.0 to 1.0
  433. * @returns the array index
  434. */
  435. public getPreviousPointIndexAt(position: number) {
  436. this._updatePointAtData(position);
  437. return this._pointAtData.previousPointArrayIndex;
  438. }
  439. /**
  440. * 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)
  441. * @param position the position of the point to interpolate along this path, from 0.0 to 1.0
  442. * @returns the sub position
  443. */
  444. public getSubPositionAt(position: number) {
  445. this._updatePointAtData(position);
  446. return this._pointAtData.subPosition;
  447. }
  448. /**
  449. * Returns the position of the closest virtual point on this path to an arbitrary Vector3, from 0.0 to 1.0
  450. * @param target the vector of which to get the closest position to
  451. * @returns the position of the closest virtual point on this path to the target vector
  452. */
  453. public getClosestPositionTo(target: Vector3) {
  454. let smallestDistance = Number.MAX_VALUE;
  455. let closestPosition = 0.0;
  456. for (let i = 0; i < this._curve.length - 1; i++) {
  457. let point = this._curve[i + 0];
  458. let tangent = this._curve[i + 1].subtract(point).normalize();
  459. let subLength = this._distances[i + 1] - this._distances[i + 0];
  460. let subPosition = Math.min(Math.max(Vector3.Dot(tangent, target.subtract(point).normalize()), 0.0) * Vector3.Distance(point, target) / subLength, 1.0);
  461. let distance = Vector3.Distance(point.add(tangent.scale(subPosition * subLength)), target);
  462. if (distance < smallestDistance) {
  463. smallestDistance = distance;
  464. closestPosition = (this._distances[i + 0] + subLength * subPosition) / this.length();
  465. }
  466. }
  467. return closestPosition;
  468. }
  469. /**
  470. * Returns a sub path (slice) of this path
  471. * @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
  472. * @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
  473. * @returns a sub path (slice) of this path
  474. */
  475. public slice(start: number = 0.0, end: number = 1.0) {
  476. if (start < 0.0) {
  477. start = 1 - (start * -1.0) % 1.0;
  478. }
  479. if (end < 0.0) {
  480. end = 1 - (end * -1.0) % 1.0;
  481. }
  482. if (start > end) {
  483. let _start = start;
  484. start = end;
  485. end = _start;
  486. }
  487. let curvePoints = this.getCurve();
  488. let startPoint = this.getPointAt(start);
  489. let startIndex = this.getPreviousPointIndexAt(start);
  490. let endPoint = this.getPointAt(end);
  491. let endIndex = this.getPreviousPointIndexAt(end) + 1;
  492. let slicePoints: Vector3[] = [];
  493. if (start !== 0.0) {
  494. startIndex++;
  495. slicePoints.push(startPoint);
  496. }
  497. slicePoints.push(...curvePoints.slice(startIndex, endIndex));
  498. if (end !== 1.0 || start === 1.0) {
  499. slicePoints.push(endPoint);
  500. }
  501. return new Path3D(slicePoints, this.getNormalAt(start), this._raw, this._alignTangentsWithPath);
  502. }
  503. /**
  504. * Forces the Path3D tangent, normal, binormal and distance recomputation.
  505. * @param path path which all values are copied into the curves points
  506. * @param firstNormal which should be projected onto the curve
  507. * @param alignTangentsWithPath (optional, default false) : boolean, if true the tangents will be aligned with the path
  508. * @returns the same object updated.
  509. */
  510. public update(path: Vector3[], firstNormal: Nullable<Vector3> = null, alignTangentsWithPath = false): Path3D {
  511. for (var p = 0; p < path.length; p++) {
  512. this._curve[p].x = path[p].x;
  513. this._curve[p].y = path[p].y;
  514. this._curve[p].z = path[p].z;
  515. }
  516. this._compute(firstNormal, alignTangentsWithPath);
  517. return this;
  518. }
  519. // private function compute() : computes tangents, normals and binormals
  520. private _compute(firstNormal: Nullable<Vector3>, alignTangentsWithPath = false): void {
  521. var l = this._curve.length;
  522. // first and last tangents
  523. this._tangents[0] = this._getFirstNonNullVector(0);
  524. if (!this._raw) {
  525. this._tangents[0].normalize();
  526. }
  527. this._tangents[l - 1] = this._curve[l - 1].subtract(this._curve[l - 2]);
  528. if (!this._raw) {
  529. this._tangents[l - 1].normalize();
  530. }
  531. // normals and binormals at first point : arbitrary vector with _normalVector()
  532. var tg0 = this._tangents[0];
  533. var pp0 = this._normalVector(tg0, firstNormal);
  534. this._normals[0] = pp0;
  535. if (!this._raw) {
  536. this._normals[0].normalize();
  537. }
  538. this._binormals[0] = Vector3.Cross(tg0, this._normals[0]);
  539. if (!this._raw) {
  540. this._binormals[0].normalize();
  541. }
  542. this._distances[0] = 0.0;
  543. // normals and binormals : next points
  544. var prev: Vector3; // previous vector (segment)
  545. var cur: Vector3; // current vector (segment)
  546. var curTang: Vector3; // current tangent
  547. // previous normal
  548. var prevNor: Vector3; // previous normal
  549. var prevBinor: Vector3; // previous binormal
  550. for (var i = 1; i < l; i++) {
  551. // tangents
  552. prev = this._getLastNonNullVector(i);
  553. if (i < l - 1) {
  554. cur = this._getFirstNonNullVector(i);
  555. this._tangents[i] = alignTangentsWithPath ? cur : prev.add(cur);
  556. this._tangents[i].normalize();
  557. }
  558. this._distances[i] = this._distances[i - 1] + this._curve[i].subtract(this._curve[i - 1]).length();
  559. // normals and binormals
  560. // http://www.cs.cmu.edu/afs/andrew/scs/cs/15-462/web/old/asst2camera.html
  561. curTang = this._tangents[i];
  562. prevBinor = this._binormals[i - 1];
  563. this._normals[i] = Vector3.Cross(prevBinor, curTang);
  564. if (!this._raw) {
  565. if (this._normals[i].length() === 0) {
  566. prevNor = this._normals[i - 1];
  567. this._normals[i] = prevNor.clone();
  568. } else {
  569. this._normals[i].normalize();
  570. }
  571. }
  572. this._binormals[i] = Vector3.Cross(curTang, this._normals[i]);
  573. if (!this._raw) {
  574. this._binormals[i].normalize();
  575. }
  576. }
  577. this._pointAtData.id = NaN;
  578. }
  579. // private function getFirstNonNullVector(index)
  580. // returns the first non null vector from index : curve[index + N].subtract(curve[index])
  581. private _getFirstNonNullVector(index: number): Vector3 {
  582. var i = 1;
  583. var nNVector: Vector3 = this._curve[index + i].subtract(this._curve[index]);
  584. while (nNVector.length() === 0 && index + i + 1 < this._curve.length) {
  585. i++;
  586. nNVector = this._curve[index + i].subtract(this._curve[index]);
  587. }
  588. return nNVector;
  589. }
  590. // private function getLastNonNullVector(index)
  591. // returns the last non null vector from index : curve[index].subtract(curve[index - N])
  592. private _getLastNonNullVector(index: number): Vector3 {
  593. var i = 1;
  594. var nLVector: Vector3 = this._curve[index].subtract(this._curve[index - i]);
  595. while (nLVector.length() === 0 && index > i + 1) {
  596. i++;
  597. nLVector = this._curve[index].subtract(this._curve[index - i]);
  598. }
  599. return nLVector;
  600. }
  601. // private function normalVector(v0, vt, va) :
  602. // returns an arbitrary point in the plane defined by the point v0 and the vector vt orthogonal to this plane
  603. // if va is passed, it returns the va projection on the plane orthogonal to vt at the point v0
  604. private _normalVector(vt: Vector3, va: Nullable<Vector3>): Vector3 {
  605. var normal0: Vector3;
  606. var tgl = vt.length();
  607. if (tgl === 0.0) {
  608. tgl = 1.0;
  609. }
  610. if (va === undefined || va === null) {
  611. var point: Vector3;
  612. if (!Scalar.WithinEpsilon(Math.abs(vt.y) / tgl, 1.0, Epsilon)) { // search for a point in the plane
  613. point = new Vector3(0.0, -1.0, 0.0);
  614. }
  615. else if (!Scalar.WithinEpsilon(Math.abs(vt.x) / tgl, 1.0, Epsilon)) {
  616. point = new Vector3(1.0, 0.0, 0.0);
  617. }
  618. else if (!Scalar.WithinEpsilon(Math.abs(vt.z) / tgl, 1.0, Epsilon)) {
  619. point = new Vector3(0.0, 0.0, 1.0);
  620. }
  621. else {
  622. point = Vector3.Zero();
  623. }
  624. normal0 = Vector3.Cross(vt, point);
  625. }
  626. else {
  627. normal0 = Vector3.Cross(vt, va);
  628. Vector3.CrossToRef(normal0, vt, normal0);
  629. }
  630. normal0.normalize();
  631. return normal0;
  632. }
  633. /**
  634. * Updates the point at data for an interpolated point along this curve
  635. * @param position the position of the point along this curve, from 0.0 to 1.0
  636. * @interpolateTNB wether to compute the interpolated tangent, normal and binormal
  637. * @returns the (updated) point at data
  638. */
  639. private _updatePointAtData(position: number, interpolateTNB: boolean = false) {
  640. // set an id for caching the result
  641. if (this._pointAtData.id === position) {
  642. if (!this._pointAtData.interpolateReady) {
  643. this._updateInterpolationMatrix();
  644. }
  645. return this._pointAtData;
  646. } else {
  647. this._pointAtData.id = position;
  648. }
  649. let curvePoints = this.getPoints();
  650. // clamp position between 0.0 and 1.0
  651. if (position <= 0.0) {
  652. return this._setPointAtData(0.0, 0.0, curvePoints[0], 0, interpolateTNB);
  653. } else if (position >= 1.0) {
  654. return this._setPointAtData(1.0, 1.0, curvePoints[curvePoints.length - 1], curvePoints.length - 1, interpolateTNB);
  655. }
  656. let previousPoint: Vector3 = curvePoints[0];
  657. let currentPoint: Vector3;
  658. let currentLength = 0.0;
  659. let targetLength = position * this.length();
  660. for (let i = 1; i < curvePoints.length; i++) {
  661. currentPoint = curvePoints[i];
  662. let distance = Vector3.Distance(previousPoint, currentPoint);
  663. currentLength += distance;
  664. if (currentLength === targetLength) {
  665. return this._setPointAtData(position, 1.0, currentPoint, i, interpolateTNB);
  666. } else if (currentLength > targetLength) {
  667. let toLength = currentLength - targetLength;
  668. let diff = toLength / distance;
  669. let dir = previousPoint.subtract(currentPoint);
  670. let point = currentPoint.add(dir.scaleInPlace(diff));
  671. return this._setPointAtData(position, 1 - diff, point, i - 1, interpolateTNB);
  672. }
  673. previousPoint = currentPoint;
  674. }
  675. return this._pointAtData;
  676. }
  677. /**
  678. * Updates the point at data from the specified parameters
  679. * @param position where along the path the interpolated point is, from 0.0 to 1.0
  680. * @param point the interpolated point
  681. * @param parentIndex the index of an existing curve point that is on, or else positionally the first behind, the interpolated point
  682. */
  683. private _setPointAtData(position: number, subPosition: number, point: Vector3, parentIndex: number, interpolateTNB: boolean) {
  684. this._pointAtData.point = point;
  685. this._pointAtData.position = position;
  686. this._pointAtData.subPosition = subPosition;
  687. this._pointAtData.previousPointArrayIndex = parentIndex;
  688. this._pointAtData.interpolateReady = interpolateTNB;
  689. if (interpolateTNB) {
  690. this._updateInterpolationMatrix();
  691. }
  692. return this._pointAtData;
  693. }
  694. /**
  695. * Updates the point at interpolation matrix for the tangents, normals and binormals
  696. */
  697. private _updateInterpolationMatrix() {
  698. this._pointAtData.interpolationMatrix = Matrix.Identity();
  699. let parentIndex = this._pointAtData.previousPointArrayIndex;
  700. if (parentIndex !== this._tangents.length - 1) {
  701. let index = parentIndex + 1;
  702. let tangentFrom = this._tangents[parentIndex].clone();
  703. let normalFrom = this._normals[parentIndex].clone();
  704. let binormalFrom = this._binormals[parentIndex].clone();
  705. let tangentTo = this._tangents[index].clone();
  706. let normalTo = this._normals[index].clone();
  707. let binormalTo = this._binormals[index].clone();
  708. let quatFrom = Quaternion.RotationQuaternionFromAxis(normalFrom, binormalFrom, tangentFrom);
  709. let quatTo = Quaternion.RotationQuaternionFromAxis(normalTo, binormalTo, tangentTo);
  710. let quatAt = Quaternion.Slerp(quatFrom, quatTo, this._pointAtData.subPosition);
  711. quatAt.toRotationMatrix(this._pointAtData.interpolationMatrix);
  712. }
  713. }
  714. }
  715. /**
  716. * A Curve3 object is a logical object, so not a mesh, to handle curves in the 3D geometric space.
  717. * A Curve3 is designed from a series of successive Vector3.
  718. * @see https://doc.babylonjs.com/how_to/how_to_use_curve3
  719. */
  720. export class Curve3 {
  721. private _points: Vector3[];
  722. private _length: number = 0.0;
  723. /**
  724. * Returns a Curve3 object along a Quadratic Bezier curve : https://doc.babylonjs.com/how_to/how_to_use_curve3#quadratic-bezier-curve
  725. * @param v0 (Vector3) the origin point of the Quadratic Bezier
  726. * @param v1 (Vector3) the control point
  727. * @param v2 (Vector3) the end point of the Quadratic Bezier
  728. * @param nbPoints (integer) the wanted number of points in the curve
  729. * @returns the created Curve3
  730. */
  731. public static CreateQuadraticBezier(v0: DeepImmutable<Vector3>, v1: DeepImmutable<Vector3>, v2: DeepImmutable<Vector3>, nbPoints: number): Curve3 {
  732. nbPoints = nbPoints > 2 ? nbPoints : 3;
  733. var bez = new Array<Vector3>();
  734. var equation = (t: number, val0: number, val1: number, val2: number) => {
  735. var res = (1.0 - t) * (1.0 - t) * val0 + 2.0 * t * (1.0 - t) * val1 + t * t * val2;
  736. return res;
  737. };
  738. for (var i = 0; i <= nbPoints; i++) {
  739. 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)));
  740. }
  741. return new Curve3(bez);
  742. }
  743. /**
  744. * Returns a Curve3 object along a Cubic Bezier curve : https://doc.babylonjs.com/how_to/how_to_use_curve3#cubic-bezier-curve
  745. * @param v0 (Vector3) the origin point of the Cubic Bezier
  746. * @param v1 (Vector3) the first control point
  747. * @param v2 (Vector3) the second control point
  748. * @param v3 (Vector3) the end point of the Cubic Bezier
  749. * @param nbPoints (integer) the wanted number of points in the curve
  750. * @returns the created Curve3
  751. */
  752. public static CreateCubicBezier(v0: DeepImmutable<Vector3>, v1: DeepImmutable<Vector3>, v2: DeepImmutable<Vector3>, v3: DeepImmutable<Vector3>, nbPoints: number): Curve3 {
  753. nbPoints = nbPoints > 3 ? nbPoints : 4;
  754. var bez = new Array<Vector3>();
  755. var equation = (t: number, val0: number, val1: number, val2: number, val3: number) => {
  756. var 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;
  757. return res;
  758. };
  759. for (var i = 0; i <= nbPoints; i++) {
  760. 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)));
  761. }
  762. return new Curve3(bez);
  763. }
  764. /**
  765. * Returns a Curve3 object along a Hermite Spline curve : https://doc.babylonjs.com/how_to/how_to_use_curve3#hermite-spline
  766. * @param p1 (Vector3) the origin point of the Hermite Spline
  767. * @param t1 (Vector3) the tangent vector at the origin point
  768. * @param p2 (Vector3) the end point of the Hermite Spline
  769. * @param t2 (Vector3) the tangent vector at the end point
  770. * @param nbPoints (integer) the wanted number of points in the curve
  771. * @returns the created Curve3
  772. */
  773. public static CreateHermiteSpline(p1: DeepImmutable<Vector3>, t1: DeepImmutable<Vector3>, p2: DeepImmutable<Vector3>, t2: DeepImmutable<Vector3>, nbPoints: number): Curve3 {
  774. var hermite = new Array<Vector3>();
  775. var step = 1.0 / nbPoints;
  776. for (var i = 0; i <= nbPoints; i++) {
  777. hermite.push(Vector3.Hermite(p1, t1, p2, t2, i * step));
  778. }
  779. return new Curve3(hermite);
  780. }
  781. /**
  782. * Returns a Curve3 object along a CatmullRom Spline curve :
  783. * @param points (array of Vector3) the points the spline must pass through. At least, four points required
  784. * @param nbPoints (integer) the wanted number of points between each curve control points
  785. * @param closed (boolean) optional with default false, when true forms a closed loop from the points
  786. * @returns the created Curve3
  787. */
  788. public static CreateCatmullRomSpline(points: DeepImmutable<Vector3[]>, nbPoints: number, closed?: boolean): Curve3 {
  789. var catmullRom = new Array<Vector3>();
  790. var step = 1.0 / nbPoints;
  791. var amount = 0.0;
  792. if (closed) {
  793. var pointsCount = points.length;
  794. for (var i = 0; i < pointsCount; i++) {
  795. amount = 0;
  796. for (var c = 0; c < nbPoints; c++) {
  797. catmullRom.push(Vector3.CatmullRom(points[i % pointsCount], points[(i + 1) % pointsCount], points[(i + 2) % pointsCount], points[(i + 3) % pointsCount], amount));
  798. amount += step;
  799. }
  800. }
  801. catmullRom.push(catmullRom[0]);
  802. }
  803. else {
  804. var totalPoints = new Array<Vector3>();
  805. totalPoints.push(points[0].clone());
  806. Array.prototype.push.apply(totalPoints, points);
  807. totalPoints.push(points[points.length - 1].clone());
  808. for (var i = 0; i < totalPoints.length - 3; i++) {
  809. amount = 0;
  810. for (var c = 0; c < nbPoints; c++) {
  811. catmullRom.push(Vector3.CatmullRom(totalPoints[i], totalPoints[i + 1], totalPoints[i + 2], totalPoints[i + 3], amount));
  812. amount += step;
  813. }
  814. }
  815. i--;
  816. catmullRom.push(Vector3.CatmullRom(totalPoints[i], totalPoints[i + 1], totalPoints[i + 2], totalPoints[i + 3], amount));
  817. }
  818. return new Curve3(catmullRom);
  819. }
  820. /**
  821. * A Curve3 object is a logical object, so not a mesh, to handle curves in the 3D geometric space.
  822. * A Curve3 is designed from a series of successive Vector3.
  823. * Tuto : https://doc.babylonjs.com/how_to/how_to_use_curve3#curve3-object
  824. * @param points points which make up the curve
  825. */
  826. constructor(points: Vector3[]) {
  827. this._points = points;
  828. this._length = this._computeLength(points);
  829. }
  830. /**
  831. * @returns the Curve3 stored array of successive Vector3
  832. */
  833. public getPoints() {
  834. return this._points;
  835. }
  836. /**
  837. * @returns the computed length (float) of the curve.
  838. */
  839. public length() {
  840. return this._length;
  841. }
  842. /**
  843. * Returns a new instance of Curve3 object : var curve = curveA.continue(curveB);
  844. * This new Curve3 is built by translating and sticking the curveB at the end of the curveA.
  845. * curveA and curveB keep unchanged.
  846. * @param curve the curve to continue from this curve
  847. * @returns the newly constructed curve
  848. */
  849. public continue(curve: DeepImmutable<Curve3>): Curve3 {
  850. var lastPoint = this._points[this._points.length - 1];
  851. var continuedPoints = this._points.slice();
  852. var curvePoints = curve.getPoints();
  853. for (var i = 1; i < curvePoints.length; i++) {
  854. continuedPoints.push(curvePoints[i].subtract(curvePoints[0]).add(lastPoint));
  855. }
  856. var continuedCurve = new Curve3(continuedPoints);
  857. return continuedCurve;
  858. }
  859. private _computeLength(path: DeepImmutable<Vector3[]>): number {
  860. var l = 0;
  861. for (var i = 1; i < path.length; i++) {
  862. l += (path[i].subtract(path[i - 1])).length();
  863. }
  864. return l;
  865. }
  866. }