Consolidate and update

This commit is contained in:
2026-10-27 15:58:22 -07:00
parent eb211da457
commit 6c13722fc6
42 changed files with 498 additions and 169 deletions

View File

@ -3,8 +3,8 @@
{ {
"name": "probe_cubemap", "name": "probe_cubemap",
"depth_write": false, "depth_write": false,
"compare_mode": "less", "compare_mode": "greater",
"cull_mode": "clockwise", "cull_mode": "counter_clockwise",
"blend_source": "blend_one", "blend_source": "blend_one",
"blend_destination": "blend_one", "blend_destination": "blend_one",
"blend_operation": "add", "blend_operation": "add",

View File

@ -3,8 +3,8 @@
{ {
"name": "probe_planar", "name": "probe_planar",
"depth_write": false, "depth_write": false,
"compare_mode": "less", "compare_mode": "greater",
"cull_mode": "clockwise", "cull_mode": "counter_clockwise",
"blend_source": "blend_one", "blend_source": "blend_one",
"blend_destination": "blend_one", "blend_destination": "blend_one",
"blend_operation": "add", "blend_operation": "add",

View File

@ -34,7 +34,6 @@ import iron.math.Quat;
class RenderPath { class RenderPath {
public static var active: RenderPath; public static var active: RenderPath;
#if lnx_vr #if lnx_vr
static var vrSimulateMode: Bool = false;
static var vrCameraOffsetSet:Bool = false; static var vrCameraOffsetSet:Bool = false;
static var vrCameraOffset:Vec4 = new Vec4(); static var vrCameraOffset:Vec4 = new Vec4();
static var wasVRPresenting:Bool = false; static var wasVRPresenting:Bool = false;
@ -45,6 +44,9 @@ class RenderPath {
public static var vrCenterCameraWorld:Mat4 = null; public static var vrCenterCameraWorld:Mat4 = null;
static var vrOriginalSuperSample:Float = -1.0; static var vrOriginalSuperSample:Float = -1.0;
#end
static var vrSimulateMode: Bool = false;
public static inline function isVRPresenting(): Bool { public static inline function isVRPresenting(): Bool {
#if (kha_webgl && lnx_vr) #if (kha_webgl && lnx_vr)
return kha.vr.VrInterface.instance != null && kha.vr.VrInterface.instance.IsPresenting(); return kha.vr.VrInterface.instance != null && kha.vr.VrInterface.instance.IsPresenting();
@ -57,8 +59,6 @@ class RenderPath {
return vrSimulateMode; return vrSimulateMode;
} }
#end
public var frameScissor = false; public var frameScissor = false;
public var frameScissorX = 0; public var frameScissorX = 0;
public var frameScissorY = 0; public var frameScissorY = 0;

View File

@ -1003,7 +1003,7 @@ class Scene {
} }
if (o.vertex_groups != null) { if (o.vertex_groups != null) {
cast(object, MeshObject).vertexGroups = new Map(); object.vertexGroups = new Map();
for (p in o.vertex_groups) { for (p in o.vertex_groups) {
var verts:Array<iron.math.Vec4> = []; var verts:Array<iron.math.Vec4> = [];
@ -1014,7 +1014,7 @@ class Scene {
verts.push(new iron.math.Vec4(data[i * 3], data[i * 3 + 1], data[i * 3 + 2], 1.0)); verts.push(new iron.math.Vec4(data[i * 3], data[i * 3 + 1], data[i * 3 + 2], 1.0));
} }
} }
cast(object, MeshObject).vertexGroups.set(p.name, verts); object.vertexGroups.set(p.name, verts);
} }
} }

View File

@ -58,6 +58,7 @@ class Geometry {
// Skinned // Skinned
#if lnx_skin #if lnx_skin
public var skeletonTransformsI: Array<Mat4> = null; public var skeletonTransformsI: Array<Mat4> = null;
public var skeletonBoneRefs: Array<String> = null;
public var actions: Map<String, Array<iron.data.SceneFormat.TObj>> = new Map(); public var actions: Map<String, Array<iron.data.SceneFormat.TObj>> = new Map();
public var mats: Map<String, Array<Mat4>> = new Map(); public var mats: Map<String, Array<Mat4>> = new Map();
#end #end
@ -326,6 +327,33 @@ class Geometry {
// Skinned // Skinned
#if lnx_skin #if lnx_skin
public function addArmature(armature: iron.data.Armature) {
for (a in armature.actions) {
addAction(a.bones, a.name);
}
}
public function addAction(bones: Array<TObj>, name: String) {
if (bones == null || skeletonBoneRefs == null) return;
if (actions.get(name) != null) return;
var actionBones: Array<TObj> = [];
for (s in skeletonBoneRefs) {
for (b in bones) {
if (b.name == s) {
actionBones.push(b);
}
}
}
actions.set(name, actionBones);
var actionMats: Array<Mat4> = [];
for (b in actionBones) {
actionMats.push(Mat4.fromFloat32Array(b.transform.values));
}
mats.set(name, actionMats);
}
public function initSkeletonTransforms(transformsI: Array<Float32Array>) { public function initSkeletonTransforms(transformsI: Array<Float32Array>) {
skeletonTransformsI = []; skeletonTransformsI = [];
for (t in transformsI) { for (t in transformsI) {

View File

@ -108,6 +108,7 @@ class MeshData {
// Skinned // Skinned
#if lnx_skin #if lnx_skin
if (raw.skin != null) { if (raw.skin != null) {
dat.geom.skeletonBoneRefs = raw.skin.bone_ref_array;
dat.geom.initSkeletonTransforms(raw.skin.transformsI); dat.geom.initSkeletonTransforms(raw.skin.transformsI);
} }
#end #end

View File

@ -49,7 +49,7 @@ class Reader {
* @see https://msdn.microsoft.com/en-us/library/windows/desktop/dd318229(v=vs.85).aspx * @see https://msdn.microsoft.com/en-us/library/windows/desktop/dd318229(v=vs.85).aspx
* @see https://en.wikipedia.org/wiki/BMP_file_format#Bitmap_file_header * @see https://en.wikipedia.org/wiki/BMP_file_format#Bitmap_file_header
*/ */
public function read() : format.bmp.Data { public function read() : iron.format.bmp.Data {
// Read Header // Read Header
for (b in ["B".code, "M".code]) { for (b in ["B".code, "M".code]) {
if (input.readByte() != b) throw "Invalid header"; if (input.readByte() != b) throw "Invalid header";

View File

@ -1,5 +1,5 @@
package format.gif; package iron.format.gif;
import format.gif.Data; import iron.format.gif.Data;
import haxe.ds.Vector; import haxe.ds.Vector;
import haxe.io.Bytes; import haxe.io.Bytes;
import haxe.io.Output; import haxe.io.Output;

View File

@ -29,7 +29,7 @@
*/ */
package iron.format.jpg; package iron.format.jpg;
class Writer { class WriterOriginal {
var ZigZag: Array<Int>; var ZigZag: Array<Int>;
// Static table initialization // Static table initialization

View File

@ -40,11 +40,6 @@ class Animation {
public var paused = false; public var paused = false;
var frameTime: FastFloat = 1 / 60; var frameTime: FastFloat = 1 / 60;
var blendTime: FastFloat = 0.0;
var blendCurrent: FastFloat = 0.0;
var blendFactor: FastFloat = 0.0;
var lastFrameIndex = -1;
var markerEvents: Map<ActionSampler, Map<String, Array<Void->Void>>> = null; var markerEvents: Map<ActionSampler, Map<String, Array<Void->Void>>> = null;
public var activeActions: Map<String, ActionSampler> = null; public var activeActions: Map<String, ActionSampler> = null;
@ -63,8 +58,6 @@ class Animation {
public function play(action = "", onComplete: Void->Void = null, blendTime = 0.0, speed = 1.0, loop = true) { public function play(action = "", onComplete: Void->Void = null, blendTime = 0.0, speed = 1.0, loop = true) {
if (blendTime > 0) { if (blendTime > 0) {
this.blendTime = blendTime;
this.blendCurrent = 0.0;
frameIndex = 0; frameIndex = 0;
time = 0.0; time = 0.0;
} }
@ -93,6 +86,7 @@ class Animation {
} }
public function update(delta: FastFloat) { public function update(delta: FastFloat) {
if (paused) return;
if(activeActions == null) return; if(activeActions == null) return;
for(sampler in activeActions){ for(sampler in activeActions){
@ -160,26 +154,27 @@ class Animation {
while (checkFrameIndex(track.frames, time, frameIndex, speed)) frameIndex += sign; while (checkFrameIndex(track.frames, time, frameIndex, speed)) frameIndex += sign;
// Marker events // Marker events
if (markerEvents != null && anim.marker_names != null && frameIndex != lastFrameIndex) { if (markerEvents != null && anim.marker_names != null && frameIndex != sampler.offsetOld) {
if(markerEvents.get(sampler) != null){ var markerListeners = markerEvents.get(sampler);
if (markerListeners != null) {
var fromFrame = sampler.offsetOld < 0 ?
(speed > 0 ? -1 : track.frames[track.frames.length - 1] + 1) :
track.frames[sampler.offsetOld];
var toFrame = track.frames[frameIndex];
for (i in 0...anim.marker_frames.length) { for (i in 0...anim.marker_frames.length) {
if (frameIndex == anim.marker_frames[i]) { var markerFrame = anim.marker_frames[i];
var markerAct = markerEvents.get(sampler); var crossed =
var ar = markerAct.get(anim.marker_names[i]); (speed > 0 && toFrame >= fromFrame && markerFrame > fromFrame && markerFrame <= toFrame) ||
if (ar != null) for (f in ar) f(); (speed < 0 && toFrame <= fromFrame && markerFrame <= fromFrame && markerFrame > toFrame) ||
} else { (speed > 0 && toFrame < fromFrame && (markerFrame > fromFrame || markerFrame <= toFrame)) ||
for (j in 0...(frameIndex - lastFrameIndex)) { (speed < 0 && toFrame > fromFrame && (markerFrame >= toFrame || markerFrame < fromFrame));
if (lastFrameIndex + j + 1 == anim.marker_frames[i]) { if (crossed) {
var markerAct = markerEvents.get(sampler); var listeners = markerListeners.get(anim.marker_names[i]);
var ar = markerAct.get(anim.marker_names[i]); if (listeners != null) for (listener in listeners) listener();
if (ar != null) for (f in ar) f();
} }
} }
} }
} }
lastFrameIndex = frameIndex;
}
}
// End of track // End of track
if (isTrackEnd(track, frameIndex, speed)) { if (isTrackEnd(track, frameIndex, speed)) {
@ -191,8 +186,8 @@ class Animation {
else { else {
frameIndex -= sign; frameIndex -= sign;
sampler.paused = true; sampler.paused = true;
}
if (sampler.onComplete != null) for(func in sampler.onComplete){ func();}; if (sampler.onComplete != null) for(func in sampler.onComplete){ func();};
}
sampler.trackEnd = true; sampler.trackEnd = true;
} }

View File

@ -112,6 +112,10 @@ class BoneAnimation extends Animation {
this.data = mo != null ? mo.data : null; this.data = mo != null ? mo.data : null;
this.isSkinned = data != null ? data.isSkinned : false; this.isSkinned = data != null ? data.isSkinned : false;
if (this.isSkinned) { if (this.isSkinned) {
if (armature != null && data.geom.skeletonBoneRefs != null
&& data.geom.actions.keys().hasNext() == false) {
data.geom.addArmature(armature);
}
var boneSize = 12; // Dual-quat skinning + scaling var boneSize = 12; // Dual-quat skinning + scaling
this.skinBuffer = new Float32Array(skinMaxBones * boneSize); this.skinBuffer = new Float32Array(skinMaxBones * boneSize);
for (i in 0...this.skinBuffer.length) this.skinBuffer[i] = 0; for (i in 0...this.skinBuffer.length) this.skinBuffer[i] = 0;
@ -205,18 +209,37 @@ class BoneAnimation extends Animation {
} }
function setAction(action: String) { function setAction(action: String) {
if (isSkinned) {
skeletonBones = data.geom.actions.get(action);
var geomMats = data.geom.mats.get(action);
if (geomMats != null) {
skeletonMats = [];
for (i in 0...geomMats.length) {
var m = Mat4.identity();
m.setFrom(geomMats[i]);
skeletonMats.push(m);
}
}
else skeletonMats = null;
}
if (skeletonBones == null) {
if (armature == null) return; if (armature == null) return;
armature.initMats(); armature.initMats();
var a = armature.getAction(action); var a = armature.getAction(action);
if (a == null) return; if (a == null) return;
skeletonBones = a.bones; skeletonBones = a.bones;
skeletonMats = a.mats; skeletonMats = a.mats;
if(! rootMotionCacheInit) skeletonMats.push(Mat4.identity()); }
if (skeletonMats.length == skeletonBones.length) skeletonMats.push(Mat4.identity()); // Root motion cache
rootMotionCacheInit = true; rootMotionCacheInit = true;
setMats(); setMats();
} }
function getAction(action: String): Array<TObj> { function getAction(action: String): Array<TObj> {
if (isSkinned) {
var bones = data.geom.actions.get(action);
if (bones != null) return bones;
}
if (armature == null) return null; if (armature == null) return null;
armature.initMats(); armature.initMats();
var a = armature.getAction(action); var a = armature.getAction(action);
@ -251,15 +274,91 @@ class BoneAnimation extends Animation {
updateAnimation = f; updateAnimation = f;
} }
var blendSampler1: ActionSampler = null;
var blendSampler2: ActionSampler = null;
var blendMats: Array<Mat4> = null;
public function blend(action1: String, action2: String, factor: FastFloat) {
var actionName1: String = getName(action1);
var actionName2: String = getName(action2);
if (blendSampler1 == null || blendSampler1.action != actionName1) {
blendSampler1 = new ActionSampler(actionName1);
registerAction("blendSampler1", blendSampler1);
}
if (blendSampler2 == null || blendSampler2.action != actionName2) {
blendSampler2 = new ActionSampler(actionName2);
registerAction("blendSampler2", blendSampler2);
}
blendSampler1.paused = false;
blendSampler2.paused = false;
if (transitionSampler != null) {
transitionSampler.paused = true;
transitionSampler = null;
}
if (currentPlaySampler != null) currentPlaySampler.paused = true;
currentPlaySampler = blendSampler1;
if (skeletonMats == null) setAction(actionName1);
if (skeletonMats == null) return;
if (blendMats == null) blendMats = initMatsEmpty();
updateAnimation = function(mats) {
sampleAction(blendSampler1, mats);
sampleAction(blendSampler2, blendMats);
blendAction(mats, blendMats, mats, factor);
}
}
var playSamplers: Map<String, ActionSampler> = [];
var currentPlaySampler: ActionSampler = null;
var transitionSampler: ActionSampler = null;
var transitionMats: Array<Mat4> = null;
var transitionTime: FastFloat = 0.0;
var transitionDuration: FastFloat = 0.0;
override public function play(action = "", onComplete: Void->Void = null, blendTime = 0.2, speed = 1.0, loop = true) { override public function play(action = "", onComplete: Void->Void = null, blendTime = 0.2, speed = 1.0, loop = true) {
var actionName: String = getName(action); var actionName: String = getName(action);
if (actionName != "") { if (actionName != "") {
setAction(actionName); setAction(actionName);
super.play(actionName, onComplete, blendTime, speed, loop); super.play(actionName, onComplete, blendTime, speed, loop);
var tempAnimParam = new ActionSampler(actionName); var sampler = playSamplers.get(actionName);
registerAction("tempAction", tempAnimParam); if (sampler == null) {
if (activeActions != null) sampler = activeActions.get(actionName);
if (sampler == null) sampler = new ActionSampler(actionName);
playSamplers.set(actionName, sampler);
}
if (transitionSampler != null) {
transitionSampler.paused = true;
transitionSampler = null;
}
if (blendTime > 0 && currentPlaySampler != null && currentPlaySampler != sampler && skeletonMats != null) {
transitionSampler = currentPlaySampler;
if (transitionMats == null) transitionMats = initMatsEmpty();
transitionTime = 0.0;
transitionDuration = blendTime;
if (blendSampler1 != null && blendSampler1 != transitionSampler) blendSampler1.paused = true;
if (blendSampler2 != null && blendSampler2 != transitionSampler) blendSampler2.paused = true;
}
else if (currentPlaySampler != null) currentPlaySampler.paused = true;
currentPlaySampler = sampler;
sampler.speed = speed;
sampler.loop = loop;
sampler.onComplete = onComplete != null ? [onComplete] : null;
sampler.restartAction();
registerAction(actionName, sampler);
updateAnimation = function(mats){ updateAnimation = function(mats){
sampleAction(tempAnimParam, mats); if (transitionSampler != null) {
sampleAction(transitionSampler, transitionMats);
sampleAction(sampler, mats);
var f = transitionTime / transitionDuration;
if (f > 1.0) f = 1.0;
f = f * f * (3.0 - 2.0 * f); // Smoothstep
blendAction(transitionMats, mats, mats, f, -1, 0.0);
transitionTime += delta;
if (transitionTime >= transitionDuration) {
transitionSampler.paused = true;
transitionSampler = null;
}
}
else sampleAction(sampler, mats);
} }
} }
} }
@ -279,6 +378,7 @@ class BoneAnimation extends Animation {
#end #end
super.update(delta); super.update(delta);
if (paused) return;
if(updateAnimation != null) { if(updateAnimation != null) {
updateAnimation(skeletonMats); updateAnimation(skeletonMats);
} }
@ -540,12 +640,14 @@ class BoneAnimation extends Animation {
inline function interpolateSample(track: TTrack, m: Mat4, t: FastFloat, ti: Int, sign: Int) { inline function interpolateSample(track: TTrack, m: Mat4, t: FastFloat, ti: Int, sign: Int) {
var tj = ti + sign;
if (tj < 0 || tj >= track.frames.length) tj = ti;
var t1 = track.frames[ti] * frameTime; var t1 = track.frames[ti] * frameTime;
var t2 = track.frames[ti + sign] * frameTime; var t2 = track.frames[tj] * frameTime;
var s: FastFloat = (t - t1) / (t2 - t1); // Linear var s: FastFloat = t2 != t1 ? (t - t1) / (t2 - t1) : 0.0; // Linear
m1.setF32(track.values, ti * 16); // Offset to 4x4 matrix array m1.setF32(track.values, ti * 16); // Offset to 4x4 matrix array
m2.setF32(track.values, (ti + sign) * 16); m2.setF32(track.values, tj * 16);
// Decompose // Decompose
m1.decompose(vpos, q1, vscl); m1.decompose(vpos, q1, vscl);
@ -643,7 +745,7 @@ class BoneAnimation extends Animation {
return actionMats != null ? actionMats[getBoneIndex(bone)] : null; return actionMats != null ? actionMats[getBoneIndex(bone)] : null;
} }
function getWorldMat(actionMats: Array<Mat4>, bone: TObj): Mat4 { public function getWorldMat(actionMats: Array<Mat4>, bone: TObj): Mat4 {
if (applyParent == null) { if (applyParent == null) {
applyParent = []; applyParent = [];
for (m in actionMats) applyParent.push(true); for (m in actionMats) applyParent.push(true);
@ -728,7 +830,7 @@ class BoneAnimation extends Animation {
// Skip if root motion is disabled // Skip if root motion is disabled
if(j < 0) continue; if(j < 0) continue;
// Use Action 2 // Use Action 2
if(skeletonBones[j].bone_layers[layerMask] || layerMask < 0){ if(layerMask < 0 || skeletonBones[j].bone_layers[layerMask]){
resultMat[i].setFrom(actionMats2[i]); resultMat[i].setFrom(actionMats2[i]);
} }
// Use Action 1 if not in layer // Use Action 1 if not in layer
@ -744,7 +846,7 @@ class BoneAnimation extends Animation {
// Skip if root motion is disabled // Skip if root motion is disabled
if(j < 0) continue; if(j < 0) continue;
// Blend // Blend
if(skeletonBones[j].bone_layers[j] || layerMask < 0) { if(layerMask < 0 || skeletonBones[j].bone_layers[layerMask]) {
// Decompose // Decompose
m.setFrom(actionMats1[i]); m.setFrom(actionMats1[i]);
m1.setFrom(actionMats2[i]); m1.setFrom(actionMats2[i]);
@ -786,8 +888,10 @@ class BoneAnimation extends Animation {
} }
else{ else{
for(i in 0...baseActionMats.length){ for(i in 0...baseActionMats.length){
var j = i == skeletonBones.length ? rootMotionIndex : i;
if(j < 0) continue;
if(skeletonBones[i].bone_layers[layerMask] || layerMask < 0) { if(layerMask < 0 || skeletonBones[j].bone_layers[layerMask]) {
// Decompose // Decompose
m.setFrom(baseActionMats[i]); m.setFrom(baseActionMats[i]);
m1.setFrom(addActionMats[i]); m1.setFrom(addActionMats[i]);

View File

@ -13,7 +13,10 @@ class Constraint {
} }
public function apply(transform: Transform) { public function apply(transform: Transform) {
if (target == null && raw.target != null) target = Scene.active.getChild(raw.target).transform; if (target == null && raw.target != null) {
var o = Scene.active.getChild(raw.target);
if (o != null) target = o.transform;
}
if (target == null && raw.type != "LIMIT_LOCATION" && raw.type != "LIMIT_ROTATION" && raw.type != "LIMIT_SCALE") return; if (target == null && raw.type != "LIMIT_LOCATION" && raw.type != "LIMIT_ROTATION" && raw.type != "LIMIT_SCALE") return;

View File

@ -48,7 +48,6 @@ class MeshObject extends Object {
#if lnx_morph_target #if lnx_morph_target
public var morphTarget: MorphTarget = null; public var morphTarget: MorphTarget = null;
#end #end
public var vertexGroups: Map<String, Array<Vec4>> = null;
#if lnx_veloc #if lnx_veloc
public var prevMatrix = Mat4.identity(); public var prevMatrix = Mat4.identity();
@ -240,6 +239,7 @@ class MeshObject extends Object {
return setCulled(isShadow, true); return setCulled(isShadow, true);
} }
#if rp_shadowmap
if (isShadow && light.data.raw.type == "sun") { if (isShadow && light.data.raw.type == "sun") {
var frame = RenderPath.active.frame; var frame = RenderPath.active.frame;
if (shadowCameraCullFrame == frame && shadowCameraCullCamera == camera && if (shadowCameraCullFrame == frame && shadowCameraCullCamera == camera &&
@ -270,10 +270,8 @@ class MeshObject extends Object {
} }
} }
} }
#end
} }
//culled = false;
//return culled;
return setCulled(isShadow, false); return setCulled(isShadow, false);
} }

View File

@ -15,6 +15,7 @@ class Object {
public var transform: Transform; public var transform: Transform;
public var constraints: Array<Constraint> = null; public var constraints: Array<Constraint> = null;
public var traits: Array<Trait> = []; public var traits: Array<Trait> = [];
public var vertexGroups: Map<String, Array<iron.math.Vec4>> = null;
public var parent: Object = null; public var parent: Object = null;
public var children: Array<Object> = []; public var children: Array<Object> = [];

View File

@ -17,6 +17,7 @@ class ObjectAnimation extends Animation {
public var oactions: Array<TSceneFormat>; public var oactions: Array<TSceneFormat>;
var oaction: TObj; var oaction: TObj;
var s0: FastFloat = 0.0; var s0: FastFloat = 0.0;
var delta: FastFloat = 0;
var bezierFrameIndex = -1; var bezierFrameIndex = -1;
var updateAnimation: Map<String, FastFloat> -> Void; var updateAnimation: Map<String, FastFloat> -> Void;
@ -26,7 +27,11 @@ class ObjectAnimation extends Animation {
public var transformMap: Map<String, FastFloat>; public var transformMap: Map<String, FastFloat>;
var defaultSampler: ActionSampler = null; var defaultSampler: ActionSampler = null;
static inline var DEFAULT_SAMPLER_ID = "__object_default_action__"; var playSamplers: Map<String, ActionSampler> = [];
var transitionSampler: ActionSampler = null;
var transitionMap: Map<String, FastFloat> = null;
var transitionTime: FastFloat = 0.0;
var transitionDuration: FastFloat = 0.0;
public static var trackNames: Array<String> = [ "xloc", "yloc", "zloc", public static var trackNames: Array<String> = [ "xloc", "yloc", "zloc",
"xrot", "yrot", "zrot", "xrot", "yrot", "zrot",
@ -67,27 +72,59 @@ class ObjectAnimation extends Animation {
oaction = getAction(this.action); oaction = getAction(this.action);
if (oaction != null) { if (oaction != null) {
isSampled = oaction.sampled != null && oaction.sampled; isSampled = oaction.sampled != null && oaction.sampled;
if (defaultSampler != null) { var sampler = playSamplers.get(this.action);
deRegisterAction(DEFAULT_SAMPLER_ID); if (sampler == null) {
if (activeActions != null) sampler = activeActions.get(this.action);
if (sampler == null) sampler = new ActionSampler(this.action);
playSamplers.set(this.action, sampler);
} }
var callbacks = onComplete != null ? [onComplete] : null; if (transitionSampler != null) {
defaultSampler = new ActionSampler(this.action, speed, loop, false, callbacks); transitionSampler.paused = true;
registerAction(DEFAULT_SAMPLER_ID, defaultSampler); transitionSampler = null;
if (paused) defaultSampler.paused = true; }
if (blendTime > 0 && defaultSampler != null && defaultSampler != sampler) {
transitionSampler = defaultSampler;
transitionMap = initTransformMap();
transitionTime = 0.0;
transitionDuration = blendTime;
}
else if (defaultSampler != null) defaultSampler.paused = true;
defaultSampler = sampler;
sampler.speed = speed;
sampler.loop = loop;
sampler.onComplete = onComplete != null ? [onComplete] : null;
sampler.restartAction();
registerAction(this.action, sampler);
updateAnimation = function(map: Map<String, FastFloat>) { updateAnimation = function(map: Map<String, FastFloat>) {
sampleAction(defaultSampler, map); if (transitionSampler != null) {
sampleAction(transitionSampler, transitionMap);
sampleAction(sampler, map);
var f = transitionTime / transitionDuration;
if (f > 1.0) f = 1.0;
f = f * f * (3.0 - 2.0 * f); // Smoothstep
blendActionObject(transitionMap, map, map, f);
transitionTime += delta;
if (transitionTime >= transitionDuration) {
transitionSampler.paused = true;
transitionSampler = null;
}
}
else sampleAction(sampler, map);
}; };
} }
else { else {
if (defaultSampler != null) { if (transitionSampler != null) {
deRegisterAction(DEFAULT_SAMPLER_ID); transitionSampler.paused = true;
defaultSampler = null; transitionSampler = null;
} }
if (defaultSampler != null) defaultSampler.paused = true;
defaultSampler = null;
updateAnimation = null; updateAnimation = null;
} }
} }
override public function update(delta: FastFloat) { override public function update(delta: FastFloat) {
this.delta = delta;
if (!object.visible || object.culled) return; if (!object.visible || object.culled) return;
#if lnx_debug #if lnx_debug
@ -98,7 +135,6 @@ class ObjectAnimation extends Animation {
transformMap = initTransformMap(); transformMap = initTransformMap();
super.update(delta); super.update(delta);
if (defaultSampler != null) defaultSampler.paused = paused;
if (paused) return; if (paused) return;
if (updateAnimation == null) return; if (updateAnimation == null) return;
if (!isSkinned) updateObjectAnimation(); if (!isSkinned) updateObjectAnimation();
@ -160,11 +196,13 @@ class ObjectAnimation extends Animation {
var ti = sampler.offset; var ti = sampler.offset;
//ti = ti < 0 ? 1 : ti; //ti = ti < 0 ? 1 : ti;
var tj = ti + sign;
if (tj < 0 || tj >= track.frames.length) tj = ti;
var t1 = track.frames[ti] * frameTime; var t1 = track.frames[ti] * frameTime;
var t2 = track.frames[ti + sign] * frameTime; var t2 = track.frames[tj] * frameTime;
var v1 = track.values[ti]; var v1 = track.values[ti];
var v2 = track.values[ti + sign]; var v2 = track.values[tj];
var value = interpolateLinear(t, t1, t2, v1, v2); var value = interpolateLinear(t, t1, t2, v1, v2);
@ -202,6 +240,7 @@ class ObjectAnimation extends Animation {
} }
inline function interpolateLinear(t: FastFloat, t1: FastFloat, t2: FastFloat, v1: FastFloat, v2: FastFloat): Null<FastFloat> { inline function interpolateLinear(t: FastFloat, t1: FastFloat, t2: FastFloat, v1: FastFloat, v2: FastFloat): Null<FastFloat> {
if (t2 == t1) return v1;
var s = (t - t1) / (t2 - t1); var s = (t - t1) / (t2 - t1);
return (1.0 - s) * v1 + s * v2; return (1.0 - s) * v1 + s * v2;
} }

View File

@ -131,13 +131,20 @@ class Transform {
dscale = new Vec4().set(1, 1, 1); dscale = new Vec4().set(1, 1, 1);
} }
static var _cdLoc = new Vec4();
static var _cdRot = new Quat();
static var _cdScale = new Vec4();
function composeDelta() { function composeDelta() {
// Delta transform // Delta transform: additive location, multiplicative rotation and scale
dloc.addvecs(loc, dloc); _cdLoc.addvecs(loc, dloc);
dscale.addvecs(dscale, scale); _cdScale.x = scale.x * dscale.x;
drot.fromEuler(_deulerX, _deulerY, _deulerZ); _cdScale.y = scale.y * dscale.y;
drot.multquats(rot, drot); _cdScale.z = scale.z * dscale.z;
local.compose(dloc, drot, dscale); _cdRot.fromEuler(_deulerX, _deulerY, _deulerZ);
_cdRot.multquats(_cdRot, drot);
_cdRot.multquats(rot, _cdRot);
local.compose(_cdLoc, _cdRot, _cdScale);
} }
/** /**

View File

@ -38,14 +38,14 @@ class Time {
static var normalModeLogged: Bool = false; static var normalModeLogged: Bool = false;
#end #end
static var lastTime = 0.0; static var lastTime = -1.0;
static var _delta = 0.0; static var _delta = 0.0;
public static var delta(get, never): Float; public static var delta(get, never): Float;
static function get_delta(): Float { static function get_delta(): Float {
return _delta; return _delta;
} }
static var lastRenderTime = 0.0; static var lastRenderTime = -1.0;
static var _renderDelta = 0.0; static var _renderDelta = 0.0;
public static var renderDelta(get, never): Float; public static var renderDelta(get, never): Float;
static function get_renderDelta(): Float { static function get_renderDelta(): Float {
@ -79,12 +79,14 @@ class Time {
} }
#end #end
_delta = realTime() - lastTime; var now = realTime();
lastTime = realTime(); _delta = lastTime < 0.0 ? 0.0 : now - lastTime;
lastTime = now;
} }
public static function render() { public static function render() {
_renderDelta = realTime() - lastRenderTime; var now = realTime();
lastRenderTime = realTime(); _renderDelta = lastRenderTime < 0.0 ? 0.0 : now - lastRenderTime;
lastRenderTime = now;
} }
} }

View File

@ -35,13 +35,13 @@ class AnimActionNode extends LogicNode {
if(object.animation == null) { if(object.animation == null) {
#if lnx_skin #if lnx_skin
animationBone = object.getBoneAnimation(object.uid); animationBone = object.getBoneAnimation(object.uid);
animationBone.registerAction(property0, sampler); animationBone.registerAction(sampler.action, sampler);
func = sampleBonaAction; func = sampleBonaAction;
#end #end
} }
else{ else{
animationObject = cast(object.animation, ObjectAnimation); animationObject = cast(object.animation, ObjectAnimation);
animationObject.registerAction(property0, sampler); animationObject.registerAction(sampler.action, sampler);
func = sampleObjectAction; func = sampleObjectAction;
} }

View File

@ -61,14 +61,16 @@ class BlendActionNode extends LogicNode {
return; return;
} }
if(factor > 0.95) { if(factor > 0.95) {
var action2 = inputs[2].get();
inputs[2].get()(animMats); if (action2 != null) action2(animMats); else inputs[1].get()(animMats);
return; return;
} }
} }
inputs[1].get()(animMats); inputs[1].get()(animMats);
inputs[2].get()(tempMats); var action2 = inputs[2].get();
if (action2 == null) return;
action2(tempMats);
animationBone.blendAction(animMats, tempMats, animMats, factor, boneLayer); animationBone.blendAction(animMats, tempMats, animMats, factor, boneLayer);
} }

View File

@ -7,6 +7,9 @@ class OnActionMarkerNode extends LogicNode {
public var property0: String; public var property0: String;
public var property1: String; public var property1: String;
var registeredSamplers: Array<iron.object.Animation.ActionSampler> = [];
var registeredCallbacks: Map<iron.object.Animation.ActionSampler, Void->Void> = new Map();
public function new(tree: LogicTree) { public function new(tree: LogicTree) {
super(tree); super(tree);
@ -21,9 +24,33 @@ class OnActionMarkerNode extends LogicNode {
assert(Error, object != null, "Object input cannot be null"); assert(Error, object != null, "Object input cannot be null");
var animation = object.animation; var animation = object.animation;
if (animation == null) animation = object.getBoneAnimation(object.uid); if (animation == null) animation = object.getBoneAnimation(object.uid);
var action = animation.activeActions.get(actionID); if (animation == null) return;
if(action == null) return; var active = animation.activeActions;
animation.notifyOnMarker(action, marker, function() { runOutput(0); }); if (active == null) return;
tree.removeUpdate(init);
var i = registeredSamplers.length;
while (i-- > 0) {
var alive = false;
for (sampler in active) if (sampler == registeredSamplers[i]) {
alive = true;
break;
}
if (!alive) {
var s = registeredSamplers[i];
animation.removeMarker(s, marker, registeredCallbacks.get(s));
registeredCallbacks.remove(s);
registeredSamplers.splice(i, 1);
}
}
for (key in active.keys()) {
var sampler = active.get(key);
if (actionID != "" && key != actionID && sampler.action != actionID) continue;
if (registeredSamplers.indexOf(sampler) != -1) continue;
registeredSamplers.push(sampler);
var cb = function() { runOutput(0); };
registeredCallbacks.set(sampler, cb);
animation.notifyOnMarker(sampler, marker, cb);
}
} }
} }

View File

@ -27,7 +27,7 @@ class PlayAnimationTreeNode extends LogicNode {
} }
function playAnim() { function playAnim() {
if (init = false) return; if (init == false) return;
init = false; init = false;
tree.removeUpdate(playAnim); tree.removeUpdate(playAnim);

View File

@ -16,8 +16,10 @@ class RestartActionNode extends LogicNode {
var animation = object.animation; var animation = object.animation;
if (animation == null) animation = object.getBoneAnimation(object.uid); if (animation == null) animation = object.getBoneAnimation(object.uid);
if(animation.activeActions == null) return; if (animation == null || animation.activeActions == null) return;
animation.activeActions.get(action).restartAction(); var sampler = animation.activeActions.get(action);
if (sampler == null) return;
sampler.restartAction();
runOutput(0); runOutput(0);
} }

View File

@ -19,8 +19,10 @@ class SetActionPausedNode extends LogicNode {
var animation = object.animation; var animation = object.animation;
if (animation == null) animation = object.getBoneAnimation(object.uid); if (animation == null) animation = object.getBoneAnimation(object.uid);
if(animation.activeActions == null) return; if (animation == null || animation.activeActions == null) return;
animation.activeActions.get(action).paused = paused; var sampler = animation.activeActions.get(action);
if (sampler == null) return;
sampler.paused = paused;
runOutput(0); runOutput(0);
} }

View File

@ -1,4 +1,4 @@
// Reference: https://github.com/leenkx3d/leenkx_docs/blob/master/dev/renderpath.md // Reference: https://dev.leenkx.com/LeenkxTeam/LNXSDK/src/branch/main/api/src/manual/renderpath.md
package leenkx.renderpath; package leenkx.renderpath;
import iron.RenderPath; import iron.RenderPath;

View File

@ -222,7 +222,7 @@ class RenderPathForward {
} }
#end #end
#if ((rp_antialiasing == "SMAA") || (rp_antialiasing == "TAA") || (rp_ssr && !rp_ssr_half) || (rp_water) || (rp_depth_texture)) #if ((rp_antialiasing == "SMAA") || (rp_antialiasing == "TAA") || (rp_fsr1 && rp_supersampling != 4) || (rp_ssr && !rp_ssr_half) || (rp_water) || (rp_depth_texture))
{ {
var t = new RenderTargetRaw(); var t = new RenderTargetRaw();
t.name = "bufa"; t.name = "bufa";

View File

@ -139,10 +139,10 @@ class NavAgent extends Trait {
if (forward.length() > 0.0001) { if (forward.length() > 0.0001) {
forward.normalize(); forward.normalize();
var xAxis = new Vec4(1, 0, 0, 0); var yAxis = new Vec4(0, -1, 0, 0);
xAxis.applyQuat(targetRot); yAxis.applyQuat(targetRot);
var currForward = new Vec4().setFrom(xAxis); var currForward = new Vec4().setFrom(yAxis);
var twist = new Quat().fromTo(currForward, forward); var twist = new Quat().fromTo(currForward, forward);

View File

@ -263,8 +263,6 @@ class VehicleWheel {
} }
} }
#end
#elseif lnx_jolt #elseif lnx_jolt
typedef VehicleBody = leenkx.trait.physics.jolt.VehicleBody; typedef VehicleBody = leenkx.trait.physics.jolt.VehicleBody;

View File

@ -4,30 +4,72 @@ package leenkx.trait.internal;
class LoadingScreen { class LoadingScreen {
public static var bgImageName = 'splash.png';
public static var barColor: kha.Color = 0xff2ae0e0;
public static var bgColor: kha.Color = 0xff002a35;
public static var imageFit = 'Center';
public static var showPercentage = true;
static var font: kha.Font = null; static var font: kha.Font = null;
static var fontRequested: Bool = false; static var fontRequested: Bool = false;
static var randomColorR: Float = 0; static var bgImage: kha.Image = null;
static var randomColorG: Float = 0; static var bgRequested: Bool = false;
static var randomColorB: Float = 0;
static var isInitialized = false;
public static function render(g: kha.graphics2.Graphics, assetsLoaded: Int, assetsTotal: Int) { public static function render(g: kha.graphics2.Graphics, assetsLoaded: Int, assetsTotal: Int) {
if (!isInitialized) { var progress = assetsLoaded / assetsTotal;
randomColorR = Math.random(); final barHeight = 18;
randomColorG = Math.random(); var w = iron.App.w();
randomColorB = Math.random(); var h = iron.App.h();
isInitialized = true;
g.color = bgColor;
g.fillRect(0, 0, w, h);
if (bgImageName != '' && bgImage == null && !bgRequested) {
bgRequested = true;
iron.data.Data.getImage(bgImageName, (i: kha.Image) -> {
bgImage = i;
});
}
if (bgImage != null) {
g.color = 0xffffffff;
if (imageFit == 'Stretch') {
g.drawScaledImage(bgImage, 0, 0, w, h);
}
else {
var anchor = imageFit;
var dw: Float = bgImage.width;
var dh: Float = bgImage.height;
if (anchor.indexOf('Contain') == 0) {
var scale = Math.min(w / dw, h / dh);
dw *= scale;
dh *= scale;
anchor = anchor.length > 7 ? anchor.substr(7) : 'Center';
}
var x = 0.0;
var y = 0.0;
switch (anchor) {
case 'TopCenter' | 'Center' | 'BottomCenter':
x = (w - dw) / 2;
case 'TopRight' | 'CenterRight' | 'BottomRight':
x = w - dw;
default:
}
switch (anchor) {
case 'CenterLeft' | 'Center' | 'CenterRight':
y = (h - dh) / 2;
case 'BottomLeft' | 'BottomCenter' | 'BottomRight':
y = h - dh;
default:
}
g.drawScaledImage(bgImage, x, y, dw, dh);
}
} }
var progress = assetsLoaded / assetsTotal; g.color = barColor;
var r = progress * randomColorR; g.fillRect(0, h - barHeight, w / assetsTotal * assetsLoaded, barHeight);
var gg = progress * randomColorG;
var b = progress * randomColorB;
g.color = kha.Color.fromFloats(r, gg, b, 1);
g.fillRect(0, iron.App.h() - 50, iron.App.w() / assetsTotal * assetsLoaded, 50);
if (showPercentage) {
if (font == null && !fontRequested) { if (font == null && !fontRequested) {
fontRequested = true; fontRequested = true;
iron.data.Data.getFont('font_default.ttf', (f: kha.Font) -> { iron.data.Data.getFont('font_default.ttf', (f: kha.Font) -> {
@ -36,9 +78,10 @@ class LoadingScreen {
} }
else if (font != null) { else if (font != null) {
g.font = font; g.font = font;
g.fontSize = 50; g.fontSize = 30;
g.color = 0xffffffff; g.color = barColor;
g.drawString(Math.floor(progress * 100) + '%', iron.App.w() / assetsTotal * assetsLoaded - 80, iron.App.h() - 50); g.drawString(Math.floor(progress * 100) + '%', w / assetsTotal * assetsLoaded - 60, h - barHeight - 34);
}
} }
} }
} }

View File

@ -2724,6 +2724,8 @@ class LeenkxExporter:
self.extract_projection(o, proj) self.extract_projection(o, proj)
else: else:
self.extract_ortho(o, proj) self.extract_ortho(o, proj)
if par_x == par_y:
o.pop('aspect', None)
o['frustum_culling'] = objref.lnx_frustum_culling o['frustum_culling'] = objref.lnx_frustum_culling
o['clear_color'] = self.get_camera_clear_color() o['clear_color'] = self.get_camera_clear_color()
@ -3124,7 +3126,8 @@ class LeenkxExporter:
def export_world(self): def export_world(self):
"""Exports the world of the current scene.""" """Exports the world of the current scene."""
for world in bpy.data.worlds: world = self.scene.world
if world is not None: if world is not None:
world_name = lnx.utils.safestr(lnx.utils.asset_name(world) if world.library else world.name) world_name = lnx.utils.safestr(lnx.utils.asset_name(world) if world.library else world.name)
@ -3397,6 +3400,7 @@ class LeenkxExporter:
else: else:
self.extract_ortho(out_camera, proj) self.extract_ortho(out_camera, proj)
if par_x != par_y:
out_camera['aspect'] = effective_aspect out_camera['aspect'] = effective_aspect
self.output['camera_datas'].append(out_camera) self.output['camera_datas'].append(out_camera)

View File

@ -79,7 +79,7 @@ class MergeNode(LnxLogicTreeNode):
# Recreate all original inputs # Recreate all original inputs
array_nodes[str(id(newnode))] = newnode array_nodes[str(id(newnode))] = newnode
for idx, input in enumerate(self.inputs): for idx, input in enumerate(self.inputs):
bpy.ops.lnx.node_add_input('EXEC_DEFAULT', node_index=str(id(newnode)), socket_type='LnxNodeSocketAction') newnode.add_input('LnxNodeSocketAction', f'Input {idx}')
for link in input.links: for link in input.links:
node_tree.links.new(link.from_socket, newnode.inputs[idx]) node_tree.links.new(link.from_socket, newnode.inputs[idx])

View File

@ -278,6 +278,7 @@ def replace_all():
log.error(f'There were errors in the node update procedure, a detailed report has been written to {reportfile}') log.error(f'There were errors in the node update procedure, a detailed report has been written to {reportfile}')
if not bpy.app.background:
bpy.ops.lnx.show_node_update_errors() bpy.ops.lnx.show_node_update_errors()

View File

@ -232,7 +232,6 @@ def build_node_tree(world: bpy.types.World, frag: Shader, vert: Shader, con: Sha
wrd.world_defs += '_EnvClouds' wrd.world_defs += '_EnvClouds'
frag_write_clouds(world, frag) frag_write_clouds(world, frag)
if '_EnvSky' in world.world_defs or '_EnvTex' in world.world_defs or '_EnvImg' in world.world_defs or '_EnvCol' in world.world_defs or '_EnvClouds' in world.world_defs:
frag.add_uniform('float envmapStrength', link='_envmapStrength') frag.add_uniform('float envmapStrength', link='_envmapStrength')
# Clear background color # Clear background color
@ -242,7 +241,6 @@ def build_node_tree(world: bpy.types.World, frag: Shader, vert: Shader, con: Sha
else: else:
if '_EnvTex' in world.world_defs and '_EnvLDR' in world.world_defs: if '_EnvTex' in world.world_defs and '_EnvLDR' in world.world_defs:
frag.write('fragColor.rgb = pow(fragColor.rgb, vec3(2.2));') frag.write('fragColor.rgb = pow(fragColor.rgb, vec3(2.2));')
frag.write('fragColor.rgb *= envmapStrength;')
if '_EnvClouds' in world.world_defs: if '_EnvClouds' in world.world_defs:
frag.write('if (pos.z > 0.0) fragColor.rgb = mix(fragColor.rgb, traceClouds(fragColor.rgb, pos), clamp(pos.z * 5.0, 0, 1));') frag.write('if (pos.z > 0.0) fragColor.rgb = mix(fragColor.rgb, traceClouds(fragColor.rgb, pos), clamp(pos.z * 5.0, 0, 1));')

View File

@ -3,12 +3,14 @@ import bpy
import lnx.material.cycles as cycles import lnx.material.cycles as cycles
import lnx.material.mat_state as mat_state import lnx.material.mat_state as mat_state
import lnx.material.make_finalize as make_finalize import lnx.material.make_finalize as make_finalize
import lnx.material.make_mesh as make_mesh
import lnx.utils import lnx.utils
if lnx.is_reload(__name__): if lnx.is_reload(__name__):
cycles = lnx.reload_module(cycles) cycles = lnx.reload_module(cycles)
mat_state = lnx.reload_module(mat_state) mat_state = lnx.reload_module(mat_state)
make_finalize = lnx.reload_module(make_finalize) make_finalize = lnx.reload_module(make_finalize)
make_mesh = lnx.reload_module(make_mesh)
lnx.utils = lnx.reload_module(lnx.utils) lnx.utils = lnx.reload_module(lnx.utils)
else: else:
lnx.enable_reload(__name__) lnx.enable_reload(__name__)
@ -64,14 +66,8 @@ def make(context_id):
frag.write_attrib(' if (abs(mpos.z) > 1.0) discard;') frag.write_attrib(' if (abs(mpos.z) > 1.0) discard;')
frag.write_attrib(' vec2 texCoord = mpos.xy * 0.5 + 0.5;') frag.write_attrib(' vec2 texCoord = mpos.xy * 0.5 + 0.5;')
frag.write('vec3 basecol;') # Declared to prevent compiler errors, but decals currently don't output any emission
frag.write('float roughness;') make_mesh._write_material_attribs_default(frag, parse_opacity=True)
frag.write('float metallic;')
frag.write('float occlusion;')
frag.write('float specular;')
frag.write('float opacity;')
frag.write('vec3 emissionCol;') # Declared to prevent compiler errors, but decals currently don't output any emission
frag.write('float ior;')
cycles.parse(mat_state.nodes, con_decal, vert, frag, geom, tesc, tese) cycles.parse(mat_state.nodes, con_decal, vert, frag, geom, tesc, tese)
frag.write('n /= (abs(n.x) + abs(n.y) + abs(n.z));') frag.write('n /= (abs(n.x) + abs(n.y) + abs(n.z));')

View File

@ -241,8 +241,7 @@ def make(context_id, rpasses, shadowmap=False, shadowmap_transparent=False):
vert.add_out('vec4 wvpposition') vert.add_out('vec4 wvpposition')
vert.write('wvpposition = gl_Position;') vert.write('wvpposition = gl_Position;')
frag.write('float depth = (wvpposition.z / wvpposition.w) * 0.5 + 0.5;') frag.write('float depth = (wvpposition.z / wvpposition.w) * 0.5 + 0.5;')
frag.write('vec3 color = basecol;') frag.write('vec3 color = mix(vec3(1.0), basecol, opacity);')
frag.write('color *= 1.0 - opacity;')
frag.write('fragColor = vec4(color, depth);') frag.write('fragColor = vec4(color, depth);')
if parse_opacity and not shadowmap_transparent: if parse_opacity and not shadowmap_transparent:

View File

@ -398,11 +398,14 @@ def make_forward_mobile(con_mesh):
return return
is_shadows = '_ShadowMap' in wrd.world_defs is_shadows = '_ShadowMap' in wrd.world_defs
is_transparent_shadows = '_ShadowMapTransparent' in wrd.world_defs
is_shadows_atlas = '_ShadowMapAtlas' in wrd.world_defs is_shadows_atlas = '_ShadowMapAtlas' in wrd.world_defs
shadowmap_sun = 'shadowMap' shadowmap_sun = 'shadowMap'
shadowmap_sun_tr = 'shadowMapTransparent'
if is_shadows_atlas: if is_shadows_atlas:
is_single_atlas = '_SingleAtlas' in wrd.world_defs is_single_atlas = '_SingleAtlas' in wrd.world_defs
shadowmap_sun = 'shadowMapAtlasSun' if not is_single_atlas else 'shadowMapAtlas' shadowmap_sun = 'shadowMapAtlasSun' if not is_single_atlas else 'shadowMapAtlas'
shadowmap_sun_tr = 'shadowMapAtlasSunTransparent' if not is_single_atlas else 'shadowMapAtlasTransparent'
frag.add_uniform('vec2 smSizeUniform', '_shadowMapSize', included=True) frag.add_uniform('vec2 smSizeUniform', '_shadowMapSize', included=True)
frag.write('vec3 direct = vec3(0.0);') frag.write('vec3 direct = vec3(0.0);')
@ -417,6 +420,8 @@ def make_forward_mobile(con_mesh):
vert.write('lightPosition = LWVP * spos;') vert.write('lightPosition = LWVP * spos;')
frag.add_uniform('bool receiveShadow') frag.add_uniform('bool receiveShadow')
frag.add_uniform(f'sampler2DShadow {shadowmap_sun}') frag.add_uniform(f'sampler2DShadow {shadowmap_sun}')
if is_transparent_shadows:
frag.add_uniform(f'sampler2D {shadowmap_sun_tr}')
frag.add_uniform('float shadowsBias', '_sunShadowsBias') frag.add_uniform('float shadowsBias', '_sunShadowsBias')
frag.write('if (receiveShadow) {') frag.write('if (receiveShadow) {')
@ -427,14 +432,20 @@ def make_forward_mobile(con_mesh):
if is_shadows_atlas: if is_shadows_atlas:
frag.add_uniform('vec4 tileBoundsSunArray[maxLights * shadowmapCascades]', '_tileBoundsSunArray', included=True) frag.add_uniform('vec4 tileBoundsSunArray[maxLights * shadowmapCascades]', '_tileBoundsSunArray', included=True)
frag.write('tileBounds = tileBoundsSunArray[0];') frag.write('tileBounds = tileBoundsSunArray[0];')
frag.write(f'svisibility = shadowTestCascade({shadowmap_sun}, eye, wposition + n * shadowsBias * 10, shadowsBias, opacity != 1.0);') frag.write(f'svisibility = shadowTestCascade({shadowmap_sun},')
if is_transparent_shadows:
frag.write(f'{shadowmap_sun_tr},')
frag.write('eye, wposition + n * shadowsBias * 10, shadowsBias')
if is_transparent_shadows:
frag.write(', opacity != 1.0')
frag.write(');')
else: else:
frag.write('if (lightPosition.w > 0.0) {') frag.write('if (lightPosition.w > 0.0) {')
frag.write(' vec3 lPos = lightPosition.xyz / lightPosition.w;') frag.write(' vec3 lPos = lightPosition.xyz / lightPosition.w;')
if '_Legacy' in wrd.world_defs: if '_Legacy' in wrd.world_defs:
frag.write(f' svisibility = float(texture({shadowmap_sun}, vec2(lPos.xy)).r > lPos.z - shadowsBias, opacity != 1.0);') frag.write(f' svisibility = vec3(float(texture({shadowmap_sun}, vec2(lPos.xy)).r > lPos.z - shadowsBias));')
else: else:
frag.write(f' svisibility = texture({shadowmap_sun}, vec3(lPos.xy, lPos.z - shadowsBias), opacity != 1.0).r;') frag.write(f' svisibility = vec3(texture({shadowmap_sun}, vec3(lPos.xy, lPos.z - shadowsBias)).r);')
frag.write('}') frag.write('}')
frag.write('}') # receiveShadow frag.write('}') # receiveShadow
frag.write('direct += basecol * sdotNL * sunCol * svisibility;') frag.write('direct += basecol * sdotNL * sunCol * svisibility;')

View File

@ -80,9 +80,6 @@ def write(vert, particle_info=None, shadowmap=False):
vert.add_const('float', 'P_NORMAL_FACTOR', str(normal_factor)) vert.add_const('float', 'P_NORMAL_FACTOR', str(normal_factor))
if (shadowmap and normal_factor != 0.0):
vert.add_out('vec3 N')
if ramp_el_len != 0: if ramp_el_len != 0:
vert.add_const('float', 'P_SIZE_OVER_TIME_FACTOR', str(size_over_time_factor)) vert.add_const('float', 'P_SIZE_OVER_TIME_FACTOR', str(size_over_time_factor))
@ -156,6 +153,7 @@ def write(vert, particle_info=None, shadowmap=False):
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];') 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];')
if normal_factor != 0.0: if normal_factor != 0.0:
vert.add_uniform('mat3 N', '_normalMatrix')
vert.write('if (P_NORMAL_FACTOR != 0.0) {') vert.write('if (P_NORMAL_FACTOR != 0.0) {')
vert.write(' vec3 local_normal_dir = normalize(ipos);') vert.write(' vec3 local_normal_dir = normalize(ipos);')
vert.write(' vec3 world_emitter_normal = normalize(N * local_normal_dir);') vert.write(' vec3 world_emitter_normal = normalize(N * local_normal_dir);')

View File

@ -123,6 +123,8 @@ def set_socket_default(socket: NodeSocket, value: Any):
"""Set the socket's default value if it exists.""" """Set the socket's default value if it exists."""
if isinstance(socket, lnx.logicnode.lnx_sockets.LnxCustomSocket): if isinstance(socket, lnx.logicnode.lnx_sockets.LnxCustomSocket):
if socket.lnx_socket_type != 'NONE': if socket.lnx_socket_type != 'NONE':
if socket.lnx_socket_type == 'INT' and isinstance(value, float):
value = int(value)
socket.default_value_raw = value socket.default_value_raw = value
# Shader-type sockets don't have a default value # Shader-type sockets don't have a default value

View File

@ -31,7 +31,7 @@ else:
lnx.enable_reload(__name__) lnx.enable_reload(__name__)
# Leenkx version # Leenkx version
lnx_version = '2026.9' lnx_version = '2026.10'
lnx_commit = '$Id: 6b2644d47db169cedd95593497cc283207d23a74 $' lnx_commit = '$Id: 6b2644d47db169cedd95593497cc283207d23a74 $'
LNX_TORRENT_DEFAULT_TRACKERS = [ LNX_TORRENT_DEFAULT_TRACKERS = [
@ -382,6 +382,27 @@ def init_properties():
('Hashlink', 'Hashlink', 'Hashlink')], ('Hashlink', 'Hashlink', 'Hashlink')],
name="Runtime", description="Runtime to use when launching the game", default='Krom', update=assets.invalidate_shader_cache) name="Runtime", description="Runtime to use when launching the game", default='Krom', update=assets.invalidate_shader_cache)
bpy.types.World.lnx_loadscreen = BoolProperty(name="Loading Screen", description="Show asset loading progress on published builds", default=True) bpy.types.World.lnx_loadscreen = BoolProperty(name="Loading Screen", description="Show asset loading progress on published builds", default=True)
bpy.types.World.lnx_loadscreen_mode = EnumProperty(
items=[('Image', 'Image', 'Background image with a progress bar'),
('Simple', 'Simple', 'Background color with a progress bar')],
name="Loading Screen Mode", description="What the loading screen displays behind the progress bar", default='Image', update=assets.invalidate_compiler_cache)
bpy.types.World.lnx_loadscreen_bgcolor = FloatVectorProperty(name="Loading Background Color", description="Background color shown behind the loading bar and image", default=(0.0, 0.165, 0.208, 1.0), min=0.0, max=1.0, subtype='COLOR', size=4, update=assets.invalidate_compiler_cache)
bpy.types.World.lnx_loadscreen_image = StringProperty(name="Loading Screen Image", description="Background image shown behind the loading bar, leave as 'Default' to use the Leenkx logo wallpaper", default="Default", subtype="FILE_PATH", update=assets.invalidate_compiler_cache)
bpy.types.World.lnx_loadscreen_fit = EnumProperty(
items=[('TopLeft', 'Top Left', 'Draw the image at its native size anchored to the top left'),
('TopCenter', 'Top Center', 'Draw the image at its native size anchored to the top center'),
('TopRight', 'Top Right', 'Draw the image at its native size anchored to the top right'),
('CenterLeft', 'Center Left', 'Draw the image at its native size anchored to the center left'),
('Center', 'Center', 'Draw the image at its native size centered on screen'),
('CenterRight', 'Center Right', 'Draw the image at its native size anchored to the center right'),
('BottomLeft', 'Bottom Left', 'Draw the image at its native size anchored to the bottom left'),
('BottomCenter', 'Bottom Center', 'Draw the image at its native size anchored to the bottom center'),
('BottomRight', 'Bottom Right', 'Draw the image at its native size anchored to the bottom right'),
('Contain', 'Contain', 'Scale the image to fit inside the screen, preserving aspect ratio'),
('Stretch', 'Stretch to Fit', 'Stretch the image to fill the screen, ignoring aspect ratio')],
name="Image Fit", description="How the loading screen image is positioned on screen", default='Center', update=assets.invalidate_compiler_cache)
bpy.types.World.lnx_loadscreen_show_percentage = BoolProperty(name="Loading Screen Percentage", description="Draw the loading progress percentage above the progress bar", default=False, update=assets.invalidate_compiler_cache)
bpy.types.World.lnx_loadscreen_color = FloatVectorProperty(name="Loading Bar Color", description="Color of the loading progress bar and percentage text", default=(0.1647, 0.8784, 0.8784, 1.0), min=0.0, max=1.0, subtype='COLOR', size=4)
bpy.types.World.lnx_vsync = BoolProperty(name="VSync", description="Vertical Synchronization", default=True, update=assets.invalidate_compiler_cache) bpy.types.World.lnx_vsync = BoolProperty(name="VSync", description="Vertical Synchronization", default=True, update=assets.invalidate_compiler_cache)
bpy.types.World.lnx_dce = BoolProperty(name="DCE", description="Enable dead code elimination", default=False, update=assets.invalidate_compiler_cache) bpy.types.World.lnx_dce = BoolProperty(name="DCE", description="Enable dead code elimination", default=False, update=assets.invalidate_compiler_cache)
bpy.types.World.lnx_asset_compression = BoolProperty(name="Asset Compression", description="Enable scene data compression with LZ4. Warning: This will slow down export!", default=False, update=assets.invalidate_compiler_cache) bpy.types.World.lnx_asset_compression = BoolProperty(name="Asset Compression", description="Enable scene data compression with LZ4. Warning: This will slow down export!", default=False, update=assets.invalidate_compiler_cache)
@ -763,8 +784,8 @@ def update_leenkx_world():
# This allows for seamless migration from earlier versions of Leenkx # This allows for seamless migration from earlier versions of Leenkx
for rp in wrd.lnx_rplist: # TODO: deprecated for rp in wrd.lnx_rplist: # TODO: deprecated
if hasattr(rp, 'rp_gi') and rp.rp_gi != 'Off': if hasattr(rp, 'rp_gi') and rp.rp_gi != 'Off':
rp.rp_gi = 'Off'
rp.rp_voxels = rp.rp_gi rp.rp_voxels = rp.rp_gi
rp.rp_gi = 'Off'
# For some breaking changes we need to use a special update # For some breaking changes we need to use a special update
# routine first before regularly replacing nodes # routine first before regularly replacing nodes

View File

@ -168,7 +168,7 @@ def update_preset(self, context):
rpdat.rp_compositornodes = True rpdat.rp_compositornodes = True
rpdat.rp_volumetriclight = False rpdat.rp_volumetriclight = False
rpdat.rp_ssao = True rpdat.rp_ssao = True
rpdat.rp_ssgi = True rpdat.rp_ssgi = False
rpdat.lnx_ssrs = True rpdat.lnx_ssrs = True
rpdat.lnx_micro_shadowing = True rpdat.lnx_micro_shadowing = True
rpdat.rp_ssr = True rpdat.rp_ssr = True

View File

@ -1263,7 +1263,6 @@ class LNX_PT_ProjectFlagsPanel(bpy.types.Panel):
col = column_with_heading(layout, 'Runtime', align=True) col = column_with_heading(layout, 'Runtime', align=True)
col.prop(wrd, 'lnx_live_patch') col.prop(wrd, 'lnx_live_patch')
col.prop(wrd, 'lnx_stream_scene') col.prop(wrd, 'lnx_stream_scene')
col.prop(wrd, 'lnx_loadscreen')
col.prop(wrd, 'lnx_webview') col.prop(wrd, 'lnx_webview')
col.prop(wrd, 'lnx_write_config') col.prop(wrd, 'lnx_write_config')
@ -1289,6 +1288,33 @@ class LNX_PT_ProjectFlagsPanel(bpy.types.Panel):
col.prop(wrd, 'lnx_copy_override') col.prop(wrd, 'lnx_copy_override')
col.operator('lnx.copy_to_bundled', icon='IMAGE_DATA') col.operator('lnx.copy_to_bundled', icon='IMAGE_DATA')
class LNX_PT_ProjectFlagsLoadscreenPanel(bpy.types.Panel):
bl_label = "Loading Screen"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "render"
bl_options = {'DEFAULT_CLOSED'}
bl_parent_id = "LNX_PT_ProjectFlagsPanel"
def draw_header(self, context):
wrd = bpy.data.worlds['Lnx']
self.layout.prop(wrd, 'lnx_loadscreen', text='')
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
wrd = bpy.data.worlds['Lnx']
col = layout.column()
col.enabled = wrd.lnx_loadscreen
col.prop(wrd, 'lnx_loadscreen_mode')
col.prop(wrd, 'lnx_loadscreen_bgcolor')
col.prop(wrd, 'lnx_loadscreen_color')
if wrd.lnx_loadscreen_mode == 'Image':
col.prop(wrd, 'lnx_loadscreen_image')
col.prop(wrd, 'lnx_loadscreen_fit')
col.prop(wrd, 'lnx_loadscreen_show_percentage')
class LNX_PT_ProjectFlagsDebugConsolePanel(bpy.types.Panel): class LNX_PT_ProjectFlagsDebugConsolePanel(bpy.types.Panel):
bl_label = "Debug Console" bl_label = "Debug Console"
bl_space_type = "PROPERTIES" bl_space_type = "PROPERTIES"
@ -3017,9 +3043,9 @@ class LNX_OT_UpdateFileSDK(bpy.types.Operator):
wrd = bpy.data.worlds['Lnx'] wrd = bpy.data.worlds['Lnx']
# This allows for seamless migration from earlier versions of Leenkx # This allows for seamless migration from earlier versions of Leenkx
for rp in wrd.lnx_rplist: # TODO: deprecated for rp in wrd.lnx_rplist: # TODO: deprecated
if rp.rp_gi != 'Off': if hasattr(rp, 'rp_gi') and rp.rp_gi != 'Off':
rp.rp_voxels = rp.rp_gi
rp.rp_gi = 'Off' rp.rp_gi = 'Off'
rp.rp_voxelao = True
# Replace deprecated nodes # Replace deprecated nodes
lnx.logicnode.replacement.replace_all() lnx.logicnode.replacement.replace_all()
@ -3265,6 +3291,7 @@ __REG_CLASSES = (
LNX_PT_LeenkxExporterWindowsSettingsPanel, LNX_PT_LeenkxExporterWindowsSettingsPanel,
LNX_PT_LeenkxProjectPanel, LNX_PT_LeenkxProjectPanel,
LNX_PT_ProjectFlagsPanel, LNX_PT_ProjectFlagsPanel,
LNX_PT_ProjectFlagsLoadscreenPanel,
LNX_PT_ProjectFlagsDebugConsolePanel, LNX_PT_ProjectFlagsDebugConsolePanel,
LNX_PT_ProjectWindowPanel, LNX_PT_ProjectWindowPanel,
LNX_PT_ProjectModulesPanel, LNX_PT_ProjectModulesPanel,

View File

@ -539,6 +539,12 @@ let project = new Project('""" + lnx.utils.safesrc(wrd.lnx_project_name + '-' +
if wrd.lnx_loadscreen: if wrd.lnx_loadscreen:
assets.add_khafile_def('lnx_loadscreen') assets.add_khafile_def('lnx_loadscreen')
custom_loadscreen = os.path.isfile(lnx.utils.get_fp() + '/Sources/' + wrd.lnx_project_package + '/LoadingScreen.hx')
if wrd.lnx_loadscreen_mode == 'Image' or custom_loadscreen:
loadscreen_image = bpy.path.abspath(wrd.lnx_loadscreen_image) if wrd.lnx_loadscreen_image else ''
if loadscreen_image == '' or not os.path.isfile(loadscreen_image):
loadscreen_image = os.path.join(sdk_path, 'leenkx', 'blender', 'theme', 'splash.png')
khafile.write(add_assets(loadscreen_image.replace('\\', '/'), use_data_dir=use_data_dir, rel_path=do_relpath_sdk))
if wrd.lnx_winresize or state.target == 'html5': if wrd.lnx_winresize or state.target == 'html5':
assets.add_khafile_def('lnx_resizable') assets.add_khafile_def('lnx_resizable')
@ -742,6 +748,20 @@ class Main {
f.write("\n" + content_indent + "leenkx.system.Starter.numAssets = " + str(len(asset_references)) + ";") f.write("\n" + content_indent + "leenkx.system.Starter.numAssets = " + str(len(asset_references)) + ";")
f.write("\n" + content_indent + "leenkx.system.Starter.drawLoading = " + loadscreen_class + ".render;") f.write("\n" + content_indent + "leenkx.system.Starter.drawLoading = " + loadscreen_class + ".render;")
f.write("\n" + content_indent + "leenkx.system.Starter.assets = " + str([os.path.basename(path) for path in asset_references if 'Bundled' in path]) + ";") f.write("\n" + content_indent + "leenkx.system.Starter.assets = " + str([os.path.basename(path) for path in asset_references if 'Bundled' in path]) + ";")
if loadscreen_class == 'leenkx.trait.internal.LoadingScreen':
if wrd.lnx_loadscreen_mode == 'Image':
li = bpy.path.abspath(wrd.lnx_loadscreen_image) if wrd.lnx_loadscreen_image else ''
if li == '' or not os.path.isfile(li):
li = os.path.join(lnx.utils.get_sdk_path(), 'leenkx', 'blender', 'theme', 'splash.png')
f.write("\n" + content_indent + loadscreen_class + ".bgImageName = '" + os.path.basename(li) + "';")
f.write("\n" + content_indent + loadscreen_class + ".imageFit = '" + wrd.lnx_loadscreen_fit + "';")
else:
f.write("\n" + content_indent + loadscreen_class + ".bgImageName = '';")
c = wrd.lnx_loadscreen_color
f.write("\n" + content_indent + loadscreen_class + ".barColor = " + hex(0xff000000 + (int(c[0] * 255) << 16) + (int(c[1] * 255) << 8) + int(c[2] * 255)) + ";")
c = wrd.lnx_loadscreen_bgcolor
f.write("\n" + content_indent + loadscreen_class + ".bgColor = " + hex(0xff000000 + (int(c[0] * 255) << 16) + (int(c[1] * 255) << 8) + int(c[2] * 255)) + ";")
f.write("\n" + content_indent + loadscreen_class + ".showPercentage = " + ('true' if wrd.lnx_loadscreen_show_percentage else 'false') + ";")
scenes_list = [f"'{scene.name}'" for scene in bpy.data.scenes if getattr(scene, 'lnx_export', True)] scenes_list = [f"'{scene.name}'" for scene in bpy.data.scenes if getattr(scene, 'lnx_export', True)]
scenes_array = "[" + ", ".join(scenes_list) + "]" scenes_array = "[" + ", ".join(scenes_list) + "]"
@ -819,7 +839,7 @@ def write_indexhtml(w, h, is_publish):
<title>"""+html.escape( wrd.lnx_project_name)+"""</title> <title>"""+html.escape( wrd.lnx_project_name)+"""</title>
"""+icon+""" """+icon+"""
</head> </head>
<body style='margin: 0; padding: 0'> <body style='margin: 0; padding: 0; background: #001A22'>
""") """)
if rpdat.rp_stereo or wrd.lnx_winmode == 'Fullscreen': if rpdat.rp_stereo or wrd.lnx_winmode == 'Fullscreen':
f.write(""" f.write("""

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 KiB