HermiteSpline.js 20 KB

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