123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480 |
- package h2d;
- import h2d.RenderContext;
- import h2d.impl.BatchDrawState;
- private class ElementsIterator {
- var e : BatchElement;
- public inline function new(e) {
- this.e = e;
- }
- public inline function hasNext() {
- return e != null;
- }
- public inline function next() {
- var n = e;
- e = @:privateAccess e.next;
- return n;
- }
- }
- /**
- A base class for `SpriteBatch` elements which can be extended with custom logic.
- See `BasicElement` as an example of custom element logic.
- **/
- @:allow(h2d.SpriteBatch)
- class BatchElement {
- /**
- Element X position.
- **/
- public var x : Float = 0;
- /**
- Element Y position.
- **/
- public var y : Float = 0;
- /**
- Shortcut to set both `BatchElement.scaleX` and `BatchElement.scaleY` at the same time.
- Equivalent to `el.scaleX = el.scaleY = scale`.
- **/
- public var scale(never,set) : Float;
- /**
- X-axis scaling factor of the element.
- This variable is used only if `SpriteBatch.hasRotationScale` is set to `true`.
- **/
- public var scaleX : Float = 1;
- /**
- Y-axis scaling factor of the element.
- This variable is used only if `SpriteBatch.hasRotationScale` is set to `true`.
- **/
- public var scaleY : Float = 1;
- /**
- Element rotation in radians.
- This variable is used only if `SpriteBatch.hasRotationScale` is set to `true`.
- **/
- public var rotation : Float = 0;
- /**
- Red tint value (0...1 range) of the element.
- **/
- public var r : Float = 1;
- /**
- Green tint value (0...1 range) of the element.
- **/
- public var g : Float = 1;
- /**
- Blue tint value (0...1 range) of the element.
- **/
- public var b : Float = 1;
- /**
- Alpha value of the element.
- **/
- public var a : Float = 1;
- /**
- The Tile this element renders.
- Due to implementation specifics, this Tile instance is used only to provide rendering area, not the Texture itself,
- as `SpriteBatch.tile` used as a source of rendered texture.
- **/
- public var t : Tile;
- /**
- Alpha value of the element.
- Alias of `BatchElement.a`.
- **/
- public var alpha(get,set) : Float;
- /**
- If set to `false`, element will not be rendered.
- **/
- public var visible : Bool = true;
- /**
- Reference to parent SpriteBatch instance.
- **/
- public var batch(default, null) : SpriteBatch;
- var prev : BatchElement;
- var next : BatchElement;
- /**
- Create a new BatchElement instance with provided Tile.
- @param t The tile used to render this BatchElement.
- **/
- public function new( t : Tile ) {
- this.t = t;
- }
- inline function set_scale(v) {
- return scaleX = scaleY = v;
- }
- inline function get_alpha() {
- return a;
- }
- inline function set_alpha(v) {
- return a = v;
- }
- /**
- Override this method to perform custom logic per batch element.
- Update method called only if `SpriteBatch.hasUpdate` is set to `true`.
- @param dt The elapsed time in seconds since last update from `RenderContext.elapsedTime`.
- @returns If method returns `false`, element will be removed from the SpriteBatch.
- **/
- @:dox(show)
- function update(et:Float) {
- return true;
- }
- /**
- Remove this BatchElement from the parent SpriteBatch instance.
- **/
- public function remove() {
- if( batch != null )
- batch.delete(this);
- }
- }
- /**
- A simple `BatchElement` that provides primitive simulation of velocity, friction and gravity.
- Parent `SpriteBatch` should have `SpriteBatch.hasUpdate` set to `true` in order for BasicElement to work properly.
- **/
- class BasicElement extends BatchElement {
- /**
- X-axis velocity of the element.
- **/
- public var vx : Float = 0.;
- /**
- Y-axis velocity of the element.
- **/
- public var vy : Float = 0.;
- /**
- The velocity friction.
- When not `1`, multiplies velocity by `pow(friction, dt * 60)`.
- **/
- public var friction : Float = 1.;
- /**
- The gravity applied to vertical velocity in pixels per second.
- **/
- public var gravity : Float = 0.;
- override function update(dt:Float) {
- vy += gravity * dt;
- x += vx * dt;
- y += vy * dt;
- if( friction != 1 ) {
- var p = Math.pow(friction, dt * 60);
- vx *= p;
- vy *= p;
- }
- return true;
- }
- }
- /**
- An active batched tile renderer.
- Compared to `TileGroup` which is expected to be used as a static geometry,
- SpriteBatch uploads GPU buffer each frame by collecting data from added `BatchElement` instance.
- Due to that, dynamically removing and adding new geometry is fairly simple.
- Usage note: While SpriteBatch allows for multiple unique textures, each texture swap causes a new drawcall,
- and due to that it's recommended to minimize the amount of used textures per SpriteBatch instance,
- ideally limiting to only one texture.
- **/
- class SpriteBatch extends Drawable {
- /**
- The Tile used as a base Texture to draw contents with.
- **/
- public var tile : Tile;
- /**
- Enables usage of rotation and scaling of SpriteBatch elements at the cost of extra calculus.
- Makes use of `BatchElement.scaleX`, `BatchElement.scaleY` and `BatchElement.rotation`.
- **/
- public var hasRotationScale : Bool = false;
- /**
- Enables usage of `update` method in SpriteBatch elements.
- **/
- public var hasUpdate : Bool = false;
- var first : BatchElement;
- var last : BatchElement;
- var tmpBuf : hxd.FloatBuffer;
- var buffer : h3d.Buffer;
- var state : BatchDrawState;
- var empty : Bool;
- /**
- Create new SpriteBatch instance.
- @param t The Tile used as a base Texture to draw contents with.
- @param parent An optional parent `h2d.Object` instance to which SpriteBatch adds itself if set.
- **/
- public function new(t,?parent) {
- super(parent);
- tile = t;
- state = new BatchDrawState();
- }
- /**
- Adds a new BatchElement to the SpriteBatch.
- @param e The element to add.
- @param before When set, element will be added to the beginning of the element chain (rendered first).
- **/
- public function add(e:BatchElement,before=false) {
- e.batch = this;
- if( first == null ) {
- first = last = e;
- e.prev = e.next = null;
- } else if( before ) {
- e.prev = null;
- e.next = first;
- first.prev = e;
- first = e;
- } else {
- last.next = e;
- e.prev = last;
- e.next = null;
- last = e;
- }
- return e;
- }
- /**
- Removes all elements from the SpriteBatch.
- Usage note: Does not clear the `BatchElement.batch` nor `next`/`prev` variables on the child elements.
- **/
- public function clear() {
- first = last = null;
- flush();
- }
- /**
- Creates a new BatchElement and returns it. Shortcut to `add(new BatchElement(t))`
- @param t The Tile element will render.
- **/
- public function alloc( t : Tile ) : BatchElement {
- return add(new BatchElement(t));
- }
- @:allow(h2d.BatchElement)
- function delete(e : BatchElement) {
- if( e.prev == null ) {
- if( first == e )
- first = e.next;
- } else
- e.prev.next = e.next;
- if( e.next == null ) {
- if( last == e )
- last = e.prev;
- } else
- e.next.prev = e.prev;
- e.batch = null;
- }
- override function sync(ctx) {
- super.sync(ctx);
- if( hasUpdate ) {
- var e = first;
- while( e != null ) {
- if( !e.update(ctx.elapsedTime) )
- e.remove();
- e = e.next;
- }
- }
- flush();
- }
- override function getBoundsRec( relativeTo, out, forSize ) {
- super.getBoundsRec(relativeTo, out, forSize);
- var e = first;
- while( e != null ) {
- var t = e.t;
- if( hasRotationScale ) {
- var ca = Math.cos(e.rotation), sa = Math.sin(e.rotation);
- var hx = t.width, hy = t.height;
- var px = t.dx * e.scaleX, py = t.dy * e.scaleY;
- var x, y;
- x = px * ca - py * sa + e.x;
- y = py * ca + px * sa + e.y;
- addBounds(relativeTo, out, x, y, 1e-10, 1e-10);
- var px = (t.dx + hx) * e.scaleX, py = t.dy * e.scaleY;
- x = px * ca - py * sa + e.x;
- y = py * ca + px * sa + e.y;
- addBounds(relativeTo, out, x, y, 1e-10, 1e-10);
- var px = t.dx * e.scaleX, py = (t.dy + hy) * e.scaleY;
- x = px * ca - py * sa + e.x;
- y = py * ca + px * sa + e.y;
- addBounds(relativeTo, out, x, y, 1e-10, 1e-10);
- var px = (t.dx + hx) * e.scaleX, py = (t.dy + hy) * e.scaleY;
- x = px * ca - py * sa + e.x;
- y = py * ca + px * sa + e.y;
- addBounds(relativeTo, out, x, y, 1e-10, 1e-10);
- } else
- addBounds(relativeTo, out, e.x + t.dx, e.y + t.dy, t.width, t.height);
- e = e.next;
- }
- }
- function flush() {
- if( first == null ) {
- return;
- }
- if( tmpBuf == null ) tmpBuf = new hxd.FloatBuffer();
- var pos = 0;
- var e = first;
- var tmp = tmpBuf;
- var bufferVertices = 0;
- state.clear();
- while( e != null ) {
- if( !e.visible ) {
- e = e.next;
- continue;
- }
- var t = e.t;
- state.setTile(t);
- state.add(4);
- tmp.grow(pos + 8 * 4);
- if( hasRotationScale ) {
- var ca = Math.cos(e.rotation), sa = Math.sin(e.rotation);
- var hx = t.width, hy = t.height;
- var px = t.dx * e.scaleX, py = t.dy * e.scaleY;
- tmp[pos++] = px * ca - py * sa + e.x;
- tmp[pos++] = py * ca + px * sa + e.y;
- tmp[pos++] = t.u;
- tmp[pos++] = t.v;
- tmp[pos++] = e.r;
- tmp[pos++] = e.g;
- tmp[pos++] = e.b;
- tmp[pos++] = e.a;
- var px = (t.dx + hx) * e.scaleX, py = t.dy * e.scaleY;
- tmp[pos++] = px * ca - py * sa + e.x;
- tmp[pos++] = py * ca + px * sa + e.y;
- tmp[pos++] = t.u2;
- tmp[pos++] = t.v;
- tmp[pos++] = e.r;
- tmp[pos++] = e.g;
- tmp[pos++] = e.b;
- tmp[pos++] = e.a;
- var px = t.dx * e.scaleX, py = (t.dy + hy) * e.scaleY;
- tmp[pos++] = px * ca - py * sa + e.x;
- tmp[pos++] = py * ca + px * sa + e.y;
- tmp[pos++] = t.u;
- tmp[pos++] = t.v2;
- tmp[pos++] = e.r;
- tmp[pos++] = e.g;
- tmp[pos++] = e.b;
- tmp[pos++] = e.a;
- var px = (t.dx + hx) * e.scaleX, py = (t.dy + hy) * e.scaleY;
- tmp[pos++] = px * ca - py * sa + e.x;
- tmp[pos++] = py * ca + px * sa + e.y;
- tmp[pos++] = t.u2;
- tmp[pos++] = t.v2;
- tmp[pos++] = e.r;
- tmp[pos++] = e.g;
- tmp[pos++] = e.b;
- tmp[pos++] = e.a;
- } else {
- var sx = e.x + t.dx;
- var sy = e.y + t.dy;
- tmp[pos++] = sx;
- tmp[pos++] = sy;
- tmp[pos++] = t.u;
- tmp[pos++] = t.v;
- tmp[pos++] = e.r;
- tmp[pos++] = e.g;
- tmp[pos++] = e.b;
- tmp[pos++] = e.a;
- tmp[pos++] = sx + t.width + 0.1;
- tmp[pos++] = sy;
- tmp[pos++] = t.u2;
- tmp[pos++] = t.v;
- tmp[pos++] = e.r;
- tmp[pos++] = e.g;
- tmp[pos++] = e.b;
- tmp[pos++] = e.a;
- tmp[pos++] = sx;
- tmp[pos++] = sy + t.height + 0.1;
- tmp[pos++] = t.u;
- tmp[pos++] = t.v2;
- tmp[pos++] = e.r;
- tmp[pos++] = e.g;
- tmp[pos++] = e.b;
- tmp[pos++] = e.a;
- tmp[pos++] = sx + t.width + 0.1;
- tmp[pos++] = sy + t.height + 0.1;
- tmp[pos++] = t.u2;
- tmp[pos++] = t.v2;
- tmp[pos++] = e.r;
- tmp[pos++] = e.g;
- tmp[pos++] = e.b;
- tmp[pos++] = e.a;
- }
- e = e.next;
- }
- bufferVertices = pos>>3;
- if( buffer != null && !buffer.isDisposed() ) {
- if( buffer.vertices >= bufferVertices ){
- buffer.uploadVector(tmpBuf, 0, bufferVertices);
- return;
- }
- buffer.dispose();
- buffer = null;
- }
- empty = bufferVertices == 0;
- if( bufferVertices > 0 )
- buffer = h3d.Buffer.ofSubFloats(tmpBuf, 8, bufferVertices, [Dynamic, Quads, RawFormat]);
- }
- override function draw( ctx : RenderContext ) {
- drawWith(ctx, this);
- }
- @:allow(h2d)
- function drawWith( ctx:RenderContext, obj : Drawable ) {
- if( first == null || buffer == null || buffer.isDisposed() || empty ) return;
- if( !ctx.beginDrawBatchState(obj) ) return;
- var engine = ctx.engine;
- state.drawQuads(ctx, buffer);
- }
- /**
- Checks if SpriteBatch contains any elements.
- **/
- public inline function isEmpty() {
- return first == null;
- }
- /**
- Returns an Iterator of all SpriteBatch elements.
- Adding or removing the elements will affect the Iterator results.
- **/
- public inline function getElements() {
- return new ElementsIterator(first);
- }
- override function onRemove() {
- super.onRemove();
- if( buffer != null ) {
- buffer.dispose();
- buffer = null;
- }
- state.clear();
- }
- }
|