forked from LeenkxTeam/LNXSDK
merge upstream
This commit is contained in:
@ -302,6 +302,10 @@ class MeshObject extends Object {
|
||||
|
||||
// Render mesh
|
||||
var ldata = lod.data;
|
||||
|
||||
// Next pass rendering first (inverse order)
|
||||
renderNextPass(g, context, bindParams, lod);
|
||||
|
||||
for (i in 0...ldata.geom.indexBuffers.length) {
|
||||
|
||||
var mi = ldata.geom.materialIndices[i];
|
||||
@ -405,4 +409,85 @@ class MeshObject extends Object {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function renderNextPass(g: Graphics, context: String, bindParams: Array<String>, lod: MeshObject) {
|
||||
var ldata = lod.data;
|
||||
for (i in 0...ldata.geom.indexBuffers.length) {
|
||||
var mi = ldata.geom.materialIndices[i];
|
||||
if (mi >= materials.length) continue;
|
||||
|
||||
var currentMaterial: MaterialData = materials[mi];
|
||||
if (currentMaterial == null || currentMaterial.shader == null) continue;
|
||||
|
||||
var nextPassName: String = currentMaterial.shader.nextPass;
|
||||
if (nextPassName == null || nextPassName == "") continue;
|
||||
|
||||
var nextMaterial: MaterialData = null;
|
||||
for (mat in materials) {
|
||||
// First try exact match
|
||||
if (mat.name == nextPassName) {
|
||||
nextMaterial = mat;
|
||||
break;
|
||||
}
|
||||
// If no exact match, try to match base name for linked materials
|
||||
if (mat.name.indexOf("_") > 0 && mat.name.substr(mat.name.length - 6) == ".blend") {
|
||||
var baseName = mat.name.substring(0, mat.name.indexOf("_"));
|
||||
if (baseName == nextPassName) {
|
||||
nextMaterial = mat;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (nextMaterial == null) continue;
|
||||
|
||||
var nextMaterialContext: MaterialContext = null;
|
||||
var nextShaderContext: ShaderContext = null;
|
||||
|
||||
for (j in 0...nextMaterial.raw.contexts.length) {
|
||||
if (nextMaterial.raw.contexts[j].name.substr(0, context.length) == context) {
|
||||
nextMaterialContext = nextMaterial.contexts[j];
|
||||
nextShaderContext = nextMaterial.shader.getContext(context);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (nextShaderContext == null) continue;
|
||||
if (skipContext(context, nextMaterial)) continue;
|
||||
|
||||
var elems = nextShaderContext.raw.vertex_elements;
|
||||
|
||||
// Uniforms
|
||||
if (nextShaderContext.pipeState != lastPipeline) {
|
||||
g.setPipeline(nextShaderContext.pipeState);
|
||||
lastPipeline = nextShaderContext.pipeState;
|
||||
}
|
||||
Uniforms.setContextConstants(g, nextShaderContext, bindParams);
|
||||
Uniforms.setObjectConstants(g, nextShaderContext, this);
|
||||
Uniforms.setMaterialConstants(g, nextShaderContext, nextMaterialContext);
|
||||
|
||||
// VB / IB
|
||||
#if lnx_deinterleaved
|
||||
g.setVertexBuffers(ldata.geom.get(elems));
|
||||
#else
|
||||
if (ldata.geom.instancedVB != null) {
|
||||
g.setVertexBuffers([ldata.geom.get(elems), ldata.geom.instancedVB]);
|
||||
}
|
||||
else {
|
||||
g.setVertexBuffer(ldata.geom.get(elems));
|
||||
}
|
||||
#end
|
||||
|
||||
g.setIndexBuffer(ldata.geom.indexBuffers[i]);
|
||||
|
||||
// Draw next pass for this specific geometry section
|
||||
if (ldata.geom.instanced) {
|
||||
g.drawIndexedVerticesInstanced(ldata.geom.instanceCount, ldata.geom.start, ldata.geom.count);
|
||||
}
|
||||
else {
|
||||
g.drawIndexedVertices(ldata.geom.start, ldata.geom.count);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,6 +8,8 @@ import kha.arrays.Float32Array;
|
||||
import iron.data.Data;
|
||||
import iron.data.ParticleData;
|
||||
import iron.data.SceneFormat;
|
||||
import iron.data.Geometry;
|
||||
import iron.data.MeshData;
|
||||
import iron.system.Time;
|
||||
import iron.math.Mat4;
|
||||
import iron.math.Quat;
|
||||
@ -17,6 +19,7 @@ import iron.math.Vec4;
|
||||
class ParticleSystem {
|
||||
public var data: ParticleData;
|
||||
public var speed = 1.0;
|
||||
public var dynamicEmitter: Bool = true;
|
||||
var currentSpeed = 0.0;
|
||||
var particles: Array<Particle>;
|
||||
var ready: Bool;
|
||||
@ -52,6 +55,12 @@ class ParticleSystem {
|
||||
|
||||
var random = 0.0;
|
||||
|
||||
var tmpV4 = new Vec4();
|
||||
|
||||
var instancedData: Float32Array = null;
|
||||
var lastSpawnedCount: Int = 0;
|
||||
var hasUniqueGeom: Bool = false;
|
||||
|
||||
public function new(sceneName: String, pref: TParticleReference) {
|
||||
seed = pref.seed;
|
||||
currentSpeed = speed;
|
||||
@ -62,6 +71,11 @@ class ParticleSystem {
|
||||
Data.getParticle(sceneName, pref.particle, function(b: ParticleData) {
|
||||
data = b;
|
||||
r = data.raw;
|
||||
if (r.dynamic_emitter != null){
|
||||
dynamicEmitter = r.dynamic_emitter;
|
||||
} else {
|
||||
dynamicEmitter = true;
|
||||
}
|
||||
if (Scene.active.raw.gravity != null) {
|
||||
gx = Scene.active.raw.gravity[0] * r.weight_gravity;
|
||||
gy = Scene.active.raw.gravity[1] * r.weight_gravity;
|
||||
@ -98,6 +112,8 @@ class ParticleSystem {
|
||||
lap = 0;
|
||||
lapTime = 0;
|
||||
speed = currentSpeed;
|
||||
lastSpawnedCount = 0;
|
||||
instancedData = null;
|
||||
}
|
||||
|
||||
public function pause() {
|
||||
@ -130,8 +146,13 @@ class ParticleSystem {
|
||||
|
||||
// Copy owner world transform but discard scale
|
||||
owner.transform.world.decompose(ownerLoc, ownerRot, ownerScl);
|
||||
object.transform.loc = ownerLoc;
|
||||
object.transform.rot = ownerRot;
|
||||
if (dynamicEmitter) {
|
||||
object.transform.loc.x = 0; object.transform.loc.y = 0; object.transform.loc.z = 0;
|
||||
object.transform.rot = new Quat();
|
||||
} else {
|
||||
object.transform.loc = ownerLoc;
|
||||
object.transform.rot = ownerRot;
|
||||
}
|
||||
|
||||
// Set particle size per particle system
|
||||
object.transform.scale = new Vec4(r.particle_size, r.particle_size, r.particle_size, 1);
|
||||
@ -158,13 +179,18 @@ class ParticleSystem {
|
||||
if (lap > prevLap && !r.loop) {
|
||||
end();
|
||||
}
|
||||
|
||||
if (lap > prevLap && r.loop) {
|
||||
lastSpawnedCount = 0;
|
||||
}
|
||||
|
||||
updateGpu(object, owner);
|
||||
}
|
||||
|
||||
public function getData(): Mat4 {
|
||||
var hair = r.type == 1;
|
||||
m._00 = animtime;
|
||||
// Store loop flag in the sign: positive -> loop, negative -> no loop
|
||||
m._00 = r.loop ? animtime : -animtime;
|
||||
m._01 = hair ? 1 / particles.length : spawnRate;
|
||||
m._02 = hair ? 1 : lifetime;
|
||||
m._03 = particles.length;
|
||||
@ -187,17 +213,26 @@ class ParticleSystem {
|
||||
return r.size_random;
|
||||
}
|
||||
|
||||
public function getRandom(): FastFloat {
|
||||
public inline function getRandom(): FastFloat {
|
||||
return random;
|
||||
}
|
||||
|
||||
public function getSize(): FastFloat {
|
||||
public inline function getSize(): FastFloat {
|
||||
return r.particle_size;
|
||||
}
|
||||
|
||||
function updateGpu(object: MeshObject, owner: MeshObject) {
|
||||
if (!object.data.geom.instanced) setupGeomGpu(object, owner);
|
||||
// GPU particles transform is attached to owner object
|
||||
if (dynamicEmitter) {
|
||||
if (!hasUniqueGeom) ensureUniqueGeom(object);
|
||||
var needSetup = instancedData == null || object.data.geom.instancedVB == null;
|
||||
if (needSetup) setupGeomGpuDynamic(object, owner);
|
||||
updateSpawnedInstances(object, owner);
|
||||
}
|
||||
else {
|
||||
if (!hasUniqueGeom) ensureUniqueGeom(object);
|
||||
if (!object.data.geom.instanced) setupGeomGpu(object, owner);
|
||||
}
|
||||
// GPU particles transform is attached to owner object in static mode
|
||||
}
|
||||
|
||||
function setupGeomGpu(object: MeshObject, owner: MeshObject) {
|
||||
@ -258,18 +293,134 @@ class ParticleSystem {
|
||||
object.data.geom.setupInstanced(instancedData, 1, Usage.StaticUsage);
|
||||
}
|
||||
|
||||
function fhash(n: Int): Float {
|
||||
var s = n + 1.0;
|
||||
s *= 9301.0 % s;
|
||||
s = (s * 9301.0 + 49297.0) % 233280.0;
|
||||
return s / 233280.0;
|
||||
// allocate instanced VB once for this object
|
||||
function setupGeomGpuDynamic(object: MeshObject, owner: MeshObject) {
|
||||
if (instancedData == null) instancedData = new Float32Array(particles.length * 3);
|
||||
lastSpawnedCount = 0;
|
||||
// Create instanced VB once if missing (seed with our instancedData)
|
||||
if (object.data.geom.instancedVB == null) {
|
||||
object.data.geom.setupInstanced(instancedData, 1, Usage.DynamicUsage);
|
||||
}
|
||||
}
|
||||
|
||||
function ensureUniqueGeom(object: MeshObject) {
|
||||
if (hasUniqueGeom) return;
|
||||
var newData: MeshData = null;
|
||||
new MeshData(object.data.raw, function(dat: MeshData) {
|
||||
dat.scalePos = object.data.scalePos;
|
||||
dat.scaleTex = object.data.scaleTex;
|
||||
dat.format = object.data.format;
|
||||
newData = dat;
|
||||
});
|
||||
if (newData != null) object.setData(newData);
|
||||
hasUniqueGeom = true;
|
||||
}
|
||||
|
||||
function updateSpawnedInstances(object: MeshObject, owner: MeshObject) {
|
||||
if (instancedData == null) return;
|
||||
var targetCount = count;
|
||||
if (targetCount > particles.length) targetCount = particles.length;
|
||||
if (targetCount <= lastSpawnedCount) return;
|
||||
|
||||
var normFactor = 1 / 32767;
|
||||
var scalePosOwner = owner.data.scalePos;
|
||||
var scalePosParticle = object.data.scalePos;
|
||||
var particleSize = r.particle_size;
|
||||
var base = 1.0 / (particleSize * scalePosParticle);
|
||||
|
||||
switch (r.emit_from) {
|
||||
case 0: // Vert
|
||||
var pa = owner.data.geom.positions;
|
||||
var osx = owner.transform.scale.x;
|
||||
var osy = owner.transform.scale.y;
|
||||
var osz = owner.transform.scale.z;
|
||||
var pCount = Std.int(pa.values.length / pa.size);
|
||||
for (idx in lastSpawnedCount...targetCount) {
|
||||
var j = Std.int(fhash(idx) * pCount);
|
||||
var lx = pa.values[j * pa.size ] * normFactor * scalePosOwner * osx;
|
||||
var ly = pa.values[j * pa.size + 1] * normFactor * scalePosOwner * osy;
|
||||
var lz = pa.values[j * pa.size + 2] * normFactor * scalePosOwner * osz;
|
||||
tmpV4.x = lx; tmpV4.y = ly; tmpV4.z = lz; tmpV4.w = 1;
|
||||
tmpV4.applyQuat(ownerRot);
|
||||
var o = idx * 3;
|
||||
instancedData.set(o , (tmpV4.x + ownerLoc.x) * base);
|
||||
instancedData.set(o + 1, (tmpV4.y + ownerLoc.y) * base);
|
||||
instancedData.set(o + 2, (tmpV4.z + ownerLoc.z) * base);
|
||||
}
|
||||
|
||||
case 1: // Face
|
||||
var positions = owner.data.geom.positions.values;
|
||||
var osx1 = owner.transform.scale.x;
|
||||
var osy1 = owner.transform.scale.y;
|
||||
var osz1 = owner.transform.scale.z;
|
||||
for (idx in lastSpawnedCount...targetCount) {
|
||||
var ia = owner.data.geom.indices[Std.random(owner.data.geom.indices.length)];
|
||||
var faceIndex = Std.random(Std.int(ia.length / 3));
|
||||
var i0 = ia[faceIndex * 3 + 0];
|
||||
var i1 = ia[faceIndex * 3 + 1];
|
||||
var i2 = ia[faceIndex * 3 + 2];
|
||||
var v0x = positions[i0 * 4 ], v0y = positions[i0 * 4 + 1], v0z = positions[i0 * 4 + 2];
|
||||
var v1x = positions[i1 * 4 ], v1y = positions[i1 * 4 + 1], v1z = positions[i1 * 4 + 2];
|
||||
var v2x = positions[i2 * 4 ], v2y = positions[i2 * 4 + 1], v2z = positions[i2 * 4 + 2];
|
||||
var rx = Math.random(); var ry = Math.random(); if (rx + ry > 1) { rx = 1 - rx; ry = 1 - ry; }
|
||||
var pxs = v0x + rx * (v1x - v0x) + ry * (v2x - v0x);
|
||||
var pys = v0y + rx * (v1y - v0y) + ry * (v2y - v0y);
|
||||
var pzs = v0z + rx * (v1z - v0z) + ry * (v2z - v0z);
|
||||
var px = pxs * normFactor * scalePosOwner * osx1;
|
||||
var py = pys * normFactor * scalePosOwner * osy1;
|
||||
var pz = pzs * normFactor * scalePosOwner * osz1;
|
||||
tmpV4.x = px; tmpV4.y = py; tmpV4.z = pz; tmpV4.w = 1;
|
||||
tmpV4.applyQuat(ownerRot);
|
||||
var o1 = idx * 3;
|
||||
instancedData.set(o1 , (tmpV4.x + ownerLoc.x) * base);
|
||||
instancedData.set(o1 + 1, (tmpV4.y + ownerLoc.y) * base);
|
||||
instancedData.set(o1 + 2, (tmpV4.z + ownerLoc.z) * base);
|
||||
}
|
||||
|
||||
case 2: // Volume
|
||||
var dim = object.transform.dim;
|
||||
for (idx in lastSpawnedCount...targetCount) {
|
||||
tmpV4.x = (Math.random() * 2.0 - 1.0) * (dim.x * 0.5);
|
||||
tmpV4.y = (Math.random() * 2.0 - 1.0) * (dim.y * 0.5);
|
||||
tmpV4.z = (Math.random() * 2.0 - 1.0) * (dim.z * 0.5);
|
||||
tmpV4.w = 1;
|
||||
tmpV4.applyQuat(ownerRot);
|
||||
var o2 = idx * 3;
|
||||
instancedData.set(o2 , (tmpV4.x + ownerLoc.x) * base);
|
||||
instancedData.set(o2 + 1, (tmpV4.y + ownerLoc.y) * base);
|
||||
instancedData.set(o2 + 2, (tmpV4.z + ownerLoc.z) * base);
|
||||
}
|
||||
}
|
||||
|
||||
// Upload full active range [0..targetCount) to this object's instanced VB
|
||||
var geom = object.data.geom;
|
||||
if (geom.instancedVB == null) {
|
||||
geom.setupInstanced(instancedData, 1, Usage.DynamicUsage);
|
||||
}
|
||||
var vb = geom.instancedVB.lock();
|
||||
var totalFloats = targetCount * 3; // xyz per instance
|
||||
var i = 0;
|
||||
while (i < totalFloats) {
|
||||
vb.setFloat32(i * 4, instancedData[i]);
|
||||
i++;
|
||||
}
|
||||
geom.instancedVB.unlock();
|
||||
geom.instanceCount = targetCount;
|
||||
lastSpawnedCount = targetCount;
|
||||
}
|
||||
|
||||
inline function fhash(n: Int): Float {
|
||||
var s = n + 1.0;
|
||||
s *= 9301.0 % s;
|
||||
s = (s * 9301.0 + 49297.0) % 233280.0;
|
||||
return s / 233280.0;
|
||||
}
|
||||
|
||||
public function remove() {}
|
||||
|
||||
/**
|
||||
Generates a random point in the triangle with vertex positions abc.
|
||||
|
||||
|
||||
Please note that the given position vectors are changed in-place by this
|
||||
function and can be considered garbage afterwards, so make sure to clone
|
||||
them first if needed.
|
||||
|
||||
Reference in New Issue
Block a user