forked from LeenkxTeam/LNXSDK
		
	
		
			
				
	
	
		
			461 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Haxe
		
	
	
	
	
	
			
		
		
	
	
			461 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Haxe
		
	
	
	
	
	
package iron.object;
 | 
						|
 | 
						|
#if lnx_particles
 | 
						|
 | 
						|
import kha.FastFloat;
 | 
						|
import kha.graphics4.Usage;
 | 
						|
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;
 | 
						|
import iron.math.Vec3;
 | 
						|
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;
 | 
						|
	var frameRate = 24;
 | 
						|
	var lifetime = 0.0;
 | 
						|
	var looptime = 0.0;
 | 
						|
	var animtime = 0.0;
 | 
						|
	var time = 0.0;
 | 
						|
	var spawnRate = 0.0;
 | 
						|
	var seed = 0;
 | 
						|
 | 
						|
	var r: TParticleData;
 | 
						|
	var gx: Float;
 | 
						|
	var gy: Float;
 | 
						|
	var gz: Float;
 | 
						|
	var alignx: Float;
 | 
						|
	var aligny: Float;
 | 
						|
	var alignz: Float;
 | 
						|
	var dimx: Float;
 | 
						|
	var dimy: Float;
 | 
						|
	var tilesx: Int;
 | 
						|
	var tilesy: Int;
 | 
						|
	var tilesFramerate: Int;
 | 
						|
 | 
						|
	var count = 0;
 | 
						|
	var lap = 0;
 | 
						|
	var lapTime = 0.0;
 | 
						|
	var m = Mat4.identity();
 | 
						|
 | 
						|
	var ownerLoc = new Vec4();
 | 
						|
	var ownerRot = new Quat();
 | 
						|
	var ownerScl = new Vec4();
 | 
						|
	
 | 
						|
	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;
 | 
						|
		speed = 0;
 | 
						|
		particles = [];
 | 
						|
		ready = false;
 | 
						|
		
 | 
						|
		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;
 | 
						|
				gz = Scene.active.raw.gravity[2] * r.weight_gravity;
 | 
						|
			}
 | 
						|
			else {
 | 
						|
				gx = 0;
 | 
						|
				gy = 0;
 | 
						|
				gz = -9.81 * r.weight_gravity;
 | 
						|
			}
 | 
						|
			alignx = r.object_align_factor[0];
 | 
						|
			aligny = r.object_align_factor[1];
 | 
						|
			alignz = r.object_align_factor[2];
 | 
						|
			looptime = (r.frame_end - r.frame_start) / frameRate;
 | 
						|
			lifetime = r.lifetime / frameRate;
 | 
						|
			animtime = r.loop ? looptime : looptime + lifetime;
 | 
						|
			spawnRate = ((r.frame_end - r.frame_start) / r.count) / frameRate;
 | 
						|
 | 
						|
			for (i in 0...r.count) {
 | 
						|
				particles.push(new Particle(i));
 | 
						|
			}
 | 
						|
 | 
						|
			ready = true;
 | 
						|
			if (r.auto_start){
 | 
						|
				start();
 | 
						|
			}
 | 
						|
		});
 | 
						|
	}
 | 
						|
 | 
						|
	public function start() {
 | 
						|
		if (r.is_unique) random = Math.random();
 | 
						|
		lifetime = r.lifetime / frameRate;
 | 
						|
		time = 0;
 | 
						|
		lap = 0;
 | 
						|
		lapTime = 0;
 | 
						|
		speed = currentSpeed;
 | 
						|
		lastSpawnedCount = 0;
 | 
						|
		instancedData = null;
 | 
						|
	}
 | 
						|
 | 
						|
	public function pause() {
 | 
						|
		speed = 0;
 | 
						|
	}
 | 
						|
 | 
						|
	public function resume() {
 | 
						|
		lifetime = r.lifetime / frameRate;
 | 
						|
		speed = currentSpeed;
 | 
						|
	}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
	// TODO: interrupt smoothly
 | 
						|
	public function stop() {
 | 
						|
		end();
 | 
						|
	}
 | 
						|
 | 
						|
	function end() {
 | 
						|
		lifetime = 0;
 | 
						|
		speed = 0;
 | 
						|
		lap = 0;
 | 
						|
	}
 | 
						|
	
 | 
						|
	public function update(object: MeshObject, owner: MeshObject) {
 | 
						|
		if (!ready || object == null || speed == 0.0) return;
 | 
						|
		if (iron.App.pauseUpdates) return;
 | 
						|
		
 | 
						|
		var prevLap = lap;
 | 
						|
 | 
						|
		// Copy owner world transform but discard scale
 | 
						|
		owner.transform.world.decompose(ownerLoc, ownerRot, ownerScl);
 | 
						|
		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);
 | 
						|
 | 
						|
		object.transform.buildMatrix();
 | 
						|
		owner.transform.buildMatrix();
 | 
						|
		object.transform.dim.setFrom(owner.transform.dim);
 | 
						|
 | 
						|
		dimx = object.transform.dim.x;
 | 
						|
		dimy = object.transform.dim.y;
 | 
						|
 | 
						|
		if (object.activeTilesheet != null) {
 | 
						|
			tilesx = object.activeTilesheet.raw.tilesx;
 | 
						|
			tilesy = object.activeTilesheet.raw.tilesy;
 | 
						|
			tilesFramerate = object.activeTilesheet.raw.framerate;
 | 
						|
		}
 | 
						|
 | 
						|
		// Animate
 | 
						|
		time += Time.renderDelta * speed; // realDelta to renderDelta
 | 
						|
		lap = Std.int(time / animtime);
 | 
						|
		lapTime = time - lap * animtime;
 | 
						|
		count = Std.int(lapTime / spawnRate);
 | 
						|
 | 
						|
		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;
 | 
						|
		// 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;
 | 
						|
		m._10 = hair ? 0 : alignx;
 | 
						|
		m._11 = hair ? 0 : aligny;
 | 
						|
		m._12 = hair ? 0 : alignz;
 | 
						|
		m._13 = hair ? 0 : r.factor_random;
 | 
						|
		m._20 = hair ? 0 : gx;
 | 
						|
		m._21 = hair ? 0 : gy;
 | 
						|
		m._22 = hair ? 0 : gz;
 | 
						|
		m._23 = hair ? 0 : r.lifetime_random;
 | 
						|
		m._30 = tilesx;
 | 
						|
		m._31 = tilesy;
 | 
						|
		m._32 = 1 / tilesFramerate;
 | 
						|
		m._33 = hair ? 1 : lapTime;
 | 
						|
		return m;
 | 
						|
	}
 | 
						|
 | 
						|
	public function getSizeRandom(): FastFloat {
 | 
						|
		return r.size_random;
 | 
						|
	}
 | 
						|
 | 
						|
	public inline function getRandom(): FastFloat {
 | 
						|
		return random;
 | 
						|
	}
 | 
						|
 | 
						|
	public inline function getSize(): FastFloat {
 | 
						|
		return r.particle_size;
 | 
						|
	}
 | 
						|
 | 
						|
	function updateGpu(object: MeshObject, owner: MeshObject) {
 | 
						|
		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) {
 | 
						|
		var instancedData = new Float32Array(particles.length * 3);
 | 
						|
		var i = 0;
 | 
						|
 | 
						|
		var normFactor = 1 / 32767; // pa.values are not normalized
 | 
						|
		var scalePosOwner = owner.data.scalePos;
 | 
						|
		var scalePosParticle = object.data.scalePos;
 | 
						|
		var particleSize = r.particle_size;
 | 
						|
		var scaleFactor = new Vec4().setFrom(owner.transform.scale);
 | 
						|
		scaleFactor.mult(scalePosOwner / (particleSize * scalePosParticle));
 | 
						|
 | 
						|
		switch (r.emit_from) {
 | 
						|
			case 0: // Vert
 | 
						|
				var pa = owner.data.geom.positions;
 | 
						|
 | 
						|
				for (p in particles) {
 | 
						|
					var j = Std.int(fhash(i) * (pa.values.length / pa.size));
 | 
						|
					instancedData.set(i, pa.values[j * pa.size    ] * normFactor * scaleFactor.x); i++;
 | 
						|
					instancedData.set(i, pa.values[j * pa.size + 1] * normFactor * scaleFactor.y); i++;
 | 
						|
					instancedData.set(i, pa.values[j * pa.size + 2] * normFactor * scaleFactor.z); i++;
 | 
						|
				}
 | 
						|
 | 
						|
			case 1: // Face
 | 
						|
				var positions = owner.data.geom.positions.values;
 | 
						|
 | 
						|
				for (p in particles) {
 | 
						|
					// Choose random index array (there is one per material) and random face
 | 
						|
					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 v0 = new Vec3(positions[i0 * 4], positions[i0 * 4 + 1], positions[i0 * 4 + 2]);
 | 
						|
					var v1 = new Vec3(positions[i1 * 4], positions[i1 * 4 + 1], positions[i1 * 4 + 2]);
 | 
						|
					var v2 = new Vec3(positions[i2 * 4], positions[i2 * 4 + 1], positions[i2 * 4 + 2]);
 | 
						|
 | 
						|
					var pos = randomPointInTriangle(v0, v1, v2);
 | 
						|
 | 
						|
					instancedData.set(i, pos.x * normFactor * scaleFactor.x); i++;
 | 
						|
					instancedData.set(i, pos.y * normFactor * scaleFactor.y); i++;
 | 
						|
					instancedData.set(i, pos.z * normFactor * scaleFactor.z); i++;
 | 
						|
				}
 | 
						|
 | 
						|
			case 2: // Volume
 | 
						|
				var scaleFactorVolume = new Vec4().setFrom(object.transform.dim);
 | 
						|
				scaleFactorVolume.mult(0.5 / (particleSize * scalePosParticle));
 | 
						|
 | 
						|
				for (p in particles) {
 | 
						|
					instancedData.set(i, (Math.random() * 2.0 - 1.0) * scaleFactorVolume.x); i++;
 | 
						|
					instancedData.set(i, (Math.random() * 2.0 - 1.0) * scaleFactorVolume.y); i++;
 | 
						|
					instancedData.set(i, (Math.random() * 2.0 - 1.0) * scaleFactorVolume.z); i++;
 | 
						|
				}
 | 
						|
		}
 | 
						|
		object.data.geom.setupInstanced(instancedData, 1, Usage.StaticUsage);
 | 
						|
	}
 | 
						|
 | 
						|
	// 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.
 | 
						|
	**/
 | 
						|
	public static inline function randomPointInTriangle(a: Vec3, b: Vec3, c: Vec3): Vec3 {
 | 
						|
		// Generate a random point in a square where (0, 0) <= (x, y) < (1, 1)
 | 
						|
		var x = Math.random();
 | 
						|
		var y = Math.random();
 | 
						|
 | 
						|
		if (x + y > 1) {
 | 
						|
			// We're in the upper right triangle in the square, mirror to lower left
 | 
						|
			x = 1 - x;
 | 
						|
			y = 1 - y;
 | 
						|
		}
 | 
						|
 | 
						|
		// Transform the point to the triangle abc
 | 
						|
		var u = b.sub(a);
 | 
						|
		var v = c.sub(a);
 | 
						|
		return a.add(u.mult(x).add(v.mult(y)));
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
class Particle {
 | 
						|
	public var i: Int;
 | 
						|
	
 | 
						|
	public var x = 0.0;
 | 
						|
	public var y = 0.0;
 | 
						|
	public var z = 0.0;
 | 
						|
 | 
						|
	public var cameraDistance: Float;
 | 
						|
 | 
						|
	public function new(i: Int) {
 | 
						|
		this.i = i;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
#end
 |