merge upstream

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

View File

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

View File

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

View File

@ -5,7 +5,7 @@
uniform sampler2D tex; uniform sampler2D tex;
#ifdef _CPostprocess #ifdef _CPostprocess
uniform vec3 PPComp13; uniform vec4 PPComp13;
#endif #endif
in vec2 texCoord; in vec2 texCoord;
@ -43,13 +43,17 @@ void main() {
#ifdef _CPostprocess #ifdef _CPostprocess
float max_distort = PPComp13.x; float max_distort = PPComp13.x;
int num_iter = int(PPComp13.y); int num_iter = int(PPComp13.y);
int CAType = int(PPComp13.z);
int on = int(PPComp13.w);
#else #else
float max_distort = compoChromaticStrength; float max_distort = compoChromaticStrength;
int num_iter = compoChromaticSamples; int num_iter = compoChromaticSamples;
int CAType = compoChromaticType;
int on = 1;
#endif #endif
// Spectral // Spectral
if (compoChromaticType == 1) { if (CAType == 1) {
float reci_num_iter_f = 1.0 / float(num_iter); float reci_num_iter_f = 1.0 / float(num_iter);
vec2 resolution = vec2(1,1); vec2 resolution = vec2(1,1);
@ -64,7 +68,7 @@ void main() {
sumcol += w * texture(tex, barrelDistortion(uv, 0.6 * max_distort * t)); 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 // Simple
@ -73,6 +77,7 @@ void main() {
col.x = texture(tex, texCoord + ((vec2(0.0, 1.0) * max_distort) / vec2(1000.0))).x; 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.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; 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 PPComp6;
uniform vec3 PPComp7; uniform vec3 PPComp7;
uniform vec3 PPComp8; uniform vec3 PPComp8;
uniform vec3 PPComp11;
uniform vec3 PPComp14; uniform vec3 PPComp14;
uniform vec4 PPComp15; uniform vec4 PPComp15;
uniform vec4 PPComp16;
uniform vec4 PPComp18;
#endif #endif
// #ifdef _CPos // #ifdef _CPos
@ -106,6 +109,16 @@ in vec2 texCoord;
out vec4 fragColor; out vec4 fragColor;
#ifdef _CFog #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 vec3 compoFogColor = vec3(0.5, 0.6, 0.7);
// const float compoFogAmountA = 1.0; // b = 0.01 // const float compoFogAmountA = 1.0; // b = 0.01
// const float compoFogAmountB = 1.0; // c = 0.1 // const float compoFogAmountB = 1.0; // c = 0.1
@ -118,8 +131,8 @@ out vec4 fragColor;
// } // }
vec3 applyFog(vec3 rgb, float distance) { vec3 applyFog(vec3 rgb, float distance) {
// float fogAmount = 1.0 - exp(-distance * compoFogAmountA); // float fogAmount = 1.0 - exp(-distance * compoFogAmountA);
float fogAmount = 1.0 - exp(-distance * (compoFogAmountA / 100)); float fogAmount = 1.0 - exp(-distance * (FogAmountA / 100));
return mix(rgb, compoFogColor, fogAmount); return mix(rgb, FogColor, fogAmount);
} }
#endif #endif
@ -349,16 +362,22 @@ void main() {
#ifdef _CSharpen #ifdef _CSharpen
#ifdef _CPostprocess #ifdef _CPostprocess
float strengthSharpen = PPComp14.y; float strengthSharpen = PPComp14.y;
vec3 SharpenColor = vec3(PPComp16.x, PPComp16.y, PPComp16.z);
float SharpenSize = PPComp16.w;
#else #else
float strengthSharpen = compoSharpenStrength; float strengthSharpen = compoSharpenStrength;
vec3 SharpenColor = compoSharpenColor;
float SharpenSize = compoSharpenSize;
#endif #endif
vec3 col1 = 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) * 1.5, 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) * 1.5, 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) * 1.5, 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; 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 #endif
#ifdef _CFog #ifdef _CFog
@ -407,7 +426,11 @@ void main() {
#endif #endif
#ifdef _CExposure #ifdef _CExposure
fragColor.rgb += fragColor.rgb * compoExposureStrength; #ifdef _CPostprocess
fragColor.rgb+=fragColor.rgb*PPComp8.x;
#else
fragColor.rgb+= fragColor.rgb*compoExposureStrength;
#endif
#endif #endif
#ifdef _CPostprocess #ifdef _CPostprocess
@ -415,8 +438,13 @@ void main() {
#endif #endif
#ifdef _AutoExposure #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); 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 #endif
// Clamp color to get rid of INF values that don't work for the tone mapping below // 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", "name": "PPComp15",
"link": "_PPComp15", "link": "_PPComp15",
"ifdef": ["_CPostprocess"] "ifdef": ["_CPostprocess"]
},
{
"name": "PPComp16",
"link": "_PPComp16",
"ifdef": ["_CPostprocess"]
},
{
"name": "PPComp18",
"link": "_PPComp18",
"ifdef": ["_CPostprocess"]
} }
], ],
"texture_params": [], "texture_params": [],

View File

@ -2,13 +2,22 @@
#include "compiled.inc" #include "compiled.inc"
#ifdef _CPostprocess
uniform vec3 PPComp8;
#endif
uniform sampler2D tex; uniform sampler2D tex;
in vec2 texCoord; in vec2 texCoord;
out vec4 fragColor; out vec4 fragColor;
void main() { 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 + 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.2, 0.2), 0.0).rgb +
textureLod(tex, vec2(0.8, 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_source": "source_alpha",
"blend_destination": "inverse_source_alpha", "blend_destination": "inverse_source_alpha",
"blend_operation": "add", "blend_operation": "add",
"links": [], "links": [
{
"name": "PPComp8",
"link": "_PPComp8",
"ifdef": ["_CPostprocess"]
}
],
"texture_params": [], "texture_params": [],
"vertex_shader": "../include/pass.vert.glsl", "vertex_shader": "../include/pass.vert.glsl",
"fragment_shader": "histogram_pass.frag.glsl" "fragment_shader": "histogram_pass.frag.glsl"

View File

@ -11,6 +11,11 @@
#include "std/light_common.glsl" #include "std/light_common.glsl"
#endif #endif
#ifdef _CPostprocess
uniform vec3 PPComp11;
uniform vec4 PPComp17;
#endif
uniform sampler2D gbufferD; uniform sampler2D gbufferD;
uniform sampler2D snoise; uniform sampler2D snoise;
@ -87,7 +92,13 @@ out float fragColor;
const float tScat = 0.08; const float tScat = 0.08;
const float tAbs = 0.0; const float tAbs = 0.0;
const float tExt = tScat + tAbs; 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; const float lighting = 0.4;
void rayStep(inout vec3 curPos, inout float curOpticalDepth, inout float scatteredLightAmount, float stepLenWorld, vec3 viewVecNorm) { 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); rayStep(curPos, curOpticalDepth, scatteredLightAmount, stepLenWorld, viewVecNorm);
} }
fragColor = scatteredLightAmount * volumAirTurbidity; fragColor = scatteredLightAmount * AirTurbidity;
} }

View File

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

View File

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

View File

