KeyFrames.hx 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. package h2d;
  2. import hxd.fmt.kframes.Data;
  3. /**
  4. [Keyframes](https://github.com/heapsio/keyframes/) integration; A `KeyFrames` animation layer.
  5. **/
  6. typedef KeyframesLayer = {
  7. var id : Int;
  8. var name : String;
  9. var spr : Object;
  10. var tiles : Array<h2d.Tile>;
  11. var animations : Array<KFAnimation>;
  12. var from : Int;
  13. var to : Int;
  14. }
  15. /**
  16. Adobe After effect player, see [Keyframes](https://github.com/heapsio/keyframes/) library.
  17. **/
  18. class KeyFrames extends Mask {
  19. var layers : Array<KeyframesLayer>;
  20. var filePrefix : String;
  21. var curFrame : Float;
  22. /**
  23. The FPS provided by the KeyFrames file.
  24. **/
  25. public var frameRate : Float;
  26. /**
  27. The total amount of frames in the animation.
  28. **/
  29. public var frameCount : Int;
  30. /**
  31. The current playback frame with the frame display progress fraction.
  32. **/
  33. public var currentFrame(get,set) : Float;
  34. /**
  35. The playback speed multiplier.
  36. **/
  37. public var speed : Float = 1.;
  38. /**
  39. Pauses the playback when enabled.
  40. **/
  41. public var pause : Bool = false;
  42. /**
  43. Whether to loop the animation or not.
  44. **/
  45. public var loop : Bool = false;
  46. /**
  47. When looping, will interpolate between last frame and first frame.
  48. **/
  49. public var loopInterpolate : Bool = false;
  50. /**
  51. Use bilinear texture sampling instead of nearest neighbor.
  52. @see `Drawable.smooth`
  53. **/
  54. public var smooth(default,set) = true;
  55. /**
  56. Create a new KeyFrames animation instance.
  57. @param file The source file of the animation.
  58. @param filePrefix An optional directory prefix when looking up images.
  59. @param parent An optional parent `h2d.Object` instance to which KeyFrames adds itself if set.
  60. **/
  61. public function new( file : KeyframesFile, ?filePrefix : String, ?parent ) {
  62. super(0,0,parent);
  63. if( file.formatVersion == null )
  64. throw "Invalid keyframe file";
  65. if( file.formatVersion.split(".")[0] != "1" )
  66. throw "Invalid format version " + file.formatVersion;
  67. this.filePrefix = filePrefix;
  68. this.width = file.canvas_size.x;
  69. this.height = file.canvas_size.y;
  70. this.frameCount = file.animation_frame_count;
  71. this.frameRate = file.frame_rate;
  72. this.curFrame = 0;
  73. layers = [];
  74. for( f in file.features ) {
  75. var spr, tiles = null;
  76. if( f.backed_image == null ) {
  77. spr = new h2d.Object(this);
  78. } else {
  79. var reg = ~/(.*?)\[([0-9]+)-([0-9]+)\](.*)/;
  80. if( reg.match(f.backed_image) ){
  81. var from = Std.parseInt(reg.matched(2));
  82. var to = Std.parseInt(reg.matched(3));
  83. var l = reg.matched(2).length;
  84. tiles = [for( i in from...to+1) loadTile(reg.matched(1)+StringTools.lpad(Std.string(i), "0", l)+reg.matched(4))];
  85. }else{
  86. tiles = [loadTile(f.backed_image)];
  87. }
  88. if (f.size != null) {
  89. for( t in tiles ) t.scaleToSize(f.size.x, f.size.y);
  90. }
  91. var bmp = new h2d.Bitmap(tiles[0], this);
  92. bmp.smooth = smooth;
  93. if( f.name.toLowerCase().indexOf("(add)") >= 0 )
  94. bmp.blendMode = Add;
  95. spr = bmp;
  96. }
  97. var l : KeyframesLayer = {
  98. id: f.feature_id,
  99. name: f.name,
  100. from: f.from_frame == null ? 0 : f.from_frame,
  101. to: f.to_frame == null ? file.animation_frame_count : f.to_frame,
  102. tiles: tiles,
  103. spr: spr,
  104. animations : [],
  105. };
  106. for( f in f.feature_animations ) {
  107. switch( f.key_values.length ) {
  108. case 0: // nothing
  109. case 1:
  110. apply(l, f);
  111. default:
  112. apply(l, f);
  113. l.animations.push(f);
  114. }
  115. }
  116. layers.push(l);
  117. }
  118. }
  119. @:dox(hide) @:noCompletion
  120. public function set_smooth( v : Bool ) : Bool {
  121. for( l in layers ){
  122. var bmp = hxd.impl.Api.downcast(l.spr, h2d.Bitmap);
  123. if( bmp != null )
  124. bmp.smooth = v;
  125. }
  126. return smooth = v;
  127. }
  128. /**
  129. Unpauses the playback and starts it at the specified frame.
  130. @param speed The playback speed multiplier at which animation should run.
  131. @param startFrame The frame at which the animation should start.
  132. **/
  133. public function play( speed : Float = 1., startFrame = 0 ) {
  134. this.speed = speed;
  135. pause = false;
  136. currentFrame = startFrame;
  137. }
  138. function apply( l : KeyframesLayer, f : KFAnimation ) {
  139. var index = 0;
  140. for( i in 0...f.key_values.length ) {
  141. var v = f.key_values[i];
  142. if( curFrame >= v.start_frame )
  143. index = i;
  144. }
  145. var cur = f.key_values[index];
  146. var next = f.key_values[index + 1];
  147. var yVal;
  148. var xVal;
  149. if( next == null ) {
  150. if( loop && loopInterpolate ) {
  151. next = f.key_values[0];
  152. yVal = ((next.start_frame + frameCount) - cur.start_frame);
  153. xVal = (curFrame - cur.start_frame) / yVal;
  154. } else {
  155. next = cur;
  156. yVal = 1;
  157. xVal = 0.;
  158. }
  159. } else {
  160. yVal = (next.start_frame - cur.start_frame);
  161. xVal = (curFrame - cur.start_frame) / yVal;
  162. }
  163. function calcValue( index : Int ) : Float {
  164. var v0 = cur.data[index];
  165. if( xVal <= 0 ) return v0;
  166. var minT = 0.;
  167. var maxT = 1.;
  168. var maxDelta = 1 / yVal;
  169. inline function bezier(c1:Float, c2:Float, t:Float) {
  170. var u = 1 - t;
  171. return u * u * u * 0 + c1 * 3 * t * u * u + c2 * 3 * t * t * u + t * t * t * 1;
  172. }
  173. var curves = f.timing_curves[0];
  174. var c1x = curves[0].x;
  175. var c2x = curves[1].x;
  176. var c1y = curves[0].y;
  177. var c2y = curves[1].y;
  178. {
  179. // For now, force control points to stay within the [0,1] range
  180. // See https://github.com/facebookincubator/Keyframes/issues/148
  181. if(c1y > 1.0) {
  182. c1x /= c1y;
  183. c1y = 1.0;
  184. }
  185. else if(c1y < 0) {
  186. c1y = 0;
  187. }
  188. if(c2y > 1.0) {
  189. c2x /= c2y;
  190. c2y = 1.0;
  191. }
  192. else if(c2y < 0) {
  193. c2y = 0;
  194. }
  195. }
  196. var count = 0;
  197. while( maxT - minT > maxDelta ) {
  198. var t = (maxT + minT) * 0.5;
  199. var x = bezier(c1x, c2x, t);
  200. if( x > xVal )
  201. maxT = t;
  202. else
  203. minT = t;
  204. count++;
  205. }
  206. var x0 = bezier(c1x, c2x, minT);
  207. var x1 = bezier(c1x, c2x, maxT);
  208. var dx = x1 - x0;
  209. var xfactor = dx == 0 ? 0.5 : (xVal - x0) / dx;
  210. var y0 = bezier(c1y, c2y, minT);
  211. var y1 = bezier(c1y, c2y, maxT);
  212. var y = y0 + (y1 - y0) * xfactor;
  213. var v1 = next.data[index];
  214. return v0 + (v1 - v0) * y;
  215. }
  216. switch( f.property ) {
  217. case AnchorPoint:
  218. var bmp = hxd.impl.Api.downcast(l.spr, h2d.Bitmap);
  219. if( bmp != null ) {
  220. bmp.tile.dx = -calcValue(0);
  221. bmp.tile.dy = -calcValue(1);
  222. }
  223. case XPosition:
  224. l.spr.x = calcValue(0);
  225. case YPosition:
  226. l.spr.y = calcValue(0);
  227. case Scale:
  228. l.spr.scaleX = calcValue(0) / 100.;
  229. l.spr.scaleY = calcValue(1) / 100.;
  230. case Opacity:
  231. l.spr.alpha = calcValue(0) / 100.;
  232. case Rotation:
  233. l.spr.rotation = calcValue(0) * Math.PI / 180;
  234. }
  235. }
  236. inline function get_currentFrame() {
  237. return curFrame;
  238. }
  239. function set_currentFrame( frame : Float ) {
  240. curFrame = frameCount == 0 ? 0 : frame % frameCount;
  241. if( curFrame < 0 ) curFrame += frameCount;
  242. return curFrame;
  243. }
  244. function loadTile( path : String ) {
  245. return hxd.res.Loader.currentInstance.load(filePrefix == null ? path : filePrefix + path).toTile();
  246. }
  247. /**
  248. Returns the animation layer objects under specified name.
  249. **/
  250. public function getLayer( name : String ) {
  251. var layer = null;
  252. for( l in layers ) {
  253. if( l.name == name ){
  254. layer = l;
  255. break;
  256. }
  257. }
  258. return layer == null ? null : layer.spr;
  259. }
  260. override function sync( ctx : RenderContext ) {
  261. super.sync(ctx);
  262. var prev = curFrame;
  263. if( !pause ){
  264. curFrame += speed * frameRate * ctx.elapsedTime;
  265. if( curFrame > frameCount )
  266. curFrame = frameCount;
  267. }
  268. for( l in layers ){
  269. l.spr.visible = curFrame >= l.from && curFrame <= l.to;
  270. if( l.spr.visible && l.tiles != null && l.tiles.length > 1 ){
  271. var bmp : h2d.Bitmap = cast l.spr;
  272. var curTile = hxd.Math.iclamp( Std.int( (curFrame - l.from) * l.tiles.length / (l.to - l.from) ), 0, l.tiles.length );
  273. var newTile = l.tiles[curTile];
  274. if( bmp.tile != newTile ){
  275. newTile.dx = bmp.tile.dx;
  276. newTile.dy = bmp.tile.dy;
  277. bmp.tile = newTile;
  278. }
  279. }
  280. for( a in l.animations )
  281. apply(l, a);
  282. }
  283. if( curFrame < frameCount )
  284. return;
  285. if( loop ) {
  286. if( frameCount == 0 )
  287. curFrame = 0;
  288. else
  289. curFrame %= frameCount;
  290. onAnimEnd();
  291. } else if( curFrame >= frameCount ) {
  292. curFrame = frameCount;
  293. if( curFrame != prev ) onAnimEnd();
  294. }
  295. }
  296. /**
  297. Sent when animation reaches the end.
  298. `KeyFrames.currentFrame` equals to `KeyFrames.frameCount` when `KeyFrames.loop` is disabled,
  299. is wrapped around to 0th frame if loop is enabled.
  300. **/
  301. public dynamic function onAnimEnd() {
  302. }
  303. }