HermiteSpline.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585
  1. import Cartesian3 from "./Cartesian3.js";
  2. import Cartesian4 from "./Cartesian4.js";
  3. import defaultValue from "./defaultValue.js";
  4. import defined from "./defined.js";
  5. import DeveloperError from "./DeveloperError.js";
  6. import LinearSpline from "./LinearSpline.js";
  7. import Matrix4 from "./Matrix4.js";
  8. import Spline from "./Spline.js";
  9. import TridiagonalSystemSolver from "./TridiagonalSystemSolver.js";
  10. var scratchLower = [];
  11. var scratchDiagonal = [];
  12. var scratchUpper = [];
  13. var scratchRight = [];
  14. function generateClamped(points, firstTangent, lastTangent) {
  15. var l = scratchLower;
  16. var u = scratchUpper;
  17. var d = scratchDiagonal;
  18. var r = scratchRight;
  19. l.length = u.length = points.length - 1;
  20. d.length = r.length = points.length;
  21. var i;
  22. l[0] = d[0] = 1.0;
  23. u[0] = 0.0;
  24. var right = r[0];
  25. if (!defined(right)) {
  26. right = r[0] = new Cartesian3();
  27. }
  28. Cartesian3.clone(firstTangent, right);
  29. for (i = 1; i < l.length - 1; ++i) {
  30. l[i] = u[i] = 1.0;
  31. d[i] = 4.0;
  32. right = r[i];
  33. if (!defined(right)) {
  34. right = r[i] = new Cartesian3();
  35. }
  36. Cartesian3.subtract(points[i + 1], points[i - 1], right);
  37. Cartesian3.multiplyByScalar(right, 3.0, right);
  38. }
  39. l[i] = 0.0;
  40. u[i] = 1.0;
  41. d[i] = 4.0;
  42. right = r[i];
  43. if (!defined(right)) {
  44. right = r[i] = new Cartesian3();
  45. }
  46. Cartesian3.subtract(points[i + 1], points[i - 1], right);
  47. Cartesian3.multiplyByScalar(right, 3.0, right);
  48. d[i + 1] = 1.0;
  49. right = r[i + 1];
  50. if (!defined(right)) {
  51. right = r[i + 1] = new Cartesian3();
  52. }
  53. Cartesian3.clone(lastTangent, right);
  54. return TridiagonalSystemSolver.solve(l, d, u, r);
  55. }
  56. function generateNatural(points) {
  57. var l = scratchLower;
  58. var u = scratchUpper;
  59. var d = scratchDiagonal;
  60. var r = scratchRight;
  61. l.length = u.length = points.length - 1;
  62. d.length = r.length = points.length;
  63. var i;
  64. l[0] = u[0] = 1.0;
  65. d[0] = 2.0;
  66. var right = r[0];
  67. if (!defined(right)) {
  68. right = r[0] = new Cartesian3();
  69. }
  70. Cartesian3.subtract(points[1], points[0], right);
  71. Cartesian3.multiplyByScalar(right, 3.0, right);
  72. for (i = 1; i < l.length; ++i) {
  73. l[i] = u[i] = 1.0;
  74. d[i] = 4.0;
  75. right = r[i];
  76. if (!defined(right)) {
  77. right = r[i] = new Cartesian3();
  78. }
  79. Cartesian3.subtract(points[i + 1], points[i - 1], right);
  80. Cartesian3.multiplyByScalar(right, 3.0, right);
  81. }
  82. d[i] = 2.0;
  83. right = r[i];
  84. if (!defined(right)) {
  85. right = r[i] = new Cartesian3();
  86. }
  87. Cartesian3.subtract(points[i], points[i - 1], right);
  88. Cartesian3.multiplyByScalar(right, 3.0, right);
  89. return TridiagonalSystemSolver.solve(l, d, u, r);
  90. }
  91. /**
  92. * A Hermite spline is a cubic interpolating spline. Points, incoming tangents, outgoing tangents, and times
  93. * must be defined for each control point. The outgoing tangents are defined for points [0, n - 2] and the incoming
  94. * tangents are defined for points [1, n - 1]. For example, when interpolating a segment of the curve between <code>points[i]</code> and
  95. * <code>points[i + 1]</code>, the tangents at the points will be <code>outTangents[i]</code> and <code>inTangents[i]</code>,
  96. * respectively.
  97. *
  98. * @alias HermiteSpline
  99. * @constructor
  100. *
  101. * @param {Object} options Object with the following properties:
  102. * @param {Number[]} options.times An array of strictly increasing, unit-less, floating-point times at each point.
  103. * The values are in no way connected to the clock time. They are the parameterization for the curve.
  104. * @param {Cartesian3[]} options.points The array of {@link Cartesian3} control points.
  105. * @param {Cartesian3[]} options.inTangents The array of {@link Cartesian3} incoming tangents at each control point.
  106. * @param {Cartesian3[]} options.outTangents The array of {@link Cartesian3} outgoing tangents at each control point.
  107. *
  108. * @exception {DeveloperError} points.length must be greater than or equal to 2.
  109. * @exception {DeveloperError} times.length must be equal to points.length.
  110. * @exception {DeveloperError} inTangents and outTangents must have a length equal to points.length - 1.
  111. *
  112. *
  113. * @example
  114. * // Create a G<sup>1</sup> continuous Hermite spline
  115. * var times = [ 0.0, 1.5, 3.0, 4.5, 6.0 ];
  116. * var spline = new Cesium.HermiteSpline({
  117. * times : times,
  118. * points : [
  119. * new Cesium.Cartesian3(1235398.0, -4810983.0, 4146266.0),
  120. * new Cesium.Cartesian3(1372574.0, -5345182.0, 4606657.0),
  121. * new Cesium.Cartesian3(-757983.0, -5542796.0, 4514323.0),
  122. * new Cesium.Cartesian3(-2821260.0, -5248423.0, 4021290.0),
  123. * new Cesium.Cartesian3(-2539788.0, -4724797.0, 3620093.0)
  124. * ],
  125. * outTangents : [
  126. * new Cesium.Cartesian3(1125196, -161816, 270551),
  127. * new Cesium.Cartesian3(-996690.5, -365906.5, 184028.5),
  128. * new Cesium.Cartesian3(-2096917, 48379.5, -292683.5),
  129. * new Cesium.Cartesian3(-890902.5, 408999.5, -447115)
  130. * ],
  131. * inTangents : [
  132. * new Cesium.Cartesian3(-1993381, -731813, 368057),
  133. * new Cesium.Cartesian3(-4193834, 96759, -585367),
  134. * new Cesium.Cartesian3(-1781805, 817999, -894230),
  135. * new Cesium.Cartesian3(1165345, 112641, 47281)
  136. * ]
  137. * });
  138. *
  139. * var p0 = spline.evaluate(times[0]);
  140. *
  141. * @see CatmullRomSpline
  142. * @see LinearSpline
  143. * @see QuaternionSpline
  144. * @see WeightSpline
  145. */
  146. function HermiteSpline(options) {
  147. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  148. var points = options.points;
  149. var times = options.times;
  150. var inTangents = options.inTangents;
  151. var outTangents = options.outTangents;
  152. //>>includeStart('debug', pragmas.debug);
  153. if (
  154. !defined(points) ||
  155. !defined(times) ||
  156. !defined(inTangents) ||
  157. !defined(outTangents)
  158. ) {
  159. throw new DeveloperError(
  160. "times, points, inTangents, and outTangents are required."
  161. );
  162. }
  163. if (points.length < 2) {
  164. throw new DeveloperError(
  165. "points.length must be greater than or equal to 2."
  166. );
  167. }
  168. if (times.length !== points.length) {
  169. throw new DeveloperError("times.length must be equal to points.length.");
  170. }
  171. if (
  172. inTangents.length !== outTangents.length ||
  173. inTangents.length !== points.length - 1
  174. ) {
  175. throw new DeveloperError(
  176. "inTangents and outTangents must have a length equal to points.length - 1."
  177. );
  178. }
  179. //>>includeEnd('debug');
  180. this._times = times;
  181. this._points = points;
  182. this._inTangents = inTangents;
  183. this._outTangents = outTangents;
  184. this._lastTimeIndex = 0;
  185. }
  186. Object.defineProperties(HermiteSpline.prototype, {
  187. /**
  188. * An array of times for the control points.
  189. *
  190. * @memberof HermiteSpline.prototype
  191. *
  192. * @type {Number[]}
  193. * @readonly
  194. */
  195. times: {
  196. get: function () {
  197. return this._times;
  198. },
  199. },
  200. /**
  201. * An array of {@link Cartesian3} control points.
  202. *
  203. * @memberof HermiteSpline.prototype
  204. *
  205. * @type {Cartesian3[]}
  206. * @readonly
  207. */
  208. points: {
  209. get: function () {
  210. return this._points;
  211. },
  212. },
  213. /**
  214. * An array of {@link Cartesian3} incoming tangents at each control point.
  215. *
  216. * @memberof HermiteSpline.prototype
  217. *
  218. * @type {Cartesian3[]}
  219. * @readonly
  220. */
  221. inTangents: {
  222. get: function () {
  223. return this._inTangents;
  224. },
  225. },
  226. /**
  227. * An array of {@link Cartesian3} outgoing tangents at each control point.
  228. *
  229. * @memberof HermiteSpline.prototype
  230. *
  231. * @type {Cartesian3[]}
  232. * @readonly
  233. */
  234. outTangents: {
  235. get: function () {
  236. return this._outTangents;
  237. },
  238. },
  239. });
  240. /**
  241. * Creates a spline where the tangents at each control point are the same.
  242. * The curves are guaranteed to be at least in the class C<sup>1</sup>.
  243. *
  244. * @param {Object} options Object with the following properties:
  245. * @param {Number[]} options.times The array of control point times.
  246. * @param {Cartesian3[]} options.points The array of control points.
  247. * @param {Cartesian3[]} options.tangents The array of tangents at the control points.
  248. * @returns {HermiteSpline} A hermite spline.
  249. *
  250. * @exception {DeveloperError} points, times and tangents are required.
  251. * @exception {DeveloperError} points.length must be greater than or equal to 2.
  252. * @exception {DeveloperError} times, points and tangents must have the same length.
  253. *
  254. * @example
  255. * var points = [
  256. * new Cesium.Cartesian3(1235398.0, -4810983.0, 4146266.0),
  257. * new Cesium.Cartesian3(1372574.0, -5345182.0, 4606657.0),
  258. * new Cesium.Cartesian3(-757983.0, -5542796.0, 4514323.0),
  259. * new Cesium.Cartesian3(-2821260.0, -5248423.0, 4021290.0),
  260. * new Cesium.Cartesian3(-2539788.0, -4724797.0, 3620093.0)
  261. * ];
  262. *
  263. * // Add tangents
  264. * var tangents = new Array(points.length);
  265. * tangents[0] = new Cesium.Cartesian3(1125196, -161816, 270551);
  266. * var temp = new Cesium.Cartesian3();
  267. * for (var i = 1; i < tangents.length - 1; ++i) {
  268. * tangents[i] = Cesium.Cartesian3.multiplyByScalar(Cesium.Cartesian3.subtract(points[i + 1], points[i - 1], temp), 0.5, new Cesium.Cartesian3());
  269. * }
  270. * tangents[tangents.length - 1] = new Cesium.Cartesian3(1165345, 112641, 47281);
  271. *
  272. * var spline = Cesium.HermiteSpline.createC1({
  273. * times : times,
  274. * points : points,
  275. * tangents : tangents
  276. * });
  277. */
  278. HermiteSpline.createC1 = function (options) {
  279. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  280. var times = options.times;
  281. var points = options.points;
  282. var tangents = options.tangents;
  283. //>>includeStart('debug', pragmas.debug);
  284. if (!defined(points) || !defined(times) || !defined(tangents)) {
  285. throw new DeveloperError("points, times and tangents are required.");
  286. }
  287. if (points.length < 2) {
  288. throw new DeveloperError(
  289. "points.length must be greater than or equal to 2."
  290. );
  291. }
  292. if (times.length !== points.length || times.length !== tangents.length) {
  293. throw new DeveloperError(
  294. "times, points and tangents must have the same length."
  295. );
  296. }
  297. //>>includeEnd('debug');
  298. var outTangents = tangents.slice(0, tangents.length - 1);
  299. var inTangents = tangents.slice(1, tangents.length);
  300. return new HermiteSpline({
  301. times: times,
  302. points: points,
  303. inTangents: inTangents,
  304. outTangents: outTangents,
  305. });
  306. };
  307. /**
  308. * Creates a natural cubic spline. The tangents at the control points are generated
  309. * to create a curve in the class C<sup>2</sup>.
  310. *
  311. * @param {Object} options Object with the following properties:
  312. * @param {Number[]} options.times The array of control point times.
  313. * @param {Cartesian3[]} options.points The array of control points.
  314. * @returns {HermiteSpline|LinearSpline} A hermite spline or a linear spline if less than 3 control points were given.
  315. *
  316. * @exception {DeveloperError} points and times are required.
  317. * @exception {DeveloperError} points.length must be greater than or equal to 2.
  318. * @exception {DeveloperError} times.length must be equal to points.length.
  319. *
  320. * @example
  321. * // Create a natural cubic spline above the earth from Philadelphia to Los Angeles.
  322. * var spline = Cesium.HermiteSpline.createNaturalCubic({
  323. * times : [ 0.0, 1.5, 3.0, 4.5, 6.0 ],
  324. * points : [
  325. * new Cesium.Cartesian3(1235398.0, -4810983.0, 4146266.0),
  326. * new Cesium.Cartesian3(1372574.0, -5345182.0, 4606657.0),
  327. * new Cesium.Cartesian3(-757983.0, -5542796.0, 4514323.0),
  328. * new Cesium.Cartesian3(-2821260.0, -5248423.0, 4021290.0),
  329. * new Cesium.Cartesian3(-2539788.0, -4724797.0, 3620093.0)
  330. * ]
  331. * });
  332. */
  333. HermiteSpline.createNaturalCubic = function (options) {
  334. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  335. var times = options.times;
  336. var points = options.points;
  337. //>>includeStart('debug', pragmas.debug);
  338. if (!defined(points) || !defined(times)) {
  339. throw new DeveloperError("points and times are required.");
  340. }
  341. if (points.length < 2) {
  342. throw new DeveloperError(
  343. "points.length must be greater than or equal to 2."
  344. );
  345. }
  346. if (times.length !== points.length) {
  347. throw new DeveloperError("times.length must be equal to points.length.");
  348. }
  349. //>>includeEnd('debug');
  350. if (points.length < 3) {
  351. return new LinearSpline({
  352. points: points,
  353. times: times,
  354. });
  355. }
  356. var tangents = generateNatural(points);
  357. var outTangents = tangents.slice(0, tangents.length - 1);
  358. var inTangents = tangents.slice(1, tangents.length);
  359. return new HermiteSpline({
  360. times: times,
  361. points: points,
  362. inTangents: inTangents,
  363. outTangents: outTangents,
  364. });
  365. };
  366. /**
  367. * Creates a clamped cubic spline. The tangents at the interior control points are generated
  368. * to create a curve in the class C<sup>2</sup>.
  369. *
  370. * @param {Object} options Object with the following properties:
  371. * @param {Number[]} options.times The array of control point times.
  372. * @param {Cartesian3[]} options.points The array of control points.
  373. * @param {Cartesian3} options.firstTangent The outgoing tangent of the first control point.
  374. * @param {Cartesian3} options.lastTangent The incoming tangent of the last control point.
  375. * @returns {HermiteSpline|LinearSpline} A hermite spline or a linear spline if less than 3 control points were given.
  376. *
  377. * @exception {DeveloperError} points, times, firstTangent and lastTangent are required.
  378. * @exception {DeveloperError} points.length must be greater than or equal to 2.
  379. * @exception {DeveloperError} times.length must be equal to points.length.
  380. *
  381. * @example
  382. * // Create a clamped cubic spline above the earth from Philadelphia to Los Angeles.
  383. * var spline = Cesium.HermiteSpline.createClampedCubic({
  384. * times : [ 0.0, 1.5, 3.0, 4.5, 6.0 ],
  385. * points : [
  386. * new Cesium.Cartesian3(1235398.0, -4810983.0, 4146266.0),
  387. * new Cesium.Cartesian3(1372574.0, -5345182.0, 4606657.0),
  388. * new Cesium.Cartesian3(-757983.0, -5542796.0, 4514323.0),
  389. * new Cesium.Cartesian3(-2821260.0, -5248423.0, 4021290.0),
  390. * new Cesium.Cartesian3(-2539788.0, -4724797.0, 3620093.0)
  391. * ],
  392. * firstTangent : new Cesium.Cartesian3(1125196, -161816, 270551),
  393. * lastTangent : new Cesium.Cartesian3(1165345, 112641, 47281)
  394. * });
  395. */
  396. HermiteSpline.createClampedCubic = function (options) {
  397. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  398. var times = options.times;
  399. var points = options.points;
  400. var firstTangent = options.firstTangent;
  401. var lastTangent = options.lastTangent;
  402. //>>includeStart('debug', pragmas.debug);
  403. if (
  404. !defined(points) ||
  405. !defined(times) ||
  406. !defined(firstTangent) ||
  407. !defined(lastTangent)
  408. ) {
  409. throw new DeveloperError(
  410. "points, times, firstTangent and lastTangent are required."
  411. );
  412. }
  413. if (points.length < 2) {
  414. throw new DeveloperError(
  415. "points.length must be greater than or equal to 2."
  416. );
  417. }
  418. if (times.length !== points.length) {
  419. throw new DeveloperError("times.length must be equal to points.length.");
  420. }
  421. //>>includeEnd('debug');
  422. if (points.length < 3) {
  423. return new LinearSpline({
  424. points: points,
  425. times: times,
  426. });
  427. }
  428. var tangents = generateClamped(points, firstTangent, lastTangent);
  429. var outTangents = tangents.slice(0, tangents.length - 1);
  430. var inTangents = tangents.slice(1, tangents.length);
  431. return new HermiteSpline({
  432. times: times,
  433. points: points,
  434. inTangents: inTangents,
  435. outTangents: outTangents,
  436. });
  437. };
  438. HermiteSpline.hermiteCoefficientMatrix = new Matrix4(
  439. 2.0,
  440. -3.0,
  441. 0.0,
  442. 1.0,
  443. -2.0,
  444. 3.0,
  445. 0.0,
  446. 0.0,
  447. 1.0,
  448. -2.0,
  449. 1.0,
  450. 0.0,
  451. 1.0,
  452. -1.0,
  453. 0.0,
  454. 0.0
  455. );
  456. /**
  457. * Finds an index <code>i</code> in <code>times</code> such that the parameter
  458. * <code>time</code> is in the interval <code>[times[i], times[i + 1]]</code>.
  459. * @function
  460. *
  461. * @param {Number} time The time.
  462. * @returns {Number} The index for the element at the start of the interval.
  463. *
  464. * @exception {DeveloperError} time must be in the range <code>[t<sub>0</sub>, t<sub>n</sub>]</code>, where <code>t<sub>0</sub></code>
  465. * is the first element in the array <code>times</code> and <code>t<sub>n</sub></code> is the last element
  466. * in the array <code>times</code>.
  467. */
  468. HermiteSpline.prototype.findTimeInterval = Spline.prototype.findTimeInterval;
  469. var scratchTimeVec = new Cartesian4();
  470. var scratchTemp = new Cartesian3();
  471. /**
  472. * Wraps the given time to the period covered by the spline.
  473. * @function
  474. *
  475. * @param {Number} time The time.
  476. * @return {Number} The time, wrapped around to the updated animation.
  477. */
  478. HermiteSpline.prototype.wrapTime = Spline.prototype.wrapTime;
  479. /**
  480. * Clamps the given time to the period covered by the spline.
  481. * @function
  482. *
  483. * @param {Number} time The time.
  484. * @return {Number} The time, clamped to the animation period.
  485. */
  486. HermiteSpline.prototype.clampTime = Spline.prototype.clampTime;
  487. /**
  488. * Evaluates the curve at a given time.
  489. *
  490. * @param {Number} time The time at which to evaluate the curve.
  491. * @param {Cartesian3} [result] The object onto which to store the result.
  492. * @returns {Cartesian3} The modified result parameter or a new instance of the point on the curve at the given time.
  493. *
  494. * @exception {DeveloperError} time must be in the range <code>[t<sub>0</sub>, t<sub>n</sub>]</code>, where <code>t<sub>0</sub></code>
  495. * is the first element in the array <code>times</code> and <code>t<sub>n</sub></code> is the last element
  496. * in the array <code>times</code>.
  497. */
  498. HermiteSpline.prototype.evaluate = function (time, result) {
  499. if (!defined(result)) {
  500. result = new Cartesian3();
  501. }
  502. var points = this.points;
  503. var times = this.times;
  504. var inTangents = this.inTangents;
  505. var outTangents = this.outTangents;
  506. var i = (this._lastTimeIndex = this.findTimeInterval(
  507. time,
  508. this._lastTimeIndex
  509. ));
  510. var u = (time - times[i]) / (times[i + 1] - times[i]);
  511. var timeVec = scratchTimeVec;
  512. timeVec.z = u;
  513. timeVec.y = u * u;
  514. timeVec.x = timeVec.y * u;
  515. timeVec.w = 1.0;
  516. var coefs = Matrix4.multiplyByVector(
  517. HermiteSpline.hermiteCoefficientMatrix,
  518. timeVec,
  519. timeVec
  520. );
  521. result = Cartesian3.multiplyByScalar(points[i], coefs.x, result);
  522. Cartesian3.multiplyByScalar(points[i + 1], coefs.y, scratchTemp);
  523. Cartesian3.add(result, scratchTemp, result);
  524. Cartesian3.multiplyByScalar(outTangents[i], coefs.z, scratchTemp);
  525. Cartesian3.add(result, scratchTemp, result);
  526. Cartesian3.multiplyByScalar(inTangents[i], coefs.w, scratchTemp);
  527. return Cartesian3.add(result, scratchTemp, result);
  528. };
  529. export default HermiteSpline;