forked from LeenkxTeam/LNXSDK
merge upstream
This commit is contained in:
@ -331,15 +331,18 @@ class RenderPath {
|
||||
});
|
||||
}
|
||||
|
||||
public static function sortMeshesShader(meshes: Array<MeshObject>) {
|
||||
public static function sortMeshesIndex(meshes: Array<MeshObject>) {
|
||||
meshes.sort(function(a, b): Int {
|
||||
#if rp_depth_texture
|
||||
var depthDiff = boolToInt(a.depthRead) - boolToInt(b.depthRead);
|
||||
if (depthDiff != 0) return depthDiff;
|
||||
#end
|
||||
|
||||
return a.materials[0].name >= b.materials[0].name ? 1 : -1;
|
||||
});
|
||||
if (a.data.sortingIndex != b.data.sortingIndex) {
|
||||
return a.data.sortingIndex > b.data.sortingIndex ? 1 : -1;
|
||||
}
|
||||
|
||||
return a.data.name >= b.data.name ? 1 : -1; });
|
||||
}
|
||||
|
||||
public function drawMeshes(context: String) {
|
||||
@ -399,7 +402,7 @@ class RenderPath {
|
||||
#if lnx_batch
|
||||
sortMeshesDistance(Scene.active.meshBatch.nonBatched);
|
||||
#else
|
||||
drawOrder == DrawOrder.Shader ? sortMeshesShader(meshes) : sortMeshesDistance(meshes);
|
||||
drawOrder == DrawOrder.Index ? sortMeshesIndex(meshes) : sortMeshesDistance(meshes);
|
||||
#end
|
||||
meshesSorted = true;
|
||||
}
|
||||
@ -914,6 +917,6 @@ class CachedShaderContext {
|
||||
|
||||
@:enum abstract DrawOrder(Int) from Int {
|
||||
var Distance = 0; // Early-z
|
||||
var Shader = 1; // Less state changes
|
||||
var Index = 1; // Less state changes
|
||||
// var Mix = 2; // Distance buckets sorted by shader
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@ import iron.data.SceneFormat;
|
||||
class MeshData {
|
||||
|
||||
public var name: String;
|
||||
public var sortingIndex: Int;
|
||||
public var raw: TMeshData;
|
||||
public var format: TSceneFormat;
|
||||
public var geom: Geometry;
|
||||
@ -23,7 +24,8 @@ class MeshData {
|
||||
public function new(raw: TMeshData, done: MeshData->Void) {
|
||||
this.raw = raw;
|
||||
this.name = raw.name;
|
||||
|
||||
this.sortingIndex = raw.sorting_index;
|
||||
|
||||
if (raw.scale_pos != null) scalePos = raw.scale_pos;
|
||||
if (raw.scale_tex != null) scaleTex = raw.scale_tex;
|
||||
|
||||
|
||||
@ -49,6 +49,7 @@ typedef TMeshData = {
|
||||
@:structInit class TMeshData {
|
||||
#end
|
||||
public var name: String;
|
||||
public var sorting_index: Int;
|
||||
public var vertex_arrays: Array<TVertexArray>;
|
||||
public var index_arrays: Array<TIndexArray>;
|
||||
@:optional public var dynamic_usage: Null<Bool>;
|
||||
@ -222,6 +223,7 @@ typedef TShaderData = {
|
||||
@:structInit class TShaderData {
|
||||
#end
|
||||
public var name: String;
|
||||
public var next_pass: String;
|
||||
public var contexts: Array<TShaderContext>;
|
||||
}
|
||||
|
||||
@ -393,6 +395,7 @@ typedef TParticleData = {
|
||||
public var name: String;
|
||||
public var type: Int; // 0 - Emitter, Hair
|
||||
public var auto_start: Bool;
|
||||
public var dynamic_emitter: Bool;
|
||||
public var is_unique: Bool;
|
||||
public var loop: Bool;
|
||||
public var count: Int;
|
||||
|
||||
@ -22,6 +22,7 @@ using StringTools;
|
||||
class ShaderData {
|
||||
|
||||
public var name: String;
|
||||
public var nextPass: String;
|
||||
public var raw: TShaderData;
|
||||
public var contexts: Array<ShaderContext> = [];
|
||||
|
||||
@ -33,6 +34,7 @@ class ShaderData {
|
||||
public function new(raw: TShaderData, done: ShaderData->Void, overrideContext: TShaderOverride = null) {
|
||||
this.raw = raw;
|
||||
this.name = raw.name;
|
||||
this.nextPass = raw.next_pass;
|
||||
|
||||
for (c in raw.contexts) contexts.push(null);
|
||||
var contextsLoaded = 0;
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -39,11 +39,11 @@ class Time {
|
||||
}
|
||||
|
||||
public static inline function time(): Float {
|
||||
return kha.Scheduler.time();
|
||||
return kha.Scheduler.time() * scale;
|
||||
}
|
||||
|
||||
public static inline function realTime(): Float {
|
||||
return kha.Scheduler.realTime();
|
||||
return kha.Scheduler.realTime() * scale;
|
||||
}
|
||||
|
||||
public static function update() {
|
||||
|
||||
@ -94,34 +94,34 @@ class Tween {
|
||||
|
||||
// Way too much Reflect trickery..
|
||||
var ps = Reflect.fields(a.props);
|
||||
for (i in 0...ps.length) {
|
||||
var p = ps[i];
|
||||
for (j in 0...ps.length) {
|
||||
var p = ps[j];
|
||||
var k = a._time / a.duration;
|
||||
if (k > 1) k = 1;
|
||||
|
||||
if (a._comps[i] == 1) {
|
||||
var fromVal: Float = a._x[i];
|
||||
if (a._comps[j] == 1) {
|
||||
var fromVal: Float = a._x[j];
|
||||
var toVal: Float = Reflect.getProperty(a.props, p);
|
||||
var val: Float = fromVal + (toVal - fromVal) * eases[a.ease](k);
|
||||
Reflect.setProperty(a.target, p, val);
|
||||
}
|
||||
else { // _comps[i] == 4
|
||||
else { // _comps[j] == 4
|
||||
var obj = Reflect.getProperty(a.props, p);
|
||||
var toX: Float = Reflect.getProperty(obj, "x");
|
||||
var toY: Float = Reflect.getProperty(obj, "y");
|
||||
var toZ: Float = Reflect.getProperty(obj, "z");
|
||||
var toW: Float = Reflect.getProperty(obj, "w");
|
||||
if (a._normalize[i]) {
|
||||
var qdot = (a._x[i] * toX) + (a._y[i] * toY) + (a._z[i] * toZ) + (a._w[i] * toW);
|
||||
if (a._normalize[j]) {
|
||||
var qdot = (a._x[j] * toX) + (a._y[j] * toY) + (a._z[j] * toZ) + (a._w[j] * toW);
|
||||
if (qdot < 0.0) {
|
||||
toX = -toX; toY = -toY; toZ = -toZ; toW = -toW;
|
||||
}
|
||||
}
|
||||
var x: Float = a._x[i] + (toX - a._x[i]) * eases[a.ease](k);
|
||||
var y: Float = a._y[i] + (toY - a._y[i]) * eases[a.ease](k);
|
||||
var z: Float = a._z[i] + (toZ - a._z[i]) * eases[a.ease](k);
|
||||
var w: Float = a._w[i] + (toW - a._w[i]) * eases[a.ease](k);
|
||||
if (a._normalize[i]) {
|
||||
var x: Float = a._x[j] + (toX - a._x[j]) * eases[a.ease](k);
|
||||
var y: Float = a._y[j] + (toY - a._y[j]) * eases[a.ease](k);
|
||||
var z: Float = a._z[j] + (toZ - a._z[j]) * eases[a.ease](k);
|
||||
var w: Float = a._w[j] + (toW - a._w[j]) * eases[a.ease](k);
|
||||
if (a._normalize[j]) {
|
||||
var l = Math.sqrt(x * x + y * y + z * z + w * w);
|
||||
if (l > 0.0) {
|
||||
l = 1.0 / l;
|
||||
|
||||
41
leenkx/Sources/leenkx/logicnode/ProbabilisticIndexNode.hx
Normal file
41
leenkx/Sources/leenkx/logicnode/ProbabilisticIndexNode.hx
Normal file
@ -0,0 +1,41 @@
|
||||
package leenkx.logicnode;
|
||||
|
||||
class ProbabilisticIndexNode extends LogicNode {
|
||||
|
||||
public function new(tree: LogicTree) {
|
||||
super(tree);
|
||||
}
|
||||
|
||||
override function get(from: Int): Dynamic {
|
||||
|
||||
var probs: Array<Float> = [];
|
||||
var probs_acum: Array<Float> = [];
|
||||
var sum: Float = 0;
|
||||
|
||||
for (p in 0...inputs.length){
|
||||
probs.push(inputs[p].get());
|
||||
sum += probs[p];
|
||||
}
|
||||
|
||||
if (sum > 1){
|
||||
for (p in 0...probs.length)
|
||||
probs[p] /= sum;
|
||||
}
|
||||
|
||||
sum = 0;
|
||||
for (p in 0...probs.length){
|
||||
sum += probs[p];
|
||||
probs_acum.push(sum);
|
||||
}
|
||||
|
||||
var rand: Float = Math.random();
|
||||
|
||||
for (p in 0...probs.length){
|
||||
if (p == 0 && rand <= probs_acum[p]) return p;
|
||||
else if (0 < p && p < probs.length-1 && probs_acum[p-1] < rand && rand <= probs_acum[p]) return p;
|
||||
else if (p == probs.length-1 && probs_acum[p-1] < rand) return p;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,7 @@
|
||||
package leenkx.logicnode;
|
||||
|
||||
import iron.data.SceneFormat;
|
||||
|
||||
class SetWorldNode extends LogicNode {
|
||||
|
||||
public function new(tree: LogicTree) {
|
||||
@ -10,25 +12,6 @@ class SetWorldNode extends LogicNode {
|
||||
var world: String = inputs[1].get();
|
||||
|
||||
if (world != null){
|
||||
|
||||
//check if world shader data exists
|
||||
var file: String = 'World_'+world+'_data';
|
||||
#if lnx_json
|
||||
file += ".json";
|
||||
#elseif lnx_compress
|
||||
file += ".lz4";
|
||||
#else
|
||||
file += '.lnx';
|
||||
#end
|
||||
|
||||
var exists: Bool = false;
|
||||
|
||||
iron.data.Data.getBlob(file, function(b: kha.Blob) {
|
||||
if (b != null) exists = true;
|
||||
});
|
||||
|
||||
assert(Error, exists == true, "World must be either associated to a scene or have fake user");
|
||||
|
||||
iron.Scene.active.raw.world_ref = world;
|
||||
var npath = leenkx.renderpath.RenderPathCreator.get();
|
||||
npath.loadShader("shader_datas/World_" + world + "/World_" + world);
|
||||
|
||||
@ -672,18 +672,20 @@ class RenderPathForward {
|
||||
var framebuffer = "";
|
||||
#end
|
||||
|
||||
#if ((rp_antialiasing == "Off") || (rp_antialiasing == "FXAA"))
|
||||
RenderPathCreator.finalTarget = path.currentTarget;
|
||||
|
||||
var target = "";
|
||||
#if ((rp_antialiasing == "Off") || (rp_antialiasing == "FXAA") || (!rp_render_to_texture))
|
||||
{
|
||||
RenderPathCreator.finalTarget = path.currentTarget;
|
||||
path.setTarget(framebuffer);
|
||||
target = framebuffer;
|
||||
}
|
||||
#else
|
||||
{
|
||||
path.setTarget("buf");
|
||||
RenderPathCreator.finalTarget = path.currentTarget;
|
||||
target = "buf";
|
||||
}
|
||||
#end
|
||||
|
||||
path.setTarget(target);
|
||||
|
||||
#if rp_compositordepth
|
||||
{
|
||||
path.bindTarget("_main", "gbufferD");
|
||||
@ -702,6 +704,15 @@ class RenderPathForward {
|
||||
}
|
||||
#end
|
||||
|
||||
#if rp_overlays
|
||||
{
|
||||
path.setTarget(target);
|
||||
path.clearTarget(null, 1.0);
|
||||
path.drawMeshes("overlay");
|
||||
}
|
||||
#end
|
||||
|
||||
|
||||
#if ((rp_antialiasing == "SMAA") || (rp_antialiasing == "TAA"))
|
||||
{
|
||||
path.setTarget("bufa");
|
||||
@ -732,12 +743,6 @@ class RenderPathForward {
|
||||
}
|
||||
#end
|
||||
|
||||
#if rp_overlays
|
||||
{
|
||||
path.clearTarget(null, 1.0);
|
||||
path.drawMeshes("overlay");
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
public static function setupDepthTexture() {
|
||||
|
||||
@ -3,33 +3,35 @@ package leenkx.system;
|
||||
import haxe.Constraints.Function;
|
||||
|
||||
class Signal {
|
||||
var callbacks:Array<Function> = [];
|
||||
var callbacks: Array<Function> = [];
|
||||
|
||||
public function new() {
|
||||
|
||||
|
||||
}
|
||||
|
||||
public function connect(callback:Function) {
|
||||
public function connect(callback: Function) {
|
||||
if (!callbacks.contains(callback)) callbacks.push(callback);
|
||||
}
|
||||
|
||||
public function disconnect(callback:Function) {
|
||||
public function disconnect(callback: Function) {
|
||||
if (callbacks.contains(callback)) callbacks.remove(callback);
|
||||
}
|
||||
|
||||
public function emit(...args:Any) {
|
||||
for (callback in callbacks) Reflect.callMethod(this, callback, args);
|
||||
public function emit(...args: Any) {
|
||||
for (callback in callbacks.copy()) {
|
||||
if (callbacks.contains(callback)) Reflect.callMethod(null, callback, args);
|
||||
}
|
||||
}
|
||||
|
||||
public function getConnections():Array<Function> {
|
||||
public function getConnections(): Array<Function> {
|
||||
return callbacks;
|
||||
}
|
||||
|
||||
public function isConnected(callBack:Function):Bool {
|
||||
public function isConnected(callBack: Function):Bool {
|
||||
return callbacks.contains(callBack);
|
||||
}
|
||||
|
||||
public function isNull():Bool {
|
||||
public function isNull(): Bool {
|
||||
return callbacks.length == 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -61,7 +61,7 @@ class Starter {
|
||||
iron.Scene.getRenderPath = getRenderPath;
|
||||
#end
|
||||
#if lnx_draworder_shader
|
||||
iron.RenderPath.active.drawOrder = iron.RenderPath.DrawOrder.Shader;
|
||||
iron.RenderPath.active.drawOrder = iron.RenderPath.DrawOrder.Index;
|
||||
#end // else Distance
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,87 +1,243 @@
|
||||
package leenkx.trait;
|
||||
|
||||
import iron.Trait;
|
||||
import iron.math.Vec4;
|
||||
import iron.system.Input;
|
||||
import iron.object.Object;
|
||||
import iron.object.CameraObject;
|
||||
import leenkx.trait.physics.PhysicsWorld;
|
||||
import leenkx.trait.internal.CameraController;
|
||||
import leenkx.trait.physics.RigidBody;
|
||||
import kha.FastFloat;
|
||||
|
||||
class FirstPersonController extends CameraController {
|
||||
class FirstPersonController extends Trait {
|
||||
|
||||
#if (!lnx_physics)
|
||||
public function new() { super(); }
|
||||
#else
|
||||
#if (!lnx_physics)
|
||||
public function new() { super(); }
|
||||
#else
|
||||
|
||||
var head: Object;
|
||||
static inline var rotationSpeed = 2.0;
|
||||
@prop public var rotationSpeed:Float = 0.15;
|
||||
@prop public var maxPitch:Float = 2.2;
|
||||
@prop public var minPitch:Float = 0.5;
|
||||
@prop public var enableJump:Bool = true;
|
||||
@prop public var jumpForce:Float = 22.0;
|
||||
@prop public var moveSpeed:Float = 500.0;
|
||||
|
||||
public function new() {
|
||||
super();
|
||||
@prop public var forwardKey:String = "w";
|
||||
@prop public var backwardKey:String = "s";
|
||||
@prop public var leftKey:String = "a";
|
||||
@prop public var rightKey:String = "d";
|
||||
@prop public var jumpKey:String = "space";
|
||||
|
||||
iron.Scene.active.notifyOnInit(init);
|
||||
}
|
||||
@prop public var allowAirJump:Bool = false;
|
||||
|
||||
function init() {
|
||||
head = object.getChildOfType(CameraObject);
|
||||
@prop public var canRun:Bool = true;
|
||||
@prop public var runKey:String = "shift";
|
||||
@prop public var runSpeed:Float = 1000.0;
|
||||
|
||||
PhysicsWorld.active.notifyOnPreUpdate(preUpdate);
|
||||
notifyOnUpdate(update);
|
||||
notifyOnRemove(removed);
|
||||
}
|
||||
// Sistema de estamina
|
||||
@prop public var stamina:Bool = false;
|
||||
@prop public var staminaBase:Float = 75.0;
|
||||
@prop public var staRecoverPerSec:Float = 5.0;
|
||||
@prop public var staDecreasePerSec:Float = 5.0;
|
||||
@prop public var staRecoverTime:Float = 2.0;
|
||||
@prop public var staDecreasePerJump:Float = 5.0;
|
||||
@prop public var enableFatigue:Bool = false;
|
||||
@prop public var fatigueSpeed:Float = 0.5; // the reduction of movement when fatigue is activated...
|
||||
@prop public var fatigueThreshold:Float = 30.0; // Tiempo corriendo sin parar para la activacion // Time running non-stop for activation...
|
||||
@prop public var fatRecoveryThreshold:Float = 7.5; // Tiempo sin correr/saltar para salir de fatiga // Time without running/jumping to get rid of fatigue...
|
||||
|
||||
|
||||
var xVec = Vec4.xAxis();
|
||||
var zVec = Vec4.zAxis();
|
||||
function preUpdate() {
|
||||
if (Input.occupied || !body.ready) return;
|
||||
// Var Privadas
|
||||
var head:CameraObject;
|
||||
var pitch:Float = 0.0;
|
||||
var body:RigidBody;
|
||||
|
||||
var mouse = Input.getMouse();
|
||||
var kb = Input.getKeyboard();
|
||||
var moveForward:Bool = false;
|
||||
var moveBackward:Bool = false;
|
||||
var moveLeft:Bool = false;
|
||||
var moveRight:Bool = false;
|
||||
var isRunning:Bool = false;
|
||||
|
||||
if (mouse.started() && !mouse.locked) mouse.lock();
|
||||
else if (kb.started("escape") && mouse.locked) mouse.unlock();
|
||||
var canJump:Bool = true;
|
||||
var staminaValue:Float = 0.0;
|
||||
var timeSinceStop:Float = 0.0;
|
||||
|
||||
if (mouse.locked || mouse.down()) {
|
||||
head.transform.rotate(xVec, -mouse.movementY / 250 * rotationSpeed);
|
||||
transform.rotate(zVec, -mouse.movementX / 250 * rotationSpeed);
|
||||
body.syncTransform();
|
||||
var fatigueTimer:Float = 0.0;
|
||||
var fatigueCooldown:Float = 0.0;
|
||||
var isFatigueActive:Bool = false;
|
||||
|
||||
public function new() {
|
||||
super();
|
||||
iron.Scene.active.notifyOnInit(init);
|
||||
}
|
||||
|
||||
function init() {
|
||||
body = object.getTrait(RigidBody);
|
||||
head = object.getChildOfType(CameraObject);
|
||||
PhysicsWorld.active.notifyOnPreUpdate(preUpdate);
|
||||
notifyOnUpdate(update);
|
||||
notifyOnRemove(removed);
|
||||
staminaValue = staminaBase;
|
||||
}
|
||||
|
||||
function removed() {
|
||||
PhysicsWorld.active.removePreUpdate(preUpdate);
|
||||
}
|
||||
|
||||
var zVec = Vec4.zAxis();
|
||||
|
||||
function preUpdate() {
|
||||
if (Input.occupied || body == null) return;
|
||||
var mouse = Input.getMouse();
|
||||
var kb = Input.getKeyboard();
|
||||
|
||||
if (mouse.started() && !mouse.locked)
|
||||
mouse.lock();
|
||||
else if (kb.started("escape") && mouse.locked)
|
||||
mouse.unlock();
|
||||
|
||||
if (mouse.locked || mouse.down()) {
|
||||
var deltaTime:Float = iron.system.Time.delta;
|
||||
object.transform.rotate(zVec, -mouse.movementX * rotationSpeed * deltaTime);
|
||||
var deltaPitch:Float = -(mouse.movementY * rotationSpeed * deltaTime);
|
||||
pitch += deltaPitch;
|
||||
pitch = Math.max(minPitch, Math.min(maxPitch, pitch));
|
||||
head.transform.setRotation(pitch, 0.0, 0.0);
|
||||
body.syncTransform();
|
||||
}
|
||||
}
|
||||
|
||||
var dir:Vec4 = new Vec4();
|
||||
|
||||
function isFatigued():Bool {
|
||||
return enableFatigue && isFatigueActive;
|
||||
}
|
||||
|
||||
function update() {
|
||||
if (body == null) return;
|
||||
var deltaTime:Float = iron.system.Time.delta;
|
||||
var kb = Input.getKeyboard();
|
||||
|
||||
moveForward = kb.down(forwardKey);
|
||||
moveBackward = kb.down(backwardKey);
|
||||
moveLeft = kb.down(leftKey);
|
||||
moveRight = kb.down(rightKey);
|
||||
var isMoving = moveForward || moveBackward || moveLeft || moveRight;
|
||||
|
||||
var isGrounded:Bool = false;
|
||||
#if lnx_physics
|
||||
var vel = body.getLinearVelocity();
|
||||
if (Math.abs(vel.z) < 0.1) {
|
||||
isGrounded = true;
|
||||
}
|
||||
#end
|
||||
|
||||
// Dejo establecido el salto para tener en cuenta la (enableFatigue) si es que es false/true....
|
||||
if (isGrounded && !isFatigued()) {
|
||||
canJump = true;
|
||||
}
|
||||
}
|
||||
// Saltar con estamina
|
||||
if (enableJump && kb.started(jumpKey) && canJump) {
|
||||
var jumpPower = jumpForce;
|
||||
// Disminuir el salto al 50% si la (stamina) esta por debajo o en el 20%.
|
||||
if (stamina) {
|
||||
if (staminaValue <= 0) {
|
||||
jumpPower = 0;
|
||||
} else if (staminaValue <= staminaBase * 0.2) {
|
||||
jumpPower *= 0.5;
|
||||
}
|
||||
|
||||
function removed() {
|
||||
PhysicsWorld.active.removePreUpdate(preUpdate);
|
||||
}
|
||||
staminaValue -= staDecreasePerJump;
|
||||
if (staminaValue < 0.0) staminaValue = 0.0;
|
||||
timeSinceStop = 0.0;
|
||||
}
|
||||
|
||||
var dir = new Vec4();
|
||||
function update() {
|
||||
if (!body.ready) return;
|
||||
if (jumpPower > 0) {
|
||||
body.applyImpulse(new Vec4(0, 0, jumpPower));
|
||||
if (!allowAirJump) canJump = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (jump) {
|
||||
body.applyImpulse(new Vec4(0, 0, 16));
|
||||
jump = false;
|
||||
// Control de estamina y correr
|
||||
if (canRun && kb.down(runKey) && isMoving) {
|
||||
if (stamina) {
|
||||
if (staminaValue > 0.0) {
|
||||
isRunning = true;
|
||||
staminaValue -= staDecreasePerSec * deltaTime;
|
||||
if (staminaValue < 0.0) staminaValue = 0.0;
|
||||
} else {
|
||||
isRunning = false;
|
||||
}
|
||||
} else {
|
||||
isRunning = true;
|
||||
}
|
||||
} else {
|
||||
isRunning = false;
|
||||
}
|
||||
|
||||
// (temporizadores aparte)
|
||||
if (isRunning) {
|
||||
timeSinceStop = 0.0;
|
||||
fatigueTimer += deltaTime;
|
||||
fatigueCooldown = 0.0;
|
||||
} else {
|
||||
timeSinceStop += deltaTime;
|
||||
fatigueCooldown += deltaTime;
|
||||
}
|
||||
|
||||
// Evitar correr y saltar al estar fatigado...
|
||||
if (isFatigued()) {
|
||||
isRunning = false;
|
||||
canJump = false;
|
||||
}
|
||||
|
||||
// Move
|
||||
dir.set(0, 0, 0);
|
||||
if (moveForward) dir.add(transform.look());
|
||||
if (moveBackward) dir.add(transform.look().mult(-1));
|
||||
if (moveLeft) dir.add(transform.right().mult(-1));
|
||||
if (moveRight) dir.add(transform.right());
|
||||
// Activar fatiga despues de correr continuamente durante cierto umbral
|
||||
if (enableFatigue && fatigueTimer >= fatigueThreshold) {
|
||||
isFatigueActive = true;
|
||||
}
|
||||
|
||||
// Push down
|
||||
var btvec = body.getLinearVelocity();
|
||||
body.setLinearVelocity(0.0, 0.0, btvec.z - 1.0);
|
||||
// Eliminar la fatiga despues de recuperarse
|
||||
if (enableFatigue && isFatigueActive && fatigueCooldown >= fatRecoveryThreshold) {
|
||||
isFatigueActive = false;
|
||||
fatigueTimer = 0.0;
|
||||
}
|
||||
|
||||
if (moveForward || moveBackward || moveLeft || moveRight) {
|
||||
var dirN = dir.normalize();
|
||||
dirN.mult(6);
|
||||
body.activate();
|
||||
body.setLinearVelocity(dirN.x, dirN.y, btvec.z - 1.0);
|
||||
}
|
||||
// Recuperar estamina si no esta corriendo
|
||||
if (stamina && !isRunning && staminaValue < staminaBase && !isFatigued()) {
|
||||
if (timeSinceStop >= staRecoverTime) {
|
||||
staminaValue += staRecoverPerSec * deltaTime;
|
||||
if (staminaValue > staminaBase) staminaValue = staminaBase;
|
||||
}
|
||||
}
|
||||
|
||||
// Keep vertical
|
||||
body.setAngularFactor(0, 0, 0);
|
||||
camera.buildMatrix();
|
||||
}
|
||||
#end
|
||||
// Movimiento ejes (local)
|
||||
dir.set(0, 0, 0);
|
||||
if (moveForward) dir.add(object.transform.look());
|
||||
if (moveBackward) dir.add(object.transform.look().mult(-1));
|
||||
if (moveLeft) dir.add(object.transform.right().mult(-1));
|
||||
if (moveRight) dir.add(object.transform.right());
|
||||
|
||||
var btvec = body.getLinearVelocity();
|
||||
body.setLinearVelocity(0.0, 0.0, btvec.z - 1.0);
|
||||
|
||||
if (isMoving) {
|
||||
var dirN = dir.normalize();
|
||||
var baseSpeed = moveSpeed;
|
||||
if (isRunning && moveForward) {
|
||||
baseSpeed = runSpeed;
|
||||
}
|
||||
var currentSpeed = isFatigued() ? baseSpeed * fatigueSpeed : baseSpeed;
|
||||
dirN.mult(currentSpeed * deltaTime);
|
||||
body.activate();
|
||||
body.setLinearVelocity(dirN.x, dirN.y, btvec.z - 1.0);
|
||||
}
|
||||
|
||||
body.setAngularFactor(0, 0, 0);
|
||||
head.buildMatrix();
|
||||
}
|
||||
|
||||
#end
|
||||
}
|
||||
|
||||
|
||||
// Stamina and fatigue system.....
|
||||
Reference in New Issue
Block a user