diff --git a/away3d/tools/helpers/ParticleGeometryBuffer.hx b/away3d/tools/helpers/ParticleGeometryBuffer.hx new file mode 100644 index 0000000..71758b5 --- /dev/null +++ b/away3d/tools/helpers/ParticleGeometryBuffer.hx @@ -0,0 +1,394 @@ +package away3d.tools.helpers; + +import away3d.core.base.CompactSubGeometry; +import away3d.core.base.data.ParticleData; +import away3d.core.base.Geometry; +import away3d.core.base.ISubGeometry; +import away3d.core.base.ParticleGeometry; +import away3d.core.base.SubGeometry; +import away3d.tools.helpers.data.ParticleGeometryTransform; + +import openfl.geom.Matrix; +import openfl.geom.Matrix3D; +import openfl.geom.Point; +import openfl.geom.Vector3D; +import openfl.Vector; + +/** + * A collection of particle geometry data. Particles may be added one at a time + * (see `addParticle()`) or in batches (see `addParticles()` and + * `addTransformedParticles()`). Once enough particles are added, call + * `getParticleGeometry()` to export them as `ParticleGeometry`. + * + * Note: if you have multiple subgeometries and want to assign each one a + * different material, see `getSubGeometryMapping()`. + */ +class ParticleGeometryBuffer +{ + /** + * One build group per output subgeometry. (For instance, if at least one + * particle has 3 subgeometries, then we must output 3 subgeometries, and + * this will have length 3.) + */ + private var buildGroups:Vector; + + /** + * The total number of particles added so far. + */ + public var numParticles(default, null):Int; + + /** + * All particle data built so far. + */ + public var particles:Vector; + + public inline function new() + { + buildGroups = new Vector(); + numParticles = 0; + particles = new Vector(); + } + + /** + * Adds one particle to the buffer. + * @param geometry The particle's geometry, not including `transform`. + * @param transform An optional transform to apply to `geometry`. This + * transformation will be calculated only once, and will be baked into the + * output `ParticleGeometry`. + * @return The index of the newly-added particle. + */ + public function addParticle(geometry:Geometry, ?transform:ParticleGeometryTransform):Int + { + for (i in 0...geometry.subGeometries.length) + { + var subGeometry:ISubGeometry = geometry.subGeometries[i]; + var buildGroup:BuildGroup = buildGroups[i]; + if (buildGroup == null) + { + buildGroups[i] = buildGroup = new BuildGroup(); + } + + addParticleData(buildGroup, subGeometry.numVertices); + buildGroup.addISubGeometry(subGeometry, transform); + } + + return numParticles++; + } + + /** + * Generates and saves the new `ParticleData`. Always call this before + * adding anything to `buildGroup`. + */ + private inline function addParticleData(buildGroup:BuildGroup, numVertices:Int):Void + { + var particleData:ParticleData = new ParticleData(); + particleData.numVertices = numVertices; + particleData.startVertexIndex = buildGroup.vertexCount; + particleData.particleIndex = numParticles; + particleData.subGeometry = buildGroup.subGeometry; + particles.push(particleData); + } + + /** + * Adds multiple copies of a particle to the buffer. + * @param geometry The base geometry to copy. + * @param copies The total number of particles to add. + */ + public inline function addParticles(geometry:Geometry, copies:Int):Void + { + for (i in 0...copies) + { + addParticle(geometry); + } + } + + /** + * Adds multiple copies of a particle to the buffer. + * @param geometry The base geometry to copy. + * @param transforms One transform for each copy of the particle. + */ + public inline function addTransformedParticles(geometry:Geometry, transforms:Vector):Void + { + for (transform in transforms) + { + addParticle(geometry, transform); + } + } + + /** + * Exports the current particle data as a single `ParticleGeometry`. + * + * Note: this does not dispose the buffer, so it's possible to export + * multiple `ParticleGeometry` instances with the same data, or to add + * particles to the second that weren't in the first. + */ + public inline function getParticleGeometry():ParticleGeometry + { + var particleGeometry:ParticleGeometry = new ParticleGeometry(); + particleGeometry.particles = particles.copy(); + particleGeometry.numParticles = numParticles; + + for (buildGroup in buildGroups) + { + buildGroup.uploadAndReset(); + + for (subGeometry in buildGroup.output) + { + particleGeometry.addSubGeometry(subGeometry); + } + } + + return particleGeometry; + } + + /** + * Gets a list with one entry per output subgeometry. Each entry's value is + * the corresponding input subgeometry index. If all of your particles have + * only one subgeometry, you may safely ignore this function. + * + * If you use multiple subgeometries (typically because you use multiple + * materials), each input subgeometry's index will be preserved as much as + * possible. For instance, all particles have subgeometry 0, and this data + * will be combined into one large `CompactSubGeometry` if possible. + * + * However, no subgeometry may have more than 65535 vertices, so if too many + * particles are added, a new `CompactSubGeometry` instance must be created + * to store further vertices. This is represented by a mapping of `[0, 0]`: + * two output subgeometries, each corresponding to input index 0. + * + * If some of the input particles also use subgeometry 1, this data will be + * added to a separate `CompactSubGeometry`. If subgeometry 0 does not + * overflow, the output mapping will be `[0, 1]`. If it does, the mapping + * will instead be `[0, 0, 1]`. If both overflow, you get `[0, 0, 1, 1]`, + * and so on. + */ + public inline function getSubGeometryMapping():Vector + { + var mapping:Vector = new Vector(); + + for (inputIndex in 0...buildGroups.length) + { + for (i in 0...buildGroups[inputIndex].outputCount) + { + mapping.push(inputIndex); + } + } + + return mapping; + } +} + +/** + * A buffer collecting data corresponding to a single input subgeometry index. + * + * To get this buffer's data, call `uploadAndReset()`, then examine `output`. + */ +@:forward +abstract BuildGroup({ + var vertices:Vector; + var indices:Vector; + var subGeometry:CompactSubGeometry; + var vertexCount:Int; + var output:Vector; +}) +{ + public static inline var MAX_VERTEX:Int = 65535; + + /** + * The number of subgeometries that will be in `output` after the next call + * to `uploadAndReset()`. + */ + public var outputCount(get, never):Int; + + public inline function new() + { + this = { + vertices: new Vector(), + indices: new Vector(), + vertexCount: 0, + subGeometry: new CompactSubGeometry(), + output: new Vector() + }; + } + + public function addCompactSubGeometry(subGeometry:CompactSubGeometry, ?transform:ParticleGeometryTransform):Void + { + var oldVertexCount:Int = this.vertexCount; + var newVertexCount:Int = this.vertexCount + subGeometry.numVertices; + if (newVertexCount > MAX_VERTEX) + { + uploadAndReset(); + } + + this.vertexCount += subGeometry.numVertices; + + var vertices:BuildVector = this.vertices; + var sourceVertices:Vector = subGeometry.vertexData; + + if (transform != null) + { + var vertexTransform:Matrix3D = transform.vertexTransform; + var invVertexTransform:Matrix3D = transform.invVertexTransform; + var uvTransform:Matrix = transform.UVTransform; + + for (i in 0...subGeometry.numVertices) + { + var start:Int = i * 13; + + // 0 - 2: vertex position X, Y, Z + // 3 - 5: normal X, Y, Z + // 6 - 8: tangent X, Y, Z + // 9 - 10: U V + // 11 - 12: Secondary U V + + var vertex:Vector3D = #if haxe4 inline #end new Vector3D( + sourceVertices[start], + sourceVertices[start + 1], + sourceVertices[start + 2] + ); + var normal:Vector3D = #if haxe4 inline #end new Vector3D( + sourceVertices[start + 3], + sourceVertices[start + 4], + sourceVertices[start + 5] + ); + var tangent:Vector3D = #if haxe4 inline #end new Vector3D( + sourceVertices[start + 6], + sourceVertices[start + 7], + sourceVertices[start + 8] + ); + var uv:Point = #if haxe4 inline #end new Point( + sourceVertices[start + 9], + sourceVertices[start + 10] + ); + var secondaryUV:Point = #if haxe4 inline #end new Point( + sourceVertices[start + 11], + sourceVertices[start + 12] + ); + + if (vertexTransform != null) + { + vertices.pushVector3D(#if haxe4 inline #end + vertexTransform.transformVector(vertex)); + vertices.pushVector3D(#if haxe4 inline #end + invVertexTransform.deltaTransformVector(normal)); + vertices.pushVector3D(#if haxe4 inline #end + invVertexTransform.deltaTransformVector(tangent)); + } + else + { + vertices.pushVector3D(vertex); + vertices.pushVector3D(normal); + vertices.pushVector3D(tangent); + } + + if (uvTransform != null) + { + vertices.pushPoint(#if haxe4 inline #end + uvTransform.transformPoint(uv)); + vertices.pushPoint(#if haxe4 inline #end + uvTransform.transformPoint(secondaryUV)); + } + else + { + vertices.pushPoint(uv); + vertices.pushPoint(secondaryUV); + } + } + } + else + { + for (vertex in 0...subGeometry.numVertices) + { + var start:Int = vertex * 13; + + //`push()` is faster than `concat()`. + vertices.push(sourceVertices[start]); + vertices.push(sourceVertices[start + 1]); + vertices.push(sourceVertices[start + 2]); + vertices.push(sourceVertices[start + 3]); + vertices.push(sourceVertices[start + 4]); + vertices.push(sourceVertices[start + 5]); + vertices.push(sourceVertices[start + 6]); + vertices.push(sourceVertices[start + 7]); + vertices.push(sourceVertices[start + 8]); + vertices.push(sourceVertices[start + 9]); + vertices.push(sourceVertices[start + 10]); + vertices.push(sourceVertices[start + 11]); + vertices.push(sourceVertices[start + 12]); + } + } + + var sourceIndices:Vector = subGeometry.indexData; + for (i in 0...subGeometry.numTriangles) + { + var start:Int = i * 3; + this.indices.push(sourceIndices[start] + oldVertexCount); + this.indices.push(sourceIndices[start + 1] + oldVertexCount); + this.indices.push(sourceIndices[start + 2] + oldVertexCount); + } + } + + public inline function addISubGeometry(subGeometry:ISubGeometry, ?transform:ParticleGeometryTransform):Void + { + if (#if (haxe_ver >= 4.2) Std.isOfType #else Std.is #end(subGeometry, CompactSubGeometry)) + { + addCompactSubGeometry(cast subGeometry, transform); + } + else + { + // Not implemented yet. + throw 'Expected a CompactSubGeometry, got $subGeometry.'; + } + } + + /** + * Saves all current data to `output`, then clears everything to make space + * for new data. + */ + public inline function uploadAndReset():Void + { + if (this.vertexCount > 0) + { + this.subGeometry.updateData(this.vertices); + this.subGeometry.updateIndexData(this.indices); + this.output.push(this.subGeometry); + + this.vertices = new Vector(); + this.indices = new Vector(); + this.subGeometry = new CompactSubGeometry(); + this.vertexCount = 0; + } + } + + // Getters & Setters + + private inline function get_outputCount():Int + { + return this.output.length + this.vertexCount > 0 ? 1 : 0; + } +} + +/** + * A few `Vector` utility methods used by `BuildGroup`. + */ +@:forward +private abstract BuildVector(Vector) from Vector to Vector { + /** + * Pushes the given point's `x` and `y` values in that order. + */ + public inline function pushPoint(point:Point):Void + { + this.push(point.x); + this.push(point.y); + } + + /** + * Pushes the given vector's `x`, `y`, and `z` values in that order. + */ + public inline function pushVector3D(vector:Vector3D):Void + { + this.push(vector.x); + this.push(vector.y); + this.push(vector.z); + } +} diff --git a/away3d/tools/helpers/ParticleGeometryHelper.hx b/away3d/tools/helpers/ParticleGeometryHelper.hx index 5a87b0b..bbd2cf2 100644 --- a/away3d/tools/helpers/ParticleGeometryHelper.hx +++ b/away3d/tools/helpers/ParticleGeometryHelper.hx @@ -1,196 +1,31 @@ package away3d.tools.helpers; import away3d.core.base.ParticleGeometry; -import away3d.core.base.CompactSubGeometry; -import away3d.core.base.data.ParticleData; import away3d.core.base.Geometry; -import away3d.core.base.ISubGeometry; import away3d.tools.helpers.data.ParticleGeometryTransform; - -import openfl.geom.Matrix; -import openfl.geom.Matrix3D; -import openfl.geom.Point; -import openfl.geom.Vector3D; import openfl.Vector; -/** - * ... - */ class ParticleGeometryHelper { - public static inline var MAX_VERTEX:Int = 65535; - - public static function generateGeometry(geometries:Vector, transforms:Vector = null):ParticleGeometry + public static inline function generateGeometry(geometries:Vector, transforms:Vector = null):ParticleGeometry { - var verticesVector:Vector> = new Vector>(); - var indicesVector:Vector> = new Vector>(); - var vertexCounters:Vector = new Vector(); - var particles:Vector = new Vector(); - var subGeometries:Vector = new Vector(); - var numParticles:Int = geometries.length; - - var sourceSubGeometries:Vector; - var sourceSubGeometry:ISubGeometry; - var numSubGeometries:Int; - var vertices:Vector; - var indices:Vector; - var vertexCounter:Int; - var subGeometry:CompactSubGeometry; - var i:Int; - var j:Int; - var sub2SubMap:Vector = new Vector(); - - var tempVertex:Vector3D = new Vector3D(); - var tempNormal:Vector3D = new Vector3D(); - var tempTangents:Vector3D = new Vector3D(); - var tempUV:Point = new Point(); + var buffer:ParticleGeometryBuffer = new ParticleGeometryBuffer(); - for (i in 0...numParticles) { - sourceSubGeometries = geometries[i].subGeometries; - numSubGeometries = sourceSubGeometries.length; - for (srcIndex in 0...numSubGeometries) { - //create a different particle subgeometry group for each source subgeometry in a particle. - if (sub2SubMap.length <= srcIndex) { - sub2SubMap.push(subGeometries.length); - verticesVector.push(new Vector()); - indicesVector.push(new Vector()); - subGeometries.push(new CompactSubGeometry()); - vertexCounters.push(0); - } - - sourceSubGeometry = sourceSubGeometries[srcIndex]; - - //add a new particle subgeometry if this source subgeometry will take us over the maxvertex limit - if (Std.int(sourceSubGeometry.numVertices + vertexCounters[sub2SubMap[srcIndex]]) > MAX_VERTEX) { - //update submap and add new subgeom vectors - sub2SubMap[srcIndex] = subGeometries.length; - verticesVector.push(new Vector()); - indicesVector.push(new Vector()); - subGeometries.push(new CompactSubGeometry()); - vertexCounters.push(0); - } - - j = sub2SubMap[srcIndex]; - - //select the correct vector - vertices = verticesVector[j]; - indices = indicesVector[j]; - vertexCounter = vertexCounters[j]; - subGeometry = subGeometries[j]; - - var particleData:ParticleData = new ParticleData(); - particleData.numVertices = sourceSubGeometry.numVertices; - particleData.startVertexIndex = vertexCounter; - particleData.particleIndex = i; - particleData.subGeometry = subGeometry; - particles.push(particleData); - - vertexCounters[j] += sourceSubGeometry.numVertices; - - var k:Int; - var tempLen:Int; - var compact:CompactSubGeometry = #if (haxe_ver >= 4.2) Std.isOfType #else Std.is #end(sourceSubGeometry, CompactSubGeometry) ? cast sourceSubGeometry : null; - var product:Int; - var sourceVertices:Vector; - - if (compact != null) { - tempLen = compact.numVertices; - compact.numTriangles; - sourceVertices = compact.vertexData; - - if (transforms != null) { - var particleGeometryTransform:ParticleGeometryTransform = transforms[i]; - var vertexTransform:Matrix3D = particleGeometryTransform.vertexTransform; - var invVertexTransform:Matrix3D = particleGeometryTransform.invVertexTransform; - var UVTransform:Matrix = particleGeometryTransform.UVTransform; - - for (k in 0...tempLen) { - /* - * 0 - 2: vertex position X, Y, Z - * 3 - 5: normal X, Y, Z - * 6 - 8: tangent X, Y, Z - * 9 - 10: U V - * 11 - 12: Secondary U V*/ - product = k*13; - tempVertex.x = sourceVertices[product]; - tempVertex.y = sourceVertices[product + 1]; - tempVertex.z = sourceVertices[product + 2]; - tempNormal.x = sourceVertices[product + 3]; - tempNormal.y = sourceVertices[product + 4]; - tempNormal.z = sourceVertices[product + 5]; - tempTangents.x = sourceVertices[product + 6]; - tempTangents.y = sourceVertices[product + 7]; - tempTangents.z = sourceVertices[product + 8]; - tempUV.x = sourceVertices[product + 9]; - tempUV.y = sourceVertices[product + 10]; - if (vertexTransform != null) { - tempVertex = vertexTransform.transformVector(tempVertex); - tempNormal = invVertexTransform.deltaTransformVector(tempNormal); - tempTangents = invVertexTransform.deltaTransformVector(tempNormal); - } - if (UVTransform != null) - tempUV = UVTransform.transformPoint(tempUV); - //this is faster than that only push one data - vertices.push(tempVertex.x); - vertices.push(tempVertex.y); - vertices.push(tempVertex.z); - vertices.push(tempNormal.x); - vertices.push(tempNormal.y); - vertices.push(tempNormal.z); - vertices.push(tempTangents.x); - vertices.push(tempTangents.y); - vertices.push(tempTangents.z); - vertices.push(tempUV.x); - vertices.push(tempUV.y); - vertices.push(sourceVertices[product + 11]); - vertices.push(sourceVertices[product + 12]); - } - } else { - for (k in 0...tempLen) { - product = k*13; - //this is faster than that only push one data - vertices.push(sourceVertices[product]); - vertices.push(sourceVertices[product + 1]); - vertices.push(sourceVertices[product + 2]); - vertices.push(sourceVertices[product + 3]); - vertices.push(sourceVertices[product + 4]); - vertices.push(sourceVertices[product + 5]); - vertices.push(sourceVertices[product + 6]); - vertices.push(sourceVertices[product + 7]); - vertices.push(sourceVertices[product + 8]); - vertices.push(sourceVertices[product + 9]); - vertices.push(sourceVertices[product + 10]); - vertices.push(sourceVertices[product + 11]); - vertices.push(sourceVertices[product + 12]); - } - } - } else { - //Todo - } - - var sourceIndices:Vector = sourceSubGeometry.indexData; - tempLen = sourceSubGeometry.numTriangles; - for (k in 0...tempLen) { - product = k*3; - indices.push(sourceIndices[product] + vertexCounter); - indices.push(sourceIndices[product + 1] + vertexCounter); - indices.push(sourceIndices[product + 2] + vertexCounter); - } + if (transforms != null) + { + for (i in 0...geometries.length) + { + buffer.addParticle(geometries[i], transforms[i]); } } - - var particleGeometry:ParticleGeometry = new ParticleGeometry(); - particleGeometry.particles = particles; - particleGeometry.numParticles = numParticles; - - numParticles = subGeometries.length; - for (i in 0...numParticles) { - subGeometry = subGeometries[i]; - subGeometry.updateData(verticesVector[i]); - subGeometry.updateIndexData(indicesVector[i]); - particleGeometry.addSubGeometry(subGeometry); + else + { + for (geometry in geometries) + { + buffer.addParticle(geometry); + } } - return particleGeometry; + return buffer.getParticleGeometry(); } -} \ No newline at end of file +}