diff --git a/src/ParticleSystem.hx b/src/ParticleSystem.hx index af38d75b..9ef36a1d 100644 --- a/src/ParticleSystem.hx +++ b/src/ParticleSystem.hx @@ -1,11 +1,9 @@ package src; import shaders.DtsTexture; -import h3d.parts.Particles; import h3d.Matrix; import src.TimeState; import h3d.prim.UV; -import h3d.parts.Data.BlendMode; import src.MarbleWorld; import src.Util; import h3d.mat.Data.Wrap; @@ -33,7 +31,7 @@ class ParticleData { @:publicFields class Particle { - public var part:h3d.parts.Particle; + public var part:src.ParticlesMesh.ParticleElement; var data:ParticleData; var manager:ParticleManager; @@ -61,7 +59,7 @@ class Particle { this.lifeTime = this.o.lifetime + this.o.lifetimeVariance * (Math.random() * 2 - 1); this.initialSpin = Util.lerp(this.o.spinRandomMin, this.o.spinRandomMax, Math.random()); - this.part = new h3d.parts.Particle(); + this.part = new src.ParticlesMesh.ParticleElement(); } public function update(time:Float, dt:Float) { @@ -298,7 +296,7 @@ class ParticleManager { var scene:Scene; var currentTime:Float; - var particleGroups:Map = []; + var particleGroups:Map = []; var particles:Array = []; var emitters:Array = []; @@ -321,7 +319,7 @@ class ParticleManager { if (particleGroups.exists(particleData.identifier)) { particleGroups[particleData.identifier].add(particle.part); } else { - var pGroup = new Particles(particle.data.texture, this.scene); + var pGroup = new src.ParticlesMesh.ParticlesMesh(particle.data.texture, this.scene); pGroup.hasColor = true; pGroup.material.setDefaultProps("ui"); // var pdts = new DtsTexture(pGroup.material.texture); diff --git a/src/ParticlesMesh.hx b/src/ParticlesMesh.hx new file mode 100644 index 00000000..ed90a18b --- /dev/null +++ b/src/ParticlesMesh.hx @@ -0,0 +1,396 @@ +package src; + +private class ParticleIterator { + var p:ParticleElement; + + public inline function new(p) { + this.p = p; + } + + public inline function hasNext() { + return p != null; + } + + public inline function next() { + var v = p; + p = p.next; + return v; + } +} + +enum SortMode { + Front; + Back; + Sort; + InvSort; +} + +class ParticleElement { + public var parts:ParticlesMesh; + + public var x:Float; + public var y:Float; + public var z:Float; + + public var w:Float; // used for sorting + + public var r:Float; + public var g:Float; + public var b:Float; + public var a:Float; + public var alpha(get, set):Float; + + public var frame:Int; + + public var size:Float; + public var ratio:Float; + public var rotation:Float; + + public var prev:ParticleElement; + public var next:ParticleElement; + + // --- Particle emitter --- + public var time:Float; + public var lifeTimeFactor:Float; + + public var dx:Float; + public var dy:Float; + public var dz:Float; + + public var fx:Float; + public var fy:Float; + public var fz:Float; + + public var randIndex = 0; + public var randValues:Array; + + // ------------------------- + + public function new() { + r = 1; + g = 1; + b = 1; + a = 1; + frame = 0; + } + + inline function get_alpha() + return a; + + inline function set_alpha(v) + return a = v; + + public function setColor(color:Int, alpha = 1.) { + a = alpha; + r = ((color >> 16) & 0xFF) / 255.; + g = ((color >> 8) & 0xFF) / 255.; + b = (color & 0xFF) / 255.; + } + + public function remove() { + if (parts != null) { + @:privateAccess parts.kill(this); + parts = null; + } + } + + public function rand():Float { + if (randValues == null) + randValues = []; + if (randValues.length <= randIndex) + randValues.push(Math.random()); + return randValues[randIndex++]; + } +} + +class ParticlesMesh extends h3d.scene.Mesh { + var pshader:h3d.shader.ParticleShader; + + public var frames:Array; + public var count(default, null):Int = 0; + public var hasColor(default, set):Bool; + public var sortMode:SortMode; + public var globalSize:Float = 1; + + var head:ParticleElement; + var tail:ParticleElement; + var pool:ParticleElement; + + var tmp:h3d.Vector; + var tmpBuf:hxd.FloatBuffer; + var buffer:h3d.Buffer; + var bufferSize:Int = 0; + + public function new(?texture, ?parent) { + super(null, null, parent); + material.props = material.getDefaultProps("particles3D"); + sortMode = Back; + pshader = new h3d.shader.ParticleShader(); + pshader.isAbsolute = true; + material.mainPass.addShader(pshader); + material.mainPass.dynamicParameters = true; + material.texture = texture; + tmp = new h3d.Vector(); + } + + function set_hasColor(b) { + var c = material.mainPass.getShader(h3d.shader.VertexColorAlpha); + if (b) { + if (c == null) + material.mainPass.addShader(new h3d.shader.VertexColorAlpha()); + } else { + if (c != null) + material.mainPass.removeShader(c); + } + return hasColor = b; + } + + /** + Offset all existing particles by the given values. + **/ + public function offsetParticles(dx:Float, dy:Float, dz = 0.) { + var p = head; + while (p != null) { + p.x += dx; + p.y += dy; + p.z += dz; + p = p.next; + } + } + + public function clear() { + while (head != null) + kill(head); + } + + public function alloc() { + var p = emitParticle(); + if (posChanged) + syncPos(); + p.parts = this; + p.x = absPos.tx; + p.y = absPos.ty; + p.z = absPos.tz; + p.rotation = 0; + p.ratio = 1; + p.size = 1; + p.r = p.g = p.b = p.a = 1; + return p; + } + + public function add(p) { + emitParticle(p); + return p; + } + + function emitParticle(?p) { + if (p == null) { + if (pool == null) + p = new ParticleElement(); + else { + p = pool; + pool = p.next; + } + } + count++; + switch (sortMode) { + case Front, Sort, InvSort: + if (head == null) { + p.next = null; + head = tail = p; + } else { + head.prev = p; + p.next = head; + head = p; + } + case Back: + if (head == null) { + p.next = null; + head = tail = p; + } else { + tail.next = p; + p.prev = tail; + p.next = null; + tail = p; + } + } + return p; + } + + function kill(p:ParticleElement) { + if (p.prev == null) + head = p.next + else + p.prev.next = p.next; + if (p.next == null) + tail = p.prev + else + p.next.prev = p.prev; + p.prev = null; + p.next = pool; + pool = p; + count--; + } + + function sort(list:ParticleElement) { + return haxe.ds.ListSort.sort(list, function(p1, p2) return p1.w < p2.w ? 1 : -1); + } + + function sortInv(list:ParticleElement) { + return haxe.ds.ListSort.sort(list, function(p1, p2) return p1.w < p2.w ? -1 : 1); + } + + public inline function getParticles() { + return new ParticleIterator(head); + } + + @:access(h2d.Tile) + @:noDebug + override function draw(ctx:h3d.scene.RenderContext) { + if (head == null) + return; + switch (sortMode) { + case Sort, InvSort: + var p = head; + var m = ctx.camera.m; + while (p != null) { + p.w = (p.x * m._13 + p.y * m._23 + p.z * m._33 + m._43) / (p.x * m._14 + p.y * m._24 + p.z * m._34 + m._44); + p = p.next; + } + head = sortMode == Sort ? sort(head) : sortInv(head); + tail = head.prev; + head.prev = null; + default: + } + if (tmpBuf == null) + tmpBuf = new hxd.FloatBuffer(); + var pos = 0; + var p = head; + var tmp = tmpBuf; + var surface = 0.; + if (frames == null || frames.length == 0) { + var t = material.texture == null ? h2d.Tile.fromColor(0xFF00FF) : h2d.Tile.fromTexture(material.texture); + frames = [t]; + } + material.texture = frames[0].getTexture(); + + while (p != null) { + var f = frames[p.frame]; + if (f == null) + f = frames[0]; + var ratio = p.size * p.ratio * (f.height / f.width); + + if (pos >= tmp.length) { + tmp.grow(tmp.length + 40 + (hasColor ? 16 : 0)); + } + + tmp[pos++] = p.x; + tmp[pos++] = p.y; + tmp[pos++] = p.z; + tmp[pos++] = p.size; + tmp[pos++] = ratio; + tmp[pos++] = p.rotation; + // delta + tmp[pos++] = -0.5; + tmp[pos++] = -0.5; + // UV + tmp[pos++] = f.u; + tmp[pos++] = f.v2; + // RBGA + if (hasColor) { + tmp[pos++] = p.r; + tmp[pos++] = p.g; + tmp[pos++] = p.b; + tmp[pos++] = p.a; + } + + tmp[pos++] = p.x; + tmp[pos++] = p.y; + tmp[pos++] = p.z; + tmp[pos++] = p.size; + tmp[pos++] = ratio; + tmp[pos++] = p.rotation; + tmp[pos++] = -0.5; + tmp[pos++] = 0.5; + tmp[pos++] = f.u; + tmp[pos++] = f.v; + if (hasColor) { + tmp[pos++] = p.r; + tmp[pos++] = p.g; + tmp[pos++] = p.b; + tmp[pos++] = p.a; + } + + tmp[pos++] = p.x; + tmp[pos++] = p.y; + tmp[pos++] = p.z; + tmp[pos++] = p.size; + tmp[pos++] = ratio; + tmp[pos++] = p.rotation; + tmp[pos++] = 0.5; + tmp[pos++] = -0.5; + tmp[pos++] = f.u2; + tmp[pos++] = f.v2; + if (hasColor) { + tmp[pos++] = p.r; + tmp[pos++] = p.g; + tmp[pos++] = p.b; + tmp[pos++] = p.a; + } + + tmp[pos++] = p.x; + tmp[pos++] = p.y; + tmp[pos++] = p.z; + tmp[pos++] = p.size; + tmp[pos++] = ratio; + tmp[pos++] = p.rotation; + tmp[pos++] = 0.5; + tmp[pos++] = 0.5; + tmp[pos++] = f.u2; + tmp[pos++] = f.v; + if (hasColor) { + tmp[pos++] = p.r; + tmp[pos++] = p.g; + tmp[pos++] = p.b; + tmp[pos++] = p.a; + } + + p = p.next; + } + + if (pos != 0) { + var stride = 10; + if (hasColor) + stride += 4; + if (buffer == null) { + buffer = h3d.Buffer.ofSubFloats(tmp, stride, Std.int(pos / stride), [Quads, Dynamic, RawFormat]); + bufferSize = Std.int(pos / stride); + } else { + var len = Std.int(pos / stride); + if (bufferSize < len) { + buffer.dispose(); + buffer = h3d.Buffer.ofSubFloats(tmp, stride, Std.int(pos / stride), [Quads, Dynamic, RawFormat]); + bufferSize = Std.int(pos / stride); + } else { + buffer.uploadVector(tmp, 0, len); + } + } + if (pshader.is3D) + pshader.size.set(globalSize, globalSize); + else + pshader.size.set(globalSize * ctx.engine.height / ctx.engine.width * 4, globalSize * 4); + ctx.uploadParams(); + var verts = Std.int(pos / stride); + var vertsPerTri = 2; + ctx.engine.renderQuadBuffer(buffer, 0, verts >> 1); // buffer, 0, Std.int(pos / stride)); + } + } + + override function onRemove() { + super.onRemove(); + if (buffer != null) { + buffer.dispose(); + buffer = null; + } + } +}