ModelExperimentalAnimationChannel.js 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. import Cartesian3 from "../../Core/Cartesian3.js";
  2. import Check from "../../Core/Check.js";
  3. import ConstantSpline from "../../Core/ConstantSpline.js";
  4. import defaultValue from "../../Core/defaultValue.js";
  5. import defined from "../../Core/defined.js";
  6. import HermiteSpline from "../../Core/HermiteSpline.js";
  7. import InterpolationType from "../../Core/InterpolationType.js";
  8. import LinearSpline from "../../Core/LinearSpline.js";
  9. import ModelComponents from "../ModelComponents.js";
  10. import SteppedSpline from "../../Core/SteppedSpline.js";
  11. import Quaternion from "../../Core/Quaternion.js";
  12. import QuaternionSpline from "../../Core/QuaternionSpline.js";
  13. const AnimatedPropertyType = ModelComponents.AnimatedPropertyType;
  14. /**
  15. * A runtime animation channel for a {@link ModelExperimentalAnimation}. An animation
  16. * channel is responsible for interpolating between the keyframe values of an animated
  17. * property, then applying the change to the target node.
  18. *
  19. * @param {Object} options An object containing the following options:
  20. * @param {ModelComponents.AnimationChannel} options.channel The corresponding animation channel components from the 3D model.
  21. * @param {ModelExperimentalAnimation} options.runtimeAnimation The runtime animation containing this channel.
  22. * @param {ModelExperimentalNode} options.runtimeNode The runtime node that this channel will animate.
  23. *
  24. * @alias ModelExperimentalAnimationChannel
  25. * @constructor
  26. *
  27. * @private
  28. */
  29. function ModelExperimentalAnimationChannel(options) {
  30. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  31. const channel = options.channel;
  32. const runtimeAnimation = options.runtimeAnimation;
  33. const runtimeNode = options.runtimeNode;
  34. //>>includeStart('debug', pragmas.debug);
  35. Check.typeOf.object("options.channel", channel);
  36. Check.typeOf.object("options.runtimeAnimation", runtimeAnimation);
  37. Check.typeOf.object("options.runtimeNode", runtimeNode);
  38. //>>includeEnd('debug');
  39. this._channel = channel;
  40. this._runtimeAnimation = runtimeAnimation;
  41. this._runtimeNode = runtimeNode;
  42. // An animation channel can have multiple splines if it animates
  43. // a node's morph weights, which will involve multiple morph targets.
  44. this._splines = [];
  45. this._path = undefined;
  46. initialize(this);
  47. }
  48. Object.defineProperties(ModelExperimentalAnimationChannel.prototype, {
  49. /**
  50. * The glTF animation channel.
  51. *
  52. * @memberof ModelExperimentalAnimationChannel.prototype
  53. *
  54. * @type {ModelComponents.AnimationChannel}
  55. * @readonly
  56. *
  57. * @private
  58. */
  59. channel: {
  60. get: function () {
  61. return this._channel;
  62. },
  63. },
  64. /**
  65. * The runtime animation that owns this channel.
  66. *
  67. * @memberof ModelExperimentalAnimationChannel.prototype
  68. *
  69. * @type {ModelExperimentalAnimation}
  70. * @readonly
  71. *
  72. * @private
  73. */
  74. runtimeAnimation: {
  75. get: function () {
  76. return this._runtimeAnimation;
  77. },
  78. },
  79. /**
  80. * The runtime node that this channel animates.
  81. *
  82. * @memberof ModelExperimentalAnimationChannel.prototype
  83. *
  84. * @type {ModelExperimentalNode}
  85. * @readonly
  86. *
  87. * @private
  88. */
  89. runtimeNode: {
  90. get: function () {
  91. return this._runtimeNode;
  92. },
  93. },
  94. /**
  95. * The splines used to evaluate this animation channel.
  96. *
  97. * @memberof ModelExperimentalAnimationChannel.prototype
  98. *
  99. * @type {Spline[]}
  100. * @readonly
  101. *
  102. * @private
  103. */
  104. splines: {
  105. get: function () {
  106. return this._splines;
  107. },
  108. },
  109. });
  110. function createCubicSpline(times, points) {
  111. const cubicPoints = [];
  112. const inTangents = [];
  113. const outTangents = [];
  114. const length = points.length;
  115. for (let i = 0; i < length; i += 3) {
  116. inTangents.push(points[i]);
  117. cubicPoints.push(points[i + 1]);
  118. outTangents.push(points[i + 2]);
  119. }
  120. // Remove the first in-tangent and last out-tangent, since they
  121. // are not used in the spline calculations
  122. inTangents.splice(0, 1);
  123. outTangents.length = outTangents.length - 1;
  124. return new HermiteSpline({
  125. times: times,
  126. points: cubicPoints,
  127. inTangents: inTangents,
  128. outTangents: outTangents,
  129. });
  130. }
  131. function createSpline(times, points, interpolation, path) {
  132. if (times.length === 1 && points.length === 1) {
  133. return new ConstantSpline(points[0]);
  134. }
  135. switch (interpolation) {
  136. case InterpolationType.STEP:
  137. return new SteppedSpline({
  138. times: times,
  139. points: points,
  140. });
  141. case InterpolationType.CUBICSPLINE:
  142. return createCubicSpline(times, points);
  143. case InterpolationType.LINEAR:
  144. if (path === AnimatedPropertyType.ROTATION) {
  145. return new QuaternionSpline({
  146. times: times,
  147. points: points,
  148. });
  149. }
  150. return new LinearSpline({
  151. times: times,
  152. points: points,
  153. });
  154. }
  155. }
  156. function createSplines(times, points, interpolation, path, count) {
  157. const splines = [];
  158. if (path === AnimatedPropertyType.WEIGHTS) {
  159. const pointsLength = points.length;
  160. // Get the number of keyframes in each weight's output.
  161. const outputLength = pointsLength / count;
  162. // Iterate over the array using the number of morph targets in the model.
  163. let targetIndex, i;
  164. for (targetIndex = 0; targetIndex < count; targetIndex++) {
  165. const output = new Array(outputLength);
  166. // Weights are ordered such that they are keyframed in the order in which
  167. // their targets appear the glTF. For example, the weights of three targets
  168. // may appear as [w(0,0), w(0,1), w(0,2), w(1,0), w(1,1), w(1,2) ...],
  169. // where i and j in w(i,j) are the time indices and target indices, respectively.
  170. // However, for morph targets with cubic interpolation, the data is stored per
  171. // keyframe in the order [a1, a2, ..., an, v1, v2, ... vn, b1, b2, ..., bn],
  172. // where ai, vi, and bi are the in-tangent, property, and out-tangents of
  173. // the ith morph target respectively.
  174. let pointsIndex = targetIndex;
  175. if (interpolation === InterpolationType.CUBICSPLINE) {
  176. for (i = 0; i < outputLength; i += 3) {
  177. output[i] = points[pointsIndex];
  178. output[i + 1] = points[pointsIndex + count];
  179. output[i + 2] = points[pointsIndex + 2 * count];
  180. pointsIndex += count * 3;
  181. }
  182. } else {
  183. for (i = 0; i < outputLength; i++) {
  184. output[i] = points[pointsIndex];
  185. pointsIndex += count;
  186. }
  187. }
  188. splines.push(createSpline(times, output, interpolation, path));
  189. }
  190. } else {
  191. splines.push(createSpline(times, points, interpolation, path));
  192. }
  193. return splines;
  194. }
  195. let scratchVariable;
  196. function initialize(runtimeChannel) {
  197. const channel = runtimeChannel._channel;
  198. const sampler = channel.sampler;
  199. const times = sampler.input;
  200. const points = sampler.output;
  201. const interpolation = sampler.interpolation;
  202. const target = channel.target;
  203. const path = target.path;
  204. const runtimeNode = runtimeChannel._runtimeNode;
  205. const count = defined(runtimeNode.morphWeights)
  206. ? runtimeNode.morphWeights.length
  207. : 1;
  208. const splines = createSplines(times, points, interpolation, path, count);
  209. runtimeChannel._splines = splines;
  210. runtimeChannel._path = path;
  211. switch (path) {
  212. case AnimatedPropertyType.TRANSLATION:
  213. case AnimatedPropertyType.SCALE:
  214. scratchVariable = new Cartesian3();
  215. break;
  216. case AnimatedPropertyType.ROTATION:
  217. scratchVariable = new Quaternion();
  218. break;
  219. case AnimatedPropertyType.WEIGHTS:
  220. // This is unused when setting a node's morph weights.
  221. break;
  222. }
  223. }
  224. /**
  225. * Animates the target node property based on its spline.
  226. *
  227. * @param {Number} time The local animation time.
  228. *
  229. * @private
  230. */
  231. ModelExperimentalAnimationChannel.prototype.animate = function (time) {
  232. const splines = this._splines;
  233. const path = this._path;
  234. const model = this._runtimeAnimation.model;
  235. // Weights are handled differently than the other properties because
  236. // they need to be updated in place.
  237. if (path === AnimatedPropertyType.WEIGHTS) {
  238. const morphWeights = this._runtimeNode.morphWeights;
  239. const length = morphWeights.length;
  240. for (let i = 0; i < length; i++) {
  241. const spline = splines[i];
  242. const localAnimationTime = model.clampAnimations
  243. ? spline.clampTime(time)
  244. : spline.wrapTime(time);
  245. morphWeights[i] = spline.evaluate(localAnimationTime);
  246. }
  247. } else {
  248. const spline = splines[0];
  249. const localAnimationTime = model.clampAnimations
  250. ? spline.clampTime(time)
  251. : spline.wrapTime(time);
  252. // This sets the translate, rotate, and scale properties.
  253. this._runtimeNode[path] = spline.evaluate(
  254. localAnimationTime,
  255. scratchVariable
  256. );
  257. }
  258. };
  259. export default ModelExperimentalAnimationChannel;