SpriteBatch.hx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
  1. package h2d;
  2. import h2d.RenderContext;
  3. import h2d.impl.BatchDrawState;
  4. private class ElementsIterator {
  5. var e : BatchElement;
  6. public inline function new(e) {
  7. this.e = e;
  8. }
  9. public inline function hasNext() {
  10. return e != null;
  11. }
  12. public inline function next() {
  13. var n = e;
  14. e = @:privateAccess e.next;
  15. return n;
  16. }
  17. }
  18. /**
  19. A base class for `SpriteBatch` elements which can be extended with custom logic.
  20. See `BasicElement` as an example of custom element logic.
  21. **/
  22. @:allow(h2d.SpriteBatch)
  23. class BatchElement {
  24. /**
  25. Element X position.
  26. **/
  27. public var x : Float = 0;
  28. /**
  29. Element Y position.
  30. **/
  31. public var y : Float = 0;
  32. /**
  33. Shortcut to set both `BatchElement.scaleX` and `BatchElement.scaleY` at the same time.
  34. Equivalent to `el.scaleX = el.scaleY = scale`.
  35. **/
  36. public var scale(never,set) : Float;
  37. /**
  38. X-axis scaling factor of the element.
  39. This variable is used only if `SpriteBatch.hasRotationScale` is set to `true`.
  40. **/
  41. public var scaleX : Float = 1;
  42. /**
  43. Y-axis scaling factor of the element.
  44. This variable is used only if `SpriteBatch.hasRotationScale` is set to `true`.
  45. **/
  46. public var scaleY : Float = 1;
  47. /**
  48. Element rotation in radians.
  49. This variable is used only if `SpriteBatch.hasRotationScale` is set to `true`.
  50. **/
  51. public var rotation : Float = 0;
  52. /**
  53. Red tint value (0...1 range) of the element.
  54. **/
  55. public var r : Float = 1;
  56. /**
  57. Green tint value (0...1 range) of the element.
  58. **/
  59. public var g : Float = 1;
  60. /**
  61. Blue tint value (0...1 range) of the element.
  62. **/
  63. public var b : Float = 1;
  64. /**
  65. Alpha value of the element.
  66. **/
  67. public var a : Float = 1;
  68. /**
  69. The Tile this element renders.
  70. Due to implementation specifics, this Tile instance is used only to provide rendering area, not the Texture itself,
  71. as `SpriteBatch.tile` used as a source of rendered texture.
  72. **/
  73. public var t : Tile;
  74. /**
  75. Alpha value of the element.
  76. Alias of `BatchElement.a`.
  77. **/
  78. public var alpha(get,set) : Float;
  79. /**
  80. If set to `false`, element will not be rendered.
  81. **/
  82. public var visible : Bool = true;
  83. /**
  84. Reference to parent SpriteBatch instance.
  85. **/
  86. public var batch(default, null) : SpriteBatch;
  87. var prev : BatchElement;
  88. var next : BatchElement;
  89. /**
  90. Create a new BatchElement instance with provided Tile.
  91. @param t The tile used to render this BatchElement.
  92. **/
  93. public function new( t : Tile ) {
  94. this.t = t;
  95. }
  96. inline function set_scale(v) {
  97. return scaleX = scaleY = v;
  98. }
  99. inline function get_alpha() {
  100. return a;
  101. }
  102. inline function set_alpha(v) {
  103. return a = v;
  104. }
  105. /**
  106. Override this method to perform custom logic per batch element.
  107. Update method called only if `SpriteBatch.hasUpdate` is set to `true`.
  108. @param dt The elapsed time in seconds since last update from `RenderContext.elapsedTime`.
  109. @returns If method returns `false`, element will be removed from the SpriteBatch.
  110. **/
  111. @:dox(show)
  112. function update(et:Float) {
  113. return true;
  114. }
  115. /**
  116. Remove this BatchElement from the parent SpriteBatch instance.
  117. **/
  118. public function remove() {
  119. if( batch != null )
  120. batch.delete(this);
  121. }
  122. }
  123. /**
  124. A simple `BatchElement` that provides primitive simulation of velocity, friction and gravity.
  125. Parent `SpriteBatch` should have `SpriteBatch.hasUpdate` set to `true` in order for BasicElement to work properly.
  126. **/
  127. class BasicElement extends BatchElement {
  128. /**
  129. X-axis velocity of the element.
  130. **/
  131. public var vx : Float = 0.;
  132. /**
  133. Y-axis velocity of the element.
  134. **/
  135. public var vy : Float = 0.;
  136. /**
  137. The velocity friction.
  138. When not `1`, multiplies velocity by `pow(friction, dt * 60)`.
  139. **/
  140. public var friction : Float = 1.;
  141. /**
  142. The gravity applied to vertical velocity in pixels per second.
  143. **/
  144. public var gravity : Float = 0.;
  145. override function update(dt:Float) {
  146. vy += gravity * dt;
  147. x += vx * dt;
  148. y += vy * dt;
  149. if( friction != 1 ) {
  150. var p = Math.pow(friction, dt * 60);
  151. vx *= p;
  152. vy *= p;
  153. }
  154. return true;
  155. }
  156. }
  157. /**
  158. An active batched tile renderer.
  159. Compared to `TileGroup` which is expected to be used as a static geometry,
  160. SpriteBatch uploads GPU buffer each frame by collecting data from added `BatchElement` instance.
  161. Due to that, dynamically removing and adding new geometry is fairly simple.
  162. Usage note: While SpriteBatch allows for multiple unique textures, each texture swap causes a new drawcall,
  163. and due to that it's recommended to minimize the amount of used textures per SpriteBatch instance,
  164. ideally limiting to only one texture.
  165. **/
  166. class SpriteBatch extends Drawable {
  167. /**
  168. The Tile used as a base Texture to draw contents with.
  169. **/
  170. public var tile : Tile;
  171. /**
  172. Enables usage of rotation and scaling of SpriteBatch elements at the cost of extra calculus.
  173. Makes use of `BatchElement.scaleX`, `BatchElement.scaleY` and `BatchElement.rotation`.
  174. **/
  175. public var hasRotationScale : Bool = false;
  176. /**
  177. Enables usage of `update` method in SpriteBatch elements.
  178. **/
  179. public var hasUpdate : Bool = false;
  180. var first : BatchElement;
  181. var last : BatchElement;
  182. var tmpBuf : hxd.FloatBuffer;
  183. var buffer : h3d.Buffer;
  184. var state : BatchDrawState;
  185. var empty : Bool;
  186. /**
  187. Create new SpriteBatch instance.
  188. @param t The Tile used as a base Texture to draw contents with.
  189. @param parent An optional parent `h2d.Object` instance to which SpriteBatch adds itself if set.
  190. **/
  191. public function new(t,?parent) {
  192. super(parent);
  193. tile = t;
  194. state = new BatchDrawState();
  195. }
  196. /**
  197. Adds a new BatchElement to the SpriteBatch.
  198. @param e The element to add.
  199. @param before When set, element will be added to the beginning of the element chain (rendered first).
  200. **/
  201. public function add(e:BatchElement,before=false) {
  202. e.batch = this;
  203. if( first == null ) {
  204. first = last = e;
  205. e.prev = e.next = null;
  206. } else if( before ) {
  207. e.prev = null;
  208. e.next = first;
  209. first.prev = e;
  210. first = e;
  211. } else {
  212. last.next = e;
  213. e.prev = last;
  214. e.next = null;
  215. last = e;
  216. }
  217. return e;
  218. }
  219. /**
  220. Removes all elements from the SpriteBatch.
  221. Usage note: Does not clear the `BatchElement.batch` nor `next`/`prev` variables on the child elements.
  222. **/
  223. public function clear() {
  224. first = last = null;
  225. flush();
  226. }
  227. /**
  228. Creates a new BatchElement and returns it. Shortcut to `add(new BatchElement(t))`
  229. @param t The Tile element will render.
  230. **/
  231. public function alloc( t : Tile ) : BatchElement {
  232. return add(new BatchElement(t));
  233. }
  234. @:allow(h2d.BatchElement)
  235. function delete(e : BatchElement) {
  236. if( e.prev == null ) {
  237. if( first == e )
  238. first = e.next;
  239. } else
  240. e.prev.next = e.next;
  241. if( e.next == null ) {
  242. if( last == e )
  243. last = e.prev;
  244. } else
  245. e.next.prev = e.prev;
  246. e.batch = null;
  247. }
  248. override function sync(ctx) {
  249. super.sync(ctx);
  250. if( hasUpdate ) {
  251. var e = first;
  252. while( e != null ) {
  253. if( !e.update(ctx.elapsedTime) )
  254. e.remove();
  255. e = e.next;
  256. }
  257. }
  258. flush();
  259. }
  260. override function getBoundsRec( relativeTo, out, forSize ) {
  261. super.getBoundsRec(relativeTo, out, forSize);
  262. var e = first;
  263. while( e != null ) {
  264. var t = e.t;
  265. if( hasRotationScale ) {
  266. var ca = Math.cos(e.rotation), sa = Math.sin(e.rotation);
  267. var hx = t.width, hy = t.height;
  268. var px = t.dx * e.scaleX, py = t.dy * e.scaleY;
  269. var x, y;
  270. x = px * ca - py * sa + e.x;
  271. y = py * ca + px * sa + e.y;
  272. addBounds(relativeTo, out, x, y, 1e-10, 1e-10);
  273. var px = (t.dx + hx) * e.scaleX, py = t.dy * e.scaleY;
  274. x = px * ca - py * sa + e.x;
  275. y = py * ca + px * sa + e.y;
  276. addBounds(relativeTo, out, x, y, 1e-10, 1e-10);
  277. var px = t.dx * e.scaleX, py = (t.dy + hy) * e.scaleY;
  278. x = px * ca - py * sa + e.x;
  279. y = py * ca + px * sa + e.y;
  280. addBounds(relativeTo, out, x, y, 1e-10, 1e-10);
  281. var px = (t.dx + hx) * e.scaleX, py = (t.dy + hy) * e.scaleY;
  282. x = px * ca - py * sa + e.x;
  283. y = py * ca + px * sa + e.y;
  284. addBounds(relativeTo, out, x, y, 1e-10, 1e-10);
  285. } else
  286. addBounds(relativeTo, out, e.x + t.dx, e.y + t.dy, t.width, t.height);
  287. e = e.next;
  288. }
  289. }
  290. function flush() {
  291. if( first == null ) {
  292. return;
  293. }
  294. if( tmpBuf == null ) tmpBuf = new hxd.FloatBuffer();
  295. var pos = 0;
  296. var e = first;
  297. var tmp = tmpBuf;
  298. var bufferVertices = 0;
  299. state.clear();
  300. while( e != null ) {
  301. if( !e.visible ) {
  302. e = e.next;
  303. continue;
  304. }
  305. var t = e.t;
  306. state.setTile(t);
  307. state.add(4);
  308. tmp.grow(pos + 8 * 4);
  309. if( hasRotationScale ) {
  310. var ca = Math.cos(e.rotation), sa = Math.sin(e.rotation);
  311. var hx = t.width, hy = t.height;
  312. var px = t.dx * e.scaleX, py = t.dy * e.scaleY;
  313. tmp[pos++] = px * ca - py * sa + e.x;
  314. tmp[pos++] = py * ca + px * sa + e.y;
  315. tmp[pos++] = t.u;
  316. tmp[pos++] = t.v;
  317. tmp[pos++] = e.r;
  318. tmp[pos++] = e.g;
  319. tmp[pos++] = e.b;
  320. tmp[pos++] = e.a;
  321. var px = (t.dx + hx) * e.scaleX, py = t.dy * e.scaleY;
  322. tmp[pos++] = px * ca - py * sa + e.x;
  323. tmp[pos++] = py * ca + px * sa + e.y;
  324. tmp[pos++] = t.u2;
  325. tmp[pos++] = t.v;
  326. tmp[pos++] = e.r;
  327. tmp[pos++] = e.g;
  328. tmp[pos++] = e.b;
  329. tmp[pos++] = e.a;
  330. var px = t.dx * e.scaleX, py = (t.dy + hy) * e.scaleY;
  331. tmp[pos++] = px * ca - py * sa + e.x;
  332. tmp[pos++] = py * ca + px * sa + e.y;
  333. tmp[pos++] = t.u;
  334. tmp[pos++] = t.v2;
  335. tmp[pos++] = e.r;
  336. tmp[pos++] = e.g;
  337. tmp[pos++] = e.b;
  338. tmp[pos++] = e.a;
  339. var px = (t.dx + hx) * e.scaleX, py = (t.dy + hy) * e.scaleY;
  340. tmp[pos++] = px * ca - py * sa + e.x;
  341. tmp[pos++] = py * ca + px * sa + e.y;
  342. tmp[pos++] = t.u2;
  343. tmp[pos++] = t.v2;
  344. tmp[pos++] = e.r;
  345. tmp[pos++] = e.g;
  346. tmp[pos++] = e.b;
  347. tmp[pos++] = e.a;
  348. } else {
  349. var sx = e.x + t.dx;
  350. var sy = e.y + t.dy;
  351. tmp[pos++] = sx;
  352. tmp[pos++] = sy;
  353. tmp[pos++] = t.u;
  354. tmp[pos++] = t.v;
  355. tmp[pos++] = e.r;
  356. tmp[pos++] = e.g;
  357. tmp[pos++] = e.b;
  358. tmp[pos++] = e.a;
  359. tmp[pos++] = sx + t.width + 0.1;
  360. tmp[pos++] = sy;
  361. tmp[pos++] = t.u2;
  362. tmp[pos++] = t.v;
  363. tmp[pos++] = e.r;
  364. tmp[pos++] = e.g;
  365. tmp[pos++] = e.b;
  366. tmp[pos++] = e.a;
  367. tmp[pos++] = sx;
  368. tmp[pos++] = sy + t.height + 0.1;
  369. tmp[pos++] = t.u;
  370. tmp[pos++] = t.v2;
  371. tmp[pos++] = e.r;
  372. tmp[pos++] = e.g;
  373. tmp[pos++] = e.b;
  374. tmp[pos++] = e.a;
  375. tmp[pos++] = sx + t.width + 0.1;
  376. tmp[pos++] = sy + t.height + 0.1;
  377. tmp[pos++] = t.u2;
  378. tmp[pos++] = t.v2;
  379. tmp[pos++] = e.r;
  380. tmp[pos++] = e.g;
  381. tmp[pos++] = e.b;
  382. tmp[pos++] = e.a;
  383. }
  384. e = e.next;
  385. }
  386. bufferVertices = pos>>3;
  387. if( buffer != null && !buffer.isDisposed() ) {
  388. if( buffer.vertices >= bufferVertices ){
  389. buffer.uploadFloats(tmpBuf, 0, bufferVertices);
  390. return;
  391. }
  392. buffer.dispose();
  393. buffer = null;
  394. }
  395. empty = bufferVertices == 0;
  396. if( bufferVertices > 0 )
  397. buffer = h3d.Buffer.ofSubFloats(tmpBuf, bufferVertices, hxd.BufferFormat.H2D, [Dynamic]);
  398. }
  399. override function draw( ctx : RenderContext ) {
  400. drawWith(ctx, this);
  401. }
  402. @:allow(h2d)
  403. function drawWith( ctx:RenderContext, obj : Drawable ) {
  404. if( first == null || buffer == null || buffer.isDisposed() || empty ) return;
  405. if( !ctx.beginDrawBatchState(obj) ) return;
  406. var engine = ctx.engine;
  407. state.drawQuads(ctx, buffer);
  408. }
  409. /**
  410. Checks if SpriteBatch contains any elements.
  411. **/
  412. public inline function isEmpty() {
  413. return first == null;
  414. }
  415. /**
  416. Returns an Iterator of all SpriteBatch elements.
  417. Adding or removing the elements will affect the Iterator results.
  418. **/
  419. public inline function getElements() {
  420. return new ElementsIterator(first);
  421. }
  422. override function onRemove() {
  423. super.onRemove();
  424. if( buffer != null ) {
  425. buffer.dispose();
  426. buffer = null;
  427. }
  428. state.clear();
  429. }
  430. }