merge upstream

This commit is contained in:
Onek8 2025-06-02 20:16:04 +00:00
commit e014484d27
65 changed files with 1590 additions and 639 deletions

View File

@ -3,6 +3,10 @@
#include "compiled.inc"
#ifdef _CPostprocess
uniform vec4 PPComp17;
#endif
uniform sampler2D tex;
uniform vec2 dir;
uniform vec2 screenSize;
@ -45,6 +49,12 @@ void main() {
res += factor * col;
}
#ifdef _CPostprocess
vec3 AirColor = vec3(PPComp17.x, PPComp17.y, PPComp17.z);
#else
vec3 AirColor = volumAirColor;
#endif
res /= sumfactor;
fragColor = vec4(volumAirColor * res, 1.0);
fragColor = vec4(AirColor * res, 1.0);
}

View File

@ -19,6 +19,11 @@
{
"name": "screenSize",
"link": "_screenSize"
},
{
"name": "PPComp17",
"link": "_PPComp17",
"ifdef": ["_CPostprocess"]
}
],
"texture_params": [],

View File

@ -5,7 +5,7 @@
uniform sampler2D tex;
#ifdef _CPostprocess
uniform vec3 PPComp13;
uniform vec4 PPComp13;
#endif
in vec2 texCoord;
@ -43,13 +43,17 @@ void main() {
#ifdef _CPostprocess
float max_distort = PPComp13.x;
int num_iter = int(PPComp13.y);
int CAType = int(PPComp13.z);
int on = int(PPComp13.w);
#else
float max_distort = compoChromaticStrength;
int num_iter = compoChromaticSamples;
int CAType = compoChromaticType;
int on = 1;
#endif
// Spectral
if (compoChromaticType == 1) {
if (CAType == 1) {
float reci_num_iter_f = 1.0 / float(num_iter);
vec2 resolution = vec2(1,1);
@ -64,7 +68,7 @@ void main() {
sumcol += w * texture(tex, barrelDistortion(uv, 0.6 * max_distort * t));
}
fragColor = sumcol / sumw;
if (on == 1) fragColor = sumcol / sumw; else fragColor = texture(tex, texCoord);
}
// Simple
@ -73,6 +77,7 @@ void main() {
col.x = texture(tex, texCoord + ((vec2(0.0, 1.0) * max_distort) / vec2(1000.0))).x;
col.y = texture(tex, texCoord + ((vec2(-0.85, -0.5) * max_distort) / vec2(1000.0))).y;
col.z = texture(tex, texCoord + ((vec2(0.85, -0.5) * max_distort) / vec2(1000.0))).z;
fragColor = vec4(col.x, col.y, col.z, fragColor.w);
if (on == 1) fragColor = vec4(col.x, col.y, col.z, fragColor.w);
else fragColor = texture(tex, texCoord);
}
}

View File

@ -62,8 +62,11 @@ uniform vec3 PPComp5;
uniform vec3 PPComp6;
uniform vec3 PPComp7;
uniform vec3 PPComp8;
uniform vec3 PPComp11;
uniform vec3 PPComp14;
uniform vec4 PPComp15;
uniform vec4 PPComp16;
uniform vec4 PPComp18;
#endif
// #ifdef _CPos
@ -106,6 +109,16 @@ in vec2 texCoord;
out vec4 fragColor;
#ifdef _CFog
#ifdef _CPostprocess
vec3 FogColor = vec3(PPComp18.x, PPComp18.y, PPComp18.z);
float FogAmountA = PPComp18.w;
float FogAmountB = PPComp11.z;
#else
vec3 FogColor = compoFogColor;
float FogAmountA = compoFogAmountA;
float FogAmountB = compoFogAmountB;
#endif
// const vec3 compoFogColor = vec3(0.5, 0.6, 0.7);
// const float compoFogAmountA = 1.0; // b = 0.01
// const float compoFogAmountB = 1.0; // c = 0.1
@ -118,8 +131,8 @@ out vec4 fragColor;
// }
vec3 applyFog(vec3 rgb, float distance) {
// float fogAmount = 1.0 - exp(-distance * compoFogAmountA);
float fogAmount = 1.0 - exp(-distance * (compoFogAmountA / 100));
return mix(rgb, compoFogColor, fogAmount);
float fogAmount = 1.0 - exp(-distance * (FogAmountA / 100));
return mix(rgb, FogColor, fogAmount);
}
#endif
@ -349,16 +362,22 @@ void main() {
#ifdef _CSharpen
#ifdef _CPostprocess
float strengthSharpen = PPComp14.y;
float strengthSharpen = PPComp14.y;
vec3 SharpenColor = vec3(PPComp16.x, PPComp16.y, PPComp16.z);
float SharpenSize = PPComp16.w;
#else
float strengthSharpen = compoSharpenStrength;
vec3 SharpenColor = compoSharpenColor;
float SharpenSize = compoSharpenSize;
#endif
vec3 col1 = textureLod(tex, texCo + vec2(-texStep.x, -texStep.y) * 1.5, 0.0).rgb;
vec3 col2 = textureLod(tex, texCo + vec2(texStep.x, -texStep.y) * 1.5, 0.0).rgb;
vec3 col3 = textureLod(tex, texCo + vec2(-texStep.x, texStep.y) * 1.5, 0.0).rgb;
vec3 col4 = textureLod(tex, texCo + vec2(texStep.x, texStep.y) * 1.5, 0.0).rgb;
vec3 col1 = textureLod(tex, texCo + vec2(-texStep.x, -texStep.y) * SharpenSize, 0.0).rgb;
vec3 col2 = textureLod(tex, texCo + vec2(texStep.x, -texStep.y) * SharpenSize, 0.0).rgb;
vec3 col3 = textureLod(tex, texCo + vec2(-texStep.x, texStep.y) * SharpenSize, 0.0).rgb;
vec3 col4 = textureLod(tex, texCo + vec2(texStep.x, texStep.y) * SharpenSize, 0.0).rgb;
vec3 colavg = (col1 + col2 + col3 + col4) * 0.25;
fragColor.rgb += (fragColor.rgb - colavg) * strengthSharpen;
float edgeMagnitude = length(fragColor.rgb - colavg);
fragColor.rgb = mix(fragColor.rgb, SharpenColor, min(edgeMagnitude * strengthSharpen * 2.0, 1.0));
#endif
#ifdef _CFog
@ -407,7 +426,11 @@ void main() {
#endif
#ifdef _CExposure
fragColor.rgb += fragColor.rgb * compoExposureStrength;
#ifdef _CPostprocess
fragColor.rgb+=fragColor.rgb*PPComp8.x;
#else
fragColor.rgb+= fragColor.rgb*compoExposureStrength;
#endif
#endif
#ifdef _CPostprocess
@ -415,8 +438,13 @@ void main() {
#endif
#ifdef _AutoExposure
#ifdef _CPostprocess
float AEStrength = PPComp8.y;
#else
float AEStrength = autoExposureStrength;
#endif
float expo = 2.0 - clamp(length(textureLod(histogram, vec2(0.5, 0.5), 0).rgb), 0.0, 1.0);
fragColor.rgb *= pow(expo, autoExposureStrength * 2.0);
fragColor.rgb *= pow(expo, AEStrength * 2.0);
#endif
// Clamp color to get rid of INF values that don't work for the tone mapping below

View File

@ -235,6 +235,16 @@
"name": "PPComp15",
"link": "_PPComp15",
"ifdef": ["_CPostprocess"]
},
{
"name": "PPComp16",
"link": "_PPComp16",
"ifdef": ["_CPostprocess"]
},
{
"name": "PPComp18",
"link": "_PPComp18",
"ifdef": ["_CPostprocess"]
}
],
"texture_params": [],

View File

@ -2,13 +2,22 @@
#include "compiled.inc"
#ifdef _CPostprocess
uniform vec3 PPComp8;
#endif
uniform sampler2D tex;
in vec2 texCoord;
out vec4 fragColor;
void main() {
fragColor.a = 0.01 * autoExposureSpeed;
#ifdef _CPostprocess
fragColor.a = 0.01 * PPComp8.z;
#else
fragColor.a = 0.01 * autoExposureSpeed;
#endif
fragColor.rgb = textureLod(tex, vec2(0.5, 0.5), 0.0).rgb +
textureLod(tex, vec2(0.2, 0.2), 0.0).rgb +
textureLod(tex, vec2(0.8, 0.2), 0.0).rgb +

View File

@ -8,7 +8,13 @@
"blend_source": "source_alpha",
"blend_destination": "inverse_source_alpha",
"blend_operation": "add",
"links": [],
"links": [
{
"name": "PPComp8",
"link": "_PPComp8",
"ifdef": ["_CPostprocess"]
}
],
"texture_params": [],
"vertex_shader": "../include/pass.vert.glsl",
"fragment_shader": "histogram_pass.frag.glsl"

View File

@ -11,6 +11,11 @@
#include "std/light_common.glsl"
#endif
#ifdef _CPostprocess
uniform vec3 PPComp11;
uniform vec4 PPComp17;
#endif
uniform sampler2D gbufferD;
uniform sampler2D snoise;
@ -87,7 +92,13 @@ out float fragColor;
const float tScat = 0.08;
const float tAbs = 0.0;
const float tExt = tScat + tAbs;
const float stepLen = 1.0 / volumSteps;
#ifdef _CPostprocess
float stepLen = 1.0 / int(PPComp11.y);
float AirTurbidity = PPComp17.w;
#else
const float stepLen = 1.0 / volumSteps;
float AirTurbidity = volumAirTurbidity;
#endif
const float lighting = 0.4;
void rayStep(inout vec3 curPos, inout float curOpticalDepth, inout float scatteredLightAmount, float stepLenWorld, vec3 viewVecNorm) {
@ -162,5 +173,5 @@ void main() {
rayStep(curPos, curOpticalDepth, scatteredLightAmount, stepLenWorld, viewVecNorm);
}
fragColor = scatteredLightAmount * volumAirTurbidity;
fragColor = scatteredLightAmount * AirTurbidity;
}

View File

@ -140,6 +140,16 @@
"link": "_biasLightWorldViewProjectionMatrixSpot3",
"ifndef": ["_ShadowMapAtlas"],
"ifdef": ["_Spot", "_ShadowMap"]
},
{
"name": "PPComp11",
"link": "_PPComp11",
"ifdef": ["_CPostprocess"]
},
{
"name": "PPComp17",
"link": "_PPComp17",
"ifdef": ["_CPostprocess"]
}
],
"texture_params": [],

View File

@ -12,6 +12,7 @@ class App {
static var traitInits: Array<Void->Void> = [];
static var traitUpdates: Array<Void->Void> = [];
static var traitLateUpdates: Array<Void->Void> = [];
static var traitFixedUpdates: Array<Void->Void> = [];
static var traitRenders: Array<kha.graphics4.Graphics->Void> = [];
static var traitRenders2D: Array<kha.graphics2.Graphics->Void> = [];
public static var framebuffer: kha.Framebuffer;
@ -23,6 +24,8 @@ class App {
public static var renderPathTime: Float;
public static var endFrameCallbacks: Array<Void->Void> = [];
#end
static var last = 0.0;
static var time = 0.0;
static var lastw = -1;
static var lasth = -1;
public static var onResize: Void->Void = null;
@ -34,13 +37,14 @@ class App {
function new(done: Void->Void) {
done();
kha.System.notifyOnFrames(render);
kha.Scheduler.addTimeTask(update, 0, iron.system.Time.delta);
kha.Scheduler.addTimeTask(update, 0, iron.system.Time.step);
}
public static function reset() {
traitInits = [];
traitUpdates = [];
traitLateUpdates = [];
traitFixedUpdates = [];
traitRenders = [];
traitRenders2D = [];
if (onResets != null) for (f in onResets) f();
@ -48,6 +52,8 @@ class App {
static function update() {
if (Scene.active == null || !Scene.active.ready) return;
iron.system.Time.update();
if (pauseUpdates) return;
#if lnx_debug
@ -56,6 +62,14 @@ class App {
Scene.active.updateFrame();
time += iron.system.Time.delta;
while (time >= iron.system.Time.fixedStep) {
for (f in traitFixedUpdates) f();
time -= iron.system.Time.fixedStep;
}
var i = 0;
var l = traitUpdates.length;
while (i < l) {
@ -106,7 +120,7 @@ class App {
var frame = frames[0];
framebuffer = frame;
iron.system.Time.update();
iron.system.Time.render();
if (Scene.active == null || !Scene.active.ready) {
render2D(frame);
@ -172,6 +186,14 @@ class App {
traitLateUpdates.remove(f);
}
public static function notifyOnFixedUpdate(f: Void->Void) {
traitFixedUpdates.push(f);
}
public static function removeFixedUpdate(f: Void->Void) {
traitFixedUpdates.remove(f);
}
public static function notifyOnRender(f: kha.graphics4.Graphics->Void) {
traitRenders.push(f);
}

View File

@ -16,6 +16,7 @@ class Trait {
var _remove: Array<Void->Void> = null;
var _update: Array<Void->Void> = null;
var _lateUpdate: Array<Void->Void> = null;
var _fixedUpdate: Array<Void->Void> = null;
var _render: Array<kha.graphics4.Graphics->Void> = null;
var _render2D: Array<kha.graphics2.Graphics->Void> = null;
@ -87,6 +88,23 @@ class Trait {
App.removeLateUpdate(f);
}
/**
Add fixed game logic handler.
**/
public function notifyOnFixedUpdate(f: Void->Void) {
if (_fixedUpdate == null) _fixedUpdate = [];
_fixedUpdate.push(f);
App.notifyOnFixedUpdate(f);
}
/**
Remove fixed game logic handler.
**/
public function removeFixedUpdate(f: Void->Void) {
_fixedUpdate.remove(f);
App.removeFixedUpdate(f);
}
/**
Add render handler.
**/

View File

@ -392,6 +392,8 @@ typedef TParticleData = {
#end
public var name: String;
public var type: Int; // 0 - Emitter, Hair
public var auto_start: Bool;
public var is_unique: Bool;
public var loop: Bool;
public var count: Int;
public var frame_start: FastFloat;

View File

@ -159,9 +159,17 @@ class Animation {
if(markerEvents.get(sampler) != null){
for (i in 0...anim.marker_frames.length) {
if (frameIndex == anim.marker_frames[i]) {
var marketAct = markerEvents.get(sampler);
var ar = marketAct.get(anim.marker_names[i]);
var markerAct = markerEvents.get(sampler);
var ar = markerAct.get(anim.marker_names[i]);
if (ar != null) for (f in ar) f();
} else {
for (j in 0...(frameIndex - lastFrameIndex)) {
if (lastFrameIndex + j + 1 == anim.marker_frames[i]) {
var markerAct = markerEvents.get(sampler);
var ar = markerAct.get(anim.marker_names[i]);
if (ar != null) for (f in ar) f();
}
}
}
}
lastFrameIndex = frameIndex;

View File

@ -172,6 +172,10 @@ class Object {
for (f in t._init) App.removeInit(f);
t._init = null;
}
if (t._fixedUpdate != null) {
for (f in t._fixedUpdate) App.removeFixedUpdate(f);
t._fixedUpdate = null;
}
if (t._update != null) {
for (f in t._update) App.removeUpdate(f);
t._update = null;

View File

@ -2,6 +2,7 @@ package iron.object;
#if lnx_particles
import kha.FastFloat;
import kha.graphics4.Usage;
import kha.arrays.Float32Array;
import iron.data.Data;
@ -16,39 +17,45 @@ import iron.math.Vec4;
class ParticleSystem {
public var data: ParticleData;
public var speed = 1.0;
var currentSpeed = 0.0;
var particles: Array<Particle>;
var ready: Bool;
public var frameRate = 24;
public var lifetime = 0.0;
public var animtime = 0.0;
public var time = 0.0;
public var spawnRate = 0.0;
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;
public var r: TParticleData;
public var gx: Float;
public var gy: Float;
public var gz: Float;
public var alignx: Float;
public var aligny: Float;
public var alignz: Float;
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;
public var count = 0;
public var lap = 0;
public var lapTime = 0.0;
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;
public function new(sceneName: String, pref: TParticleReference) {
seed = pref.seed;
currentSpeed = speed;
speed = 0;
particles = [];
ready = false;
@ -65,33 +72,61 @@ class ParticleSystem {
gy = 0;
gz = -9.81 * r.weight_gravity;
}
alignx = r.object_align_factor[0] / 2;
aligny = r.object_align_factor[1] / 2;
alignz = r.object_align_factor[2] / 2;
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.frame_end - r.frame_start) / frameRate;
animtime = r.loop ? looptime : looptime + lifetime;
spawnRate = ((r.frame_end - r.frame_start) / r.count) / frameRate;
for (i in 0...r.count) {
var particle = new Particle(i);
particle.sr = 1 - Math.random() * r.size_random;
particles.push(particle);
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;
}
public function pause() {
lifetime = 0;
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);
@ -115,17 +150,21 @@ class ParticleSystem {
}
// Animate
time += Time.delta * speed;
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();
}
updateGpu(object, owner);
}
public function getData(): Mat4 {
var hair = r.type == 1;
m._00 = r.loop ? animtime : -animtime;
m._00 = animtime;
m._01 = hair ? 1 / particles.length : spawnRate;
m._02 = hair ? 1 : lifetime;
m._03 = particles.length;
@ -133,9 +172,9 @@ class ParticleSystem {
m._11 = hair ? 0 : aligny;
m._12 = hair ? 0 : alignz;
m._13 = hair ? 0 : r.factor_random;
m._20 = hair ? 0 : gx * r.mass;
m._21 = hair ? 0 : gy * r.mass;
m._22 = hair ? 0 : gz * r.mass;
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;
@ -144,13 +183,25 @@ class ParticleSystem {
return m;
}
public function getSizeRandom(): FastFloat {
return r.size_random;
}
public function getRandom(): FastFloat {
return random;
}
public 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
}
public function setupGeomGpu(object: MeshObject, owner: MeshObject) {
var instancedData = new Float32Array(particles.length * 6);
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
@ -169,10 +220,6 @@ class ParticleSystem {
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++;
instancedData.set(i, p.sr); i++;
instancedData.set(i, p.sr); i++;
instancedData.set(i, p.sr); i++;
}
case 1: // Face
@ -196,10 +243,6 @@ class ParticleSystem {
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++;
instancedData.set(i, p.sr); i++;
instancedData.set(i, p.sr); i++;
instancedData.set(i, p.sr); i++;
}
case 2: // Volume
@ -210,13 +253,9 @@ class ParticleSystem {
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++;
instancedData.set(i, p.sr); i++;
instancedData.set(i, p.sr); i++;
instancedData.set(i, p.sr); i++;
}
}
object.data.geom.setupInstanced(instancedData, 3, Usage.StaticUsage);
object.data.geom.setupInstanced(instancedData, 1, Usage.StaticUsage);
}
function fhash(n: Int): Float {
@ -255,10 +294,11 @@ class ParticleSystem {
class Particle {
public var i: Int;
public var px = 0.0;
public var py = 0.0;
public var pz = 0.0;
public var sr = 1.0; // Size random
public var x = 0.0;
public var y = 0.0;
public var z = 0.0;
public var cameraDistance: Float;
public function new(i: Int) {

View File

@ -80,7 +80,7 @@ class Tilesheet {
function update() {
if (!ready || paused || action.start >= action.end) return;
time += Time.realDelta;
time += Time.renderDelta;
var frameTime = 1 / raw.framerate;
var framesToAdvance = 0;

View File

@ -1113,6 +1113,26 @@ class Uniforms {
case "_texUnpack": {
f = texUnpack != null ? texUnpack : 1.0;
}
#if lnx_particles
case "_particleSizeRandom": {
var mo = cast(object, MeshObject);
if (mo.particleOwner != null && mo.particleOwner.particleSystems != null) {
f = mo.particleOwner.particleSystems[mo.particleIndex].getSizeRandom();
}
}
case "_particleRandom": {
var mo = cast(object, MeshObject);
if (mo.particleOwner != null && mo.particleOwner.particleSystems != null) {
f = mo.particleOwner.particleSystems[mo.particleIndex].getRandom();
}
}
case "_particleSize": {
var mo = cast(object, MeshObject);
if (mo.particleOwner != null && mo.particleOwner.particleSystems != null) {
f = mo.particleOwner.particleSystems[mo.particleIndex].getSize();
}
}
#end
}
if (f == null && externalFloatLinks != null) {

View File

@ -1,37 +1,58 @@
package iron.system;
class Time {
public static var scale = 1.0;
static var frequency: Null<Int> = null;
static function initFrequency() {
frequency = kha.Display.primary != null ? kha.Display.primary.frequency : 60;
}
public static var step(get, never): Float;
static function get_step(): Float {
if (frequency == null) initFrequency();
return 1 / frequency;
}
public static var scale = 1.0;
public static var delta(get, never): Float;
static function get_delta(): Float {
if (frequency == null) initFrequency();
return (1 / frequency) * scale;
static var _fixedStep: Null<Float>;
public static var fixedStep(get, never): Float;
static function get_fixedStep(): Float {
return _fixedStep;
}
public static function initFixedStep(value: Float = 1 / 60) {
_fixedStep = value;
}
static var last = 0.0;
public static var realDelta = 0.0;
static var lastTime = 0.0;
static var _delta = 0.0;
public static var delta(get, never): Float;
static function get_delta(): Float {
return _delta;
}
static var lastRenderTime = 0.0;
static var _renderDelta = 0.0;
public static var renderDelta(get, never): Float;
static function get_renderDelta(): Float {
return _renderDelta;
}
public static inline function time(): Float {
return kha.Scheduler.time();
}
public static inline function realTime(): Float {
return kha.Scheduler.realTime();
}
static var frequency: Null<Int> = null;
static function initFrequency() {
frequency = kha.Display.primary != null ? kha.Display.primary.frequency : 60;
public static function update() {
_delta = realTime() - lastTime;
lastTime = realTime();
}
public static function update() {
realDelta = realTime() - last;
last = realTime();
public static function render() {
_renderDelta = realTime() - lastRenderTime;
lastRenderTime = realTime();
}
}

View File

@ -20,6 +20,7 @@ class Config {
var path = iron.data.Data.dataPath + "config.lnx";
var bytes = haxe.io.Bytes.ofString(haxe.Json.stringify(raw));
#if kha_krom
if (iron.data.Data.dataPath == '') path = Krom.getFilesLocation() + "/config.lnx";
Krom.fileSaveBytes(path, bytes.getData());
#elseif kha_kore
sys.io.File.saveBytes(path, bytes);
@ -47,6 +48,7 @@ typedef TConfig = {
@:optional var rp_ssr: Null<Bool>;
@:optional var rp_ssrefr: Null<Bool>;
@:optional var rp_bloom: Null<Bool>;
@:optional var rp_chromatic_aberration: Null<Bool>;
@:optional var rp_motionblur: Null<Bool>;
@:optional var rp_gi: Null<Bool>; // voxelao
@:optional var rp_dynres: Null<Bool>; // dynamic resolution scaling

View File

@ -1,99 +1,99 @@
package leenkx.logicnode;
import iron.data.SceneFormat.TSceneFormat;
import iron.data.Data;
import iron.object.Object;
class AddParticleToObjectNode extends LogicNode {
public var property0: String;
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
#if lnx_particles
if (property0 == 'Scene Active'){
var objFrom: Object = inputs[1].get();
var slot: Int = inputs[2].get();
var objTo: Object = inputs[3].get();
if (objFrom == null || objTo == null) return;
var mobjFrom = cast(objFrom, iron.object.MeshObject);
var psys = mobjFrom.particleSystems != null ? mobjFrom.particleSystems[slot] :
mobjFrom.particleOwner != null && mobjFrom.particleOwner.particleSystems != null ? mobjFrom.particleOwner.particleSystems[slot] : null;
if (psys == null) return;
var mobjTo = cast(objTo, iron.object.MeshObject);
mobjTo.setupParticleSystem(iron.Scene.active.raw.name, {name: 'LnxPS', seed: 0, particle: psys.r.name});
mobjTo.render_emitter = inputs[4].get();
iron.Scene.active.spawnObject(psys.data.raw.instance_object, null, function(o: Object) {
if (o != null) {
var c: iron.object.MeshObject = cast o;
if (mobjTo.particleChildren == null) mobjTo.particleChildren = [];
mobjTo.particleChildren.push(c);
c.particleOwner = mobjTo;
c.particleIndex = mobjTo.particleChildren.length - 1;
}
});
var oslot: Int = mobjTo.particleSystems.length-1;
var opsys = mobjTo.particleSystems[oslot];
opsys.setupGeomGpu(mobjTo.particleChildren[oslot], mobjTo);
} else {
var sceneName: String = inputs[1].get();
var objectName: String = inputs[2].get();
var slot: Int = inputs[3].get();
var mobjTo: Object = inputs[4].get();
var mobjTo = cast(mobjTo, iron.object.MeshObject);
#if lnx_json
sceneName += ".json";
#elseif lnx_compress
sceneName += ".lz4";
#end
Data.getSceneRaw(sceneName, (rawScene: TSceneFormat) -> {
for (obj in rawScene.objects) {
if (obj.name == objectName) {
mobjTo.setupParticleSystem(sceneName, obj.particle_refs[slot]);
mobjTo.render_emitter = inputs[5].get();
iron.Scene.active.spawnObject(rawScene.particle_datas[slot].instance_object, null, function(o: Object) {
if (o != null) {
var c: iron.object.MeshObject = cast o;
if (mobjTo.particleChildren == null) mobjTo.particleChildren = [];
mobjTo.particleChildren.push(c);
c.particleOwner = mobjTo;
c.particleIndex = mobjTo.particleChildren.length - 1;
}
}, true, rawScene);
var oslot: Int = mobjTo.particleSystems.length-1;
var opsys = mobjTo.particleSystems[oslot];
opsys.setupGeomGpu(mobjTo.particleChildren[oslot], mobjTo);
break;
}
}
});
}
#end
runOutput(0);
}
}
package leenkx.logicnode;
import iron.data.SceneFormat.TSceneFormat;
import iron.data.Data;
import iron.object.Object;
class AddParticleToObjectNode extends LogicNode {
public var property0: String;
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
#if lnx_particles
if (property0 == 'Scene Active'){
var objFrom: Object = inputs[1].get();
var slot: Int = inputs[2].get();
var objTo: Object = inputs[3].get();
if (objFrom == null || objTo == null) return;
var mobjFrom = cast(objFrom, iron.object.MeshObject);
var psys = mobjFrom.particleSystems != null ? mobjFrom.particleSystems[slot] :
mobjFrom.particleOwner != null && mobjFrom.particleOwner.particleSystems != null ? mobjFrom.particleOwner.particleSystems[slot] : null;
if (psys == null) return;
var mobjTo = cast(objTo, iron.object.MeshObject);
mobjTo.setupParticleSystem(iron.Scene.active.raw.name, {name: 'LnxPS', seed: 0, particle: @:privateAccess psys.r.name});
mobjTo.render_emitter = inputs[4].get();
iron.Scene.active.spawnObject(psys.data.raw.instance_object, null, function(o: Object) {
if (o != null) {
var c: iron.object.MeshObject = cast o;
if (mobjTo.particleChildren == null) mobjTo.particleChildren = [];
mobjTo.particleChildren.push(c);
c.particleOwner = mobjTo;
c.particleIndex = mobjTo.particleChildren.length - 1;
}
});
var oslot: Int = mobjTo.particleSystems.length-1;
var opsys = mobjTo.particleSystems[oslot];
@:privateAccess opsys.setupGeomGpu(mobjTo.particleChildren[oslot], mobjTo);
} else {
var sceneName: String = inputs[1].get();
var objectName: String = inputs[2].get();
var slot: Int = inputs[3].get();
var mobjTo: Object = inputs[4].get();
var mobjTo = cast(mobjTo, iron.object.MeshObject);
#if lnx_json
sceneName += ".json";
#elseif lnx_compress
sceneName += ".lz4";
#end
Data.getSceneRaw(sceneName, (rawScene: TSceneFormat) -> {
for (obj in rawScene.objects) {
if (obj.name == objectName) {
mobjTo.setupParticleSystem(sceneName, obj.particle_refs[slot]);
mobjTo.render_emitter = inputs[5].get();
iron.Scene.active.spawnObject(rawScene.particle_datas[slot].instance_object, null, function(o: Object) {
if (o != null) {
var c: iron.object.MeshObject = cast o;
if (mobjTo.particleChildren == null) mobjTo.particleChildren = [];
mobjTo.particleChildren.push(c);
c.particleOwner = mobjTo;
c.particleIndex = mobjTo.particleChildren.length - 1;
}
}, true, rawScene);
var oslot: Int = mobjTo.particleSystems.length-1;
var opsys = mobjTo.particleSystems[oslot];
@:privateAccess opsys.setupGeomGpu(mobjTo.particleChildren[oslot], mobjTo);
break;
}
}
});
}
#end
runOutput(0);
}
}

View File

@ -0,0 +1,16 @@
package leenkx.logicnode;
class AutoExposureGetNode extends LogicNode {
public function new(tree:LogicTree) {
super(tree);
}
override function get(from:Int):Dynamic {
return switch (from) {
case 0: leenkx.renderpath.Postprocess.auto_exposure_uniforms[0];
case 1: leenkx.renderpath.Postprocess.auto_exposure_uniforms[1];
default: 0.0;
}
}
}

View File

@ -0,0 +1,15 @@
package leenkx.logicnode;
class AutoExposureSetNode extends LogicNode {
public function new(tree:LogicTree) {
super(tree);
}
override function run(from:Int) {
leenkx.renderpath.Postprocess.auto_exposure_uniforms[0] = inputs[1].get();
leenkx.renderpath.Postprocess.auto_exposure_uniforms[1] = inputs[2].get();
runOutput(0);
}
}

View File

@ -1,26 +1,49 @@
package leenkx.logicnode;
class CameraSetNode extends LogicNode {
public var property0: String;
public function new(tree:LogicTree) {
super(tree);
}
override function run(from:Int) {
leenkx.renderpath.Postprocess.camera_uniforms[0] = inputs[1].get();//Camera: F-Number
leenkx.renderpath.Postprocess.camera_uniforms[1] = inputs[2].get();//Camera: Shutter time
leenkx.renderpath.Postprocess.camera_uniforms[2] = inputs[3].get();//Camera: ISO
leenkx.renderpath.Postprocess.camera_uniforms[3] = inputs[4].get();//Camera: Exposure Compensation
leenkx.renderpath.Postprocess.camera_uniforms[4] = inputs[5].get();//Fisheye Distortion
leenkx.renderpath.Postprocess.camera_uniforms[5] = inputs[6].get();//DoF AutoFocus §§ If true, it ignores the DoF Distance setting
leenkx.renderpath.Postprocess.camera_uniforms[6] = inputs[7].get();//DoF Distance
leenkx.renderpath.Postprocess.camera_uniforms[7] = inputs[8].get();//DoF Focal Length mm
leenkx.renderpath.Postprocess.camera_uniforms[8] = inputs[9].get();//DoF F-Stop
leenkx.renderpath.Postprocess.camera_uniforms[9] = inputs[10].get();//Tonemapping Method
leenkx.renderpath.Postprocess.camera_uniforms[10] = inputs[11].get();//Distort
leenkx.renderpath.Postprocess.camera_uniforms[11] = inputs[12].get();//Film Grain
leenkx.renderpath.Postprocess.camera_uniforms[12] = inputs[13].get();//Sharpen
leenkx.renderpath.Postprocess.camera_uniforms[13] = inputs[14].get();//Vignette
switch (property0) {
case 'F-stop':
leenkx.renderpath.Postprocess.camera_uniforms[0] = inputs[1].get();//Camera: F-Number
case 'Shutter Time':
leenkx.renderpath.Postprocess.camera_uniforms[1] = inputs[1].get();//Camera: Shutter time
case 'ISO':
leenkx.renderpath.Postprocess.camera_uniforms[2] = inputs[1].get();//Camera: ISO
case 'Exposure Compensation':
leenkx.renderpath.Postprocess.camera_uniforms[3] = inputs[1].get();//Camera: Exposure Compensation
case 'Fisheye Distortion':
leenkx.renderpath.Postprocess.camera_uniforms[4] = inputs[1].get();//Fisheye Distortion
case 'Auto Focus':
leenkx.renderpath.Postprocess.camera_uniforms[5] = inputs[1].get();//DoF AutoFocus §§ If true, it ignores the DoF Distance setting
case 'DoF Distance':
leenkx.renderpath.Postprocess.camera_uniforms[6] = inputs[1].get();//DoF Distance
case 'DoF Length':
leenkx.renderpath.Postprocess.camera_uniforms[7] = inputs[1].get();//DoF Focal Length mm
case 'DoF F-Stop':
leenkx.renderpath.Postprocess.camera_uniforms[8] = inputs[1].get();//DoF F-Stop
case 'Tonemapping':
leenkx.renderpath.Postprocess.camera_uniforms[9] = inputs[1].get();//Tonemapping Method
case 'Distort':
leenkx.renderpath.Postprocess.camera_uniforms[10] = inputs[1].get();//Distort
case 'Film Grain':
leenkx.renderpath.Postprocess.camera_uniforms[11] = inputs[1].get();//Film Grain
case 'Sharpen':
leenkx.renderpath.Postprocess.camera_uniforms[12] = inputs[1].get();//Sharpen
case 'Vignette':
leenkx.renderpath.Postprocess.camera_uniforms[13] = inputs[1].get();//Vignette
case 'Exposure':
leenkx.renderpath.Postprocess.exposure_uniforms[0] = inputs[1].get();//Exposure
default:
null;
}
runOutput(0);
}

View File

@ -10,6 +10,7 @@ class ChromaticAberrationGetNode extends LogicNode {
return switch (from) {
case 0: leenkx.renderpath.Postprocess.chromatic_aberration_uniforms[0];
case 1: leenkx.renderpath.Postprocess.chromatic_aberration_uniforms[1];
case 2: leenkx.renderpath.Postprocess.chromatic_aberration_uniforms[2];
default: 0.0;
}
}

View File

@ -10,6 +10,7 @@ class ChromaticAberrationSetNode extends LogicNode {
leenkx.renderpath.Postprocess.chromatic_aberration_uniforms[0] = inputs[1].get();
leenkx.renderpath.Postprocess.chromatic_aberration_uniforms[1] = inputs[2].get();
leenkx.renderpath.Postprocess.chromatic_aberration_uniforms[2] = inputs[3].get();
runOutput(0);
}

View File

@ -8,7 +8,7 @@ class GetFPSNode extends LogicNode {
override function get(from: Int): Dynamic {
if (from == 0) {
var fps = Math.round(1 / iron.system.Time.realDelta);
var fps = Math.round(1 / iron.system.Time.renderDelta);
if ((fps == Math.POSITIVE_INFINITY) || (fps == Math.NEGATIVE_INFINITY) || (Math.isNaN(fps))) {
return 0;
}

View File

@ -1,66 +1,72 @@
package leenkx.logicnode;
import iron.object.Object;
class GetParticleDataNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function get(from: Int): Dynamic {
var object: Object = inputs[0].get();
var slot: Int = inputs[1].get();
if (object == null) return null;
#if lnx_particles
var mo = cast(object, iron.object.MeshObject);
var psys = mo.particleSystems != null ? mo.particleSystems[slot] :
mo.particleOwner != null && mo.particleOwner.particleSystems != null ? mo.particleOwner.particleSystems[slot] : null;
if (psys == null) return null;
return switch (from) {
case 0:
psys.r.name;
case 1:
psys.r.particle_size;
case 2:
psys.r.frame_start;
case 3:
psys.r.frame_end;
case 4:
psys.lifetime;
case 5:
psys.r.lifetime;
case 6:
psys.r.emit_from;
case 7:
new iron.math.Vec3(psys.alignx*2, psys.aligny*2, psys.alignz*2);
case 8:
psys.r.factor_random;
case 9:
new iron.math.Vec3(psys.gx, psys.gy, psys.gz);
case 10:
psys.r.weight_gravity;
case 11:
psys.speed;
case 12:
psys.time;
case 13:
psys.lap;
case 14:
psys.lapTime;
case 15:
psys.count;
default:
null;
}
#end
return null;
}
}
package leenkx.logicnode;
import iron.object.Object;
class GetParticleDataNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function get(from: Int): Dynamic {
var object: Object = inputs[0].get();
var slot: Int = inputs[1].get();
if (object == null) return null;
#if lnx_particles
var mo = cast(object, iron.object.MeshObject);
var psys = mo.particleSystems != null ? mo.particleSystems[slot] :
mo.particleOwner != null && mo.particleOwner.particleSystems != null ? mo.particleOwner.particleSystems[slot] : null;
if (psys == null) return null;
return switch (from) {
case 0:
@:privateAccess psys.r.name;
case 1:
@:privateAccess psys.r.particle_size;
case 2:
@:privateAccess psys.r.frame_start;
case 3:
@:privateAccess psys.r.frame_end;
case 4:
@:privateAccess psys.lifetime;
case 5:
@:privateAccess psys.r.lifetime;
case 6:
@:privateAccess psys.r.emit_from;
case 7:
@:privateAccess psys.r.auto_start;
case 8:
@:privateAccess psys.r.is_unique;
case 9:
@:privateAccess psys.r.loop;
case 10:
new iron.math.Vec3(@:privateAccess psys.alignx, @:privateAccess psys.aligny, @:privateAccess psys.alignz);
case 11:
@:privateAccess psys.r.factor_random;
case 12:
new iron.math.Vec3(@:privateAccess psys.gx, @:privateAccess psys.gy, @:privateAccess psys.gz);
case 13:
@:privateAccess psys.r.weight_gravity;
case 14:
psys.speed;
case 15:
@:privateAccess psys.time;
case 16:
@:privateAccess psys.lap;
case 17:
@:privateAccess psys.lapTime;
case 18:
@:privateAccess psys.count;
default:
null;
}
#end
return null;
}
}

View File

@ -1,38 +1,38 @@
package leenkx.logicnode;
import iron.object.Object;
class GetParticleNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function get(from: Int): Dynamic {
var object: Object = inputs[0].get();
if (object == null) return null;
#if lnx_particles
var mo = cast(object, iron.object.MeshObject);
switch (from) {
case 0:
var names: Array<String> = [];
if (mo.particleSystems != null)
for (psys in mo.particleSystems)
names.push(psys.r.name);
return names;
case 1:
return mo.particleSystems != null ? mo.particleSystems.length : 0;
case 2:
return mo.render_emitter;
default:
null;
}
#end
return null;
}
}
package leenkx.logicnode;
import iron.object.Object;
class GetParticleNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function get(from: Int): Dynamic {
var object: Object = inputs[0].get();
if (object == null) return null;
#if lnx_particles
var mo = cast(object, iron.object.MeshObject);
switch (from) {
case 0:
var names: Array<String> = [];
if (mo.particleSystems != null)
for (psys in mo.particleSystems)
names.push(@:privateAccess psys.r.name);
return names;
case 1:
return mo.particleSystems != null ? mo.particleSystems.length : 0;
case 2:
return mo.render_emitter;
default:
null;
}
#end
return null;
}
}

View File

@ -1,64 +1,64 @@
package leenkx.logicnode;
import iron.object.Object;
class RemoveParticleFromObjectNode extends LogicNode {
public var property0: String;
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
#if lnx_particles
var object: Object = inputs[1].get();
if (object == null) return;
var mo = cast(object, iron.object.MeshObject);
if (mo.particleSystems == null) return;
if (property0 == 'All'){
mo.particleSystems = null;
for (c in mo.particleChildren) c.remove();
mo.particleChildren = null;
mo.particleOwner = null;
mo.render_emitter = true;
}
else {
var slot: Int = -1;
if (property0 == 'Name'){
var name: String = inputs[2].get();
for (i => psys in mo.particleSystems){
if (psys.r.name == name){ slot = i; break; }
}
}
else slot = inputs[2].get();
if (mo.particleSystems.length > slot){
for (i in slot+1...mo.particleSystems.length){
var mi = cast(mo.particleChildren[i], iron.object.MeshObject);
mi.particleIndex = mi.particleIndex - 1;
}
mo.particleSystems.splice(slot, 1);
mo.particleChildren[slot].remove();
mo.particleChildren.splice(slot, 1);
}
if (slot == 0){
mo.particleSystems = null;
mo.particleChildren = null;
mo.particleOwner = null;
mo.render_emitter = true;
}
}
#end
runOutput(0);
}
}
package leenkx.logicnode;
import iron.object.Object;
class RemoveParticleFromObjectNode extends LogicNode {
public var property0: String;
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
#if lnx_particles
var object: Object = inputs[1].get();
if (object == null) return;
var mo = cast(object, iron.object.MeshObject);
if (mo.particleSystems == null) return;
if (property0 == 'All'){
mo.particleSystems = null;
for (c in mo.particleChildren) c.remove();
mo.particleChildren = null;
mo.particleOwner = null;
mo.render_emitter = true;
}
else {
var slot: Int = -1;
if (property0 == 'Name'){
var name: String = inputs[2].get();
for (i => psys in mo.particleSystems){
if (@:privateAccess psys.r.name == name){ slot = i; break; }
}
}
else slot = inputs[2].get();
if (mo.particleSystems.length > slot){
for (i in slot+1...mo.particleSystems.length){
var mi = cast(mo.particleChildren[i], iron.object.MeshObject);
mi.particleIndex = mi.particleIndex - 1;
}
mo.particleSystems.splice(slot, 1);
mo.particleChildren[slot].remove();
mo.particleChildren.splice(slot, 1);
}
if (slot == 0){
mo.particleSystems = null;
mo.particleChildren = null;
mo.particleOwner = null;
mo.render_emitter = true;
}
}
#end
runOutput(0);
}
}

View File

@ -20,6 +20,8 @@ class RpConfigNode extends LogicNode {
on ? leenkx.data.Config.raw.rp_ssrefr = true : leenkx.data.Config.raw.rp_ssrefr = false;
case "Bloom":
on ? leenkx.data.Config.raw.rp_bloom = true : leenkx.data.Config.raw.rp_bloom = false;
case "CA":
on ? leenkx.data.Config.raw.rp_chromatic_aberration = true : leenkx.data.Config.raw.rp_chromatic_aberration = false;
case "GI":
on ? leenkx.data.Config.raw.rp_gi = true : leenkx.data.Config.raw.rp_gi = false;
case "Motion Blur":

View File

@ -1,75 +1,81 @@
package leenkx.logicnode;
import iron.object.Object;
class SetParticleDataNode extends LogicNode {
public var property0: String;
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
#if lnx_particles
var object: Object = inputs[1].get();
var slot: Int = inputs[2].get();
if (object == null) return;
var mo = cast(object, iron.object.MeshObject);
var psys = mo.particleSystems != null ? mo.particleSystems[slot] :
mo.particleOwner != null && mo.particleOwner.particleSystems != null ? mo.particleOwner.particleSystems[slot] : null; if (psys == null) return;
switch (property0) {
case 'Particle Size':
psys.r.particle_size = inputs[3].get();
case 'Frame Start':
psys.r.frame_start = inputs[3].get();
psys.animtime = (psys.r.frame_end - psys.r.frame_start) / psys.frameRate;
psys.spawnRate = ((psys.r.frame_end - psys.r.frame_start) / psys.count) / psys.frameRate;
case 'Frame End':
psys.r.frame_end = inputs[3].get();
psys.animtime = (psys.r.frame_end - psys.r.frame_start) / psys.frameRate;
psys.spawnRate = ((psys.r.frame_end - psys.r.frame_start) / psys.count) / psys.frameRate;
case 'Lifetime':
psys.lifetime = inputs[3].get() / psys.frameRate;
case 'Lifetime Random':
psys.r.lifetime_random = inputs[3].get();
case 'Emit From':
var emit_from: Int = inputs[3].get();
if (emit_from == 0 || emit_from == 1 || emit_from == 2) {
psys.r.emit_from = emit_from;
psys.setupGeomGpu(mo.particleChildren != null ? mo.particleChildren[slot] : cast(iron.Scene.active.getChild(psys.data.raw.instance_object), iron.object.MeshObject), mo);
}
case 'Velocity':
var vel: iron.math.Vec3 = inputs[3].get();
psys.alignx = vel.x / 2;
psys.aligny = vel.y / 2;
psys.alignz = vel.z / 2;
case 'Velocity Random':
psys.r.factor_random = inputs[3].get();
case 'Weight Gravity':
psys.r.weight_gravity = inputs[3].get();
if (iron.Scene.active.raw.gravity != null) {
psys.gx = iron.Scene.active.raw.gravity[0] * psys.r.weight_gravity;
psys.gy = iron.Scene.active.raw.gravity[1] * psys.r.weight_gravity;
psys.gz = iron.Scene.active.raw.gravity[2] * psys.r.weight_gravity;
}
else {
psys.gx = 0;
psys.gy = 0;
psys.gz = -9.81 * psys.r.weight_gravity;
}
case 'Speed':
psys.speed = inputs[3].get();
default:
null;
}
#end
runOutput(0);
}
}
package leenkx.logicnode;
import iron.object.Object;
class SetParticleDataNode extends LogicNode {
public var property0: String;
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
#if lnx_particles
var object: Object = inputs[1].get();
var slot: Int = inputs[2].get();
if (object == null) return;
var mo = cast(object, iron.object.MeshObject);
var psys = mo.particleSystems != null ? mo.particleSystems[slot] :
mo.particleOwner != null && mo.particleOwner.particleSystems != null ? mo.particleOwner.particleSystems[slot] : null; if (psys == null) return;
switch (property0) {
case 'Particle Size':
@:privateAccess psys.r.particle_size = inputs[3].get();
case 'Frame Start':
@:privateAccess psys.r.frame_start = inputs[3].get();
@:privateAccess psys.animtime = (@:privateAccess psys.r.frame_end - @:privateAccess psys.r.frame_start) / @:privateAccess psys.frameRate;
@:privateAccess psys.spawnRate = ((@:privateAccess psys.r.frame_end - @:privateAccess psys.r.frame_start) / @:privateAccess psys.count) / @:privateAccess psys.frameRate;
case 'Frame End':
@:privateAccess psys.r.frame_end = inputs[3].get();
@:privateAccess psys.animtime = (@:privateAccess psys.r.frame_end - @:privateAccess psys.r.frame_start) / @:privateAccess psys.frameRate;
@:privateAccess psys.spawnRate = ((@:privateAccess psys.r.frame_end - @:privateAccess psys.r.frame_start) / @:privateAccess psys.count) / @:privateAccess psys.frameRate;
case 'Lifetime':
@:privateAccess psys.lifetime = inputs[3].get() / @:privateAccess psys.frameRate;
case 'Lifetime Random':
@:privateAccess psys.r.lifetime_random = inputs[3].get();
case 'Emit From':
var emit_from: Int = inputs[3].get();
if (emit_from == 0 || emit_from == 1 || emit_from == 2) {
@:privateAccess psys.r.emit_from = emit_from;
@:privateAccess psys.setupGeomGpu(mo.particleChildren != null ? mo.particleChildren[slot] : cast(iron.Scene.active.getChild(@:privateAccess psys.data.raw.instance_object), iron.object.MeshObject), mo);
}
case 'Auto Start':
@:privateAccess psys.r.auto_start = inputs[3].get();
case 'Is Unique':
@:privateAccess psys.r.is_unique = inputs[3].get();
case 'Loop':
@:privateAccess psys.r.loop = inputs[3].get();
case 'Velocity':
var vel: iron.math.Vec3 = inputs[3].get();
@:privateAccess psys.alignx = vel.x;
@:privateAccess psys.aligny = vel.y;
@:privateAccess psys.alignz = vel.z;
case 'Velocity Random':
psys.r.factor_random = inputs[3].get();
case 'Weight Gravity':
psys.r.weight_gravity = inputs[3].get();
if (iron.Scene.active.raw.gravity != null) {
@:privateAccess psys.gx = iron.Scene.active.raw.gravity[0] * @:privateAccess psys.r.weight_gravity;
@:privateAccess psys.gy = iron.Scene.active.raw.gravity[1] * @:privateAccess psys.r.weight_gravity;
@:privateAccess psys.gz = iron.Scene.active.raw.gravity[2] * @:privateAccess psys.r.weight_gravity;
}
else {
@:privateAccess psys.gx = 0;
@:privateAccess psys.gy = 0;
@:privateAccess psys.gz = -9.81 * @:privateAccess psys.r.weight_gravity;
}
case 'Speed':
psys.speed = inputs[3].get();
default:
null;
}
#end
runOutput(0);
}
}

View File

@ -0,0 +1,17 @@
package leenkx.logicnode;
class SharpenGetNode extends LogicNode {
public function new(tree:LogicTree) {
super(tree);
}
override function get(from:Int):Dynamic {
return switch (from) {
case 0: leenkx.renderpath.Postprocess.sharpen_uniforms[0];
case 1: leenkx.renderpath.Postprocess.sharpen_uniforms[1][0];
case 2: leenkx.renderpath.Postprocess.camera_uniforms[12];
default: 0.0;
}
}
}

View File

@ -0,0 +1,18 @@
package leenkx.logicnode;
class SharpenSetNode extends LogicNode {
public function new(tree:LogicTree) {
super(tree);
}
override function run(from:Int) {
leenkx.renderpath.Postprocess.sharpen_uniforms[0][0] = inputs[1].get().x;
leenkx.renderpath.Postprocess.sharpen_uniforms[0][1] = inputs[1].get().y;
leenkx.renderpath.Postprocess.sharpen_uniforms[0][2] = inputs[1].get().z;
leenkx.renderpath.Postprocess.sharpen_uniforms[1][0] = inputs[2].get();
leenkx.renderpath.Postprocess.camera_uniforms[12] = inputs[3].get();
runOutput(0);
}
}

View File

@ -0,0 +1,17 @@
package leenkx.logicnode;
class VolumetricFogGetNode extends LogicNode {
public function new(tree:LogicTree) {
super(tree);
}
override function get(from:Int):Dynamic {
return switch (from) {
case 0: leenkx.renderpath.Postprocess.volumetric_fog_uniforms[0];
case 1: leenkx.renderpath.Postprocess.volumetric_fog_uniforms[1][0];
case 2: leenkx.renderpath.Postprocess.volumetric_fog_uniforms[2][0];
default: 0.0;
}
}
}

View File

@ -0,0 +1,18 @@
package leenkx.logicnode;
class VolumetricFogSetNode extends LogicNode {
public function new(tree:LogicTree) {
super(tree);
}
override function run(from:Int) {
leenkx.renderpath.Postprocess.volumetric_fog_uniforms[0][0] = inputs[1].get().x;
leenkx.renderpath.Postprocess.volumetric_fog_uniforms[0][1] = inputs[1].get().y;
leenkx.renderpath.Postprocess.volumetric_fog_uniforms[0][2] = inputs[1].get().z;
leenkx.renderpath.Postprocess.volumetric_fog_uniforms[1][0] = inputs[2].get();
leenkx.renderpath.Postprocess.volumetric_fog_uniforms[2][0] = inputs[3].get();
runOutput(0);
}
}

View File

@ -0,0 +1,17 @@
package leenkx.logicnode;
class VolumetricLightGetNode extends LogicNode {
public function new(tree:LogicTree) {
super(tree);
}
override function get(from:Int):Dynamic {
return switch (from) {
case 0: leenkx.renderpath.Postprocess.volumetric_light_uniforms[0];
case 1: leenkx.renderpath.Postprocess.volumetric_light_uniforms[1][0];
case 2: leenkx.renderpath.Postprocess.volumetric_light_uniforms[2][0];
default: 0.0;
}
}
}

View File

@ -0,0 +1,18 @@
package leenkx.logicnode;
class VolumetricLightSetNode extends LogicNode {
public function new(tree:LogicTree) {
super(tree);
}
override function run(from:Int) {
leenkx.renderpath.Postprocess.volumetric_light_uniforms[0][0] = inputs[1].get().x;
leenkx.renderpath.Postprocess.volumetric_light_uniforms[0][1] = inputs[1].get().y;
leenkx.renderpath.Postprocess.volumetric_light_uniforms[0][2] = inputs[1].get().z;
leenkx.renderpath.Postprocess.volumetric_light_uniforms[1][0] = inputs[2].get();
leenkx.renderpath.Postprocess.volumetric_light_uniforms[2][0] = inputs[3].get();
runOutput(0);
}
}

View File

@ -527,22 +527,29 @@ class Inc {
public static function applyConfig() {
#if lnx_config
var config = leenkx.data.Config.raw;
#if rp_chromatic_aberration
Postprocess.chromatic_aberration_uniforms[3] = config.rp_chromatic_aberration == true ? 1 : 0;
#end
// Resize shadow map
var l = path.light;
if (l.data.raw.type == "sun" && l.data.raw.shadowmap_size != config.rp_shadowmap_cascade) {
l.data.raw.shadowmap_size = config.rp_shadowmap_cascade;
var rt = path.renderTargets.get("shadowMap");
if (rt != null) {
rt.unload();
path.renderTargets.remove("shadowMap");
if (l != null){
if (l.data.raw.type == "sun" && l.data.raw.shadowmap_size != config.rp_shadowmap_cascade) {
l.data.raw.shadowmap_size = config.rp_shadowmap_cascade;
var rt = path.renderTargets.get("shadowMap");
if (rt != null) {
rt.unload();
path.renderTargets.remove("shadowMap");
}
}
}
else if (l.data.raw.shadowmap_size != config.rp_shadowmap_cube) {
l.data.raw.shadowmap_size = config.rp_shadowmap_cube;
var rt = path.renderTargets.get("shadowMapCube");
if (rt != null) {
rt.unload();
path.renderTargets.remove("shadowMapCube");
else if (l.data.raw.shadowmap_size != config.rp_shadowmap_cube) {
l.data.raw.shadowmap_size = config.rp_shadowmap_cube;
var rt = path.renderTargets.get("shadowMapCube");
if (rt != null) {
rt.unload();
path.renderTargets.remove("shadowMapCube");
}
}
}
if (superSample != config.rp_supersample) {

View File

@ -54,10 +54,15 @@ class Postprocess {
0, //9: Tonemapping Method
2.0, //10: Distort
2.0, //11: Film Grain
0.25, //12: Sharpen
0.25, //12: Sharpen Strength
0.7 //13: Vignette
];
public static var sharpen_uniforms = [
[0.0, 0.0, 0.0], //0: Sharpen Color
[2.5] //1: Sharpen Size
];
public static var tonemapper_uniforms = [
1.0, //0: Slope
1.0, //1: Toe
@ -102,7 +107,30 @@ class Postprocess {
public static var chromatic_aberration_uniforms = [
2.0, //0: Strength
32 //1: Samples
32, //1: Samples
0, //2: Type
1 //3: On/Off
];
public static var exposure_uniforms = [
1 //0: Exposure
];
public static var auto_exposure_uniforms = [
1, //0: Auto Exposure Strength
1 //1: Auto Exposure Speed
];
public static var volumetric_light_uniforms = [
[1.0, 1.0, 1.0], //0: Volumetric Light Air Color
[1.0], //1: Volumetric Light Air Turbidity
[20.0] //2: Volumetric Light Steps
];
public static var volumetric_fog_uniforms = [
[0.5, 0.6, 0.7], //0: Volumetric Fog Color
[0.25], //1: Volumetric Fog Amount A
[50.0] //2: Volumetric Fog Amount B
];
public static function vec3Link(object: Object, mat: MaterialData, link: String): iron.math.Vec4 {
@ -284,6 +312,11 @@ class Postprocess {
v.x = lenstexture_uniforms[2]; //Lum min
v.y = lenstexture_uniforms[3]; //Lum max
v.z = lenstexture_uniforms[4]; //Expo
case "_PPComp8":
v = iron.object.Uniforms.helpVec;
v.x = exposure_uniforms[0]; //Exposure
v.y = auto_exposure_uniforms[0]; //Auto Exposure Strength
v.z = auto_exposure_uniforms[1]; //Auto Exposure Speed
case "_PPComp9":
v = iron.object.Uniforms.helpVec;
v.x = ssr_uniforms[0]; //Step
@ -297,8 +330,8 @@ class Postprocess {
case "_PPComp11":
v = iron.object.Uniforms.helpVec;
v.x = bloom_uniforms[2]; // Bloom Strength
v.y = 0; // Unused
v.z = 0; // Unused
v.y = volumetric_light_uniforms[2][0]; //Volumetric Light Steps
v.z = volumetric_fog_uniforms[2][0]; //Volumetric Fog Amount B
case "_PPComp12":
v = iron.object.Uniforms.helpVec;
v.x = ssao_uniforms[0]; //SSAO Strength
@ -308,7 +341,8 @@ class Postprocess {
v = iron.object.Uniforms.helpVec;
v.x = chromatic_aberration_uniforms[0]; //CA Strength
v.y = chromatic_aberration_uniforms[1]; //CA Samples
v.z = 0;
v.z = chromatic_aberration_uniforms[2]; //CA Type
v.w = chromatic_aberration_uniforms[3]; //On/Off
case "_PPComp14":
v = iron.object.Uniforms.helpVec;
v.x = camera_uniforms[10]; //Distort
@ -338,6 +372,24 @@ class Postprocess {
v.y = letterbox_uniforms[0][1];
v.z = letterbox_uniforms[0][2];
v.w = letterbox_uniforms[1][0]; //Size
case "_PPComp16":
v = iron.object.Uniforms.helpVec;
v.x = sharpen_uniforms[0][0]; //Color
v.y = sharpen_uniforms[0][1];
v.z = sharpen_uniforms[0][2];
v.w = sharpen_uniforms[1][0]; //Size
case "_PPComp17":
v = iron.object.Uniforms.helpVec;
v.x = volumetric_light_uniforms[0][0]; //Air Color
v.y = volumetric_light_uniforms[0][1];
v.z = volumetric_light_uniforms[0][2];
v.w = volumetric_light_uniforms[1][0]; //Air Turbidity
case "_PPComp18":
v = iron.object.Uniforms.helpVec;
v.x = volumetric_fog_uniforms[0][0]; //Color
v.y = volumetric_fog_uniforms[0][1];
v.z = volumetric_fog_uniforms[0][2];
v.w = volumetric_fog_uniforms[1][0]; //Amount A
}
return v;

View File

@ -101,7 +101,7 @@ class PhysicsWorld extends Trait {
public function new(timeScale = 1.0, maxSteps = 10, solverIterations = 10, debugDrawMode: DebugDrawMode = NoDebug) {
public function new(timeScale = 1.0, maxSteps = 10, solverIterations = 10, fixedStep = 1 / 60, debugDrawMode: DebugDrawMode = NoDebug) {
super();
if (nullvec) {
@ -120,6 +120,7 @@ class PhysicsWorld extends Trait {
this.timeScale = timeScale;
this.maxSteps = maxSteps;
this.solverIterations = solverIterations;
Time.initFixedStep(fixedStep);
// First scene
if (active == null) {
@ -136,10 +137,10 @@ class PhysicsWorld extends Trait {
conMap = new Map();
active = this;
// Ensure physics are updated first in the lateUpdate list
_lateUpdate = [lateUpdate];
@:privateAccess iron.App.traitLateUpdates.insert(0, lateUpdate);
// Ensure physics are updated first in the fixedUpdate list
_fixedUpdate = [fixedUpdate];
@:privateAccess iron.App.traitFixedUpdates.insert(0, fixedUpdate);
setDebugDrawMode(debugDrawMode);
iron.Scene.active.notifyOnRemove(function() {
@ -298,8 +299,8 @@ class PhysicsWorld extends Trait {
return rb;
}
function lateUpdate() {
var t = Time.delta * timeScale;
function fixedUpdate() {
var t = Time.fixedStep * timeScale * Time.scale;
if (t == 0.0) return; // Simulation paused
#if lnx_debug
@ -308,13 +309,10 @@ class PhysicsWorld extends Trait {
if (preUpdates != null) for (f in preUpdates) f();
//Bullet physics fixed timescale
var fixedTime = 1.0 / 60;
//This condition must be satisfied to not loose time
var currMaxSteps = t < (fixedTime * maxSteps) ? maxSteps : 1;
var currMaxSteps = t < (Time.fixedStep * maxSteps) ? maxSteps : 1;
world.stepSimulation(t, currMaxSteps, fixedTime);
world.stepSimulation(t, currMaxSteps, Time.fixedStep);
updateContacts();
for (rb in rbMap) @:privateAccess rb.physicsUpdate();

View File

@ -2,11 +2,13 @@ package leenkx.trait.physics.bullet;
#if lnx_bullet
import leenkx.math.Helper;
import iron.data.MeshData;
import iron.math.Vec4;
import iron.math.Quat;
import iron.object.Transform;
import iron.object.MeshObject;
import iron.data.MeshData;
import iron.system.Time;
/**
RigidBody is used to allow objects to interact with Physics in your game including collisions and gravity.
@ -76,6 +78,14 @@ class RigidBody extends iron.Trait {
static var triangleMeshCache = new Map<MeshData, bullet.Bt.TriangleMesh>();
static var usersCache = new Map<MeshData, Int>();
// Interpolation
var interpolate: Bool = false;
var time: Float = 0.0;
var currentPos: bullet.Bt.Vector3 = new bullet.Bt.Vector3(0, 0, 0);
var prevPos: bullet.Bt.Vector3 = new bullet.Bt.Vector3(0, 0, 0);
var currentRot: bullet.Bt.Quaternion = new bullet.Bt.Quaternion(0, 0, 0, 1);
var prevRot: bullet.Bt.Quaternion = new bullet.Bt.Quaternion(0, 0, 0, 1);
public function new(shape = Shape.Box, mass = 1.0, friction = 0.5, restitution = 0.0, group = 1, mask = 1,
params: RigidBodyParams = null, flags: RigidBodyFlags = null) {
super();
@ -85,7 +95,7 @@ class RigidBody extends iron.Trait {
vec1 = new bullet.Bt.Vector3(0, 0, 0);
vec2 = new bullet.Bt.Vector3(0, 0, 0);
vec3 = new bullet.Bt.Vector3(0, 0, 0);
quat1 = new bullet.Bt.Quaternion(0, 0, 0, 0);
quat1 = new bullet.Bt.Quaternion(0, 0, 0, 1);
trans1 = new bullet.Bt.Transform();
trans2 = new bullet.Bt.Transform();
}
@ -117,6 +127,7 @@ class RigidBody extends iron.Trait {
animated: false,
trigger: false,
ccd: false,
interpolate: false,
staticObj: false,
useDeactivation: true
};
@ -131,6 +142,7 @@ class RigidBody extends iron.Trait {
this.animated = flags.animated;
this.trigger = flags.trigger;
this.ccd = flags.ccd;
this.interpolate = flags.interpolate;
this.staticObj = flags.staticObj;
this.useDeactivation = flags.useDeactivation;
@ -153,6 +165,7 @@ class RigidBody extends iron.Trait {
if (!Std.isOfType(object, MeshObject)) return; // No mesh data
transform = object.transform;
transform.buildMatrix();
physics = leenkx.trait.physics.PhysicsWorld.active;
if (shape == Shape.Box) {
@ -244,6 +257,9 @@ class RigidBody extends iron.Trait {
quat1.setValue(quat.x, quat.y, quat.z, quat.w);
trans1.setRotation(quat1);
currentPos.setValue(vec1.x(), vec1.y(), vec1.z());
currentRot.setValue(quat.x, quat.y, quat.z, quat.w);
var centerOfMassOffset = trans2;
centerOfMassOffset.setIdentity();
motionState = new bullet.Bt.DefaultMotionState(trans1, centerOfMassOffset);
@ -307,7 +323,8 @@ class RigidBody extends iron.Trait {
physics.addRigidBody(this);
notifyOnRemove(removeFromWorld);
if (!animated) notifyOnUpdate(update);
if (onReady != null) onReady();
#if js
@ -317,26 +334,71 @@ class RigidBody extends iron.Trait {
#end
}
function update() {
if (interpolate) {
time += Time.delta;
while (time >= Time.fixedStep) {
time -= Time.fixedStep;
}
var t: Float = time / Time.fixedStep;
t = Helper.clamp(t, 0, 1);
var tx: Float = prevPos.x() * (1.0 - t) + currentPos.x() * t;
var ty: Float = prevPos.y() * (1.0 - t) + currentPos.y() * t;
var tz: Float = prevPos.z() * (1.0 - t) + currentPos.z() * t;
var tRot: bullet.Bt.Quaternion = nlerp(prevRot, currentRot, t);
transform.loc.set(tx, ty, tz, 1.0);
transform.rot.set(tRot.x(), tRot.y(), tRot.z(), tRot.w());
} else {
transform.loc.set(currentPos.x(), currentPos.y(), currentPos.z(), 1.0);
transform.rot.set(currentRot.x(), currentRot.y(), currentRot.z(), currentRot.w());
}
if (object.parent != null) {
var ptransform = object.parent.transform;
transform.loc.x -= ptransform.worldx();
transform.loc.y -= ptransform.worldy();
transform.loc.z -= ptransform.worldz();
}
transform.buildMatrix();
}
function nlerp(q1: bullet.Bt.Quaternion, q2: bullet.Bt.Quaternion, t: Float): bullet.Bt.Quaternion {
var dot = q1.x() * q2.x() + q1.y() * q2.y() + q1.z() * q2.z() + q1.w() * q2.w();
var _q2 = dot < 0 ? new bullet.Bt.Quaternion(-q2.x(), -q2.y(), -q2.z(), -q2.w()) : q2;
var x = q1.x() * (1.0 - t) + _q2.x() * t;
var y = q1.y() * (1.0 - t) + _q2.y() * t;
var z = q1.z() * (1.0 - t) + _q2.z() * t;
var w = q1.w() * (1.0 - t) + _q2.w() * t;
var len = Math.sqrt(x * x + y * y + z * z + w * w);
return new bullet.Bt.Quaternion(x / len, y / len, z / len, w / len);
}
function physicsUpdate() {
if (!ready) return;
if (animated) {
syncTransform();
}
else {
} else {
if (interpolate) {
prevPos.setValue(currentPos.x(), currentPos.y(), currentPos.z());
prevRot.setValue(currentRot.x(), currentRot.y(), currentRot.z(), currentRot.w());
}
var trans = body.getWorldTransform();
var p = trans.getOrigin();
var q = trans.getRotation();
transform.loc.set(p.x(), p.y(), p.z());
transform.rot.set(q.x(), q.y(), q.z(), q.w());
if (object.parent != null) {
var ptransform = object.parent.transform;
transform.loc.x -= ptransform.worldx();
transform.loc.y -= ptransform.worldy();
transform.loc.z -= ptransform.worldz();
}
transform.clearDelta();
transform.buildMatrix();
// transform.buildMatrix();
currentPos.setValue(p.x(), p.y(), p.z());
currentRot.setValue(q.x(), q.y(), q.z(), q.w());
#if hl
p.delete();
@ -689,6 +751,7 @@ typedef RigidBodyFlags = {
var animated: Bool;
var trigger: Bool;
var ccd: Bool;
var interpolate: Bool;
var staticObj: Bool;
var useDeactivation: Bool;
}

View File

@ -2297,6 +2297,8 @@ class LeenkxExporter:
out_particlesys = {
'name': particleRef[1]["structName"],
'type': 0 if psettings.type == 'EMITTER' else 1, # HAIR
'auto_start': psettings.lnx_auto_start,
'is_unique': psettings.lnx_is_unique,
'loop': psettings.lnx_loop,
# Emission
'count': int(psettings.count * psettings.lnx_count_mult),
@ -2813,6 +2815,7 @@ class LeenkxExporter:
body_flags['animated'] = rb.kinematic
body_flags['trigger'] = bobject.lnx_rb_trigger
body_flags['ccd'] = bobject.lnx_rb_ccd
body_flags['interpolate'] = bobject.lnx_rb_interpolate
body_flags['staticObj'] = is_static
body_flags['useDeactivation'] = rb.use_deactivation
x['parameters'].append(lnx.utils.get_haxe_json_string(body_params))
@ -3037,7 +3040,7 @@ class LeenkxExporter:
rbw = self.scene.rigidbody_world
if rbw is not None and rbw.enabled:
out_trait['parameters'] = [str(rbw.time_scale), str(rbw.substeps_per_frame), str(rbw.solver_iterations)]
out_trait['parameters'] = [str(rbw.time_scale), str(rbw.substeps_per_frame), str(rbw.solver_iterations), str(wrd.lnx_physics_fixed_step)]
if phys_pkg == 'bullet' or phys_pkg == 'oimo':
debug_draw_mode = 1 if wrd.lnx_physics_dbg_draw_wireframe else 0

View File

@ -87,6 +87,7 @@ def on_operator_post(operator_id: str) -> None:
target_obj.lnx_rb_trigger = source_obj.lnx_rb_trigger
target_obj.lnx_rb_deactivation_time = source_obj.lnx_rb_deactivation_time
target_obj.lnx_rb_ccd = source_obj.lnx_rb_ccd
target_obj.lnx_rb_interpolate = source_obj.lnx_rb_interpolate
target_obj.lnx_rb_collision_filter_mask = source_obj.lnx_rb_collision_filter_mask
elif operator_id == "NODE_OT_new_node_tree":

View File

@ -1,31 +1,34 @@
from lnx.logicnode.lnx_nodes import *
class GetParticleDataNode(LnxLogicTreeNode):
"""Returns the data of the given Particle System."""
bl_idname = 'LNGetParticleDataNode'
bl_label = 'Get Particle Data'
lnx_version = 1
def lnx_init(self, context):
self.inputs.new('LnxNodeSocketObject', 'Object')
self.inputs.new('LnxIntSocket', 'Slot')
self.outputs.new('LnxStringSocket', 'Name')
self.outputs.new('LnxFloatSocket', 'Particle Size')
self.outputs.new('LnxIntSocket', 'Frame Start')
self.outputs.new('LnxIntSocket', 'Frame End')
self.outputs.new('LnxIntSocket', 'Lifetime')
self.outputs.new('LnxFloatSocket', 'Lifetime Random')
self.outputs.new('LnxIntSocket', 'Emit From')
self.outputs.new('LnxVectorSocket', 'Velocity')
self.outputs.new('LnxFloatSocket', 'Velocity Random')
self.outputs.new('LnxVectorSocket', 'Gravity')
self.outputs.new('LnxFloatSocket', 'Weight Gravity')
self.outputs.new('LnxFloatSocket', 'Speed')
self.outputs.new('LnxFloatSocket', 'Time')
self.outputs.new('LnxFloatSocket', 'Lap')
self.outputs.new('LnxFloatSocket', 'Lap Time')
self.outputs.new('LnxIntSocket', 'Count')
from lnx.logicnode.lnx_nodes import *
class GetParticleDataNode(LnxLogicTreeNode):
"""Returns the data of the given Particle System."""
bl_idname = 'LNGetParticleDataNode'
bl_label = 'Get Particle Data'
lnx_version = 1
def lnx_init(self, context):
self.inputs.new('LnxNodeSocketObject', 'Object')
self.inputs.new('LnxIntSocket', 'Slot')
self.outputs.new('LnxStringSocket', 'Name')
self.outputs.new('LnxFloatSocket', 'Particle Size')
self.outputs.new('LnxIntSocket', 'Frame Start')
self.outputs.new('LnxIntSocket', 'Frame End')
self.outputs.new('LnxIntSocket', 'Lifetime')
self.outputs.new('LnxFloatSocket', 'Lifetime Random')
self.outputs.new('LnxIntSocket', 'Emit From')
self.outputs.new('LnxBoolSocket', 'Auto Start')
self.outputs.new('LnxBoolSocket', 'Is Unique')
self.outputs.new('LnxBoolSocket', 'Loop')
self.outputs.new('LnxVectorSocket', 'Velocity')
self.outputs.new('LnxFloatSocket', 'Velocity Random')
self.outputs.new('LnxVectorSocket', 'Gravity')
self.outputs.new('LnxFloatSocket', 'Weight Gravity')
self.outputs.new('LnxFloatSocket', 'Speed')
self.outputs.new('LnxFloatSocket', 'Time')
self.outputs.new('LnxFloatSocket', 'Lap')
self.outputs.new('LnxFloatSocket', 'Lap Time')
self.outputs.new('LnxIntSocket', 'Count')

View File

@ -1,58 +1,67 @@
from lnx.logicnode.lnx_nodes import *
class SetParticleDataNode(LnxLogicTreeNode):
"""Sets the parameters of the given particle system."""
bl_idname = 'LNSetParticleDataNode'
bl_label = 'Set Particle Data'
lnx_version = 1
def remove_extra_inputs(self, context):
while len(self.inputs) > 3:
self.inputs.remove(self.inputs[-1])
if self.property0 == 'Particle Size':
self.add_input('LnxFloatSocket', 'Particle Size')
if self.property0 == 'Frame End':
self.add_input('LnxIntSocket', 'Frame End')
if self.property0 == 'Frame Start':
self.add_input('LnxIntSocket', 'Frame Start')
if self.property0 == 'Lifetime':
self.add_input('LnxIntSocket', 'Lifetime')
if self.property0 == 'Lifetime Random':
self.add_input('LnxFloatSocket', 'Lifetime Random')
if self.property0 == 'Emit From':
self.add_input('LnxIntSocket', 'Emit From')
if self.property0 == 'Velocity':
self.add_input('LnxVectorSocket', 'Velocity')
if self.property0 == 'Velocity Random':
self.add_input('LnxFloatSocket', 'Velocity Random')
if self.property0 == 'Weight Gravity':
self.add_input('LnxFloatSocket', 'Weight Gravity')
if self.property0 == 'Speed':
self.add_input('LnxFloatSocket', 'Speed')
property0: HaxeEnumProperty(
'property0',
items = [('Particle Size', 'Particle Size', 'for the system'),
('Frame Start', 'Frame Start', 'for the system'),
('Frame End', 'Frame End', 'for the system'),
('Lifetime', 'Lifetime', 'for the instance'),
('Lifetime Random', 'Lifetime Random', 'for the system'),
('Emit From', 'Emit From', 'for the system (Vertices:0 Faces:1 Volume: 2)'),
('Velocity', 'Velocity', 'for the instance'),
('Velocity Random', 'Velocity Random', 'for the system'),
('Weight Gravity', 'Weight Gravity', 'for the instance'),
('Speed', 'Speed', 'for the instance')],
name='', default='Speed', update=remove_extra_inputs)
def lnx_init(self, context):
self.add_input('LnxNodeSocketAction', 'In')
self.add_input('LnxNodeSocketObject', 'Object')
self.add_input('LnxIntSocket', 'Slot')
self.add_input('LnxFloatSocket', 'Speed', default_value=1.0)
self.add_output('LnxNodeSocketAction', 'Out')
def draw_buttons(self, context, layout):
layout.prop(self, 'property0')
from lnx.logicnode.lnx_nodes import *
class SetParticleDataNode(LnxLogicTreeNode):
"""Sets the parameters of the given particle system."""
bl_idname = 'LNSetParticleDataNode'
bl_label = 'Set Particle Data'
lnx_version = 1
def remove_extra_inputs(self, context):
while len(self.inputs) > 3:
self.inputs.remove(self.inputs[-1])
if self.property0 == 'Particle Size':
self.add_input('LnxFloatSocket', 'Particle Size')
if self.property0 == 'Frame End':
self.add_input('LnxIntSocket', 'Frame End')
if self.property0 == 'Frame Start':
self.add_input('LnxIntSocket', 'Frame Start')
if self.property0 == 'Lifetime':
self.add_input('LnxIntSocket', 'Lifetime')
if self.property0 == 'Lifetime Random':
self.add_input('LnxFloatSocket', 'Lifetime Random')
if self.property0 == 'Emit From':
self.add_input('LnxIntSocket', 'Emit From')
if self.property0 == 'Auto Start':
self.add_input('LnxBoolSocket', 'Auto Start')
if self.property0 == 'Is Unique':
self.add_input('LnxBoolSocket', 'Is Unique')
if self.property0 == 'Loop':
self.add_input('LnxBoolSocket', 'Loop')
if self.property0 == 'Velocity':
self.add_input('LnxVectorSocket', 'Velocity')
if self.property0 == 'Velocity Random':
self.add_input('LnxFloatSocket', 'Velocity Random')
if self.property0 == 'Weight Gravity':
self.add_input('LnxFloatSocket', 'Weight Gravity')
if self.property0 == 'Speed':
self.add_input('LnxFloatSocket', 'Speed')
property0: HaxeEnumProperty(
'property0',
items = [('Particle Size', 'Particle Size', 'for the system'),
('Frame Start', 'Frame Start', 'for the system'),
('Frame End', 'Frame End', 'for the system'),
('Lifetime', 'Lifetime', 'for the instance'),
('Lifetime Random', 'Lifetime Random', 'for the system'),
('Emit From', 'Emit From', 'for the system (Vertices:0 Faces:1 Volume: 2)'),
('Auto Start', 'Auto Start', 'for the system'),
('Is Unique', 'Is Unique', 'for the system'),
('Loop', 'Loop', 'for the system'),
('Velocity', 'Velocity', 'for the instance'),
('Velocity Random', 'Velocity Random', 'for the system'),
('Weight Gravity', 'Weight Gravity', 'for the instance'),
('Speed', 'Speed', 'for the instance')],
name='', default='Speed', update=remove_extra_inputs)
def lnx_init(self, context):
self.add_input('LnxNodeSocketAction', 'In')
self.add_input('LnxNodeSocketObject', 'Object')
self.add_input('LnxIntSocket', 'Slot')
self.add_input('LnxFloatSocket', 'Speed', default_value=1.0)
self.add_output('LnxNodeSocketAction', 'Out')
def draw_buttons(self, context, layout):
layout.prop(self, 'property0')

View File

@ -0,0 +1,11 @@
from lnx.logicnode.lnx_nodes import *
class AutoExposureGetNode(LnxLogicTreeNode):
"""Returns the auto exposure post-processing settings."""
bl_idname = 'LNAutoExposureGetNode'
bl_label = 'Get Auto Exposure Settings'
lnx_version = 1
def lnx_init(self, context):
self.add_output('LnxFloatSocket', 'Strength')
self.add_output('LnxFloatSocket', 'Speed')

View File

@ -1,11 +1,20 @@
from lnx.logicnode.lnx_nodes import *
class ChromaticAberrationGetNode(LnxLogicTreeNode):
"""Returns the chromatic aberration post-processing settings."""
"""Returns the chromatic aberration post-processing settings.
Type: Simple 0 Spectral 1.
"""
bl_idname = 'LNChromaticAberrationGetNode'
bl_label = 'Get CA Settings'
lnx_version = 1
lnx_version = 2
def lnx_init(self, context):
self.add_output('LnxFloatSocket', 'Strength')
self.add_output('LnxFloatSocket', 'Samples')
self.add_output('LnxIntSocket', 'Type')
def get_replacement_node(self, node_tree: bpy.types.NodeTree):
if self.lnx_version not in (0, 1):
raise LookupError()
return NodeReplacement.Identity(self)

View File

@ -21,6 +21,7 @@ class CameraGetNode(LnxLogicTreeNode):
self.add_output('LnxFloatSocket', 'Film Grain')#11
self.add_output('LnxFloatSocket', 'Sharpen')#12
self.add_output('LnxFloatSocket', 'Vignette')#13
self.add_output('LnxFloatSocket', 'Exposure')#14
def get_replacement_node(self, node_tree: bpy.types.NodeTree):
if self.lnx_version not in (0, 3):

View File

@ -0,0 +1,12 @@
from lnx.logicnode.lnx_nodes import *
class SharpenGetNode(LnxLogicTreeNode):
"""Returns the sharpen post-processing settings."""
bl_idname = 'LNSharpenGetNode'
bl_label = 'Get Sharpen Settings'
lnx_version = 1
def lnx_init(self, context):
self.add_output('LnxColorSocket', 'Color')
self.add_output('LnxFloatSocket', 'Size')
self.add_output('LnxFloatSocket', 'Strength')

View File

@ -0,0 +1,12 @@
from lnx.logicnode.lnx_nodes import *
class VolumetricFogGetNode(LnxLogicTreeNode):
"""Returns the volumetric fog post-processing settings."""
bl_idname = 'LNVolumetricFogGetNode'
bl_label = 'Get Volumetric Fog Settings'
lnx_version = 1
def lnx_init(self, context):
self.add_output('LnxColorSocket', 'Color')
self.add_output('LnxFloatSocket', 'Amount A')
self.add_output('LnxFloatSocket', 'Amount B')

View File

@ -0,0 +1,12 @@
from lnx.logicnode.lnx_nodes import *
class VolumetricLightGetNode(LnxLogicTreeNode):
"""Returns the volumetric light post-processing settings."""
bl_idname = 'LNVolumetricLightGetNode'
bl_label = 'Get Volumetric Light Settings'
lnx_version = 1
def lnx_init(self, context):
self.add_output('LnxColorSocket', 'Air Color')
self.add_output('LnxFloatSocket', 'Air Turbidity')
self.add_output('LnxIntSocket', 'Steps')

View File

@ -0,0 +1,14 @@
from lnx.logicnode.lnx_nodes import *
class AutoExposureSetNode(LnxLogicTreeNode):
"""Set the sharpen post-processing settings."""
bl_idname = 'LNAutoExposureSetNode'
bl_label = 'Set Auto Exposure Settings'
lnx_version = 1
def lnx_init(self, context):
self.add_input('LnxNodeSocketAction', 'In')
self.add_input('LnxFloatSocket', 'Strength', default_value=1)
self.add_input('LnxFloatSocket', 'Speed', default_value=1)
self.add_output('LnxNodeSocketAction', 'Out')

View File

@ -1,14 +1,23 @@
from lnx.logicnode.lnx_nodes import *
class ChromaticAberrationSetNode(LnxLogicTreeNode):
"""Set the chromatic aberration post-processing settings."""
"""Set the chromatic aberration post-processing settings.
Type: Simple 0 Spectral 1.
"""
bl_idname = 'LNChromaticAberrationSetNode'
bl_label = 'Set CA Settings'
lnx_version = 1
lnx_version = 2
def lnx_init(self, context):
self.add_input('LnxNodeSocketAction', 'In')
self.add_input('LnxFloatSocket', 'Strength', default_value=2.0)
self.add_input('LnxIntSocket', 'Samples', default_value=32)
self.add_input('LnxIntSocket', 'Type', default_value=0)
self.add_output('LnxNodeSocketAction', 'Out')
def get_replacement_node(self, node_tree: bpy.types.NodeTree):
if self.lnx_version not in (0, 1):
raise LookupError()
return NodeReplacement.Identity(self)

View File

@ -4,27 +4,74 @@ class CameraSetNode(LnxLogicTreeNode):
"""Set the post-processing effects of a camera."""
bl_idname = 'LNCameraSetNode'
bl_label = 'Set Camera Post Process'
lnx_version = 4
lnx_version = 5
def remove_extra_inputs(self, context):
while len(self.inputs) > 1:
self.inputs.remove(self.inputs[-1])
if self.property0 == 'F-stop':
self.add_input('LnxFloatSocket', 'F-stop', default_value=1.0)#0
if self.property0 == 'Shutter Time':
self.add_input('LnxFloatSocket', 'Shutter Time', default_value=2.8333)#1
if self.property0 == 'ISO':
self.add_input('LnxFloatSocket', 'ISO', default_value=100.0)#2
if self.property0 == 'Exposure Compensation':
self.add_input('LnxFloatSocket', 'Exposure Compensation', default_value=0.0)#3
if self.property0 == 'Fisheye Distortion':
self.add_input('LnxFloatSocket', 'Fisheye Distortion', default_value=0.01)#4
if self.property0 == 'Auto Focus':
self.add_input('LnxBoolSocket', 'Auto Focus', default_value=True)#5
if self.property0 == 'DoF Distance':
self.add_input('LnxFloatSocket', 'DoF Distance', default_value=10.0)#6
if self.property0 == 'DoF Length':
self.add_input('LnxFloatSocket', 'DoF Length', default_value=160.0)#7
if self.property0 == 'DoF F-Stop':
self.add_input('LnxFloatSocket', 'DoF F-Stop', default_value=128.0)#8
if self.property0 == 'Tonemapping':
self.add_input('LnxBoolSocket', 'Tonemapping', default_value=False)#9
if self.property0 == 'Distort':
self.add_input('LnxFloatSocket', 'Distort', default_value=2.0)#10
if self.property0 == 'Film Grain':
self.add_input('LnxFloatSocket', 'Film Grain', default_value=2.0)#11
if self.property0 == 'Sharpen':
self.add_input('LnxFloatSocket', 'Sharpen', default_value=0.25)#12
if self.property0 == 'Vignette':
self.add_input('LnxFloatSocket', 'Vignette', default_value=0.7)#13
if self.property0 == 'Exposure':
self.add_input('LnxFloatSocket', 'Exposure', default_value=1)#14
property0: HaxeEnumProperty(
'property0',
items = [('F-stop', 'F-stop', 'F-stop'),
('Shutter Time', 'Shutter Time', 'Shutter Time'),
('ISO', 'ISO', 'ISO'),
('Exposure Compensation', 'Exposure Compensation', 'Exposure Compensation'),
('Fisheye Distortion', 'Fisheye Distortion', 'Fisheye Distortion'),
('Auto Focus', 'Auto Focus', 'Auto Focus'),
('DoF Distance', 'DoF Distance', 'DoF Distance'),
('DoF Length', 'DoF Length', 'DoF Length'),
('DoF F-Stop', 'DoF F-Stop', 'DoF F-Stop'),
('Tonemapping', 'Tonemapping', 'Tonemapping'),
('Distort', 'Distort', 'Distort'),
('Film Grain', 'Film Grain', 'Film Grain'),
('Sharpen', 'Sharpen', 'Sharpen'),
('Vignette', 'Vignette', 'Vignette'),
('Exposure', 'Exposure', 'Exposure')],
name='', default='F-stop', update=remove_extra_inputs)
def lnx_init(self, context):
self.add_input('LnxNodeSocketAction', 'In')
self.add_input('LnxFloatSocket', 'F-stop', default_value=1.0)#0
self.add_input('LnxFloatSocket', 'Shutter Time', default_value=2.8333)#1
self.add_input('LnxFloatSocket', 'ISO', default_value=100.0)#2
self.add_input('LnxFloatSocket', 'Exposure Compensation', default_value=0.0)#3
self.add_input('LnxFloatSocket', 'Fisheye Distortion', default_value=0.01)#4
self.add_input('LnxBoolSocket', 'Auto Focus', default_value=True)#5
self.add_input('LnxFloatSocket', 'DoF Distance', default_value=10.0)#6
self.add_input('LnxFloatSocket', 'DoF Length', default_value=160.0)#7
self.add_input('LnxFloatSocket', 'DoF F-Stop', default_value=128.0)#8
self.add_input('LnxBoolSocket', 'Tonemapping', default_value=False)#9
self.add_input('LnxFloatSocket', 'Distort', default_value=2.0)#10
self.add_input('LnxFloatSocket', 'Film Grain', default_value=2.0)#11
self.add_input('LnxFloatSocket', 'Sharpen', default_value=0.25)#12
self.add_input('LnxFloatSocket', 'Vignette', default_value=0.7)#13
self.add_input('LnxFloatSocket', 'F-stop', default_value=1.0)
self.add_output('LnxNodeSocketAction', 'Out')
def draw_buttons(self, context, layout):
layout.prop(self, 'property0')
def get_replacement_node(self, node_tree: bpy.types.NodeTree):
if self.lnx_version not in range(0, 4):
raise LookupError()

View File

@ -0,0 +1,15 @@
from lnx.logicnode.lnx_nodes import *
class SharpenSetNode(LnxLogicTreeNode):
"""Set the sharpen post-processing settings."""
bl_idname = 'LNSharpenSetNode'
bl_label = 'Set Sharpen Settings'
lnx_version = 1
def lnx_init(self, context):
self.add_input('LnxNodeSocketAction', 'In')
self.add_input('LnxColorSocket', 'Color', default_value=[0.0, 0.0, 0.0, 1.0])
self.add_input('LnxFloatSocket', 'Size', default_value=2.5)
self.add_input('LnxFloatSocket', 'Strength', default_value=0.25)
self.add_output('LnxNodeSocketAction', 'Out')

View File

@ -0,0 +1,15 @@
from lnx.logicnode.lnx_nodes import *
class VolumetricFogSetNode(LnxLogicTreeNode):
"""Set the volumetric fog post-processing settings."""
bl_idname = 'LNVolumetricFogSetNode'
bl_label = 'Set Volumetric Fog Settings'
lnx_version = 1
def lnx_init(self, context):
self.add_input('LnxNodeSocketAction', 'In')
self.add_input('LnxColorSocket', 'Color', default_value=[0.5, 0.6, 0.7, 1.0])
self.add_input('LnxFloatSocket', 'Amount A', default_value=0.25)
self.add_input('LnxFloatSocket', 'Amount B', default_value=0.50)
self.add_output('LnxNodeSocketAction', 'Out')

View File

@ -0,0 +1,15 @@
from lnx.logicnode.lnx_nodes import *
class VolumetricLightSetNode(LnxLogicTreeNode):
"""Set the volumetric light post-processing settings."""
bl_idname = 'LNVolumetricLightSetNode'
bl_label = 'Set Volumetric Light Settings'
lnx_version = 1
def lnx_init(self, context):
self.add_input('LnxNodeSocketAction', 'In')
self.add_input('LnxColorSocket', 'Air Color', default_value=[1.0, 1.0, 1.0, 1.0])
self.add_input('LnxFloatSocket', 'Air Turbidity', default_value=1)
self.add_input('LnxIntSocket', 'Steps', default_value=20)
self.add_output('LnxNodeSocketAction', 'Out')

View File

@ -4,12 +4,13 @@ class RpConfigNode(LnxLogicTreeNode):
"""Sets the post process quality."""
bl_idname = 'LNRpConfigNode'
bl_label = 'Set Post Process Quality'
lnx_version = 1
lnx_version = 2
property0: HaxeEnumProperty(
'property0',
items = [('SSGI', 'SSGI', 'SSGI'),
('SSR', 'SSR', 'SSR'),
('Bloom', 'Bloom', 'Bloom'),
('CA', 'CA', 'CA'),
('GI', 'GI', 'GI'),
('Motion Blur', 'Motion Blur', 'Motion Blur')
],
@ -23,3 +24,10 @@ class RpConfigNode(LnxLogicTreeNode):
def draw_buttons(self, context, layout):
layout.prop(self, 'property0')
def get_replacement_node(self, node_tree: bpy.types.NodeTree):
if self.lnx_version not in (0, 1):
raise LookupError()
return NodeReplacement.Identity(self)

View File

@ -40,11 +40,11 @@ def add_world_defs():
if rpdat.rp_hdr == False:
wrd.world_defs += '_LDR'
if wrd.lnx_light_ies_texture != '':
if lnx.utils.get_active_scene().world.lnx_light_ies_texture == True:
wrd.world_defs += '_LightIES'
assets.add_embedded_data('iestexture.png')
if wrd.lnx_light_clouds_texture != '':
if lnx.utils.get_active_scene().world.lnx_light_clouds_texture == True:
wrd.world_defs += '_LightClouds'
assets.add_embedded_data('cloudstexture.png')

View File

@ -82,28 +82,37 @@ def parse_clamp(node: bpy.types.ShaderNodeClamp, out_socket: bpy.types.NodeSocke
def parse_valtorgb(node: bpy.types.ShaderNodeValToRGB, out_socket: bpy.types.NodeSocket, state: ParserState) -> Union[floatstr, vec3str]:
# Alpha (TODO: make ColorRamp calculation vec4-based and split afterwards)
if out_socket == node.outputs[1]:
return '1.0'
input_fac: bpy.types.NodeSocket = node.inputs[0]
alpha_out = out_socket == node.outputs[1]
fac: str = c.parse_value_input(input_fac) if input_fac.is_linked else c.to_vec1(input_fac.default_value)
interp = node.color_ramp.interpolation
elems = node.color_ramp.elements
if len(elems) == 1:
return c.to_vec3(elems[0].color)
# Write color array
# The last entry is included twice so that the interpolation
# between indices works (no out of bounds error)
cols_var = c.node_name(node.name).upper() + '_COLS'
if alpha_out:
return c.to_vec1(elems[0].color[3]) # Return alpha from the color
else:
return c.to_vec3(elems[0].color) # Return RGB
name_prefix = c.node_name(node.name).upper()
if alpha_out:
cols_var = name_prefix + '_ALPHAS'
else:
cols_var = name_prefix + '_COLS'
if state.current_pass == ParserPass.REGULAR:
cols_entries = ', '.join(f'vec3({elem.color[0]}, {elem.color[1]}, {elem.color[2]})' for elem in elems)
cols_entries += f', vec3({elems[len(elems) - 1].color[0]}, {elems[len(elems) - 1].color[1]}, {elems[len(elems) - 1].color[2]})'
state.curshader.add_const("vec3", cols_var, cols_entries, array_size=len(elems) + 1)
if alpha_out:
cols_entries = ', '.join(f'{elem.color[3]}' for elem in elems)
# Add last value twice to avoid out of bounds access
cols_entries += f', {elems[len(elems) - 1].color[3]}'
state.curshader.add_const("float", cols_var, cols_entries, array_size=len(elems) + 1)
else:
# Create array of RGB values for color output
cols_entries = ', '.join(f'vec3({elem.color[0]}, {elem.color[1]}, {elem.color[2]})' for elem in elems)
cols_entries += f', vec3({elems[len(elems) - 1].color[0]}, {elems[len(elems) - 1].color[1]}, {elems[len(elems) - 1].color[2]})'
state.curshader.add_const("vec3", cols_var, cols_entries, array_size=len(elems) + 1)
fac_var = c.node_name(node.name) + '_fac' + state.get_parser_pass_suffix()
state.curshader.write(f'float {fac_var} = {fac};')
@ -121,21 +130,22 @@ def parse_valtorgb(node: bpy.types.ShaderNodeValToRGB, out_socket: bpy.types.Nod
# Linear interpolation
else:
# Write factor array
facs_var = c.node_name(node.name).upper() + '_FACS'
# Write factor array - same for both color and alpha
facs_var = name_prefix + '_FACS'
if state.current_pass == ParserPass.REGULAR:
facs_entries = ', '.join(str(elem.position) for elem in elems)
# Add one more entry at the rightmost position so that the
# interpolation between indices works (no out of bounds error)
# Add one more entry at the rightmost position to avoid out of bounds access
facs_entries += ', 1.0'
state.curshader.add_const("float", facs_var, facs_entries, array_size=len(elems) + 1)
# Mix color
# Calculation for interpolation position
prev_stop_fac = f'{facs_var}[{index_var}]'
next_stop_fac = f'{facs_var}[{index_var} + 1]'
prev_stop_col = f'{cols_var}[{index_var}]'
next_stop_col = f'{cols_var}[{index_var} + 1]'
rel_pos = f'({fac_var} - {prev_stop_fac}) * (1.0 / ({next_stop_fac} - {prev_stop_fac}))'
# Use mix function for both alpha and color outputs (mix works on floats too)
return f'mix({prev_stop_col}, {next_stop_col}, max({rel_pos}, 0.0))'
if bpy.app.version > (3, 2, 0):

View File

@ -1,3 +1,4 @@
import bpy
import lnx.utils
import lnx.material.mat_state as mat_state
@ -10,6 +11,48 @@ else:
def write(vert, particle_info=None, shadowmap=False):
ramp_el_len = 0
ramp_positions = []
ramp_colors_b = []
size_over_time_factor = 0
use_rotations = False
rotation_mode = 'NONE'
rotation_factor_random = 0
phase_factor = 0
phase_factor_random = 0
for obj in bpy.data.objects:
for psys in obj.particle_systems:
psettings = psys.settings
if psettings.instance_object:
if psettings.instance_object.active_material:
# FIXME: Different particle systems may share the same particle object. This ideally should check the correct `ParticleSystem` using an id or name in the particle's object material.
if psettings.instance_object.active_material.name.replace(".", "_") == vert.context.matname:
# Rotation data
use_rotations = psettings.use_rotations
rotation_mode = psettings.rotation_mode
rotation_factor_random = psettings.rotation_factor_random
phase_factor = psettings.phase_factor
phase_factor_random = psettings.phase_factor_random
# Texture slots data
if psettings.texture_slots and len(psettings.texture_slots.items()) != 0:
for tex_slot in psettings.texture_slots:
if not tex_slot: break
if not tex_slot.use_map_size: break # TODO: check also for other influences
if tex_slot.texture and tex_slot.texture.use_color_ramp:
if tex_slot.texture.color_ramp and tex_slot.texture.color_ramp.elements:
ramp_el_len = len(tex_slot.texture.color_ramp.elements.items())
for element in tex_slot.texture.color_ramp.elements:
ramp_positions.append(element.position)
ramp_colors_b.append(element.color[2])
size_over_time_factor = tex_slot.size_factor
break
# Outs
out_index = True if particle_info != None and particle_info['index'] else False
out_age = True if particle_info != None and particle_info['age'] else False
@ -19,19 +62,50 @@ def write(vert, particle_info=None, shadowmap=False):
out_velocity = True if particle_info != None and particle_info['velocity'] else False
out_angular_velocity = True if particle_info != None and particle_info['angular_velocity'] else False
# Force Leenkx to create a new shader per material ID
vert.write(f'#ifdef PARTICLE_ID_{vert.context.material.lnx_material_id}')
vert.write('#endif')
vert.add_uniform('mat4 pd', '_particleData')
vert.add_uniform('float pd_size_random', '_particleSizeRandom')
vert.add_uniform('float pd_random', '_particleRandom')
vert.add_uniform('float pd_size', '_particleSize')
if ramp_el_len != 0:
vert.add_const('float', 'P_SIZE_OVER_TIME_FACTOR', str(size_over_time_factor))
for i in range(ramp_el_len):
vert.add_const('float', f'P_RAMP_POSITION_{i}', str(ramp_positions[i]))
vert.add_const('float', f'P_RAMP_COLOR_B_{i}', str(ramp_colors_b[i]))
str_tex_hash = "float fhash(float n) { return fract(sin(n) * 43758.5453); }\n"
vert.add_function(str_tex_hash)
if (ramp_el_len != 0):
str_ramp_scale = "float get_ramp_scale(float age) {\n"
for i in range(ramp_el_len):
if i == 0:
str_ramp_scale += f"if (age <= P_RAMP_POSITION_{i + 1})"
elif i == ramp_el_len - 1:
str_ramp_scale += f"return P_RAMP_COLOR_B_{ramp_el_len - 1};"
break
else:
str_ramp_scale += f"else if (age <= P_RAMP_POSITION_{i + 1})"
str_ramp_scale += f""" {{
float t = (age - P_RAMP_POSITION_{i}) / (P_RAMP_POSITION_{i + 1} - P_RAMP_POSITION_{i});
return mix(P_RAMP_COLOR_B_{i}, P_RAMP_COLOR_B_{i + 1}, t);
}}
"""
str_ramp_scale += "}\n"
vert.add_function(str_ramp_scale)
prep = 'float '
if out_age:
prep = ''
vert.add_out('float p_age')
# var p_age = lapTime - p.i * spawnRate
vert.write(prep + 'p_age = pd[3][3] - gl_InstanceID * pd[0][1];')
# p_age -= p_age * fhash(i) * r.lifetime_random;
vert.write('p_age -= p_age * fhash(gl_InstanceID) * pd[2][3];')
# Loop
# pd[0][0] - animtime, loop stored in sign
@ -43,13 +117,18 @@ def write(vert, particle_info=None, shadowmap=False):
if out_lifetime:
prep = ''
vert.add_out('float p_lifetime')
vert.write(prep + 'p_lifetime = pd[0][2];')
vert.write(prep + 'p_lifetime = pd[0][2] * (1 - (fhash(gl_InstanceID + 4 * pd[0][3] + pd_random) * pd[2][3]));')
# clip with nan
vert.write('if (p_age < 0 || p_age > p_lifetime) {')
vert.write(' gl_Position /= 0.0;')
vert.write(' return;')
vert.write('}')
if (ramp_el_len != 0):
vert.write('float n_age = clamp(p_age / p_lifetime, 0.0, 1.0);')
vert.write(f'spos.xyz *= 1 + (get_ramp_scale(n_age) - 1) * {size_over_time_factor};')
vert.write('spos.xyz *= 1 - (fhash(gl_InstanceID + 3 * pd[0][3] + pd_random) * pd_size_random);')
# vert.write('p_age /= 2;') # Match
# object_align_factor / 2 + gxyz
@ -57,20 +136,20 @@ def write(vert, particle_info=None, shadowmap=False):
if out_velocity:
prep = ''
vert.add_out('vec3 p_velocity')
vert.write(prep + 'p_velocity = vec3(pd[1][0], pd[1][1], pd[1][2]);')
vert.write(prep + 'p_velocity = vec3(pd[1][0] * (1 / pd_size), pd[1][1] * (1 / pd_size), pd[1][2] * (1 / pd_size));')
vert.write('p_velocity.x += fhash(gl_InstanceID) * pd[1][3] - pd[1][3] / 2;')
vert.write('p_velocity.y += fhash(gl_InstanceID + pd[0][3]) * pd[1][3] - pd[1][3] / 2;')
vert.write('p_velocity.z += fhash(gl_InstanceID + 2 * pd[0][3]) * pd[1][3] - pd[1][3] / 2;')
vert.write('p_velocity.x += (fhash(gl_InstanceID + pd_random) * 2.0 / pd_size - 1.0 / pd_size) * pd[1][3];')
vert.write('p_velocity.y += (fhash(gl_InstanceID + pd_random + pd[0][3]) * 2.0 / pd_size - 1.0 / pd_size) * pd[1][3];')
vert.write('p_velocity.z += (fhash(gl_InstanceID + pd_random + 2 * pd[0][3]) * 2.0 / pd_size - 1.0 / pd_size) * pd[1][3];')
# factor_random = pd[1][3]
# p.i = gl_InstanceID
# particles.length = pd[0][3]
# gxyz
vert.write('p_velocity.x += (pd[2][0] * p_age) / 5;')
vert.write('p_velocity.y += (pd[2][1] * p_age) / 5;')
vert.write('p_velocity.z += (pd[2][2] * p_age) / 5;')
vert.write('p_velocity.x += (pd[2][0] / (2 * pd_size)) * p_age;')
vert.write('p_velocity.y += (pd[2][1] / (2 * pd_size)) * p_age;')
vert.write('p_velocity.z += (pd[2][2] / (2 * pd_size)) * p_age;')
prep = 'vec3 '
if out_location:
@ -80,6 +159,96 @@ def write(vert, particle_info=None, shadowmap=False):
vert.write('spos.xyz += p_location;')
# Rotation
if use_rotations:
if rotation_mode != 'NONE':
vert.write(f'float p_angle = ({phase_factor} + (fhash(gl_InstanceID + pd_random + 5 * pd[0][3])) * {phase_factor_random});')
vert.write('p_angle *= 3.141592;')
vert.write('float c = cos(p_angle);')
vert.write('float s = sin(p_angle);')
vert.write('vec3 center = spos.xyz - p_location;')
match rotation_mode:
case 'OB_X':
vert.write('vec3 rz = vec3(center.y, -center.x, center.z);')
vert.write('vec2 rotation = vec2(rz.y * c - rz.z * s, rz.y * s + rz.z * c);')
vert.write('spos.xyz = vec3(rz.x, rotation.x, rotation.y) + p_location;')
if (not shadowmap):
vert.write('wnormal = vec3(wnormal.y, -wnormal.x, wnormal.z);')
vert.write('vec2 n_rot = vec2(wnormal.y * c - wnormal.z * s, wnormal.y * s + wnormal.z * c);')
vert.write('wnormal = normalize(vec3(wnormal.x, n_rot.x, n_rot.y));')
case 'OB_Y':
vert.write('vec2 rotation = vec2(center.x * c + center.z * s, -center.x * s + center.z * c);')
vert.write('spos.xyz = vec3(rotation.x, center.y, rotation.y) + p_location;')
if (not shadowmap):
vert.write('wnormal = normalize(vec3(wnormal.x * c + wnormal.z * s, wnormal.y, -wnormal.x * s + wnormal.z * c));')
case 'OB_Z':
vert.write('vec3 rz = vec3(center.y, -center.x, center.z);')
vert.write('vec3 ry = vec3(-rz.z, rz.y, rz.x);')
vert.write('vec2 rotation = vec2(ry.x * c - ry.y * s, ry.x * s + ry.y * c);')
vert.write('spos.xyz = vec3(rotation.x, rotation.y, ry.z) + p_location;')
if (not shadowmap):
vert.write('wnormal = vec3(wnormal.y, -wnormal.x, wnormal.z);')
vert.write('wnormal = vec3(-wnormal.z, wnormal.y, wnormal.x);')
vert.write('vec2 n_rot = vec2(wnormal.x * c - wnormal.y * s, wnormal.x * s + wnormal.y * c);')
vert.write('wnormal = normalize(vec3(n_rot.x, n_rot.y, wnormal.z));')
case 'VEL':
vert.write('vec3 forward = -normalize(p_velocity);')
vert.write('if (length(forward) > 1e-5) {')
vert.write('vec3 world_up = vec3(0.0, 0.0, 1.0);')
vert.write('if (abs(dot(forward, world_up)) > 0.999) {')
vert.write('world_up = vec3(-1.0, 0.0, 0.0);')
vert.write('}')
vert.write('vec3 right = cross(world_up, forward);')
vert.write('if (length(right) < 1e-5) {')
vert.write('forward = -forward;')
vert.write('right = cross(world_up, forward);')
vert.write('}')
vert.write('right = normalize(right);')
vert.write('vec3 up = normalize(cross(forward, right));')
vert.write('mat3 rot = mat3(right, -forward, up);')
vert.write('mat3 phase = mat3(vec3(c, 0.0, -s), vec3(0.0, 1.0, 0.0), vec3(s, 0.0, c));')
vert.write('mat3 final_rot = rot * phase;')
vert.write('spos.xyz = final_rot * center + p_location;')
if (not shadowmap):
vert.write('wnormal = normalize(final_rot * wnormal);')
vert.write('}')
if rotation_factor_random != 0:
str_rotate_around = '''vec3 rotate_around(vec3 v, vec3 angle) {
// Rotate around X
float cx = cos(angle.x);
float sx = sin(angle.x);
v = vec3(v.x, v.y * cx - v.z * sx, v.y * sx + v.z * cx);
// Rotate around Y
float cy = cos(angle.y);
float sy = sin(angle.y);
v = vec3(v.x * cy + v.z * sy, v.y, -v.x * sy + v.z * cy);
// Rotate around Z
float cz = cos(angle.z);
float sz = sin(angle.z);
v = vec3(v.x * cz - v.y * sz, v.x * sz + v.y * cz, v.z);
return v;
}'''
vert.add_function(str_rotate_around)
vert.write(f'''vec3 r_angle = vec3((fhash(gl_InstanceID + pd_random + 6 * pd[0][3]) * 4 - 2) * {rotation_factor_random},
(fhash(gl_InstanceID + pd_random + 7 * pd[0][3]) * 4 - 2) * {rotation_factor_random},
(fhash(gl_InstanceID + pd_random + 8 * pd[0][3]) * 4 - 2) * {rotation_factor_random});''')
vert.write('vec3 r_center = spos.xyz - p_location;')
vert.write('r_center = rotate_around(r_center, r_angle);')
vert.write('spos.xyz = r_center + p_location;')
if not shadowmap:
vert.write('wnormal = normalize(rotate_around(wnormal, r_angle));')
# Particle fade
if mat_state.material.lnx_particle_flag and lnx.utils.get_rp().lnx_particles == 'On' and mat_state.material.lnx_particle_fade:
vert.add_out('float p_fade')

View File

@ -197,6 +197,10 @@ def init_properties():
items=[('Bullet', 'Bullet', 'Bullet'),
('Oimo', 'Oimo', 'Oimo')],
name="Physics Engine", default='Bullet', update=assets.invalidate_compiler_cache)
bpy.types.World.lnx_physics_fixed_step = FloatProperty(
name="Fixed Step", default=1/60, min=0, max=1,
description="Physics steps for fixed update"
)
bpy.types.World.lnx_physics_dbg_draw_wireframe = BoolProperty(
name="Collider Wireframes", default=False,
description="Draw wireframes of the physics collider meshes and suspensions of raycast vehicle simulations"
@ -358,6 +362,7 @@ def init_properties():
bpy.types.Object.lnx_rb_trigger = BoolProperty(name="Trigger", description="Disable contact response", default=False)
bpy.types.Object.lnx_rb_deactivation_time = FloatProperty(name="Deactivation Time", description="Delay putting rigid body into sleep", default=0.0)
bpy.types.Object.lnx_rb_ccd = BoolProperty(name="Continuous Collision Detection", description="Improve collision for fast moving objects", default=False)
bpy.types.Object.lnx_rb_interpolate = BoolProperty(name="Interpolation", description="Smooths out the object's transform on physics steps", default=False)
bpy.types.Object.lnx_rb_collision_filter_mask = bpy.props.BoolVectorProperty(
name="Collision Collections Filter Mask",
description="Collision collections rigid body interacts with",
@ -506,8 +511,9 @@ def init_properties():
bpy.types.Light.lnx_clip_end = FloatProperty(name="Clip End", default=50.0)
bpy.types.Light.lnx_fov = FloatProperty(name="Field of View", default=0.84)
bpy.types.Light.lnx_shadows_bias = FloatProperty(name="Bias", description="Depth offset to fight shadow acne", default=1.0)
bpy.types.World.lnx_light_ies_texture = StringProperty(name="IES Texture", default="")
bpy.types.World.lnx_light_clouds_texture = StringProperty(name="Clouds Texture", default="")
# For world
bpy.types.World.lnx_light_ies_texture = BoolProperty(name="IES Texture (iestexture.png)", default=False, update=assets.invalidate_compiler_cache)
bpy.types.World.lnx_light_clouds_texture = BoolProperty(name="Clouds Texture (cloudstexture.png)", default=False, update=assets.invalidate_compiler_cache)
bpy.types.World.lnx_rpcache_list = CollectionProperty(type=bpy.types.PropertyGroup)
bpy.types.World.lnx_scripts_list = CollectionProperty(type=bpy.types.PropertyGroup)
@ -542,8 +548,10 @@ def init_properties():
bpy.types.Node.lnx_watch = BoolProperty(name="Watch", description="Watch value of this node in debug console", default=False)
bpy.types.Node.lnx_version = IntProperty(name="Node Version", description="The version of an instanced node", default=0)
# Particles
bpy.types.ParticleSettings.lnx_count_mult = FloatProperty(name="Multiply Count", description="Multiply particle count when rendering in Leenkx", default=1.0)
bpy.types.ParticleSettings.lnx_auto_start = BoolProperty(name="Auto Start", description="Automatically start this particle system on load", default=True)
bpy.types.ParticleSettings.lnx_is_unique = BoolProperty(name="Is Unique", description="Make this particle system look different each time it starts", default=False)
bpy.types.ParticleSettings.lnx_loop = BoolProperty(name="Loop", description="Loop this particle system", default=False)
bpy.types.ParticleSettings.lnx_count_mult = FloatProperty(name="Multiply Count", description="Multiply particle count when rendering in Leenkx", default=1.0)
# Actions
bpy.types.Action.lnx_root_motion_pos = BoolProperty(name="Root Motion Position", description="Enable position root motion", default=False)
bpy.types.Action.lnx_root_motion_rot = BoolProperty(name="Root Motion Rotation", description="Enable rotation root motion", default=False)

View File

@ -608,6 +608,8 @@ class LnxRPListItem(bpy.types.PropertyGroup):
lnx_grain: BoolProperty(name="Film Grain", default=False, update=assets.invalidate_shader_cache)
lnx_grain_strength: FloatProperty(name="Strength", default=2.0, update=assets.invalidate_shader_cache)
lnx_sharpen: BoolProperty(name="Sharpen", default=False, update=assets.invalidate_shader_cache)
lnx_sharpen_color: FloatVectorProperty(name="Color", size=3, default=[0, 0, 0], subtype='COLOR', min=0, max=1, update=assets.invalidate_shader_cache)
lnx_sharpen_size: FloatProperty(name="Size", default=2.5, update=assets.invalidate_shader_cache)
lnx_sharpen_strength: FloatProperty(name="Strength", default=0.25, update=assets.invalidate_shader_cache)
lnx_fog: BoolProperty(name="Volumetric Fog", default=False, update=assets.invalidate_shader_cache)
lnx_fog_color: FloatVectorProperty(name="Color", size=3, subtype='COLOR', default=[0.5, 0.6, 0.7], min=0, max=1, update=assets.invalidate_shader_cache)

View File

@ -205,6 +205,8 @@ class LNX_PT_ParticlesPropsPanel(bpy.types.Panel):
if obj == None:
return
layout.prop(obj.settings, 'lnx_auto_start')
layout.prop(obj.settings, 'lnx_is_unique')
layout.prop(obj.settings, 'lnx_loop')
layout.prop(obj.settings, 'lnx_count_mult')
@ -240,6 +242,7 @@ class LNX_PT_PhysicsPropsPanel(bpy.types.Panel):
layout.prop(obj, 'lnx_rb_angular_friction')
layout.prop(obj, 'lnx_rb_trigger')
layout.prop(obj, 'lnx_rb_ccd')
layout.prop(obj, 'lnx_rb_interpolate')
if obj.soft_body is not None:
layout.prop(obj, 'lnx_soft_body_margin')
@ -303,8 +306,6 @@ class LNX_PT_DataPropsPanel(bpy.types.Panel):
layout.prop(obj.data, 'lnx_clip_end')
layout.prop(obj.data, 'lnx_fov')
layout.prop(obj.data, 'lnx_shadows_bias')
layout.prop(wrd, 'lnx_light_ies_texture')
layout.prop(wrd, 'lnx_light_clouds_texture')
elif obj.type == 'SPEAKER':
layout.prop(obj.data, 'lnx_play_on_start')
layout.prop(obj.data, 'lnx_loop')
@ -332,6 +333,8 @@ class LNX_PT_WorldPropsPanel(bpy.types.Panel):
if world is None:
return
layout.prop(world, 'lnx_light_ies_texture')
layout.prop(world, 'lnx_light_clouds_texture')
layout.prop(world, 'lnx_use_clouds')
col = layout.column(align=True)
col.enabled = world.lnx_use_clouds
@ -1991,10 +1994,18 @@ class LNX_PT_RenderPathCompositorPanel(bpy.types.Panel):
col.prop(rpdat, 'lnx_letterbox_size')
layout.separator()
col = layout.column()
col.prop(rpdat, 'lnx_sharpen')
col = col.column(align=True)
col.enabled = rpdat.lnx_sharpen
col.prop(rpdat, 'lnx_sharpen_color')
col.prop(rpdat, 'lnx_sharpen_size')
col.prop(rpdat, 'lnx_sharpen_strength')
layout.separator()
col = layout.column()
draw_conditional_prop(col, 'Distort', rpdat, 'lnx_distort', 'lnx_distort_strength')
draw_conditional_prop(col, 'Film Grain', rpdat, 'lnx_grain', 'lnx_grain_strength')
draw_conditional_prop(col, 'Sharpen', rpdat, 'lnx_sharpen', 'lnx_sharpen_strength')
draw_conditional_prop(col, 'Vignette', rpdat, 'lnx_vignette', 'lnx_vignette_strength')
layout.separator()
@ -2732,8 +2743,33 @@ class LeenkxUpdateListInstalledVSButton(bpy.types.Operator):
return {'FINISHED'}
class LNX_PT_PhysicsProps(bpy.types.Panel):
bl_label = "Leenkx Props"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_options = {'DEFAULT_CLOSED'}
bl_parent_id = "SCENE_PT_rigid_body_world"
class LNX_PT_BulletDebugDrawingPanel(bpy.types.Panel):
@classmethod
def poll(cls, context):
return context.scene.rigidbody_world is not None
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
wrd = bpy.data.worlds['Lnx']
if wrd.lnx_physics_engine != 'Bullet' and wrd.lnx_physics_engine != 'Oimo':
row = layout.row()
row.alert = True
row.label(text="Physics debug drawing is only supported for the Bullet and Oimo physics engines")
col = layout.column(align=False)
col.prop(wrd, "lnx_physics_fixed_step")
class LNX_PT_PhysicsDebugDrawingPanel(bpy.types.Panel):
bl_label = "Leenkx Debug Drawing"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
@ -2899,7 +2935,8 @@ __REG_CLASSES = (
LeenkxUpdateListAndroidEmulatorButton,
LeenkxUpdateListAndroidEmulatorRunButton,
LeenkxUpdateListInstalledVSButton,
LNX_PT_BulletDebugDrawingPanel,
LNX_PT_PhysicsProps,
LNX_PT_PhysicsDebugDrawingPanel,
LNX_OT_AddArmatureRootMotion,
scene.TLM_PT_Settings,
scene.TLM_PT_Denoise,

View File

@ -453,6 +453,7 @@ def write_config(resx, resy):
'rp_ssr': rpdat.rp_ssr != 'Off',
'rp_ss_refraction': rpdat.rp_ss_refraction != 'Off',
'rp_bloom': rpdat.rp_bloom != 'Off',
'rp_chromatic_aberration': rpdat.rp_chromatic_aberration != 'Off',
'rp_motionblur': rpdat.rp_motionblur != 'Off',
'rp_gi': rpdat.rp_voxels != "Off",
'rp_dynres': rpdat.rp_dynres
@ -772,6 +773,8 @@ const vec3 compoLetterboxColor = vec3(""" + str(round(rpdat.lnx_letterbox_color[
if lnx.utils.get_active_scene().view_settings.exposure != 0.0:
f.write(
"""const float compoExposureStrength = """ + str(round(lnx.utils.get_active_scene().view_settings.exposure * 100) / 100) + """;
const float compoSharpenSize = """ + str(round(rpdat.lnx_sharpen_size * 100) / 100) + """;
const vec3 compoSharpenColor = vec3(""" + str(round(rpdat.lnx_sharpen_color[0] * 100) / 100) + """, """ + str(round(rpdat.lnx_sharpen_color[1] * 100) / 100) + """, """ + str(round(rpdat.lnx_sharpen_color[2] * 100) / 100) + """);
""")
if rpdat.lnx_fog: