math.path.js 44 KB

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