@ -16,6 +16,7 @@ class Trait {
var _remove: Array<Void->Void> = null; var _remove: Array<Void->Void> = null;
var _update: Array<Void->Void> = null; var _update: Array<Void->Void> = null;
var _lateUpdate: 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 _render: Array<kha.graphics4.Graphics->Void> = null;
var _render2D: Array<kha.graphics2.Graphics->Void> = null; var _render2D: Array<kha.graphics2.Graphics->Void> = null;
@ -87,6 +88,23 @@ class Trait {
App.removeLateUpdate(f); 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. Add render handler.
**/ **/

View File

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

View File

@ -159,9 +159,17 @@ class Animation {
if(markerEvents.get(sampler) != null){ if(markerEvents.get(sampler) != null){
for (i in 0...anim.marker_frames.length) { for (i in 0...anim.marker_frames.length) {
if (frameIndex == anim.marker_frames[i]) { if (frameIndex == anim.marker_frames[i]) {
var marketAct = markerEvents.get(sampler); var markerAct = markerEvents.get(sampler);
var ar = marketAct.get(anim.marker_names[i]); var ar = markerAct.get(anim.marker_names[i]);
if (ar != null) for (f in ar) f(); 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; lastFrameIndex = frameIndex;

View File

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

View File

@ -2,6 +2,7 @@ package iron.object;
#if lnx_particles #if lnx_particles
import kha.FastFloat;
import kha.graphics4.Usage; import kha.graphics4.Usage;
import kha.arrays.Float32Array; import kha.arrays.Float32Array;
import iron.data.Data; import iron.data.Data;
@ -16,39 +17,45 @@ import iron.math.Vec4;
class ParticleSystem { class ParticleSystem {
public var data: ParticleData; public var data: ParticleData;
public var speed = 1.0; public var speed = 1.0;
var currentSpeed = 0.0;
var particles: Array<Particle>; var particles: Array<Particle>;
var ready: Bool; var ready: Bool;
public var frameRate = 24; var frameRate = 24;
public var lifetime = 0.0; var lifetime = 0.0;
public var animtime = 0.0; var looptime = 0.0;
public var time = 0.0; var animtime = 0.0;
public var spawnRate = 0.0; var time = 0.0;
var spawnRate = 0.0;
var seed = 0; var seed = 0;
public var r: TParticleData; var r: TParticleData;
public var gx: Float; var gx: Float;
public var gy: Float; var gy: Float;
public var gz: Float; var gz: Float;
public var alignx: Float; var alignx: Float;
public var aligny: Float; var aligny: Float;
public var alignz: Float; var alignz: Float;
var dimx: Float; var dimx: Float;
var dimy: Float; var dimy: Float;
var tilesx: Int; var tilesx: Int;
var tilesy: Int; var tilesy: Int;
var tilesFramerate: Int; var tilesFramerate: Int;
public var count = 0; var count = 0;
public var lap = 0; var lap = 0;
public var lapTime = 0.0; var lapTime = 0.0;
var m = Mat4.identity(); var m = Mat4.identity();
var ownerLoc = new Vec4(); var ownerLoc = new Vec4();
var ownerRot = new Quat(); var ownerRot = new Quat();
var ownerScl = new Vec4(); var ownerScl = new Vec4();
var random = 0.0;
public function new(sceneName: String, pref: TParticleReference) { public function new(sceneName: String, pref: TParticleReference) {
seed = pref.seed; seed = pref.seed;
currentSpeed = speed;
speed = 0;
particles = []; particles = [];
ready = false; ready = false;
@ -65,33 +72,61 @@ class ParticleSystem {
gy = 0; gy = 0;
gz = -9.81 * r.weight_gravity; gz = -9.81 * r.weight_gravity;
} }
alignx = r.object_align_factor[0] / 2; alignx = r.object_align_factor[0];
aligny = r.object_align_factor[1] / 2; aligny = r.object_align_factor[1];
alignz = r.object_align_factor[2] / 2; alignz = r.object_align_factor[2];
looptime = (r.frame_end - r.frame_start) / frameRate;
lifetime = r.lifetime / 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; spawnRate = ((r.frame_end - r.frame_start) / r.count) / frameRate;
for (i in 0...r.count) { for (i in 0...r.count) {
var particle = new Particle(i); particles.push(new Particle(i));
particle.sr = 1 - Math.random() * r.size_random;
particles.push(particle);
} }
ready = true; 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() { public function pause() {
lifetime = 0; speed = 0;
} }
public function resume() { public function resume() {
lifetime = r.lifetime / frameRate; 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) { public function update(object: MeshObject, owner: MeshObject) {
if (!ready || object == null || speed == 0.0) return; if (!ready || object == null || speed == 0.0) return;
if (iron.App.pauseUpdates) return;
var prevLap = lap;
// Copy owner world transform but discard scale // Copy owner world transform but discard scale
owner.transform.world.decompose(ownerLoc, ownerRot, ownerScl); owner.transform.world.decompose(ownerLoc, ownerRot, ownerScl);
@ -115,17 +150,21 @@ class ParticleSystem {
} }
// Animate // Animate
time += Time.delta * speed; time += Time.renderDelta * speed; // realDelta to renderDelta
lap = Std.int(time / animtime); lap = Std.int(time / animtime);
lapTime = time - lap * animtime; lapTime = time - lap * animtime;
count = Std.int(lapTime / spawnRate); count = Std.int(lapTime / spawnRate);
if (lap > prevLap && !r.loop) {
end();
}
updateGpu(object, owner); updateGpu(object, owner);
} }
public function getData(): Mat4 { public function getData(): Mat4 {
var hair = r.type == 1; var hair = r.type == 1;
m._00 = r.loop ? animtime : -animtime; m._00 = animtime;
m._01 = hair ? 1 / particles.length : spawnRate; m._01 = hair ? 1 / particles.length : spawnRate;
m._02 = hair ? 1 : lifetime; m._02 = hair ? 1 : lifetime;
m._03 = particles.length; m._03 = particles.length;
@ -133,9 +172,9 @@ class ParticleSystem {
m._11 = hair ? 0 : aligny; m._11 = hair ? 0 : aligny;
m._12 = hair ? 0 : alignz; m._12 = hair ? 0 : alignz;
m._13 = hair ? 0 : r.factor_random; m._13 = hair ? 0 : r.factor_random;
m._20 = hair ? 0 : gx * r.mass; m._20 = hair ? 0 : gx;
m._21 = hair ? 0 : gy * r.mass; m._21 = hair ? 0 : gy;
m._22 = hair ? 0 : gz * r.mass; m._22 = hair ? 0 : gz;
m._23 = hair ? 0 : r.lifetime_random; m._23 = hair ? 0 : r.lifetime_random;
m._30 = tilesx; m._30 = tilesx;
m._31 = tilesy; m._31 = tilesy;
@ -144,13 +183,25 @@ class ParticleSystem {
return m; 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) { function updateGpu(object: MeshObject, owner: MeshObject) {
if (!object.data.geom.instanced) setupGeomGpu(object, owner); if (!object.data.geom.instanced) setupGeomGpu(object, owner);
// GPU particles transform is attached to owner object // GPU particles transform is attached to owner object
} }
public function setupGeomGpu(object: MeshObject, owner: MeshObject) { function setupGeomGpu(object: MeshObject, owner: MeshObject) {
var instancedData = new Float32Array(particles.length * 6); var instancedData = new Float32Array(particles.length * 3);
var i = 0; var i = 0;
var normFactor = 1 / 32767; // pa.values are not normalized 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 ] * 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 + 1] * normFactor * scaleFactor.y); i++;
instancedData.set(i, pa.values[j * pa.size + 2] * normFactor * scaleFactor.z); 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 case 1: // Face
@ -196,10 +243,6 @@ class ParticleSystem {
instancedData.set(i, pos.x * normFactor * scaleFactor.x); i++; instancedData.set(i, pos.x * normFactor * scaleFactor.x); i++;
instancedData.set(i, pos.y * normFactor * scaleFactor.y); i++; instancedData.set(i, pos.y * normFactor * scaleFactor.y); i++;
instancedData.set(i, pos.z * normFactor * scaleFactor.z); 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 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.x); i++;
instancedData.set(i, (Math.random() * 2.0 - 1.0) * scaleFactorVolume.y); 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, (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 { function fhash(n: Int): Float {
@ -255,10 +294,11 @@ class ParticleSystem {
class Particle { class Particle {
public var i: Int; public var i: Int;
public var px = 0.0;
public var py = 0.0; public var x = 0.0;
public var pz = 0.0; public var y = 0.0;
public var sr = 1.0; // Size random public var z = 0.0;
public var cameraDistance: Float; public var cameraDistance: Float;
public function new(i: Int) { public function new(i: Int) {

View File

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

View File

@ -1113,6 +1113,26 @@ class Uniforms {
case "_texUnpack": { case "_texUnpack": {
f = texUnpack != null ? texUnpack : 1.0; 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) { if (f == null && externalFloatLinks != null) {

View File

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

View File

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

View File

@ -1,99 +1,99 @@
package leenkx.logicnode; package leenkx.logicnode;
import iron.data.SceneFormat.TSceneFormat; import iron.data.SceneFormat.TSceneFormat;
import iron.data.Data; import iron.data.Data;
import iron.object.Object; import iron.object.Object;
class AddParticleToObjectNode extends LogicNode { class AddParticleToObjectNode extends LogicNode {
public var property0: String; public var property0: String;
public function new(tree: LogicTree) { public function new(tree: LogicTree) {
super(tree); super(tree);
} }
override function run(from: Int) { override function run(from: Int) {
#if lnx_particles #if lnx_particles
if (property0 == 'Scene Active'){ if (property0 == 'Scene Active'){
var objFrom: Object = inputs[1].get(); var objFrom: Object = inputs[1].get();
var slot: Int = inputs[2].get(); var slot: Int = inputs[2].get();
var objTo: Object = inputs[3].get(); var objTo: Object = inputs[3].get();
if (objFrom == null || objTo == null) return; if (objFrom == null || objTo == null) return;
var mobjFrom = cast(objFrom, iron.object.MeshObject); var mobjFrom = cast(objFrom, iron.object.MeshObject);
var psys = mobjFrom.particleSystems != null ? mobjFrom.particleSystems[slot] : var psys = mobjFrom.particleSystems != null ? mobjFrom.particleSystems[slot] :
mobjFrom.particleOwner != null && mobjFrom.particleOwner.particleSystems != null ? mobjFrom.particleOwner.particleSystems[slot] : null; mobjFrom.particleOwner != null && mobjFrom.particleOwner.particleSystems != null ? mobjFrom.particleOwner.particleSystems[slot] : null;
if (psys == null) return; if (psys == null) return;
var mobjTo = cast(objTo, iron.object.MeshObject); var mobjTo = cast(objTo, iron.object.MeshObject);
mobjTo.setupParticleSystem(iron.Scene.active.raw.name, {name: 'LnxPS', seed: 0, particle: psys.r.name}); mobjTo.setupParticleSystem(iron.Scene.active.raw.name, {name: 'LnxPS', seed: 0, particle: @:privateAccess psys.r.name});
mobjTo.render_emitter = inputs[4].get(); mobjTo.render_emitter = inputs[4].get();
iron.Scene.active.spawnObject(psys.data.raw.instance_object, null, function(o: Object) { iron.Scene.active.spawnObject(psys.data.raw.instance_object, null, function(o: Object) {
if (o != null) { if (o != null) {
var c: iron.object.MeshObject = cast o; var c: iron.object.MeshObject = cast o;
if (mobjTo.particleChildren == null) mobjTo.particleChildren = []; if (mobjTo.particleChildren == null) mobjTo.particleChildren = [];
mobjTo.particleChildren.push(c); mobjTo.particleChildren.push(c);
c.particleOwner = mobjTo; c.particleOwner = mobjTo;
c.particleIndex = mobjTo.particleChildren.length - 1; c.particleIndex = mobjTo.particleChildren.length - 1;
} }
}); });
var oslot: Int = mobjTo.particleSystems.length-1; var oslot: Int = mobjTo.particleSystems.length-1;
var opsys = mobjTo.particleSystems[oslot]; var opsys = mobjTo.particleSystems[oslot];
opsys.setupGeomGpu(mobjTo.particleChildren[oslot], mobjTo); @:privateAccess opsys.setupGeomGpu(mobjTo.particleChildren[oslot], mobjTo);
} else { } else {
var sceneName: String = inputs[1].get(); var sceneName: String = inputs[1].get();
var objectName: String = inputs[2].get(); var objectName: String = inputs[2].get();
var slot: Int = inputs[3].get(); var slot: Int = inputs[3].get();
var mobjTo: Object = inputs[4].get(); var mobjTo: Object = inputs[4].get();
var mobjTo = cast(mobjTo, iron.object.MeshObject); var mobjTo = cast(mobjTo, iron.object.MeshObject);
#if lnx_json #if lnx_json
sceneName += ".json"; sceneName += ".json";
#elseif lnx_compress #elseif lnx_compress
sceneName += ".lz4"; sceneName += ".lz4";
#end #end
Data.getSceneRaw(sceneName, (rawScene: TSceneFormat) -> { Data.getSceneRaw(sceneName, (rawScene: TSceneFormat) -> {
for (obj in rawScene.objects) { for (obj in rawScene.objects) {
if (obj.name == objectName) { if (obj.name == objectName) {
mobjTo.setupParticleSystem(sceneName, obj.particle_refs[slot]); mobjTo.setupParticleSystem(sceneName, obj.particle_refs[slot]);
mobjTo.render_emitter = inputs[5].get(); mobjTo.render_emitter = inputs[5].get();
iron.Scene.active.spawnObject(rawScene.particle_datas[slot].instance_object, null, function(o: Object) { iron.Scene.active.spawnObject(rawScene.particle_datas[slot].instance_object, null, function(o: Object) {
if (o != null) { if (o != null) {
var c: iron.object.MeshObject = cast o; var c: iron.object.MeshObject = cast o;
if (mobjTo.particleChildren == null) mobjTo.particleChildren = []; if (mobjTo.particleChildren == null) mobjTo.particleChildren = [];
mobjTo.particleChildren.push(c); mobjTo.particleChildren.push(c);
c.particleOwner = mobjTo; c.particleOwner = mobjTo;
c.particleIndex = mobjTo.particleChildren.length - 1; c.particleIndex = mobjTo.particleChildren.length - 1;
} }
}, true, rawScene); }, true, rawScene);
var oslot: Int = mobjTo.particleSystems.length-1; var oslot: Int = mobjTo.particleSystems.length-1;
var opsys = mobjTo.particleSystems[oslot]; var opsys = mobjTo.particleSystems[oslot];
opsys.setupGeomGpu(mobjTo.particleChildren[oslot], mobjTo); @:privateAccess opsys.setupGeomGpu(mobjTo.particleChildren[oslot], mobjTo);
break; break;
} }
} }
}); });
} }
#end #end
runOutput(0); 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; package leenkx.logicnode;
class CameraSetNode extends LogicNode { class CameraSetNode extends LogicNode {
public var property0: String;
public function new(tree:LogicTree) { public function new(tree:LogicTree) {
super(tree); super(tree);
} }
override function run(from:Int) { 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 switch (property0) {
leenkx.renderpath.Postprocess.camera_uniforms[2] = inputs[3].get();//Camera: ISO case 'F-stop':
leenkx.renderpath.Postprocess.camera_uniforms[3] = inputs[4].get();//Camera: Exposure Compensation leenkx.renderpath.Postprocess.camera_uniforms[0] = inputs[1].get();//Camera: F-Number
leenkx.renderpath.Postprocess.camera_uniforms[4] = inputs[5].get();//Fisheye Distortion case 'Shutter Time':
leenkx.renderpath.Postprocess.camera_uniforms[5] = inputs[6].get();//DoF AutoFocus §§ If true, it ignores the DoF Distance setting leenkx.renderpath.Postprocess.camera_uniforms[1] = inputs[1].get();//Camera: Shutter time
leenkx.renderpath.Postprocess.camera_uniforms[6] = inputs[7].get();//DoF Distance case 'ISO':
leenkx.renderpath.Postprocess.camera_uniforms[7] = inputs[8].get();//DoF Focal Length mm leenkx.renderpath.Postprocess.camera_uniforms[2] = inputs[1].get();//Camera: ISO
leenkx.renderpath.Postprocess.camera_uniforms[8] = inputs[9].get();//DoF F-Stop case 'Exposure Compensation':
leenkx.renderpath.Postprocess.camera_uniforms[9] = inputs[10].get();//Tonemapping Method leenkx.renderpath.Postprocess.camera_uniforms[3] = inputs[1].get();//Camera: Exposure Compensation
leenkx.renderpath.Postprocess.camera_uniforms[10] = inputs[11].get();//Distort case 'Fisheye Distortion':
leenkx.renderpath.Postprocess.camera_uniforms[11] = inputs[12].get();//Film Grain leenkx.renderpath.Postprocess.camera_uniforms[4] = inputs[1].get();//Fisheye Distortion
leenkx.renderpath.Postprocess.camera_uniforms[12] = inputs[13].get();//Sharpen case 'Auto Focus':
leenkx.renderpath.Postprocess.camera_uniforms[13] = inputs[14].get();//Vignette 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); runOutput(0);
} }

View File

@ -10,6 +10,7 @@ class ChromaticAberrationGetNode extends LogicNode {
return switch (from) { return switch (from) {
case 0: leenkx.renderpath.Postprocess.chromatic_aberration_uniforms[0]; case 0: leenkx.renderpath.Postprocess.chromatic_aberration_uniforms[0];
case 1: leenkx.renderpath.Postprocess.chromatic_aberration_uniforms[1]; case 1: leenkx.renderpath.Postprocess.chromatic_aberration_uniforms[1];
case 2: leenkx.renderpath.Postprocess.chromatic_aberration_uniforms[2];
default: 0.0; 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[0] = inputs[1].get();
leenkx.renderpath.Postprocess.chromatic_aberration_uniforms[1] = inputs[2].get(); leenkx.renderpath.Postprocess.chromatic_aberration_uniforms[1] = inputs[2].get();
leenkx.renderpath.Postprocess.chromatic_aberration_uniforms[2] = inputs[3].get();
runOutput(0); runOutput(0);
} }

View File

@ -8,7 +8,7 @@ class GetFPSNode extends LogicNode {
override function get(from: Int): Dynamic { override function get(from: Int): Dynamic {
if (from == 0) { 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))) { if ((fps == Math.POSITIVE_INFINITY) || (fps == Math.NEGATIVE_INFINITY) || (Math.isNaN(fps))) {
return 0; return 0;
} }

View File

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

View File

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

View File

@ -1,64 +1,64 @@
package leenkx.logicnode; package leenkx.logicnode;
import iron.object.Object; import iron.object.Object;
class RemoveParticleFromObjectNode extends LogicNode { class RemoveParticleFromObjectNode extends LogicNode {
public var property0: String; public var property0: String;
public function new(tree: LogicTree) { public function new(tree: LogicTree) {
super(tree); super(tree);
} }
override function run(from: Int) { override function run(from: Int) {
#if lnx_particles #if lnx_particles
var object: Object = inputs[1].get(); var object: Object = inputs[1].get();
if (object == null) return; if (object == null) return;
var mo = cast(object, iron.object.MeshObject); var mo = cast(object, iron.object.MeshObject);
if (mo.particleSystems == null) return; if (mo.particleSystems == null) return;
if (property0 == 'All'){ if (property0 == 'All'){
mo.particleSystems = null; mo.particleSystems = null;
for (c in mo.particleChildren) c.remove(); for (c in mo.particleChildren) c.remove();
mo.particleChildren = null; mo.particleChildren = null;
mo.particleOwner = null; mo.particleOwner = null;
mo.render_emitter = true; mo.render_emitter = true;
} }
else { else {
var slot: Int = -1; var slot: Int = -1;
if (property0 == 'Name'){ if (property0 == 'Name'){
var name: String = inputs[2].get(); var name: String = inputs[2].get();
for (i => psys in mo.particleSystems){ for (i => psys in mo.particleSystems){
if (psys.r.name == name){ slot = i; break; } if (@:privateAccess psys.r.name == name){ slot = i; break; }
} }
} }
else slot = inputs[2].get(); else slot = inputs[2].get();
if (mo.particleSystems.length > slot){ if (mo.particleSystems.length > slot){
for (i in slot+1...mo.particleSystems.length){ for (i in slot+1...mo.particleSystems.length){
var mi = cast(mo.particleChildren[i], iron.object.MeshObject); var mi = cast(mo.particleChildren[i], iron.object.MeshObject);
mi.particleIndex = mi.particleIndex - 1; mi.particleIndex = mi.particleIndex - 1;
} }
mo.particleSystems.splice(slot, 1); mo.particleSystems.splice(slot, 1);
mo.particleChildren[slot].remove(); mo.particleChildren[slot].remove();
mo.particleChildren.splice(slot, 1); mo.particleChildren.splice(slot, 1);
} }
if (slot == 0){ if (slot == 0){
mo.particleSystems = null; mo.particleSystems = null;
mo.particleChildren = null; mo.particleChildren = null;
mo.particleOwner = null; mo.particleOwner = null;
mo.render_emitter = true; mo.render_emitter = true;
} }
} }
#end #end
runOutput(0); 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; on ? leenkx.data.Config.raw.rp_ssrefr = true : leenkx.data.Config.raw.rp_ssrefr = false;
case "Bloom": case "Bloom":
on ? leenkx.data.Config.raw.rp_bloom = true : leenkx.data.Config.raw.rp_bloom = false; 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": case "GI":
on ? leenkx.data.Config.raw.rp_gi = true : leenkx.data.Config.raw.rp_gi = false; on ? leenkx.data.Config.raw.rp_gi = true : leenkx.data.Config.raw.rp_gi = false;
case "Motion Blur": case "Motion Blur":

View File

@ -1,75 +1,81 @@
package leenkx.logicnode; package leenkx.logicnode;
import iron.object.Object; import iron.object.Object;
class SetParticleDataNode extends LogicNode { class SetParticleDataNode extends LogicNode {
public var property0: String; public var property0: String;
public function new(tree: LogicTree) { public function new(tree: LogicTree) {
super(tree); super(tree);
} }
override function run(from: Int) { override function run(from: Int) {
#if lnx_particles #if lnx_particles
var object: Object = inputs[1].get(); var object: Object = inputs[1].get();
var slot: Int = inputs[2].get(); var slot: Int = inputs[2].get();
if (object == null) return; if (object == null) return;
var mo = cast(object, iron.object.MeshObject); var mo = cast(object, iron.object.MeshObject);
var psys = mo.particleSystems != null ? mo.particleSystems[slot] : var psys = mo.particleSystems != null ? mo.particleSystems[slot] :
mo.particleOwner != null && mo.particleOwner.particleSystems != null ? mo.particleOwner.particleSystems[slot] : null; if (psys == null) return; mo.particleOwner != null && mo.particleOwner.particleSystems != null ? mo.particleOwner.particleSystems[slot] : null; if (psys == null) return;
switch (property0) { switch (property0) {
case 'Particle Size': case 'Particle Size':
psys.r.particle_size = inputs[3].get(); @:privateAccess psys.r.particle_size = inputs[3].get();
case 'Frame Start': case 'Frame Start':
psys.r.frame_start = inputs[3].get(); @:privateAccess psys.r.frame_start = inputs[3].get();
psys.animtime = (psys.r.frame_end - psys.r.frame_start) / psys.frameRate; @:privateAccess psys.animtime = (@:privateAccess psys.r.frame_end - @:privateAccess psys.r.frame_start) / @:privateAccess psys.frameRate;
psys.spawnRate = ((psys.r.frame_end - psys.r.frame_start) / psys.count) / psys.frameRate; @:privateAccess psys.spawnRate = ((@:privateAccess psys.r.frame_end - @:privateAccess psys.r.frame_start) / @:privateAccess psys.count) / @:privateAccess psys.frameRate;
case 'Frame End': case 'Frame End':
psys.r.frame_end = inputs[3].get(); @:privateAccess psys.r.frame_end = inputs[3].get();
psys.animtime = (psys.r.frame_end - psys.r.frame_start) / psys.frameRate; @:privateAccess psys.animtime = (@:privateAccess psys.r.frame_end - @:privateAccess psys.r.frame_start) / @:privateAccess psys.frameRate;
psys.spawnRate = ((psys.r.frame_end - psys.r.frame_start) / psys.count) / psys.frameRate; @:privateAccess psys.spawnRate = ((@:privateAccess psys.r.frame_end - @:privateAccess psys.r.frame_start) / @:privateAccess psys.count) / @:privateAccess psys.frameRate;
case 'Lifetime': case 'Lifetime':
psys.lifetime = inputs[3].get() / psys.frameRate; @:privateAccess psys.lifetime = inputs[3].get() / @:privateAccess psys.frameRate;
case 'Lifetime Random': case 'Lifetime Random':
psys.r.lifetime_random = inputs[3].get(); @:privateAccess psys.r.lifetime_random = inputs[3].get();
case 'Emit From': case 'Emit From':
var emit_from: Int = inputs[3].get(); var emit_from: Int = inputs[3].get();
if (emit_from == 0 || emit_from == 1 || emit_from == 2) { if (emit_from == 0 || emit_from == 1 || emit_from == 2) {
psys.r.emit_from = emit_from; @:privateAccess 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); @: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 'Velocity': case 'Auto Start':
var vel: iron.math.Vec3 = inputs[3].get(); @:privateAccess psys.r.auto_start = inputs[3].get();
psys.alignx = vel.x / 2; case 'Is Unique':
psys.aligny = vel.y / 2; @:privateAccess psys.r.is_unique = inputs[3].get();
psys.alignz = vel.z / 2; case 'Loop':
case 'Velocity Random': @:privateAccess psys.r.loop = inputs[3].get();
psys.r.factor_random = inputs[3].get(); case 'Velocity':
case 'Weight Gravity': var vel: iron.math.Vec3 = inputs[3].get();
psys.r.weight_gravity = inputs[3].get(); @:privateAccess psys.alignx = vel.x;
if (iron.Scene.active.raw.gravity != null) { @:privateAccess psys.aligny = vel.y;
psys.gx = iron.Scene.active.raw.gravity[0] * psys.r.weight_gravity; @:privateAccess psys.alignz = vel.z;
psys.gy = iron.Scene.active.raw.gravity[1] * psys.r.weight_gravity; case 'Velocity Random':
psys.gz = iron.Scene.active.raw.gravity[2] * psys.r.weight_gravity; psys.r.factor_random = inputs[3].get();
} case 'Weight Gravity':
else { psys.r.weight_gravity = inputs[3].get();
psys.gx = 0; if (iron.Scene.active.raw.gravity != null) {
psys.gy = 0; @:privateAccess psys.gx = iron.Scene.active.raw.gravity[0] * @:privateAccess psys.r.weight_gravity;
psys.gz = -9.81 * 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;
case 'Speed': }
psys.speed = inputs[3].get(); else {
default: @:privateAccess psys.gx = 0;
null; @:privateAccess psys.gy = 0;
} @:privateAccess psys.gz = -9.81 * @:privateAccess psys.r.weight_gravity;
}
#end case 'Speed':
psys.speed = inputs[3].get();
runOutput(0); 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() { public static function applyConfig() {
#if lnx_config #if lnx_config
var config = leenkx.data.Config.raw; 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 // Resize shadow map
var l = path.light; var l = path.light;
if (l.data.raw.type == "sun" && l.data.raw.shadowmap_size != config.rp_shadowmap_cascade) { if (l != null){
l.data.raw.shadowmap_size = config.rp_shadowmap_cascade; if (l.data.raw.type == "sun" && l.data.raw.shadowmap_size != config.rp_shadowmap_cascade) {
var rt = path.renderTargets.get("shadowMap"); l.data.raw.shadowmap_size = config.rp_shadowmap_cascade;
if (rt != null) { var rt = path.renderTargets.get("shadowMap");
rt.unload(); if (rt != null) {
path.renderTargets.remove("shadowMap"); rt.unload();
path.renderTargets.remove("shadowMap");
}
} }
} else if (l.data.raw.shadowmap_size != config.rp_shadowmap_cube) {
else if (l.data.raw.shadowmap_size != config.rp_shadowmap_cube) { l.data.raw.shadowmap_size = config.rp_shadowmap_cube;
l.data.raw.shadowmap_size = config.rp_shadowmap_cube; var rt = path.renderTargets.get("shadowMapCube");
var rt = path.renderTargets.get("shadowMapCube"); if (rt != null) {
if (rt != null) { rt.unload();
rt.unload(); path.renderTargets.remove("shadowMapCube");
path.renderTargets.remove("shadowMapCube"); }
} }
} }
if (superSample != config.rp_supersample) { if (superSample != config.rp_supersample) {

View File

@ -54,10 +54,15 @@ class Postprocess {
0, //9: Tonemapping Method 0, //9: Tonemapping Method
2.0, //10: Distort 2.0, //10: Distort
2.0, //11: Film Grain 2.0, //11: Film Grain
0.25, //12: Sharpen 0.25, //12: Sharpen Strength
0.7 //13: Vignette 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 = [ public static var tonemapper_uniforms = [
1.0, //0: Slope 1.0, //0: Slope
1.0, //1: Toe 1.0, //1: Toe
@ -102,7 +107,30 @@ class Postprocess {
public static var chromatic_aberration_uniforms = [ public static var chromatic_aberration_uniforms = [
2.0, //0: Strength 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 { 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.x = lenstexture_uniforms[2]; //Lum min
v.y = lenstexture_uniforms[3]; //Lum max v.y = lenstexture_uniforms[3]; //Lum max
v.z = lenstexture_uniforms[4]; //Expo 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": case "_PPComp9":
v = iron.object.Uniforms.helpVec; v = iron.object.Uniforms.helpVec;
v.x = ssr_uniforms[0]; //Step v.x = ssr_uniforms[0]; //Step
@ -297,8 +330,8 @@ class Postprocess {
case "_PPComp11": case "_PPComp11":
v = iron.object.Uniforms.helpVec; v = iron.object.Uniforms.helpVec;
v.x = bloom_uniforms[2]; // Bloom Strength v.x = bloom_uniforms[2]; // Bloom Strength
v.y = 0; // Unused v.y = volumetric_light_uniforms[2][0]; //Volumetric Light Steps
v.z = 0; // Unused v.z = volumetric_fog_uniforms[2][0]; //Volumetric Fog Amount B
case "_PPComp12": case "_PPComp12":
v = iron.object.Uniforms.helpVec; v = iron.object.Uniforms.helpVec;
v.x = ssao_uniforms[0]; //SSAO Strength v.x = ssao_uniforms[0]; //SSAO Strength
@ -308,7 +341,8 @@ class Postprocess {
v = iron.object.Uniforms.helpVec; v = iron.object.Uniforms.helpVec;
v.x = chromatic_aberration_uniforms[0]; //CA Strength v.x = chromatic_aberration_uniforms[0]; //CA Strength
v.y = chromatic_aberration_uniforms[1]; //CA Samples 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": case "_PPComp14":
v = iron.object.Uniforms.helpVec; v = iron.object.Uniforms.helpVec;
v.x = camera_uniforms[10]; //Distort v.x = camera_uniforms[10]; //Distort
@ -338,6 +372,24 @@ class Postprocess {
v.y = letterbox_uniforms[0][1]; v.y = letterbox_uniforms[0][1];
v.z = letterbox_uniforms[0][2]; v.z = letterbox_uniforms[0][2];
v.w = letterbox_uniforms[1][0]; //Size 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; 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(); super();
if (nullvec) { if (nullvec) {
@ -120,6 +120,7 @@ class PhysicsWorld extends Trait {
this.timeScale = timeScale; this.timeScale = timeScale;
this.maxSteps = maxSteps; this.maxSteps = maxSteps;
this.solverIterations = solverIterations; this.solverIterations = solverIterations;
Time.initFixedStep(fixedStep);
// First scene // First scene
if (active == null) { if (active == null) {
@ -136,10 +137,10 @@ class PhysicsWorld extends Trait {
conMap = new Map(); conMap = new Map();
active = this; active = this;
// Ensure physics are updated first in the lateUpdate list // Ensure physics are updated first in the fixedUpdate list
_lateUpdate = [lateUpdate]; _fixedUpdate = [fixedUpdate];
@:privateAccess iron.App.traitLateUpdates.insert(0, lateUpdate); @:privateAccess iron.App.traitFixedUpdates.insert(0, fixedUpdate);
setDebugDrawMode(debugDrawMode); setDebugDrawMode(debugDrawMode);
iron.Scene.active.notifyOnRemove(function() { iron.Scene.active.notifyOnRemove(function() {
@ -298,8 +299,8 @@ class PhysicsWorld extends Trait {
return rb; return rb;
} }
function lateUpdate() { function fixedUpdate() {
var t = Time.delta * timeScale; var t = Time.fixedStep * timeScale * Time.scale;
if (t == 0.0) return; // Simulation paused if (t == 0.0) return; // Simulation paused
#if lnx_debug #if lnx_debug
@ -308,13 +309,10 @@ class PhysicsWorld extends Trait {
if (preUpdates != null) for (f in preUpdates) f(); 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 //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(); updateContacts();
for (rb in rbMap) @:privateAccess rb.physicsUpdate(); for (rb in rbMap) @:privateAccess rb.physicsUpdate();

View File

@ -2,11 +2,13 @@ package leenkx.trait.physics.bullet;
#if lnx_bullet #if lnx_bullet
import leenkx.math.Helper;
import iron.data.MeshData;
import iron.math.Vec4; import iron.math.Vec4;
import iron.math.Quat; import iron.math.Quat;
import iron.object.Transform; import iron.object.Transform;
import iron.object.MeshObject; 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. 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 triangleMeshCache = new Map<MeshData, bullet.Bt.TriangleMesh>();
static var usersCache = new Map<MeshData, Int>(); 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, 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) { params: RigidBodyParams = null, flags: RigidBodyFlags = null) {
super(); super();
@ -85,7 +95,7 @@ class RigidBody extends iron.Trait {
vec1 = new bullet.Bt.Vector3(0, 0, 0); vec1 = new bullet.Bt.Vector3(0, 0, 0);
vec2 = new bullet.Bt.Vector3(0, 0, 0); vec2 = new bullet.Bt.Vector3(0, 0, 0);
vec3 = 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(); trans1 = new bullet.Bt.Transform();
trans2 = new bullet.Bt.Transform(); trans2 = new bullet.Bt.Transform();
} }
@ -117,6 +127,7 @@ class RigidBody extends iron.Trait {
animated: false, animated: false,
trigger: false, trigger: false,
ccd: false, ccd: false,
interpolate: false,
staticObj: false, staticObj: false,
useDeactivation: true useDeactivation: true
}; };
@ -131,6 +142,7 @@ class RigidBody extends iron.Trait {
this.animated = flags.animated; this.animated = flags.animated;
this.trigger = flags.trigger; this.trigger = flags.trigger;
this.ccd = flags.ccd; this.ccd = flags.ccd;
this.interpolate = flags.interpolate;
this.staticObj = flags.staticObj; this.staticObj = flags.staticObj;
this.useDeactivation = flags.useDeactivation; this.useDeactivation = flags.useDeactivation;
@ -153,6 +165,7 @@ class RigidBody extends iron.Trait {
if (!Std.isOfType(object, MeshObject)) return; // No mesh data if (!Std.isOfType(object, MeshObject)) return; // No mesh data
transform = object.transform; transform = object.transform;
transform.buildMatrix();
physics = leenkx.trait.physics.PhysicsWorld.active; physics = leenkx.trait.physics.PhysicsWorld.active;
if (shape == Shape.Box) { if (shape == Shape.Box) {
@ -244,6 +257,9 @@ class RigidBody extends iron.Trait {
quat1.setValue(quat.x, quat.y, quat.z, quat.w); quat1.setValue(quat.x, quat.y, quat.z, quat.w);
trans1.setRotation(quat1); trans1.setRotation(quat1);
currentPos.setValue(vec1.x(), vec1.y(), vec1.z());
currentRot.setValue(quat.x, quat.y, quat.z, quat.w);
var centerOfMassOffset = trans2; var centerOfMassOffset = trans2;
centerOfMassOffset.setIdentity(); centerOfMassOffset.setIdentity();
motionState = new bullet.Bt.DefaultMotionState(trans1, centerOfMassOffset); motionState = new bullet.Bt.DefaultMotionState(trans1, centerOfMassOffset);
@ -307,7 +323,8 @@ class RigidBody extends iron.Trait {
physics.addRigidBody(this); physics.addRigidBody(this);
notifyOnRemove(removeFromWorld); notifyOnRemove(removeFromWorld);
if (!animated) notifyOnUpdate(update);
if (onReady != null) onReady(); if (onReady != null) onReady();
#if js #if js
@ -317,26 +334,71 @@ class RigidBody extends iron.Trait {
#end #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() { function physicsUpdate() {
if (!ready) return; if (!ready) return;
if (animated) { if (animated) {
syncTransform(); 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 trans = body.getWorldTransform();
var p = trans.getOrigin(); var p = trans.getOrigin();
var q = trans.getRotation(); 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.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 #if hl
p.delete(); p.delete();
@ -689,6 +751,7 @@ typedef RigidBodyFlags = {
var animated: Bool; var animated: Bool;
var trigger: Bool; var trigger: Bool;
var ccd: Bool; var ccd: Bool;
var interpolate: Bool;
var staticObj: Bool; var staticObj: Bool;
var useDeactivation: Bool; var useDeactivation: Bool;
} }

View File

@ -2297,6 +2297,8 @@ class LeenkxExporter:
out_particlesys = { out_particlesys = {
'name': particleRef[1]["structName"], 'name': particleRef[1]["structName"],
'type': 0 if psettings.type == 'EMITTER' else 1, # HAIR '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, 'loop': psettings.lnx_loop,
# Emission # Emission
'count': int(psettings.count * psettings.lnx_count_mult), 'count': int(psettings.count * psettings.lnx_count_mult),
@ -2813,6 +2815,7 @@ class LeenkxExporter:
body_flags['animated'] = rb.kinematic body_flags['animated'] = rb.kinematic
body_flags['trigger'] = bobject.lnx_rb_trigger body_flags['trigger'] = bobject.lnx_rb_trigger
body_flags['ccd'] = bobject.lnx_rb_ccd body_flags['ccd'] = bobject.lnx_rb_ccd
body_flags['interpolate'] = bobject.lnx_rb_interpolate
body_flags['staticObj'] = is_static body_flags['staticObj'] = is_static
body_flags['useDeactivation'] = rb.use_deactivation body_flags['useDeactivation'] = rb.use_deactivation
x['parameters'].append(lnx.utils.get_haxe_json_string(body_params)) x['parameters'].append(lnx.utils.get_haxe_json_string(body_params))
@ -3037,7 +3040,7 @@ class LeenkxExporter:
rbw = self.scene.rigidbody_world rbw = self.scene.rigidbody_world
if rbw is not None and rbw.enabled: 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': if phys_pkg == 'bullet' or phys_pkg == 'oimo':
debug_draw_mode = 1 if wrd.lnx_physics_dbg_draw_wireframe else 0 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_trigger = source_obj.lnx_rb_trigger
target_obj.lnx_rb_deactivation_time = source_obj.lnx_rb_deactivation_time 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_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 target_obj.lnx_rb_collision_filter_mask = source_obj.lnx_rb_collision_filter_mask
elif operator_id == "NODE_OT_new_node_tree": elif operator_id == "NODE_OT_new_node_tree":

View File

@ -1,31 +1,34 @@
from lnx.logicnode.lnx_nodes import * from lnx.logicnode.lnx_nodes import *
class GetParticleDataNode(LnxLogicTreeNode): class GetParticleDataNode(LnxLogicTreeNode):
"""Returns the data of the given Particle System.""" """Returns the data of the given Particle System."""
bl_idname = 'LNGetParticleDataNode' bl_idname = 'LNGetParticleDataNode'
bl_label = 'Get Particle Data' bl_label = 'Get Particle Data'
lnx_version = 1 lnx_version = 1
def lnx_init(self, context): def lnx_init(self, context):
self.inputs.new('LnxNodeSocketObject', 'Object') self.inputs.new('LnxNodeSocketObject', 'Object')
self.inputs.new('LnxIntSocket', 'Slot') self.inputs.new('LnxIntSocket', 'Slot')
self.outputs.new('LnxStringSocket', 'Name') self.outputs.new('LnxStringSocket', 'Name')
self.outputs.new('LnxFloatSocket', 'Particle Size') self.outputs.new('LnxFloatSocket', 'Particle Size')
self.outputs.new('LnxIntSocket', 'Frame Start') self.outputs.new('LnxIntSocket', 'Frame Start')
self.outputs.new('LnxIntSocket', 'Frame End') self.outputs.new('LnxIntSocket', 'Frame End')
self.outputs.new('LnxIntSocket', 'Lifetime') self.outputs.new('LnxIntSocket', 'Lifetime')
self.outputs.new('LnxFloatSocket', 'Lifetime Random') self.outputs.new('LnxFloatSocket', 'Lifetime Random')
self.outputs.new('LnxIntSocket', 'Emit From') self.outputs.new('LnxIntSocket', 'Emit From')
self.outputs.new('LnxBoolSocket', 'Auto Start')
self.outputs.new('LnxVectorSocket', 'Velocity') self.outputs.new('LnxBoolSocket', 'Is Unique')
self.outputs.new('LnxFloatSocket', 'Velocity Random') self.outputs.new('LnxBoolSocket', 'Loop')
self.outputs.new('LnxVectorSocket', 'Gravity')
self.outputs.new('LnxFloatSocket', 'Weight Gravity') self.outputs.new('LnxVectorSocket', 'Velocity')
self.outputs.new('LnxFloatSocket', 'Velocity Random')
self.outputs.new('LnxFloatSocket', 'Speed') self.outputs.new('LnxVectorSocket', 'Gravity')
self.outputs.new('LnxFloatSocket', 'Weight Gravity')
self.outputs.new('LnxFloatSocket', 'Time')
self.outputs.new('LnxFloatSocket', 'Lap') self.outputs.new('LnxFloatSocket', 'Speed')
self.outputs.new('LnxFloatSocket', 'Lap Time')
self.outputs.new('LnxIntSocket', 'Count') 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 * from lnx.logicnode.lnx_nodes import *
class SetParticleDataNode(LnxLogicTreeNode): class SetParticleDataNode(LnxLogicTreeNode):
"""Sets the parameters of the given particle system.""" """Sets the parameters of the given particle system."""
bl_idname = 'LNSetParticleDataNode' bl_idname = 'LNSetParticleDataNode'
bl_label = 'Set Particle Data' bl_label = 'Set Particle Data'
lnx_version = 1 lnx_version = 1
def remove_extra_inputs(self, context): def remove_extra_inputs(self, context):
while len(self.inputs) > 3: while len(self.inputs) > 3:
self.inputs.remove(self.inputs[-1]) self.inputs.remove(self.inputs[-1])
if self.property0 == 'Particle Size': if self.property0 == 'Particle Size':
self.add_input('LnxFloatSocket', 'Particle Size') self.add_input('LnxFloatSocket', 'Particle Size')
if self.property0 == 'Frame End': if self.property0 == 'Frame End':
self.add_input('LnxIntSocket', 'Frame End') self.add_input('LnxIntSocket', 'Frame End')
if self.property0 == 'Frame Start': if self.property0 == 'Frame Start':
self.add_input('LnxIntSocket', 'Frame Start') self.add_input('LnxIntSocket', 'Frame Start')
if self.property0 == 'Lifetime': if self.property0 == 'Lifetime':
self.add_input('LnxIntSocket', 'Lifetime') self.add_input('LnxIntSocket', 'Lifetime')
if self.property0 == 'Lifetime Random': if self.property0 == 'Lifetime Random':
self.add_input('LnxFloatSocket', 'Lifetime Random') self.add_input('LnxFloatSocket', 'Lifetime Random')
if self.property0 == 'Emit From': if self.property0 == 'Emit From':
self.add_input('LnxIntSocket', 'Emit From') self.add_input('LnxIntSocket', 'Emit From')
if self.property0 == 'Velocity': if self.property0 == 'Auto Start':
self.add_input('LnxVectorSocket', 'Velocity') self.add_input('LnxBoolSocket', 'Auto Start')
if self.property0 == 'Velocity Random': if self.property0 == 'Is Unique':
self.add_input('LnxFloatSocket', 'Velocity Random') self.add_input('LnxBoolSocket', 'Is Unique')
if self.property0 == 'Weight Gravity': if self.property0 == 'Loop':
self.add_input('LnxFloatSocket', 'Weight Gravity') self.add_input('LnxBoolSocket', 'Loop')
if self.property0 == 'Speed': if self.property0 == 'Velocity':
self.add_input('LnxFloatSocket', 'Speed') self.add_input('LnxVectorSocket', 'Velocity')
if self.property0 == 'Velocity Random':
self.add_input('LnxFloatSocket', 'Velocity Random')
property0: HaxeEnumProperty( if self.property0 == 'Weight Gravity':
'property0', self.add_input('LnxFloatSocket', 'Weight Gravity')
items = [('Particle Size', 'Particle Size', 'for the system'), if self.property0 == 'Speed':
('Frame Start', 'Frame Start', 'for the system'), self.add_input('LnxFloatSocket', 'Speed')
('Frame End', 'Frame End', 'for the system'),
('Lifetime', 'Lifetime', 'for the instance'),
('Lifetime Random', 'Lifetime Random', 'for the system'), property0: HaxeEnumProperty(
('Emit From', 'Emit From', 'for the system (Vertices:0 Faces:1 Volume: 2)'), 'property0',
('Velocity', 'Velocity', 'for the instance'), items = [('Particle Size', 'Particle Size', 'for the system'),
('Velocity Random', 'Velocity Random', 'for the system'), ('Frame Start', 'Frame Start', 'for the system'),
('Weight Gravity', 'Weight Gravity', 'for the instance'), ('Frame End', 'Frame End', 'for the system'),
('Speed', 'Speed', 'for the instance')], ('Lifetime', 'Lifetime', 'for the instance'),
name='', default='Speed', update=remove_extra_inputs) ('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'),
def lnx_init(self, context): ('Is Unique', 'Is Unique', 'for the system'),
self.add_input('LnxNodeSocketAction', 'In') ('Loop', 'Loop', 'for the system'),
self.add_input('LnxNodeSocketObject', 'Object') ('Velocity', 'Velocity', 'for the instance'),
self.add_input('LnxIntSocket', 'Slot') ('Velocity Random', 'Velocity Random', 'for the system'),
self.add_input('LnxFloatSocket', 'Speed', default_value=1.0) ('Weight Gravity', 'Weight Gravity', 'for the instance'),
('Speed', 'Speed', 'for the instance')],
self.add_output('LnxNodeSocketAction', 'Out') name='', default='Speed', update=remove_extra_inputs)
def draw_buttons(self, context, layout):
layout.prop(self, 'property0') 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 * from lnx.logicnode.lnx_nodes import *
class ChromaticAberrationGetNode(LnxLogicTreeNode): 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_idname = 'LNChromaticAberrationGetNode'
bl_label = 'Get CA Settings' bl_label = 'Get CA Settings'
lnx_version = 1 lnx_version = 2
def lnx_init(self, context): def lnx_init(self, context):
self.add_output('LnxFloatSocket', 'Strength') self.add_output('LnxFloatSocket', 'Strength')
self.add_output('LnxFloatSocket', 'Samples') 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', 'Film Grain')#11
self.add_output('LnxFloatSocket', 'Sharpen')#12 self.add_output('LnxFloatSocket', 'Sharpen')#12
self.add_output('LnxFloatSocket', 'Vignette')#13 self.add_output('LnxFloatSocket', 'Vignette')#13
self.add_output('LnxFloatSocket', 'Exposure')#14
def get_replacement_node(self, node_tree: bpy.types.NodeTree): def get_replacement_node(self, node_tree: bpy.types.NodeTree):
if self.lnx_version not in (0, 3): 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 * from lnx.logicnode.lnx_nodes import *
class ChromaticAberrationSetNode(LnxLogicTreeNode): 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_idname = 'LNChromaticAberrationSetNode'
bl_label = 'Set CA Settings' bl_label = 'Set CA Settings'
lnx_version = 1 lnx_version = 2
def lnx_init(self, context): def lnx_init(self, context):
self.add_input('LnxNodeSocketAction', 'In') self.add_input('LnxNodeSocketAction', 'In')
self.add_input('LnxFloatSocket', 'Strength', default_value=2.0) self.add_input('LnxFloatSocket', 'Strength', default_value=2.0)
self.add_input('LnxIntSocket', 'Samples', default_value=32) self.add_input('LnxIntSocket', 'Samples', default_value=32)
self.add_input('LnxIntSocket', 'Type', default_value=0)
self.add_output('LnxNodeSocketAction', 'Out') 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.""" """Set the post-processing effects of a camera."""
bl_idname = 'LNCameraSetNode' bl_idname = 'LNCameraSetNode'
bl_label = 'Set Camera Post Process' 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): def lnx_init(self, context):
self.add_input('LnxNodeSocketAction', 'In') self.add_input('LnxNodeSocketAction', 'In')
self.add_input('LnxFloatSocket', 'F-stop', default_value=1.0)#0 self.add_input('LnxFloatSocket', 'F-stop', default_value=1.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_output('LnxNodeSocketAction', 'Out') 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): def get_replacement_node(self, node_tree: bpy.types.NodeTree):
if self.lnx_version not in range(0, 4): if self.lnx_version not in range(0, 4):
raise LookupError() 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.""" """Sets the post process quality."""
bl_idname = 'LNRpConfigNode' bl_idname = 'LNRpConfigNode'
bl_label = 'Set Post Process Quality' bl_label = 'Set Post Process Quality'
lnx_version = 1 lnx_version = 2
property0: HaxeEnumProperty( property0: HaxeEnumProperty(
'property0', 'property0',
items = [('SSGI', 'SSGI', 'SSGI'), items = [('SSGI', 'SSGI', 'SSGI'),
('SSR', 'SSR', 'SSR'), ('SSR', 'SSR', 'SSR'),
('Bloom', 'Bloom', 'Bloom'), ('Bloom', 'Bloom', 'Bloom'),
('CA', 'CA', 'CA'),
('GI', 'GI', 'GI'), ('GI', 'GI', 'GI'),
('Motion Blur', 'Motion Blur', 'Motion Blur') ('Motion Blur', 'Motion Blur', 'Motion Blur')
], ],
@ -23,3 +24,10 @@ class RpConfigNode(LnxLogicTreeNode):
def draw_buttons(self, context, layout): def draw_buttons(self, context, layout):
layout.prop(self, 'property0') 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: if rpdat.rp_hdr == False:
wrd.world_defs += '_LDR' 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' wrd.world_defs += '_LightIES'
assets.add_embedded_data('iestexture.png') 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' wrd.world_defs += '_LightClouds'
assets.add_embedded_data('cloudstexture.png') 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]: 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] 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) 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 interp = node.color_ramp.interpolation
elems = node.color_ramp.elements elems = node.color_ramp.elements
if len(elems) == 1: if len(elems) == 1:
return c.to_vec3(elems[0].color) if alpha_out:
return c.to_vec1(elems[0].color[3]) # Return alpha from the color
# Write color array else:
# The last entry is included twice so that the interpolation return c.to_vec3(elems[0].color) # Return RGB
# between indices works (no out of bounds error)
cols_var = c.node_name(node.name).upper() + '_COLS' 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: if state.current_pass == ParserPass.REGULAR:
cols_entries = ', '.join(f'vec3({elem.color[0]}, {elem.color[1]}, {elem.color[2]})' for elem in elems) if alpha_out:
cols_entries += f', vec3({elems[len(elems) - 1].color[0]}, {elems[len(elems) - 1].color[1]}, {elems[len(elems) - 1].color[2]})' cols_entries = ', '.join(f'{elem.color[3]}' for elem in elems)
state.curshader.add_const("vec3", cols_var, cols_entries, array_size=len(elems) + 1) # 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() fac_var = c.node_name(node.name) + '_fac' + state.get_parser_pass_suffix()
state.curshader.write(f'float {fac_var} = {fac};') 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 # Linear interpolation
else: else:
# Write factor array # Write factor array - same for both color and alpha
facs_var = c.node_name(node.name).upper() + '_FACS' facs_var = name_prefix + '_FACS'
if state.current_pass == ParserPass.REGULAR: if state.current_pass == ParserPass.REGULAR:
facs_entries = ', '.join(str(elem.position) for elem in elems) facs_entries = ', '.join(str(elem.position) for elem in elems)
# Add one more entry at the rightmost position so that the # Add one more entry at the rightmost position to avoid out of bounds access
# interpolation between indices works (no out of bounds error)
facs_entries += ', 1.0' facs_entries += ', 1.0'
state.curshader.add_const("float", facs_var, facs_entries, array_size=len(elems) + 1) 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}]' prev_stop_fac = f'{facs_var}[{index_var}]'
next_stop_fac = f'{facs_var}[{index_var} + 1]' next_stop_fac = f'{facs_var}[{index_var} + 1]'
prev_stop_col = f'{cols_var}[{index_var}]' prev_stop_col = f'{cols_var}[{index_var}]'
next_stop_col = f'{cols_var}[{index_var} + 1]' 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}))' 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))' return f'mix({prev_stop_col}, {next_stop_col}, max({rel_pos}, 0.0))'
if bpy.app.version > (3, 2, 0): if bpy.app.version > (3, 2, 0):

View File

@ -1,3 +1,4 @@
import bpy
import lnx.utils import lnx.utils
import lnx.material.mat_state as mat_state import lnx.material.mat_state as mat_state
@ -10,6 +11,48 @@ else:
def write(vert, particle_info=None, shadowmap=False): 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 # Outs
out_index = True if particle_info != None and particle_info['index'] else False 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 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_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 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('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" str_tex_hash = "float fhash(float n) { return fract(sin(n) * 43758.5453); }\n"
vert.add_function(str_tex_hash) 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 ' prep = 'float '
if out_age: if out_age:
prep = '' prep = ''
vert.add_out('float p_age') vert.add_out('float p_age')
# var p_age = lapTime - p.i * spawnRate # var p_age = lapTime - p.i * spawnRate
vert.write(prep + 'p_age = pd[3][3] - gl_InstanceID * pd[0][1];') 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 # Loop
# pd[0][0] - animtime, loop stored in sign # pd[0][0] - animtime, loop stored in sign
@ -43,13 +117,18 @@ def write(vert, particle_info=None, shadowmap=False):
if out_lifetime: if out_lifetime:
prep = '' prep = ''
vert.add_out('float p_lifetime') 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 # clip with nan
vert.write('if (p_age < 0 || p_age > p_lifetime) {') vert.write('if (p_age < 0 || p_age > p_lifetime) {')
vert.write(' gl_Position /= 0.0;') vert.write(' gl_Position /= 0.0;')
vert.write(' return;') vert.write(' return;')
vert.write('}') 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 # vert.write('p_age /= 2;') # Match
# object_align_factor / 2 + gxyz # object_align_factor / 2 + gxyz
@ -57,20 +136,20 @@ def write(vert, particle_info=None, shadowmap=False):
if out_velocity: if out_velocity:
prep = '' prep = ''
vert.add_out('vec3 p_velocity') 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.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[0][3]) * pd[1][3] - pd[1][3] / 2;') 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 + 2 * pd[0][3]) * pd[1][3] - pd[1][3] / 2;') 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] # factor_random = pd[1][3]
# p.i = gl_InstanceID # p.i = gl_InstanceID
# particles.length = pd[0][3] # particles.length = pd[0][3]
# gxyz # gxyz
vert.write('p_velocity.x += (pd[2][0] * p_age) / 5;') vert.write('p_velocity.x += (pd[2][0] / (2 * pd_size)) * p_age;')
vert.write('p_velocity.y += (pd[2][1] * p_age) / 5;') vert.write('p_velocity.y += (pd[2][1] / (2 * pd_size)) * p_age;')
vert.write('p_velocity.z += (pd[2][2] * p_age) / 5;') vert.write('p_velocity.z += (pd[2][2] / (2 * pd_size)) * p_age;')
prep = 'vec3 ' prep = 'vec3 '
if out_location: if out_location:
@ -80,6 +159,96 @@ def write(vert, particle_info=None, shadowmap=False):
vert.write('spos.xyz += p_location;') 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 # Particle fade
if mat_state.material.lnx_particle_flag and lnx.utils.get_rp().lnx_particles == 'On' and mat_state.material.lnx_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') vert.add_out('float p_fade')

View File

@ -197,6 +197,10 @@ def init_properties():
items=[('Bullet', 'Bullet', 'Bullet'), items=[('Bullet', 'Bullet', 'Bullet'),
('Oimo', 'Oimo', 'Oimo')], ('Oimo', 'Oimo', 'Oimo')],
name="Physics Engine", default='Bullet', update=assets.invalidate_compiler_cache) 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( bpy.types.World.lnx_physics_dbg_draw_wireframe = BoolProperty(
name="Collider Wireframes", default=False, name="Collider Wireframes", default=False,
description="Draw wireframes of the physics collider meshes and suspensions of raycast vehicle simulations" 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_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_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_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( bpy.types.Object.lnx_rb_collision_filter_mask = bpy.props.BoolVectorProperty(
name="Collision Collections Filter Mask", name="Collision Collections Filter Mask",
description="Collision collections rigid body interacts with", 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_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_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.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="") # For world
bpy.types.World.lnx_light_clouds_texture = StringProperty(name="Clouds Texture", default="") 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_rpcache_list = CollectionProperty(type=bpy.types.PropertyGroup)
bpy.types.World.lnx_scripts_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_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) bpy.types.Node.lnx_version = IntProperty(name="Node Version", description="The version of an instanced node", default=0)
# Particles # 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_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 # 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_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) 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: 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_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: 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_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: 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) 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: if obj == None:
return 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_loop')
layout.prop(obj.settings, 'lnx_count_mult') 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_angular_friction')
layout.prop(obj, 'lnx_rb_trigger') layout.prop(obj, 'lnx_rb_trigger')
layout.prop(obj, 'lnx_rb_ccd') layout.prop(obj, 'lnx_rb_ccd')
layout.prop(obj, 'lnx_rb_interpolate')
if obj.soft_body is not None: if obj.soft_body is not None:
layout.prop(obj, 'lnx_soft_body_margin') 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_clip_end')
layout.prop(obj.data, 'lnx_fov') layout.prop(obj.data, 'lnx_fov')
layout.prop(obj.data, 'lnx_shadows_bias') 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': elif obj.type == 'SPEAKER':
layout.prop(obj.data, 'lnx_play_on_start') layout.prop(obj.data, 'lnx_play_on_start')
layout.prop(obj.data, 'lnx_loop') layout.prop(obj.data, 'lnx_loop')
@ -332,6 +333,8 @@ class LNX_PT_WorldPropsPanel(bpy.types.Panel):
if world is None: if world is None:
return return
layout.prop(world, 'lnx_light_ies_texture')
layout.prop(world, 'lnx_light_clouds_texture')
layout.prop(world, 'lnx_use_clouds') layout.prop(world, 'lnx_use_clouds')
col = layout.column(align=True) col = layout.column(align=True)
col.enabled = world.lnx_use_clouds col.enabled = world.lnx_use_clouds
@ -1991,10 +1994,18 @@ class LNX_PT_RenderPathCompositorPanel(bpy.types.Panel):
col.prop(rpdat, 'lnx_letterbox_size') col.prop(rpdat, 'lnx_letterbox_size')
layout.separator() 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() col = layout.column()
draw_conditional_prop(col, 'Distort', rpdat, 'lnx_distort', 'lnx_distort_strength') 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, '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') draw_conditional_prop(col, 'Vignette', rpdat, 'lnx_vignette', 'lnx_vignette_strength')
layout.separator() layout.separator()
@ -2732,8 +2743,33 @@ class LeenkxUpdateListInstalledVSButton(bpy.types.Operator):
return {'FINISHED'} 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_label = "Leenkx Debug Drawing"
bl_space_type = "PROPERTIES" bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW" bl_region_type = "WINDOW"
@ -2899,7 +2935,8 @@ __REG_CLASSES = (
LeenkxUpdateListAndroidEmulatorButton, LeenkxUpdateListAndroidEmulatorButton,
LeenkxUpdateListAndroidEmulatorRunButton, LeenkxUpdateListAndroidEmulatorRunButton,
LeenkxUpdateListInstalledVSButton, LeenkxUpdateListInstalledVSButton,
LNX_PT_BulletDebugDrawingPanel, LNX_PT_PhysicsProps,
LNX_PT_PhysicsDebugDrawingPanel,
LNX_OT_AddArmatureRootMotion, LNX_OT_AddArmatureRootMotion,
scene.TLM_PT_Settings, scene.TLM_PT_Settings,
scene.TLM_PT_Denoise, scene.TLM_PT_Denoise,

View File

@ -453,6 +453,7 @@ def write_config(resx, resy):
'rp_ssr': rpdat.rp_ssr != 'Off', 'rp_ssr': rpdat.rp_ssr != 'Off',
'rp_ss_refraction': rpdat.rp_ss_refraction != 'Off', 'rp_ss_refraction': rpdat.rp_ss_refraction != 'Off',
'rp_bloom': rpdat.rp_bloom != 'Off', 'rp_bloom': rpdat.rp_bloom != 'Off',
'rp_chromatic_aberration': rpdat.rp_chromatic_aberration != 'Off',
'rp_motionblur': rpdat.rp_motionblur != 'Off', 'rp_motionblur': rpdat.rp_motionblur != 'Off',
'rp_gi': rpdat.rp_voxels != "Off", 'rp_gi': rpdat.rp_voxels != "Off",
'rp_dynres': rpdat.rp_dynres '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: if lnx.utils.get_active_scene().view_settings.exposure != 0.0:
f.write( f.write(
"""const float compoExposureStrength = """ + str(round(lnx.utils.get_active_scene().view_settings.exposure * 100) / 100) + """; """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: if rpdat.lnx_fog: