forked from LeenkxTeam/LNXSDK
Compare commits
117 Commits
7fabd77ef8
...
main
Author | SHA1 | Date | |
---|---|---|---|
8fd05d5514 | |||
ad4013ed75 | |||
8ac567b57b | |||
43b7ae7060 | |||
29e9e71a6a | |||
bfb85b0a3b | |||
ef99b800e0 | |||
7cca955fc5 | |||
7e7bbd5eae | |||
c31b2a18ad | |||
fb47bf2564 | |||
7ae6750620 | |||
5b87010f76 | |||
97e952fc15 | |||
b440539d65 | |||
60a9db6459 | |||
3b5a93c92a | |||
4af990796e | |||
9fb4916c3c | |||
f61d5833bb | |||
40b52be713 | |||
07d8422f22 | |||
7179d42b27 | |||
99a5d7d445 | |||
5e0acd3d5d | |||
f4077e461b | |||
28943f1522 | |||
df4feac132 | |||
82412dbf81 | |||
6afc209db7 | |||
e9aae53be9 | |||
a65675ef75 | |||
8f073c5ae1 | |||
08d08e42d9 | |||
a1ee335c68 | |||
de6bf8a08a | |||
b9848cd2dc | |||
e922cc38e6 | |||
57f0e937d0 | |||
e234c8615c | |||
e594518e57 | |||
a41be0f436 | |||
1306033b36 | |||
eee0011cdd | |||
315ac0bd16 | |||
f289e6f89c | |||
700d236bf1 | |||
f228eab8d3 | |||
863d884b76 | |||
34e0f5a282 | |||
45e2e52008 | |||
444a215e63 | |||
fb2d2a1a7c | |||
f88c04abea | |||
6fdd4b3f70 | |||
a389c27d75 | |||
1909c3da9f | |||
5824bd91aa | |||
43fe559eef | |||
12c09545ce | |||
0e60951ec9 | |||
ccb8b358d3 | |||
1a8586777b | |||
3721c774a1 | |||
a58fba408d | |||
268fba6cd5 | |||
4ab14ce6c8 | |||
9023e8d1da | |||
b58c7a9632 | |||
99b70622f5 | |||
647b73b746 | |||
935c30ec08 | |||
0b0d597f89 | |||
d5878afb30 | |||
96b55a1a56 | |||
91b3072305 | |||
1d0b338d92 | |||
8e83c0d0d0 | |||
927baec4df | |||
f5c9e70d1a | |||
0423a735fc | |||
bd413917fc | |||
852377f60d | |||
e17e9a8e35 | |||
4055c979a1 | |||
06b003ecdb | |||
fd7f215bb2 | |||
6a1df9ec46 | |||
deccac3c46 | |||
432b0210b2 | |||
14cf5cebed | |||
2307e1504f | |||
1ebfecb644 | |||
175b575b23 | |||
63943a9cbf | |||
224d9be76f | |||
62d3c8757b | |||
3785f93573 | |||
ffb276745f | |||
d1c9258da5 | |||
3e0cd2be35 | |||
1fd1973470 | |||
a01c72ef76 | |||
4b01a562c9 | |||
6972d9abc4 | |||
63c6b9d98b | |||
313d24bbc8 | |||
6d2812306d | |||
e84d6ada84 | |||
5057f2b946 | |||
2715fe3398 | |||
7cb8b8a2d2 | |||
cd606009e0 | |||
965162b101 | |||
c61a57bfb3 | |||
cdc425fbcb | |||
846bb28c86 |
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
*.hdr binary
|
||||||
|
blender/lnx/props.py ident
|
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
*.DS_Store
|
@ -2,10 +2,12 @@
|
|||||||
-cp ../Kha/Backends/Krom
|
-cp ../Kha/Backends/Krom
|
||||||
-cp ../leenkx/Sources
|
-cp ../leenkx/Sources
|
||||||
-cp ../iron/Sources
|
-cp ../iron/Sources
|
||||||
|
-cp ../lib/aura/Sources
|
||||||
-cp ../lib/haxebullet/Sources
|
-cp ../lib/haxebullet/Sources
|
||||||
-cp ../lib/haxerecast/Sources
|
-cp ../lib/haxerecast/Sources
|
||||||
-cp ../lib/zui/Sources
|
-cp ../lib/zui/Sources
|
||||||
--macro include('iron', true, null, ['../iron/Sources'])
|
--macro include('iron', true, null, ['../iron/Sources'])
|
||||||
|
--macro include('aura', true, null, ['../lib/aura/Sources'])
|
||||||
--macro include('haxebullet', true, null, ['../lib/haxebullet/Sources'])
|
--macro include('haxebullet', true, null, ['../lib/haxebullet/Sources'])
|
||||||
--macro include('haxerecast', true, null, ['../lib/haxerecast/Sources'])
|
--macro include('haxerecast', true, null, ['../lib/haxerecast/Sources'])
|
||||||
--macro include('leenkx', true, ['leenkx.network'], ['../leenkx/Sources','../iron/Sources'])
|
--macro include('leenkx', true, ['leenkx.network'], ['../leenkx/Sources','../iron/Sources'])
|
||||||
|
@ -97,9 +97,9 @@ vec4 traceCone(const sampler3D voxels, const sampler3D voxelsSDF, const vec3 ori
|
|||||||
|
|
||||||
vec3 aniso_direction = -dir;
|
vec3 aniso_direction = -dir;
|
||||||
vec3 face_offset = vec3(
|
vec3 face_offset = vec3(
|
||||||
aniso_direction.x > 0.0 ? 0 : 1,
|
aniso_direction.x > 0.0 ? 0.0 : 1.0,
|
||||||
aniso_direction.y > 0.0 ? 2 : 3,
|
aniso_direction.y > 0.0 ? 2.0 : 3.0,
|
||||||
aniso_direction.z > 0.0 ? 4 : 5
|
aniso_direction.z > 0.0 ? 4.0 : 5.0
|
||||||
) / (6 + DIFFUSE_CONE_COUNT);
|
) / (6 + DIFFUSE_CONE_COUNT);
|
||||||
vec3 direction_weight = abs(dir);
|
vec3 direction_weight = abs(dir);
|
||||||
|
|
||||||
@ -201,9 +201,9 @@ float traceConeAO(const sampler3D voxels, const vec3 origin, const vec3 n, const
|
|||||||
|
|
||||||
vec3 aniso_direction = -dir;
|
vec3 aniso_direction = -dir;
|
||||||
vec3 face_offset = vec3(
|
vec3 face_offset = vec3(
|
||||||
aniso_direction.x > 0.0 ? 0 : 1,
|
aniso_direction.x > 0.0 ? 0.0 : 1.0,
|
||||||
aniso_direction.y > 0.0 ? 2 : 3,
|
aniso_direction.y > 0.0 ? 2.0 : 3.0,
|
||||||
aniso_direction.z > 0.0 ? 4 : 5
|
aniso_direction.z > 0.0 ? 4.0 : 5.0
|
||||||
) / (6 + DIFFUSE_CONE_COUNT);
|
) / (6 + DIFFUSE_CONE_COUNT);
|
||||||
vec3 direction_weight = abs(dir);
|
vec3 direction_weight = abs(dir);
|
||||||
|
|
||||||
@ -272,9 +272,9 @@ float traceConeShadow(const sampler3D voxels, const sampler3D voxelsSDF, const v
|
|||||||
|
|
||||||
vec3 aniso_direction = -dir;
|
vec3 aniso_direction = -dir;
|
||||||
vec3 face_offset = vec3(
|
vec3 face_offset = vec3(
|
||||||
aniso_direction.x > 0.0 ? 0 : 1,
|
aniso_direction.x > 0.0 ? 0.0 : 1.0,
|
||||||
aniso_direction.y > 0.0 ? 2 : 3,
|
aniso_direction.y > 0.0 ? 2.0 : 3.0,
|
||||||
aniso_direction.z > 0.0 ? 4 : 5
|
aniso_direction.z > 0.0 ? 4.0 : 5.0
|
||||||
) / (6 + DIFFUSE_CONE_COUNT);
|
) / (6 + DIFFUSE_CONE_COUNT);
|
||||||
vec3 direction_weight = abs(dir);
|
vec3 direction_weight = abs(dir);
|
||||||
float coneCoefficient = 2.0 * tan(aperture * 0.5);
|
float coneCoefficient = 2.0 * tan(aperture * 0.5);
|
||||||
|
@ -54,6 +54,22 @@ class App {
|
|||||||
if (Scene.active == null || !Scene.active.ready) return;
|
if (Scene.active == null || !Scene.active.ready) return;
|
||||||
|
|
||||||
iron.system.Time.update();
|
iron.system.Time.update();
|
||||||
|
|
||||||
|
if (lastw == -1) {
|
||||||
|
lastw = App.w();
|
||||||
|
lasth = App.h();
|
||||||
|
}
|
||||||
|
if (lastw != App.w() || lasth != App.h()) {
|
||||||
|
if (onResize != null) onResize();
|
||||||
|
else {
|
||||||
|
if (Scene.active != null && Scene.active.camera != null) {
|
||||||
|
Scene.active.camera.buildProjection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastw = App.w();
|
||||||
|
lasth = App.h();
|
||||||
|
|
||||||
if (pauseUpdates) return;
|
if (pauseUpdates) return;
|
||||||
|
|
||||||
#if lnx_debug
|
#if lnx_debug
|
||||||
@ -98,22 +114,6 @@ class App {
|
|||||||
for (cb in endFrameCallbacks) cb();
|
for (cb in endFrameCallbacks) cb();
|
||||||
updateTime = kha.Scheduler.realTime() - startTime;
|
updateTime = kha.Scheduler.realTime() - startTime;
|
||||||
#end
|
#end
|
||||||
|
|
||||||
// Rebuild projection on window resize
|
|
||||||
if (lastw == -1) {
|
|
||||||
lastw = App.w();
|
|
||||||
lasth = App.h();
|
|
||||||
}
|
|
||||||
if (lastw != App.w() || lasth != App.h()) {
|
|
||||||
if (onResize != null) onResize();
|
|
||||||
else {
|
|
||||||
if (Scene.active != null && Scene.active.camera != null) {
|
|
||||||
Scene.active.camera.buildProjection();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lastw = App.w();
|
|
||||||
lasth = App.h();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static function render(frames: Array<kha.Framebuffer>) {
|
static function render(frames: Array<kha.Framebuffer>) {
|
||||||
|
@ -518,12 +518,44 @@ class RenderPath {
|
|||||||
return Reflect.field(kha.Shaders, handle + "_comp");
|
return Reflect.field(kha.Shaders, handle + "_comp");
|
||||||
}
|
}
|
||||||
|
|
||||||
#if (kha_krom && lnx_vr)
|
#if lnx_vr
|
||||||
public function drawStereo(drawMeshes: Int->Void) {
|
public function drawStereo(drawMeshes: Void->Void) {
|
||||||
for (eye in 0...2) {
|
var vr = kha.vr.VrInterface.instance;
|
||||||
Krom.vrBeginRender(eye);
|
var appw = iron.App.w();
|
||||||
drawMeshes(eye);
|
var apph = iron.App.h();
|
||||||
Krom.vrEndRender(eye);
|
var halfw = Std.int(appw / 2);
|
||||||
|
var g = currentG;
|
||||||
|
|
||||||
|
if (vr != null && vr.IsPresenting()) {
|
||||||
|
// Left eye
|
||||||
|
Scene.active.camera.V.setFrom(Scene.active.camera.leftV);
|
||||||
|
Scene.active.camera.P.self = vr.GetProjectionMatrix(0);
|
||||||
|
g.viewport(0, 0, halfw, apph);
|
||||||
|
drawMeshes();
|
||||||
|
|
||||||
|
// Right eye
|
||||||
|
begin(g, additionalTargets);
|
||||||
|
Scene.active.camera.V.setFrom(Scene.active.camera.rightV);
|
||||||
|
Scene.active.camera.P.self = vr.GetProjectionMatrix(1);
|
||||||
|
g.viewport(halfw, 0, halfw, apph);
|
||||||
|
drawMeshes();
|
||||||
|
}
|
||||||
|
else { // Simulate
|
||||||
|
Scene.active.camera.buildProjection(halfw / apph);
|
||||||
|
|
||||||
|
// Left eye
|
||||||
|
g.viewport(0, 0, halfw, apph);
|
||||||
|
drawMeshes();
|
||||||
|
|
||||||
|
// Right eye
|
||||||
|
begin(g, additionalTargets);
|
||||||
|
Scene.active.camera.transform.move(Scene.active.camera.right(), 0.032);
|
||||||
|
Scene.active.camera.buildMatrix();
|
||||||
|
g.viewport(halfw, 0, halfw, apph);
|
||||||
|
drawMeshes();
|
||||||
|
|
||||||
|
Scene.active.camera.transform.move(Scene.active.camera.right(), -0.032);
|
||||||
|
Scene.active.camera.buildMatrix();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#end
|
#end
|
||||||
|
@ -783,6 +783,11 @@ class Scene {
|
|||||||
if (o.tilesheet_ref != null) {
|
if (o.tilesheet_ref != null) {
|
||||||
cast(object, MeshObject).setupTilesheet(sceneName, o.tilesheet_ref, o.tilesheet_action_ref);
|
cast(object, MeshObject).setupTilesheet(sceneName, o.tilesheet_ref, o.tilesheet_action_ref);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (o.camera_list != null){
|
||||||
|
cast(object, MeshObject).cameraList = o.camera_list;
|
||||||
|
}
|
||||||
|
|
||||||
returnObject(object, o, done);
|
returnObject(object, o, done);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -882,8 +887,12 @@ class Scene {
|
|||||||
var ptype: String = t.props[i * 3 + 1];
|
var ptype: String = t.props[i * 3 + 1];
|
||||||
var pval: Dynamic = t.props[i * 3 + 2];
|
var pval: Dynamic = t.props[i * 3 + 2];
|
||||||
|
|
||||||
if (StringTools.endsWith(ptype, "Object") && pval != "") {
|
if (StringTools.endsWith(ptype, "Object") && pval != "" && pval != null) {
|
||||||
Reflect.setProperty(traitInst, pname, Scene.active.getChild(pval));
|
Reflect.setProperty(traitInst, pname, Scene.active.getChild(pval));
|
||||||
|
} else if (ptype == "TSceneFormat" && pval != "") {
|
||||||
|
Data.getSceneRaw(pval, function (r: TSceneFormat) {
|
||||||
|
Reflect.setProperty(traitInst, pname, r);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
switch (ptype) {
|
switch (ptype) {
|
||||||
|
@ -441,6 +441,7 @@ typedef TObj = {
|
|||||||
@:optional public var traits: Array<TTrait>;
|
@:optional public var traits: Array<TTrait>;
|
||||||
@:optional public var properties: Array<TProperty>;
|
@:optional public var properties: Array<TProperty>;
|
||||||
@:optional public var vertex_groups: Array<TVertex_groups>;
|
@:optional public var vertex_groups: Array<TVertex_groups>;
|
||||||
|
@:optional public var camera_list: Array<String>;
|
||||||
@:optional public var constraints: Array<TConstraint>;
|
@:optional public var constraints: Array<TConstraint>;
|
||||||
@:optional public var dimensions: Float32Array; // Geometry objects
|
@:optional public var dimensions: Float32Array; // Geometry objects
|
||||||
@:optional public var object_actions: Array<String>;
|
@:optional public var object_actions: Array<String>;
|
||||||
|
@ -31,11 +31,21 @@ class CameraObject extends Object {
|
|||||||
static var vcenter = new Vec4();
|
static var vcenter = new Vec4();
|
||||||
static var vup = new Vec4();
|
static var vup = new Vec4();
|
||||||
|
|
||||||
|
#if lnx_vr
|
||||||
|
var helpMat = Mat4.identity();
|
||||||
|
public var leftV = Mat4.identity();
|
||||||
|
public var rightV = Mat4.identity();
|
||||||
|
#end
|
||||||
|
|
||||||
public function new(data: CameraData) {
|
public function new(data: CameraData) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.data = data;
|
this.data = data;
|
||||||
|
|
||||||
|
#if lnx_vr
|
||||||
|
iron.system.VR.initButton();
|
||||||
|
#end
|
||||||
|
|
||||||
buildProjection();
|
buildProjection();
|
||||||
|
|
||||||
V = Mat4.identity();
|
V = Mat4.identity();
|
||||||
@ -117,6 +127,26 @@ class CameraObject extends Object {
|
|||||||
V.getInverse(transform.world);
|
V.getInverse(transform.world);
|
||||||
VP.multmats(P, V);
|
VP.multmats(P, V);
|
||||||
|
|
||||||
|
|
||||||
|
#if lnx_vr
|
||||||
|
var vr = kha.vr.VrInterface.instance;
|
||||||
|
if (vr != null && vr.IsPresenting()) {
|
||||||
|
leftV.setFrom(V);
|
||||||
|
helpMat.self = vr.GetViewMatrix(0);
|
||||||
|
leftV.multmat(helpMat);
|
||||||
|
|
||||||
|
rightV.setFrom(V);
|
||||||
|
helpMat.self = vr.GetViewMatrix(1);
|
||||||
|
rightV.multmat(helpMat);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
leftV.setFrom(V);
|
||||||
|
}
|
||||||
|
VP.multmats(P, leftV);
|
||||||
|
#else
|
||||||
|
VP.multmats(P, V);
|
||||||
|
#end
|
||||||
|
|
||||||
if (data.raw.frustum_culling) {
|
if (data.raw.frustum_culling) {
|
||||||
buildViewFrustum(VP, frustumPlanes);
|
buildViewFrustum(VP, frustumPlanes);
|
||||||
}
|
}
|
||||||
|
@ -155,7 +155,12 @@ class LightObject extends Object {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function setCascade(camera: CameraObject, cascade: Int) {
|
public function setCascade(camera: CameraObject, cascade: Int) {
|
||||||
|
|
||||||
|
#if lnx_vr
|
||||||
|
m.setFrom(camera.leftV);
|
||||||
|
#else
|
||||||
m.setFrom(camera.V);
|
m.setFrom(camera.V);
|
||||||
|
#end
|
||||||
|
|
||||||
#if lnx_csm
|
#if lnx_csm
|
||||||
if (camSlicedP == null) {
|
if (camSlicedP == null) {
|
||||||
|
@ -24,6 +24,7 @@ class MeshObject extends Object {
|
|||||||
public var render_emitter = true;
|
public var render_emitter = true;
|
||||||
#end
|
#end
|
||||||
public var cameraDistance: Float;
|
public var cameraDistance: Float;
|
||||||
|
public var cameraList: Array<String> = null;
|
||||||
public var screenSize = 0.0;
|
public var screenSize = 0.0;
|
||||||
public var frustumCulling = true;
|
public var frustumCulling = true;
|
||||||
public var activeTilesheet: Tilesheet = null;
|
public var activeTilesheet: Tilesheet = null;
|
||||||
@ -235,6 +236,8 @@ class MeshObject extends Object {
|
|||||||
if (cullMesh(context, Scene.active.camera, RenderPath.active.light)) return;
|
if (cullMesh(context, Scene.active.camera, RenderPath.active.light)) return;
|
||||||
var meshContext = raw != null ? context == "mesh" : false;
|
var meshContext = raw != null ? context == "mesh" : false;
|
||||||
|
|
||||||
|
if (cameraList != null && cameraList.indexOf(Scene.active.camera.name) < 0) return;
|
||||||
|
|
||||||
#if lnx_particles
|
#if lnx_particles
|
||||||
if (raw != null && raw.is_particle && particleOwner == null) return; // Instancing not yet set-up by particle system owner
|
if (raw != null && raw.is_particle && particleOwner == null) return; // Instancing not yet set-up by particle system owner
|
||||||
if (particleSystems != null && meshContext) {
|
if (particleSystems != null && meshContext) {
|
||||||
@ -245,6 +248,7 @@ class MeshObject extends Object {
|
|||||||
Scene.active.spawnObject(psys.data.raw.instance_object, null, function(o: Object) {
|
Scene.active.spawnObject(psys.data.raw.instance_object, null, function(o: Object) {
|
||||||
if (o != null) {
|
if (o != null) {
|
||||||
var c: MeshObject = cast o;
|
var c: MeshObject = cast o;
|
||||||
|
c.cameraList = this.cameraList;
|
||||||
particleChildren.push(c);
|
particleChildren.push(c);
|
||||||
c.particleOwner = this;
|
c.particleOwner = this;
|
||||||
c.particleIndex = particleChildren.length - 1;
|
c.particleIndex = particleChildren.length - 1;
|
||||||
|
@ -14,7 +14,7 @@ class Time {
|
|||||||
return 1 / frequency;
|
return 1 / frequency;
|
||||||
}
|
}
|
||||||
|
|
||||||
static var _fixedStep: Null<Float>;
|
static var _fixedStep: Null<Float> = 1/60;
|
||||||
public static var fixedStep(get, never): Float;
|
public static var fixedStep(get, never): Float;
|
||||||
static function get_fixedStep(): Float {
|
static function get_fixedStep(): Float {
|
||||||
return _fixedStep;
|
return _fixedStep;
|
||||||
|
52
leenkx/Sources/iron/system/VR.hx
Normal file
52
leenkx/Sources/iron/system/VR.hx
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
package iron.system;
|
||||||
|
|
||||||
|
import iron.math.Mat4;
|
||||||
|
|
||||||
|
#if lnx_vr
|
||||||
|
class VR {
|
||||||
|
|
||||||
|
static var undistortionMatrix: Mat4 = null;
|
||||||
|
|
||||||
|
public function new() {}
|
||||||
|
|
||||||
|
public static function getUndistortionMatrix(): Mat4 {
|
||||||
|
if (undistortionMatrix == null) {
|
||||||
|
undistortionMatrix = Mat4.identity();
|
||||||
|
}
|
||||||
|
|
||||||
|
return undistortionMatrix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getMaxRadiusSq(): Float {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function initButton() {
|
||||||
|
function vrDownListener(index: Int, x: Float, y: Float) {
|
||||||
|
var vr = kha.vr.VrInterface.instance;
|
||||||
|
if (vr == null || !vr.IsVrEnabled() || vr.IsPresenting()) return;
|
||||||
|
var w: Float = iron.App.w();
|
||||||
|
var h: Float = iron.App.h();
|
||||||
|
if (x < w - 150 || y < h - 150) return;
|
||||||
|
vr.onVRRequestPresent();
|
||||||
|
}
|
||||||
|
|
||||||
|
function vrRender2D(g: kha.graphics2.Graphics) {
|
||||||
|
var vr = kha.vr.VrInterface.instance;
|
||||||
|
if (vr == null || !vr.IsVrEnabled() || vr.IsPresenting()) return;
|
||||||
|
var w: Float = iron.App.w();
|
||||||
|
var h: Float = iron.App.h();
|
||||||
|
g.color = 0xffff0000;
|
||||||
|
g.fillRect(w - 150, h - 150, 140, 140);
|
||||||
|
}
|
||||||
|
|
||||||
|
kha.input.Mouse.get().notify(vrDownListener, null, null, null);
|
||||||
|
iron.App.notifyOnRender2D(vrRender2D);
|
||||||
|
|
||||||
|
var vr = kha.vr.VrInterface.instance; // Straight to VR (Oculus Carmel)
|
||||||
|
if (vr != null && vr.IsVrEnabled()) {
|
||||||
|
vr.onVRRequestPresent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#end
|
@ -62,7 +62,7 @@ class DrawStringNode extends LogicNode {
|
|||||||
|
|
||||||
override function get(from: Int): Dynamic {
|
override function get(from: Int): Dynamic {
|
||||||
|
|
||||||
return from == 1 ? RenderToTexture.g.font.height(RenderToTexture.g.fontSize) : RenderToTexture.g.font.width(RenderToTexture.g.fontSize, string);
|
return from == 1 ? RenderToTexture.g.font.width(RenderToTexture.g.fontSize, string) : RenderToTexture.g.font.height(RenderToTexture.g.fontSize);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
17
leenkx/Sources/leenkx/logicnode/GetAudioPositionNode.hx
Normal file
17
leenkx/Sources/leenkx/logicnode/GetAudioPositionNode.hx
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package leenkx.logicnode;
|
||||||
|
|
||||||
|
import aura.Aura;
|
||||||
|
import aura.Types;
|
||||||
|
|
||||||
|
class GetAudioPositionNode extends LogicNode {
|
||||||
|
|
||||||
|
public function new(tree: LogicTree) {
|
||||||
|
super(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
override function get(from: Int): Dynamic {
|
||||||
|
var audio = inputs[0].get();
|
||||||
|
if (audio == null || audio.channel == null) return 0.0;
|
||||||
|
return audio.channel.floatPosition / audio.channel.sampleRate;
|
||||||
|
}
|
||||||
|
}
|
19
leenkx/Sources/leenkx/logicnode/GetCameraRenderFilterNode.hx
Normal file
19
leenkx/Sources/leenkx/logicnode/GetCameraRenderFilterNode.hx
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package leenkx.logicnode;
|
||||||
|
|
||||||
|
import iron.object.MeshObject;
|
||||||
|
import iron.object.CameraObject;
|
||||||
|
|
||||||
|
class GetCameraRenderFilterNode extends LogicNode {
|
||||||
|
|
||||||
|
public function new(tree: LogicTree) {
|
||||||
|
super(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
override function get(from: Int): Dynamic {
|
||||||
|
var mo: MeshObject = cast inputs[0].get();
|
||||||
|
|
||||||
|
if (mo == null) return null;
|
||||||
|
|
||||||
|
return mo.cameraList;
|
||||||
|
}
|
||||||
|
}
|
33
leenkx/Sources/leenkx/logicnode/GetPositionSpeakerNode.hx
Normal file
33
leenkx/Sources/leenkx/logicnode/GetPositionSpeakerNode.hx
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package leenkx.logicnode;
|
||||||
|
|
||||||
|
#if lnx_audio
|
||||||
|
import iron.object.SpeakerObject;
|
||||||
|
import kha.audio1.AudioChannel;
|
||||||
|
#end
|
||||||
|
|
||||||
|
class GetPositionSpeakerNode extends LogicNode {
|
||||||
|
|
||||||
|
public function new(tree: LogicTree) {
|
||||||
|
super(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
override function get(from: Int): Dynamic {
|
||||||
|
#if lnx_audio
|
||||||
|
var object: SpeakerObject = cast(inputs[0].get(), SpeakerObject);
|
||||||
|
if (object == null || object.sound == null) return 0.0;
|
||||||
|
|
||||||
|
if (object.channels.length == 0) return 0.0;
|
||||||
|
|
||||||
|
var channel = object.channels[0];
|
||||||
|
|
||||||
|
var position = 0.0;
|
||||||
|
if (channel != null) {
|
||||||
|
position = @:privateAccess channel.get_position();
|
||||||
|
}
|
||||||
|
|
||||||
|
return position;
|
||||||
|
#else
|
||||||
|
return 0.0;
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
}
|
@ -7,7 +7,6 @@ class GetWorldNode extends LogicNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override function get(from: Int): Dynamic {
|
override function get(from: Int): Dynamic {
|
||||||
trace(iron.Scene.active.world);
|
|
||||||
return iron.Scene.active.raw.world_ref;
|
return iron.Scene.active.raw.world_ref;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -3,7 +3,7 @@ package leenkx.logicnode;
|
|||||||
import iron.object.Object;
|
import iron.object.Object;
|
||||||
import iron.math.Vec4;
|
import iron.math.Vec4;
|
||||||
|
|
||||||
class GetWorldNode extends LogicNode {
|
class GetWorldOrientationNode extends LogicNode {
|
||||||
|
|
||||||
public var property0: String;
|
public var property0: String;
|
||||||
|
|
||||||
|
233
leenkx/Sources/leenkx/logicnode/MouseLookNode.hx
Normal file
233
leenkx/Sources/leenkx/logicnode/MouseLookNode.hx
Normal file
@ -0,0 +1,233 @@
|
|||||||
|
package leenkx.logicnode;
|
||||||
|
|
||||||
|
import iron.math.Vec4;
|
||||||
|
import iron.system.Input;
|
||||||
|
import iron.object.Object;
|
||||||
|
import kha.System;
|
||||||
|
import kha.FastFloat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MouseLookNode - FPS-style mouse look camera controller
|
||||||
|
*
|
||||||
|
* This node provides smooth, resolution-independent mouse look functionality for
|
||||||
|
* first-person perspective controls. It supports separate body and head objects,
|
||||||
|
* allowing for realistic FPS camera movement where the body rotates horizontally
|
||||||
|
* and the head/camera rotates vertically.
|
||||||
|
*
|
||||||
|
* Key Features:
|
||||||
|
* - Resolution-adaptive scaling for consistent feel across different screen sizes
|
||||||
|
* - Configurable axis orientations (X, Y, Z as front)
|
||||||
|
* - Optional mouse cursor locking and hiding
|
||||||
|
* - Invertible X/Y axes
|
||||||
|
* - Rotation capping/limiting for both horizontal and vertical movement
|
||||||
|
* - Smoothing support for smoother camera movement
|
||||||
|
* - Physics integration with automatic rigid body synchronization
|
||||||
|
* - Support for both local and world space head rotation
|
||||||
|
*/
|
||||||
|
class MouseLookNode extends LogicNode {
|
||||||
|
// Configuration properties (set from Blender node interface)
|
||||||
|
public var property0: String; // Front axis: "X", "Y", or "Z"
|
||||||
|
public var property1: Bool; // Hide Locked: auto-lock mouse cursor
|
||||||
|
public var property2: Bool; // Invert X: invert horizontal mouse movement
|
||||||
|
public var property3: Bool; // Invert Y: invert vertical mouse movement
|
||||||
|
public var property4: Bool; // Cap Left/Right: limit horizontal rotation
|
||||||
|
public var property5: Bool; // Cap Up/Down: limit vertical rotation
|
||||||
|
public var property6: Bool; // Head Local Space: use local space for head rotation
|
||||||
|
|
||||||
|
// Smoothing state variables - maintain previous frame values for interpolation
|
||||||
|
var smoothX: Float = 0.0; // Smoothed horizontal mouse delta
|
||||||
|
var smoothY: Float = 0.0; // Smoothed vertical mouse delta
|
||||||
|
|
||||||
|
// Rotation limits (in radians)
|
||||||
|
var maxHorizontal: Float = Math.PI; // Maximum horizontal rotation (180 degrees)
|
||||||
|
var maxVertical: Float = Math.PI / 2; // Maximum vertical rotation (90 degrees)
|
||||||
|
|
||||||
|
// Current rotation tracking for capping calculations
|
||||||
|
var currentHorizontal: Float = 0.0; // Accumulated horizontal rotation
|
||||||
|
var currentVertical: Float = 0.0; // Accumulated vertical rotation
|
||||||
|
|
||||||
|
// Resolution scaling reference - base resolution for consistent sensitivity
|
||||||
|
var baseResolutionWidth: Float = 1920.0;
|
||||||
|
|
||||||
|
// Sensitivity scaling constants
|
||||||
|
static inline var BASE_SCALE: Float = 1500.0; // Base sensitivity scale factor
|
||||||
|
static var RADIAN_SCALING_FACTOR: Float = Math.PI * 50.0 / 180.0; // Degrees to radians conversion with sensitivity scaling
|
||||||
|
|
||||||
|
public function new(tree: LogicTree) {
|
||||||
|
super(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main execution function called every frame when the node is active
|
||||||
|
*
|
||||||
|
* Input connections:
|
||||||
|
* [0] - Action trigger (not used in current implementation)
|
||||||
|
* [1] - Body Object: the main object that rotates horizontally
|
||||||
|
* [2] - Head Object: optional object that rotates vertically (typically camera)
|
||||||
|
* [3] - Sensitivity: mouse sensitivity multiplier
|
||||||
|
* [4] - Smoothing: movement smoothing factor (0.0 = no smoothing, 0.99 = maximum smoothing)
|
||||||
|
*/
|
||||||
|
override function run(from: Int) {
|
||||||
|
// Get input values from connected nodes
|
||||||
|
var bodyObject: Object = inputs[1].get();
|
||||||
|
var headObject: Object = inputs[2].get();
|
||||||
|
var sensitivity: FastFloat = inputs[3].get();
|
||||||
|
var smoothing: FastFloat = inputs[4].get();
|
||||||
|
|
||||||
|
// Early exit if no body object is provided
|
||||||
|
if (bodyObject == null) {
|
||||||
|
runOutput(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get mouse input state
|
||||||
|
var mouse = Input.getMouse();
|
||||||
|
|
||||||
|
// Handle automatic mouse cursor locking for FPS controls
|
||||||
|
if (property1) {
|
||||||
|
if (mouse.started() && !mouse.locked) {
|
||||||
|
mouse.lock(); // Center and hide cursor, enable unlimited movement
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only process mouse look when cursor is locked or mouse button is held
|
||||||
|
// This prevents unwanted camera movement when UI elements are being used
|
||||||
|
if (!mouse.locked && !mouse.down()) {
|
||||||
|
runOutput(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get raw mouse movement delta (pixels moved since last frame)
|
||||||
|
var deltaX: Float = mouse.movementX;
|
||||||
|
var deltaY: Float = mouse.movementY;
|
||||||
|
|
||||||
|
// Apply axis inversion if configured
|
||||||
|
if (property2) deltaX = -deltaX; // Invert horizontal movement
|
||||||
|
if (property3) deltaY = -deltaY; // Invert vertical movement
|
||||||
|
|
||||||
|
// Calculate resolution-adaptive scaling to maintain consistent sensitivity
|
||||||
|
// across different screen resolutions. Higher resolutions will have proportionally
|
||||||
|
// higher scaling to compensate for increased pixel density.
|
||||||
|
var resolutionMultiplier: Float = System.windowWidth() / baseResolutionWidth;
|
||||||
|
|
||||||
|
// Apply movement smoothing if enabled
|
||||||
|
// This creates a weighted average between current and previous movement values
|
||||||
|
// to reduce jittery camera movement, especially useful for low framerates
|
||||||
|
if (smoothing > 0.0) {
|
||||||
|
var smoothingFactor: Float = Math.min(smoothing, 0.99); // Cap smoothing to prevent complete freeze
|
||||||
|
smoothX = smoothX * smoothingFactor + deltaX * (1.0 - smoothingFactor);
|
||||||
|
smoothY = smoothY * smoothingFactor + deltaY * (1.0 - smoothingFactor);
|
||||||
|
deltaX = smoothX;
|
||||||
|
deltaY = smoothY;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define rotation axes based on the configured front axis
|
||||||
|
// These determine which 3D axes are used for horizontal and vertical rotation
|
||||||
|
var horizontalAxis = new Vec4(); // Axis for left/right body rotation
|
||||||
|
var verticalAxis = new Vec4(); // Axis for up/down head rotation
|
||||||
|
|
||||||
|
switch (property0) {
|
||||||
|
case "X": // X-axis forward (e.g., for side-scrolling or specific orientations)
|
||||||
|
horizontalAxis.set(0, 0, 1); // Z-axis for horizontal rotation
|
||||||
|
verticalAxis.set(0, 1, 0); // Y-axis for vertical rotation
|
||||||
|
case "Y": // Y-axis forward (most common for 3D games)
|
||||||
|
#if lnx_yaxisup
|
||||||
|
// Y-up coordinate system (Blender default)
|
||||||
|
horizontalAxis.set(0, 0, 1); // Z-axis for horizontal rotation
|
||||||
|
verticalAxis.set(1, 0, 0); // X-axis for vertical rotation
|
||||||
|
#else
|
||||||
|
// Z-up coordinate system
|
||||||
|
horizontalAxis.set(0, 0, 1); // Z-axis for horizontal rotation
|
||||||
|
verticalAxis.set(1, 0, 0); // X-axis for vertical rotation
|
||||||
|
#end
|
||||||
|
case "Z": // Z-axis forward (top-down or specific orientations)
|
||||||
|
horizontalAxis.set(0, 1, 0); // Y-axis for horizontal rotation
|
||||||
|
verticalAxis.set(1, 0, 0); // X-axis for vertical rotation
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate final sensitivity scaling combining base scale and resolution adaptation
|
||||||
|
var finalScale: Float = BASE_SCALE * resolutionMultiplier;
|
||||||
|
|
||||||
|
// Apply user-defined sensitivity multiplier
|
||||||
|
deltaX *= sensitivity;
|
||||||
|
deltaY *= sensitivity;
|
||||||
|
|
||||||
|
// Convert pixel movement to rotation angles (radians)
|
||||||
|
// Negative values ensure natural movement direction (moving mouse right rotates right)
|
||||||
|
var horizontalRotation: Float = (-deltaX / finalScale) * RADIAN_SCALING_FACTOR;
|
||||||
|
var verticalRotation: Float = (-deltaY / finalScale) * RADIAN_SCALING_FACTOR;
|
||||||
|
|
||||||
|
// Apply horizontal rotation capping if enabled
|
||||||
|
// This prevents the character from rotating beyond specified limits
|
||||||
|
if (property4) {
|
||||||
|
currentHorizontal += horizontalRotation;
|
||||||
|
// Clamp rotation to maximum horizontal range and adjust current frame rotation
|
||||||
|
if (currentHorizontal > maxHorizontal) {
|
||||||
|
horizontalRotation -= (currentHorizontal - maxHorizontal);
|
||||||
|
currentHorizontal = maxHorizontal;
|
||||||
|
} else if (currentHorizontal < -maxHorizontal) {
|
||||||
|
horizontalRotation -= (currentHorizontal + maxHorizontal);
|
||||||
|
currentHorizontal = -maxHorizontal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply vertical rotation capping if enabled
|
||||||
|
// This prevents looking too far up or down (like human neck limitations)
|
||||||
|
if (property5) {
|
||||||
|
currentVertical += verticalRotation;
|
||||||
|
// Clamp rotation to maximum vertical range and adjust current frame rotation
|
||||||
|
if (currentVertical > maxVertical) {
|
||||||
|
verticalRotation -= (currentVertical - maxVertical);
|
||||||
|
currentVertical = maxVertical;
|
||||||
|
} else if (currentVertical < -maxVertical) {
|
||||||
|
verticalRotation -= (currentVertical + maxVertical);
|
||||||
|
currentVertical = -maxVertical;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply horizontal rotation to body object (character turning left/right)
|
||||||
|
if (horizontalRotation != 0.0) {
|
||||||
|
bodyObject.transform.rotate(horizontalAxis, horizontalRotation);
|
||||||
|
|
||||||
|
// Synchronize physics rigid body if present
|
||||||
|
// This ensures physics simulation stays in sync with visual transform
|
||||||
|
#if lnx_physics
|
||||||
|
var rigidBody = bodyObject.getTrait(leenkx.trait.physics.RigidBody);
|
||||||
|
if (rigidBody != null) rigidBody.syncTransform();
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply vertical rotation to head object (camera looking up/down)
|
||||||
|
if (headObject != null && verticalRotation != 0.0) {
|
||||||
|
if (property6) {
|
||||||
|
// Local space rotation - recommended when head is a child of body
|
||||||
|
// This prevents gimbal lock and rotation inheritance issues
|
||||||
|
headObject.transform.rotate(verticalAxis, verticalRotation);
|
||||||
|
} else {
|
||||||
|
// World space rotation - uses head object's current right vector
|
||||||
|
// More accurate for independent head objects but can cause issues with parenting
|
||||||
|
var headVerticalAxis = headObject.transform.world.right();
|
||||||
|
headObject.transform.rotate(headVerticalAxis, verticalRotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Synchronize head physics rigid body if present
|
||||||
|
#if lnx_physics
|
||||||
|
var headRigidBody = headObject.getTrait(leenkx.trait.physics.RigidBody);
|
||||||
|
if (headRigidBody != null) headRigidBody.syncTransform();
|
||||||
|
#end
|
||||||
|
} else if (headObject == null && verticalRotation != 0.0) {
|
||||||
|
// Fallback: if no separate head object, apply vertical rotation to body
|
||||||
|
// This creates a simpler single-object camera control
|
||||||
|
bodyObject.transform.rotate(verticalAxis, verticalRotation);
|
||||||
|
|
||||||
|
// Synchronize body physics rigid body
|
||||||
|
#if lnx_physics
|
||||||
|
var rigidBody = bodyObject.getTrait(leenkx.trait.physics.RigidBody);
|
||||||
|
if (rigidBody != null) rigidBody.syncTransform();
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continue to next connected node in the logic tree
|
||||||
|
runOutput(0);
|
||||||
|
}
|
||||||
|
}
|
23
leenkx/Sources/leenkx/logicnode/OnceNode.hx
Normal file
23
leenkx/Sources/leenkx/logicnode/OnceNode.hx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package leenkx.logicnode;
|
||||||
|
|
||||||
|
class OnceNode extends LogicNode {
|
||||||
|
|
||||||
|
var triggered:Bool = false;
|
||||||
|
|
||||||
|
public function new(tree: LogicTree) {
|
||||||
|
super(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
override function run(from: Int) {
|
||||||
|
if(from == 1){
|
||||||
|
triggered = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!triggered) {
|
||||||
|
triggered = true;
|
||||||
|
runOutput(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -9,19 +9,38 @@ import iron.Scene;
|
|||||||
|
|
||||||
class PlayAnimationTreeNode extends LogicNode {
|
class PlayAnimationTreeNode extends LogicNode {
|
||||||
|
|
||||||
|
var object: Object;
|
||||||
|
var action: Dynamic;
|
||||||
|
var init: Bool = false;
|
||||||
|
|
||||||
public function new(tree: LogicTree) {
|
public function new(tree: LogicTree) {
|
||||||
super(tree);
|
super(tree);
|
||||||
}
|
}
|
||||||
|
|
||||||
override function run(from: Int) {
|
override function run(from: Int) {
|
||||||
var object: Object = inputs[1].get();
|
object = inputs[1].get();
|
||||||
var action: Dynamic = inputs[2].get();
|
action = inputs[2].get();
|
||||||
|
|
||||||
assert(Error, object != null, "The object input not be null");
|
assert(Error, object != null, "The object input not be null");
|
||||||
|
init = true;
|
||||||
|
tree.notifyOnUpdate(playAnim);
|
||||||
|
// TO DO: Investigate AnimAction get and PlayAnimationTree notifiers
|
||||||
|
}
|
||||||
|
|
||||||
|
function playAnim() {
|
||||||
|
if (init = false) return;
|
||||||
|
|
||||||
|
init = false;
|
||||||
|
tree.removeUpdate(playAnim);
|
||||||
|
|
||||||
var animation = object.animation;
|
var animation = object.animation;
|
||||||
if(animation == null) {
|
if(animation == null) {
|
||||||
#if lnx_skin
|
#if lnx_skin
|
||||||
animation = object.getBoneAnimation(object.uid);
|
animation = object.getBoneAnimation(object.uid);
|
||||||
|
if (animation == null) {
|
||||||
|
tree.notifyOnUpdate(playAnim);
|
||||||
|
init = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
cast(animation, BoneAnimation).setAnimationLoop(function f(mats) {
|
cast(animation, BoneAnimation).setAnimationLoop(function f(mats) {
|
||||||
action(mats);
|
action(mats);
|
||||||
});
|
});
|
||||||
@ -32,7 +51,6 @@ class PlayAnimationTreeNode extends LogicNode {
|
|||||||
action(mats);
|
action(mats);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
runOutput(0);
|
runOutput(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
23
leenkx/Sources/leenkx/logicnode/SetAudioPositionNode.hx
Normal file
23
leenkx/Sources/leenkx/logicnode/SetAudioPositionNode.hx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package leenkx.logicnode;
|
||||||
|
|
||||||
|
import aura.Aura;
|
||||||
|
import aura.Types;
|
||||||
|
|
||||||
|
class SetAudioPositionNode extends LogicNode {
|
||||||
|
|
||||||
|
public function new(tree: LogicTree) {
|
||||||
|
super(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
override function run(from: Int) {
|
||||||
|
var audio = inputs[1].get();
|
||||||
|
if (audio == null) return;
|
||||||
|
|
||||||
|
var positionInSeconds:Float = inputs[2].get();
|
||||||
|
if (positionInSeconds < 0.0) positionInSeconds = 0.0;
|
||||||
|
|
||||||
|
audio.channel.floatPosition = positionInSeconds * audio.channel.sampleRate;
|
||||||
|
|
||||||
|
runOutput(0);
|
||||||
|
}
|
||||||
|
}
|
38
leenkx/Sources/leenkx/logicnode/SetCameraRenderFilterNode.hx
Normal file
38
leenkx/Sources/leenkx/logicnode/SetCameraRenderFilterNode.hx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package leenkx.logicnode;
|
||||||
|
|
||||||
|
import iron.object.MeshObject;
|
||||||
|
import iron.object.CameraObject;
|
||||||
|
|
||||||
|
class SetCameraRenderFilterNode extends LogicNode {
|
||||||
|
|
||||||
|
public var property0: String;
|
||||||
|
|
||||||
|
public function new(tree: LogicTree) {
|
||||||
|
super(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
override function run(from: Int) {
|
||||||
|
var mo: MeshObject = cast inputs[1].get();
|
||||||
|
var camera: CameraObject = inputs[2].get();
|
||||||
|
|
||||||
|
assert(Error, Std.isOfType(camera, CameraObject), "Camera must be a camera object!");
|
||||||
|
|
||||||
|
if (camera == null || mo == null) return;
|
||||||
|
|
||||||
|
if (property0 == 'Add'){
|
||||||
|
if (mo.cameraList == null || mo.cameraList.indexOf(camera.name) == -1){
|
||||||
|
if (mo.cameraList == null) mo.cameraList = [];
|
||||||
|
mo.cameraList.push(camera.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
if (mo.cameraList != null){
|
||||||
|
mo.cameraList.remove(camera.name);
|
||||||
|
if (mo.cameraList.length == 0)
|
||||||
|
mo.cameraList = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
runOutput(0);
|
||||||
|
}
|
||||||
|
}
|
21
leenkx/Sources/leenkx/logicnode/SetLightShadowNode.hx
Normal file
21
leenkx/Sources/leenkx/logicnode/SetLightShadowNode.hx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package leenkx.logicnode;
|
||||||
|
|
||||||
|
import iron.object.LightObject;
|
||||||
|
|
||||||
|
class SetLightShadowNode extends LogicNode {
|
||||||
|
|
||||||
|
public function new(tree: LogicTree) {
|
||||||
|
super(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
override function run(from: Int) {
|
||||||
|
var light: LightObject = inputs[1].get();
|
||||||
|
var shadow: Bool = inputs[2].get();
|
||||||
|
|
||||||
|
if (light == null) return;
|
||||||
|
|
||||||
|
light.data.raw.cast_shadow = shadow;
|
||||||
|
|
||||||
|
runOutput(0);
|
||||||
|
}
|
||||||
|
}
|
206
leenkx/Sources/leenkx/logicnode/SetLookAtRotationNode.hx
Normal file
206
leenkx/Sources/leenkx/logicnode/SetLookAtRotationNode.hx
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
package leenkx.logicnode;
|
||||||
|
|
||||||
|
import iron.math.Vec4;
|
||||||
|
import iron.math.Quat;
|
||||||
|
import iron.math.Mat4;
|
||||||
|
import iron.object.Object;
|
||||||
|
|
||||||
|
class SetLookAtRotationNode extends LogicNode {
|
||||||
|
|
||||||
|
public var property0: String; // Axis to align
|
||||||
|
public var property1: String; // Use vector for target (true/false)
|
||||||
|
public var property2: String; // Use vector for source (true/false)
|
||||||
|
public var property3: String; // Damping value (backward compatibility, now input socket)
|
||||||
|
public var property4: String; // Disable rotation on aligning axis (true/false)
|
||||||
|
public var property5: String; // Use local space (true/false)
|
||||||
|
|
||||||
|
// Store the calculated rotation for output
|
||||||
|
var calculatedRotation: Quat = null;
|
||||||
|
// Store the previous rotation for smooth interpolation
|
||||||
|
var previousRotation: Quat = null;
|
||||||
|
|
||||||
|
public function new(tree: LogicTree) {
|
||||||
|
super(tree);
|
||||||
|
previousRotation = new Quat();
|
||||||
|
}
|
||||||
|
|
||||||
|
override function run(from: Int): Void {
|
||||||
|
// Determine if we're using a vector or an object as source
|
||||||
|
var useSourceVector: Bool = property2 == "true";
|
||||||
|
var objectToUse: Object = null;
|
||||||
|
var objectLoc: Vec4 = null;
|
||||||
|
|
||||||
|
if (useSourceVector) {
|
||||||
|
// Use tree.object as the object to rotate
|
||||||
|
objectToUse = tree.object;
|
||||||
|
if (objectToUse == null) {
|
||||||
|
runOutput(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the source location directly
|
||||||
|
objectLoc = inputs[1].get();
|
||||||
|
if (objectLoc == null) {
|
||||||
|
runOutput(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Get the source object (or fallback to tree.object)
|
||||||
|
objectToUse = (inputs.length > 1 && inputs[1] != null) ? inputs[1].get() : tree.object;
|
||||||
|
if (objectToUse == null) {
|
||||||
|
runOutput(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get source object's WORLD position (important for child objects)
|
||||||
|
objectLoc = new Vec4(objectToUse.transform.worldx(), objectToUse.transform.worldy(), objectToUse.transform.worldz());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine if we're using a vector or an object as target
|
||||||
|
var useTargetVector: Bool = property1 == "true";
|
||||||
|
var targetLoc: Vec4 = null;
|
||||||
|
|
||||||
|
if (useTargetVector) {
|
||||||
|
// Get the target location directly
|
||||||
|
targetLoc = inputs[2].get();
|
||||||
|
if (targetLoc == null) {
|
||||||
|
runOutput(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Get the target object
|
||||||
|
var targetObject: Object = inputs[2].get();
|
||||||
|
if (targetObject == null) {
|
||||||
|
runOutput(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get target object's WORLD position (important for child objects)
|
||||||
|
targetLoc = new Vec4(targetObject.transform.worldx(), targetObject.transform.worldy(), targetObject.transform.worldz());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate direction to target
|
||||||
|
var direction = new Vec4(
|
||||||
|
targetLoc.x - objectLoc.x,
|
||||||
|
targetLoc.y - objectLoc.y,
|
||||||
|
targetLoc.z - objectLoc.z
|
||||||
|
);
|
||||||
|
direction.normalize();
|
||||||
|
|
||||||
|
// Calculate target rotation based on selected axis
|
||||||
|
calculatedRotation = new Quat();
|
||||||
|
switch (property0) {
|
||||||
|
case "X":
|
||||||
|
calculatedRotation.fromTo(new Vec4(1, 0, 0), direction);
|
||||||
|
case "-X":
|
||||||
|
calculatedRotation.fromTo(new Vec4(-1, 0, 0), direction);
|
||||||
|
case "Y":
|
||||||
|
calculatedRotation.fromTo(new Vec4(0, 1, 0), direction);
|
||||||
|
case "-Y":
|
||||||
|
calculatedRotation.fromTo(new Vec4(0, -1, 0), direction);
|
||||||
|
case "Z":
|
||||||
|
calculatedRotation.fromTo(new Vec4(0, 0, 1), direction);
|
||||||
|
case "-Z":
|
||||||
|
calculatedRotation.fromTo(new Vec4(0, 0, -1), direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If disable rotation on aligning axis is enabled, constrain the target rotation
|
||||||
|
if (property4 == "true") {
|
||||||
|
// Apply constraint to the target rotation BEFORE damping to avoid jiggling
|
||||||
|
var eulerAngles = calculatedRotation.toEulerOrdered("XYZ");
|
||||||
|
|
||||||
|
// Set the rotation around the selected axis to 0
|
||||||
|
switch (property0) {
|
||||||
|
case "X", "-X":
|
||||||
|
eulerAngles.x = 0.0;
|
||||||
|
case "Y", "-Y":
|
||||||
|
eulerAngles.y = 0.0;
|
||||||
|
case "Z", "-Z":
|
||||||
|
eulerAngles.z = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert back to quaternion
|
||||||
|
calculatedRotation.fromEulerOrdered(eulerAngles, "XYZ");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert world rotation to local rotation if local space is enabled and object has a parent
|
||||||
|
var targetRotation = new Quat();
|
||||||
|
if (property5 == "true" && objectToUse.parent != null) {
|
||||||
|
// Get parent's world rotation
|
||||||
|
var parentWorldLoc = new Vec4();
|
||||||
|
var parentWorldRot = new Quat();
|
||||||
|
var parentWorldScale = new Vec4();
|
||||||
|
objectToUse.parent.transform.world.decompose(parentWorldLoc, parentWorldRot, parentWorldScale);
|
||||||
|
|
||||||
|
// Convert world rotation to local space by removing parent's rotation influence
|
||||||
|
// local_rotation = inverse(parent_world_rotation) * world_rotation
|
||||||
|
var invParentRot = new Quat().setFrom(parentWorldRot);
|
||||||
|
invParentRot.x = -invParentRot.x;
|
||||||
|
invParentRot.y = -invParentRot.y;
|
||||||
|
invParentRot.z = -invParentRot.z;
|
||||||
|
|
||||||
|
targetRotation.multquats(invParentRot, calculatedRotation);
|
||||||
|
} else {
|
||||||
|
// No local space conversion needed, use world rotation directly
|
||||||
|
targetRotation.setFrom(calculatedRotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply rotation with damping
|
||||||
|
var dampingValue: Float = 0.0;
|
||||||
|
|
||||||
|
// Try to get damping from input socket first (index 3), fallback to property
|
||||||
|
if (inputs.length > 3 && inputs[3] != null) {
|
||||||
|
var dampingInput: Dynamic = inputs[3].get();
|
||||||
|
if (dampingInput != null) {
|
||||||
|
dampingValue = dampingInput;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Fallback to property for backward compatibility
|
||||||
|
dampingValue = Std.parseFloat(property3);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dampingValue > 0.0) {
|
||||||
|
// Create a fixed interpolation rate that never reaches exactly 1.0
|
||||||
|
// Higher damping = slower rotation (smaller step)
|
||||||
|
var step = Math.max(0.001, (1.0 - dampingValue) * 0.2); // 0.001 to 0.2 range
|
||||||
|
|
||||||
|
// Get current local rotation as quaternion
|
||||||
|
var currentLocalRot = new Quat().setFrom(objectToUse.transform.rot);
|
||||||
|
|
||||||
|
// Calculate the difference between current and target rotation
|
||||||
|
var diffQuat = new Quat();
|
||||||
|
// q1 * inverse(q2) gives the rotation from q2 to q1
|
||||||
|
var invCurrent = new Quat().setFrom(currentLocalRot);
|
||||||
|
invCurrent.x = -invCurrent.x;
|
||||||
|
invCurrent.y = -invCurrent.y;
|
||||||
|
invCurrent.z = -invCurrent.z;
|
||||||
|
diffQuat.multquats(targetRotation, invCurrent);
|
||||||
|
|
||||||
|
// Convert to axis-angle representation
|
||||||
|
var axis = new Vec4();
|
||||||
|
var angle = diffQuat.toAxisAngle(axis);
|
||||||
|
|
||||||
|
// Apply only a portion of this rotation (step)
|
||||||
|
var partialAngle = angle * step;
|
||||||
|
|
||||||
|
// Create partial rotation quaternion
|
||||||
|
var partialRot = new Quat().fromAxisAngle(axis, partialAngle);
|
||||||
|
|
||||||
|
// Apply this partial rotation to current local rotation
|
||||||
|
var newLocalRot = new Quat();
|
||||||
|
newLocalRot.multquats(partialRot, currentLocalRot);
|
||||||
|
|
||||||
|
// Apply the new local rotation
|
||||||
|
objectToUse.transform.rot.setFrom(newLocalRot);
|
||||||
|
} else {
|
||||||
|
// No damping, apply instant rotation
|
||||||
|
objectToUse.transform.rot.setFrom(targetRotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
objectToUse.transform.buildMatrix();
|
||||||
|
|
||||||
|
runOutput(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// No output sockets needed - this node only performs actions
|
||||||
|
}
|
@ -0,0 +1,74 @@
|
|||||||
|
package leenkx.logicnode;
|
||||||
|
|
||||||
|
import iron.object.Object;
|
||||||
|
import iron.math.Vec4;
|
||||||
|
import iron.math.Mat4;
|
||||||
|
import iron.system.Time;
|
||||||
|
|
||||||
|
class SetObjectDelayedLocationNode extends LogicNode {
|
||||||
|
public var use_local_space: Bool = false;
|
||||||
|
|
||||||
|
private var initialOffset: Vec4 = null;
|
||||||
|
private var targetPos: Vec4 = new Vec4();
|
||||||
|
private var currentPos: Vec4 = new Vec4();
|
||||||
|
private var deltaVec: Vec4 = new Vec4();
|
||||||
|
private var tempVec: Vec4 = new Vec4();
|
||||||
|
|
||||||
|
private var lastParent: Object = null;
|
||||||
|
private var invParentMatrix: Mat4 = null;
|
||||||
|
|
||||||
|
public function new(tree: LogicTree) {
|
||||||
|
super(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
override function run(from: Int) {
|
||||||
|
var follower: Object = inputs[1].get();
|
||||||
|
var target: Object = inputs[2].get();
|
||||||
|
var delay: Float = inputs[3].get();
|
||||||
|
|
||||||
|
if (follower == null || target == null) return runOutput(0);
|
||||||
|
|
||||||
|
if (initialOffset == null) {
|
||||||
|
initialOffset = new Vec4();
|
||||||
|
var followerPos = follower.transform.world.getLoc();
|
||||||
|
var targetPos = target.transform.world.getLoc();
|
||||||
|
initialOffset.setFrom(followerPos);
|
||||||
|
initialOffset.sub(targetPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
targetPos.setFrom(target.transform.world.getLoc());
|
||||||
|
currentPos.setFrom(follower.transform.world.getLoc());
|
||||||
|
|
||||||
|
tempVec.setFrom(targetPos).add(initialOffset);
|
||||||
|
|
||||||
|
deltaVec.setFrom(tempVec).sub(currentPos);
|
||||||
|
|
||||||
|
if (deltaVec.length() < 0.001 && delay < 0.01) {
|
||||||
|
runOutput(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (delay == 0.0) {
|
||||||
|
currentPos.setFrom(tempVec);
|
||||||
|
} else {
|
||||||
|
var smoothFactor = Math.exp(-Time.delta / Math.max(0.0001, delay));
|
||||||
|
currentPos.x = tempVec.x + (currentPos.x - tempVec.x) * smoothFactor;
|
||||||
|
currentPos.y = tempVec.y + (currentPos.y - tempVec.y) * smoothFactor;
|
||||||
|
currentPos.z = tempVec.z + (currentPos.z - tempVec.z) * smoothFactor;
|
||||||
|
}
|
||||||
|
if (use_local_space && follower.parent != null) {
|
||||||
|
if (follower.parent != lastParent || invParentMatrix == null) {
|
||||||
|
lastParent = follower.parent;
|
||||||
|
invParentMatrix = Mat4.identity();
|
||||||
|
invParentMatrix.getInverse(follower.parent.transform.world);
|
||||||
|
}
|
||||||
|
tempVec.setFrom(currentPos);
|
||||||
|
tempVec.applymat(invParentMatrix);
|
||||||
|
follower.transform.loc.set(tempVec.x, tempVec.y, tempVec.z);
|
||||||
|
} else {
|
||||||
|
follower.transform.loc.set(currentPos.x, currentPos.y, currentPos.z);
|
||||||
|
}
|
||||||
|
follower.transform.buildMatrix();
|
||||||
|
runOutput(0);
|
||||||
|
}
|
||||||
|
}
|
@ -55,9 +55,9 @@ class SetParticleDataNode extends LogicNode {
|
|||||||
@:privateAccess psys.aligny = vel.y;
|
@:privateAccess psys.aligny = vel.y;
|
||||||
@:privateAccess psys.alignz = vel.z;
|
@:privateAccess psys.alignz = vel.z;
|
||||||
case 'Velocity Random':
|
case 'Velocity Random':
|
||||||
psys.r.factor_random = inputs[3].get();
|
@:privateAccess psys.r.factor_random = inputs[3].get();
|
||||||
case 'Weight Gravity':
|
case 'Weight Gravity':
|
||||||
psys.r.weight_gravity = inputs[3].get();
|
@:privateAccess psys.r.weight_gravity = inputs[3].get();
|
||||||
if (iron.Scene.active.raw.gravity != null) {
|
if (iron.Scene.active.raw.gravity != null) {
|
||||||
@:privateAccess psys.gx = iron.Scene.active.raw.gravity[0] * @:privateAccess psys.r.weight_gravity;
|
@:privateAccess psys.gx = iron.Scene.active.raw.gravity[0] * @:privateAccess psys.r.weight_gravity;
|
||||||
@:privateAccess psys.gy = iron.Scene.active.raw.gravity[1] * @:privateAccess psys.r.weight_gravity;
|
@:privateAccess psys.gy = iron.Scene.active.raw.gravity[1] * @:privateAccess psys.r.weight_gravity;
|
||||||
|
39
leenkx/Sources/leenkx/logicnode/SetPositionSpeakerNode.hx
Normal file
39
leenkx/Sources/leenkx/logicnode/SetPositionSpeakerNode.hx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package leenkx.logicnode;
|
||||||
|
|
||||||
|
#if lnx_audio
|
||||||
|
import iron.object.SpeakerObject;
|
||||||
|
import kha.audio1.AudioChannel;
|
||||||
|
import iron.system.Audio;
|
||||||
|
#end
|
||||||
|
|
||||||
|
class SetPositionSpeakerNode extends LogicNode {
|
||||||
|
|
||||||
|
public function new(tree: LogicTree) {
|
||||||
|
super(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
override function run(from: Int) {
|
||||||
|
#if lnx_audio
|
||||||
|
var object: SpeakerObject = cast(inputs[1].get(), SpeakerObject);
|
||||||
|
if (object == null || object.sound == null) return;
|
||||||
|
|
||||||
|
var positionInSeconds:Float = inputs[2].get();
|
||||||
|
if (positionInSeconds < 0) positionInSeconds = 0;
|
||||||
|
|
||||||
|
var volume = object.data.volume;
|
||||||
|
var loop = object.data.loop;
|
||||||
|
var stream = object.data.stream;
|
||||||
|
|
||||||
|
object.stop();
|
||||||
|
|
||||||
|
var channel = Audio.play(object.sound, loop, stream);
|
||||||
|
if (channel != null) {
|
||||||
|
object.channels.push(channel);
|
||||||
|
channel.volume = volume;
|
||||||
|
@:privateAccess channel.set_position(positionInSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
#end
|
||||||
|
runOutput(0);
|
||||||
|
}
|
||||||
|
}
|
@ -641,17 +641,19 @@ class RenderPathForward {
|
|||||||
var framebuffer = "";
|
var framebuffer = "";
|
||||||
#end
|
#end
|
||||||
|
|
||||||
#if ((rp_antialiasing == "Off") || (rp_antialiasing == "FXAA"))
|
|
||||||
{
|
|
||||||
RenderPathCreator.finalTarget = path.currentTarget;
|
RenderPathCreator.finalTarget = path.currentTarget;
|
||||||
path.setTarget(framebuffer);
|
|
||||||
|
var target = "";
|
||||||
|
#if ((rp_antialiasing == "Off") || (rp_antialiasing == "FXAA") || (!rp_render_to_texture))
|
||||||
|
{
|
||||||
|
target = framebuffer;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
{
|
{
|
||||||
path.setTarget("buf");
|
target = "buf";
|
||||||
RenderPathCreator.finalTarget = path.currentTarget;
|
|
||||||
}
|
}
|
||||||
#end
|
#end
|
||||||
|
path.setTarget(target);
|
||||||
|
|
||||||
#if rp_compositordepth
|
#if rp_compositordepth
|
||||||
{
|
{
|
||||||
@ -671,6 +673,15 @@ class RenderPathForward {
|
|||||||
}
|
}
|
||||||
#end
|
#end
|
||||||
|
|
||||||
|
#if rp_overlays
|
||||||
|
{
|
||||||
|
path.setTarget(target);
|
||||||
|
path.clearTarget(null, 1.0);
|
||||||
|
path.drawMeshes("overlay");
|
||||||
|
}
|
||||||
|
#end
|
||||||
|
|
||||||
|
|
||||||
#if ((rp_antialiasing == "SMAA") || (rp_antialiasing == "TAA"))
|
#if ((rp_antialiasing == "SMAA") || (rp_antialiasing == "TAA"))
|
||||||
{
|
{
|
||||||
path.setTarget("bufa");
|
path.setTarget("bufa");
|
||||||
@ -701,12 +712,6 @@ class RenderPathForward {
|
|||||||
}
|
}
|
||||||
#end
|
#end
|
||||||
|
|
||||||
#if rp_overlays
|
|
||||||
{
|
|
||||||
path.clearTarget(null, 1.0);
|
|
||||||
path.drawMeshes("overlay");
|
|
||||||
}
|
|
||||||
#end
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function setupDepthTexture() {
|
public static function setupDepthTexture() {
|
||||||
|
@ -73,7 +73,17 @@ class PhysicsBreak extends Trait {
|
|||||||
collisionMargin: 0.04,
|
collisionMargin: 0.04,
|
||||||
linearDeactivationThreshold: 0.0,
|
linearDeactivationThreshold: 0.0,
|
||||||
angularDeactivationThrshold: 0.0,
|
angularDeactivationThrshold: 0.0,
|
||||||
deactivationTime: 0.0
|
deactivationTime: 0.0,
|
||||||
|
linearVelocityMin: 0.0,
|
||||||
|
linearVelocityMax: 0.0,
|
||||||
|
angularVelocityMin: 0.0,
|
||||||
|
angularVelocityMax: 0.0,
|
||||||
|
lockTranslationX: false,
|
||||||
|
lockTranslationY: false,
|
||||||
|
lockTranslationZ: false,
|
||||||
|
lockRotationX: false,
|
||||||
|
lockRotationY: false,
|
||||||
|
lockRotationZ: false
|
||||||
};
|
};
|
||||||
o.addTrait(new RigidBody(Shape.ConvexHull, ud.mass, ud.friction, 0, 1, params));
|
o.addTrait(new RigidBody(Shape.ConvexHull, ud.mass, ud.friction, 0, 1, params));
|
||||||
if (cast(o, MeshObject).data.geom.positions.values.length < 600) {
|
if (cast(o, MeshObject).data.geom.positions.values.length < 600) {
|
||||||
|
@ -280,6 +280,10 @@ class DebugConsole extends Trait {
|
|||||||
|
|
||||||
function drawObjectNameInList(object: iron.object.Object, selected: Bool) {
|
function drawObjectNameInList(object: iron.object.Object, selected: Bool) {
|
||||||
var _y = ui._y;
|
var _y = ui._y;
|
||||||
|
|
||||||
|
if (object.parent.name == 'Root' && object.raw == null)
|
||||||
|
ui.text(object.uid+'_'+object.name+' ('+iron.Scene.active.raw.world_ref+')');
|
||||||
|
else
|
||||||
ui.text(object.uid+'_'+object.name);
|
ui.text(object.uid+'_'+object.name);
|
||||||
|
|
||||||
if (object == iron.Scene.active.camera) {
|
if (object == iron.Scene.active.camera) {
|
||||||
|
@ -36,6 +36,18 @@ class RigidBody extends iron.Trait {
|
|||||||
var useDeactivation: Bool;
|
var useDeactivation: Bool;
|
||||||
var deactivationParams: Array<Float>;
|
var deactivationParams: Array<Float>;
|
||||||
var ccd = false; // Continuous collision detection
|
var ccd = false; // Continuous collision detection
|
||||||
|
// New velocity limiting properties
|
||||||
|
var linearVelocityMin: Float;
|
||||||
|
var linearVelocityMax: Float;
|
||||||
|
var angularVelocityMin: Float;
|
||||||
|
var angularVelocityMax: Float;
|
||||||
|
// New lock properties
|
||||||
|
var lockTranslationX: Bool;
|
||||||
|
var lockTranslationY: Bool;
|
||||||
|
var lockTranslationZ: Bool;
|
||||||
|
var lockRotationX: Bool;
|
||||||
|
var lockRotationY: Bool;
|
||||||
|
var lockRotationZ: Bool;
|
||||||
public var group = 1;
|
public var group = 1;
|
||||||
public var mask = 1;
|
public var mask = 1;
|
||||||
var trigger = false;
|
var trigger = false;
|
||||||
@ -120,7 +132,17 @@ class RigidBody extends iron.Trait {
|
|||||||
collisionMargin: 0.0,
|
collisionMargin: 0.0,
|
||||||
linearDeactivationThreshold: 0.0,
|
linearDeactivationThreshold: 0.0,
|
||||||
angularDeactivationThrshold: 0.0,
|
angularDeactivationThrshold: 0.0,
|
||||||
deactivationTime: 0.0
|
deactivationTime: 0.0,
|
||||||
|
linearVelocityMin: 0.0,
|
||||||
|
linearVelocityMax: 0.0,
|
||||||
|
angularVelocityMin: 0.0,
|
||||||
|
angularVelocityMax: 0.0,
|
||||||
|
lockTranslationX: false,
|
||||||
|
lockTranslationY: false,
|
||||||
|
lockTranslationZ: false,
|
||||||
|
lockRotationX: false,
|
||||||
|
lockRotationY: false,
|
||||||
|
lockRotationZ: false
|
||||||
};
|
};
|
||||||
|
|
||||||
if (flags == null) flags = {
|
if (flags == null) flags = {
|
||||||
@ -139,6 +161,18 @@ class RigidBody extends iron.Trait {
|
|||||||
this.angularFactors = [params.angularFactorsX, params.angularFactorsY, params.angularFactorsZ];
|
this.angularFactors = [params.angularFactorsX, params.angularFactorsY, params.angularFactorsZ];
|
||||||
this.collisionMargin = params.collisionMargin;
|
this.collisionMargin = params.collisionMargin;
|
||||||
this.deactivationParams = [params.linearDeactivationThreshold, params.angularDeactivationThrshold, params.deactivationTime];
|
this.deactivationParams = [params.linearDeactivationThreshold, params.angularDeactivationThrshold, params.deactivationTime];
|
||||||
|
// New velocity limiting properties
|
||||||
|
this.linearVelocityMin = params.linearVelocityMin;
|
||||||
|
this.linearVelocityMax = params.linearVelocityMax;
|
||||||
|
this.angularVelocityMin = params.angularVelocityMin;
|
||||||
|
this.angularVelocityMax = params.angularVelocityMax;
|
||||||
|
// New lock properties
|
||||||
|
this.lockTranslationX = params.lockTranslationX;
|
||||||
|
this.lockTranslationY = params.lockTranslationY;
|
||||||
|
this.lockTranslationZ = params.lockTranslationZ;
|
||||||
|
this.lockRotationX = params.lockRotationX;
|
||||||
|
this.lockRotationY = params.lockRotationY;
|
||||||
|
this.lockRotationZ = params.lockRotationZ;
|
||||||
this.animated = flags.animated;
|
this.animated = flags.animated;
|
||||||
this.trigger = flags.trigger;
|
this.trigger = flags.trigger;
|
||||||
this.ccd = flags.ccd;
|
this.ccd = flags.ccd;
|
||||||
@ -291,11 +325,25 @@ class RigidBody extends iron.Trait {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (linearFactors != null) {
|
if (linearFactors != null) {
|
||||||
setLinearFactor(linearFactors[0], linearFactors[1], linearFactors[2]);
|
// Apply lock properties by overriding factors
|
||||||
|
var lx = linearFactors[0];
|
||||||
|
var ly = linearFactors[1];
|
||||||
|
var lz = linearFactors[2];
|
||||||
|
if (lockTranslationX) lx = 0.0;
|
||||||
|
if (lockTranslationY) ly = 0.0;
|
||||||
|
if (lockTranslationZ) lz = 0.0;
|
||||||
|
setLinearFactor(lx, ly, lz);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (angularFactors != null) {
|
if (angularFactors != null) {
|
||||||
setAngularFactor(angularFactors[0], angularFactors[1], angularFactors[2]);
|
// Apply lock properties by overriding factors
|
||||||
|
var ax = angularFactors[0];
|
||||||
|
var ay = angularFactors[1];
|
||||||
|
var az = angularFactors[2];
|
||||||
|
if (lockRotationX) ax = 0.0;
|
||||||
|
if (lockRotationY) ay = 0.0;
|
||||||
|
if (lockRotationZ) az = 0.0;
|
||||||
|
setAngularFactor(ax, ay, az);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (trigger) bodyColl.setCollisionFlags(bodyColl.getCollisionFlags() | CF_NO_CONTACT_RESPONSE);
|
if (trigger) bodyColl.setCollisionFlags(bodyColl.getCollisionFlags() | CF_NO_CONTACT_RESPONSE);
|
||||||
@ -411,6 +459,55 @@ class RigidBody extends iron.Trait {
|
|||||||
var rbs = physics.getContacts(this);
|
var rbs = physics.getContacts(this);
|
||||||
if (rbs != null) for (rb in rbs) for (f in onContact) f(rb);
|
if (rbs != null) for (rb in rbs) for (f in onContact) f(rb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply velocity limiting if enabled
|
||||||
|
if (!animated && !staticObj) {
|
||||||
|
applyVelocityLimits();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyVelocityLimits() {
|
||||||
|
if (!ready) return;
|
||||||
|
|
||||||
|
// Check linear velocity limits
|
||||||
|
if (linearVelocityMin > 0.0 || linearVelocityMax > 0.0) {
|
||||||
|
var velocity = getLinearVelocity();
|
||||||
|
var speed = velocity.length();
|
||||||
|
|
||||||
|
if (linearVelocityMin > 0.0 && speed < linearVelocityMin) {
|
||||||
|
// Increase velocity to minimum
|
||||||
|
if (speed > 0.0) {
|
||||||
|
velocity.normalize();
|
||||||
|
velocity.mult(linearVelocityMin);
|
||||||
|
setLinearVelocity(velocity.x, velocity.y, velocity.z);
|
||||||
|
}
|
||||||
|
} else if (linearVelocityMax > 0.0 && speed > linearVelocityMax) {
|
||||||
|
// Clamp velocity to maximum
|
||||||
|
velocity.normalize();
|
||||||
|
velocity.mult(linearVelocityMax);
|
||||||
|
setLinearVelocity(velocity.x, velocity.y, velocity.z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check angular velocity limits
|
||||||
|
if (angularVelocityMin > 0.0 || angularVelocityMax > 0.0) {
|
||||||
|
var angularVel = getAngularVelocity();
|
||||||
|
var angularSpeed = angularVel.length();
|
||||||
|
|
||||||
|
if (angularVelocityMin > 0.0 && angularSpeed < angularVelocityMin) {
|
||||||
|
// Increase angular velocity to minimum
|
||||||
|
if (angularSpeed > 0.0) {
|
||||||
|
angularVel.normalize();
|
||||||
|
angularVel.mult(angularVelocityMin);
|
||||||
|
setAngularVelocity(angularVel.x, angularVel.y, angularVel.z);
|
||||||
|
}
|
||||||
|
} else if (angularVelocityMax > 0.0 && angularSpeed > angularVelocityMax) {
|
||||||
|
// Clamp angular velocity to maximum
|
||||||
|
angularVel.normalize();
|
||||||
|
angularVel.mult(angularVelocityMax);
|
||||||
|
setAngularVelocity(angularVel.x, angularVel.y, angularVel.z);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function disableCollision() {
|
public function disableCollision() {
|
||||||
@ -745,6 +842,16 @@ typedef RigidBodyParams = {
|
|||||||
var linearDeactivationThreshold: Float;
|
var linearDeactivationThreshold: Float;
|
||||||
var angularDeactivationThrshold: Float;
|
var angularDeactivationThrshold: Float;
|
||||||
var deactivationTime: Float;
|
var deactivationTime: Float;
|
||||||
|
var linearVelocityMin: Float;
|
||||||
|
var linearVelocityMax: Float;
|
||||||
|
var angularVelocityMin: Float;
|
||||||
|
var angularVelocityMax: Float;
|
||||||
|
var lockTranslationX: Bool;
|
||||||
|
var lockTranslationY: Bool;
|
||||||
|
var lockTranslationZ: Bool;
|
||||||
|
var lockRotationX: Bool;
|
||||||
|
var lockRotationY: Bool;
|
||||||
|
var lockRotationZ: Bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef RigidBodyFlags = {
|
typedef RigidBodyFlags = {
|
||||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -151,7 +151,7 @@ class LeenkxExporter:
|
|||||||
self.default_part_material_objects = []
|
self.default_part_material_objects = []
|
||||||
self.material_to_lnx_object_dict = {}
|
self.material_to_lnx_object_dict = {}
|
||||||
# Stores the link between a blender object and its
|
# Stores the link between a blender object and its
|
||||||
# corresponding export data (arm object)
|
# corresponding export data (lnx object)
|
||||||
self.object_to_lnx_object_dict: Dict[bpy.types.Object, Dict] = {}
|
self.object_to_lnx_object_dict: Dict[bpy.types.Object, Dict] = {}
|
||||||
|
|
||||||
self.bone_tracks = []
|
self.bone_tracks = []
|
||||||
@ -540,7 +540,15 @@ class LeenkxExporter:
|
|||||||
o['material_refs'].append(lnx.utils.asset_name(material))
|
o['material_refs'].append(lnx.utils.asset_name(material))
|
||||||
|
|
||||||
def export_particle_system_ref(self, psys: bpy.types.ParticleSystem, out_object):
|
def export_particle_system_ref(self, psys: bpy.types.ParticleSystem, out_object):
|
||||||
if psys.settings.instance_object is None or psys.settings.render_type != 'OBJECT' or not psys.settings.instance_object.lnx_export or not bpy.data.objects[out_object['name']].modifiers[psys.name].show_render:
|
if psys.settings.instance_object is None or psys.settings.render_type != 'OBJECT' or not psys.settings.instance_object.lnx_export:
|
||||||
|
return
|
||||||
|
|
||||||
|
for obj in bpy.data.objects:
|
||||||
|
if obj.name == out_object['name']:
|
||||||
|
for mod in obj.modifiers:
|
||||||
|
if mod.type == 'PARTICLE_SYSTEM':
|
||||||
|
if mod.particle_system.name == psys.name:
|
||||||
|
if not mod.show_render:
|
||||||
return
|
return
|
||||||
|
|
||||||
self.particle_system_array[psys.settings] = {"structName": psys.settings.name}
|
self.particle_system_array[psys.settings] = {"structName": psys.settings.name}
|
||||||
@ -630,7 +638,10 @@ class LeenkxExporter:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
for slot in bobject.material_slots:
|
for slot in bobject.material_slots:
|
||||||
if slot.material is None or slot.material.library is not None:
|
if slot.material is None:
|
||||||
|
continue
|
||||||
|
if slot.material.library is not None:
|
||||||
|
slot.material.lnx_particle_flag = True
|
||||||
continue
|
continue
|
||||||
if slot.material.name.endswith(variant_suffix):
|
if slot.material.name.endswith(variant_suffix):
|
||||||
continue
|
continue
|
||||||
@ -835,6 +846,13 @@ class LeenkxExporter:
|
|||||||
}
|
}
|
||||||
out_object['vertex_groups'].append(out_vertex_groups)
|
out_object['vertex_groups'].append(out_vertex_groups)
|
||||||
|
|
||||||
|
if len(bobject.lnx_camera_list) > 0:
|
||||||
|
out_camera_list = []
|
||||||
|
for camera in bobject.lnx_camera_list:
|
||||||
|
if camera.lnx_camera_object_ptr != None:
|
||||||
|
out_camera_list.append(camera.lnx_camera_object_ptr.name)
|
||||||
|
if len(out_camera_list) > 0:
|
||||||
|
out_object['camera_list'] = out_camera_list
|
||||||
|
|
||||||
if len(bobject.lnx_propertylist) > 0:
|
if len(bobject.lnx_propertylist) > 0:
|
||||||
out_object['properties'] = []
|
out_object['properties'] = []
|
||||||
@ -910,7 +928,11 @@ class LeenkxExporter:
|
|||||||
out_object['particle_refs'] = []
|
out_object['particle_refs'] = []
|
||||||
out_object['render_emitter'] = bobject.show_instancer_for_render
|
out_object['render_emitter'] = bobject.show_instancer_for_render
|
||||||
for i in range(num_psys):
|
for i in range(num_psys):
|
||||||
if bobject.modifiers[bobject.particle_systems[i].name].show_render:
|
for obj in bpy.data.objects:
|
||||||
|
for mod in obj.modifiers:
|
||||||
|
if mod.type == 'PARTICLE_SYSTEM':
|
||||||
|
if mod.particle_system.name == bobject.particle_systems[i].name:
|
||||||
|
if mod.show_render:
|
||||||
self.export_particle_system_ref(bobject.particle_systems[i], out_object)
|
self.export_particle_system_ref(bobject.particle_systems[i], out_object)
|
||||||
|
|
||||||
aabb = bobject.data.lnx_aabb
|
aabb = bobject.data.lnx_aabb
|
||||||
@ -2282,12 +2304,12 @@ class LeenkxExporter:
|
|||||||
self.output['particle_datas'] = []
|
self.output['particle_datas'] = []
|
||||||
for particleRef in self.particle_system_array.items():
|
for particleRef in self.particle_system_array.items():
|
||||||
padd = False;
|
padd = False;
|
||||||
for obj in self.output['objects']:
|
for obj in bpy.data.objects:
|
||||||
if 'particle_refs' in obj:
|
for mod in obj.modifiers:
|
||||||
for pref in obj['particle_refs']:
|
if mod.type == 'PARTICLE_SYSTEM':
|
||||||
if pref['particle'] == particleRef[1]["structName"]:
|
if mod.particle_system.settings.name == particleRef[1]["structName"]:
|
||||||
if bpy.data.objects[obj['name']].modifiers[pref['name']].show_render == True:
|
if mod.show_render:
|
||||||
padd = True;
|
padd = True
|
||||||
if not padd:
|
if not padd:
|
||||||
continue;
|
continue;
|
||||||
psettings = particleRef[0]
|
psettings = particleRef[0]
|
||||||
@ -2821,6 +2843,18 @@ class LeenkxExporter:
|
|||||||
body_params['linearDeactivationThreshold'] = deact_lv
|
body_params['linearDeactivationThreshold'] = deact_lv
|
||||||
body_params['angularDeactivationThrshold'] = deact_av
|
body_params['angularDeactivationThrshold'] = deact_av
|
||||||
body_params['deactivationTime'] = deact_time
|
body_params['deactivationTime'] = deact_time
|
||||||
|
# New velocity limit properties
|
||||||
|
body_params['linearVelocityMin'] = bobject.lnx_rb_linear_velocity_min
|
||||||
|
body_params['linearVelocityMax'] = bobject.lnx_rb_linear_velocity_max
|
||||||
|
body_params['angularVelocityMin'] = bobject.lnx_rb_angular_velocity_min
|
||||||
|
body_params['angularVelocityMax'] = bobject.lnx_rb_angular_velocity_max
|
||||||
|
# New lock properties
|
||||||
|
body_params['lockTranslationX'] = bobject.lnx_rb_lock_translation_x
|
||||||
|
body_params['lockTranslationY'] = bobject.lnx_rb_lock_translation_y
|
||||||
|
body_params['lockTranslationZ'] = bobject.lnx_rb_lock_translation_z
|
||||||
|
body_params['lockRotationX'] = bobject.lnx_rb_lock_rotation_x
|
||||||
|
body_params['lockRotationY'] = bobject.lnx_rb_lock_rotation_y
|
||||||
|
body_params['lockRotationZ'] = bobject.lnx_rb_lock_rotation_z
|
||||||
body_flags = {}
|
body_flags = {}
|
||||||
body_flags['animated'] = rb.kinematic
|
body_flags['animated'] = rb.kinematic
|
||||||
body_flags['trigger'] = bobject.lnx_rb_trigger
|
body_flags['trigger'] = bobject.lnx_rb_trigger
|
||||||
@ -2981,6 +3015,9 @@ class LeenkxExporter:
|
|||||||
# mesh = obj.data
|
# mesh = obj.data
|
||||||
# for face in mesh.faces:
|
# for face in mesh.faces:
|
||||||
# face.v.reverse()
|
# face.v.reverse()
|
||||||
|
# if bpy.app.version[0] >= 4:
|
||||||
|
# bpy.ops.wm.obj_export(override, use_selection=True, filepath=nav_filepath, check_existing=False, use_normals=False, use_uvs=False, use_materials=False)
|
||||||
|
# else:
|
||||||
# bpy.ops.export_scene.obj(override, use_selection=True, filepath=nav_filepath, check_existing=False, use_normals=False, use_uvs=False, use_materials=False)
|
# bpy.ops.export_scene.obj(override, use_selection=True, filepath=nav_filepath, check_existing=False, use_normals=False, use_uvs=False, use_materials=False)
|
||||||
# bobject.scale.y *= -1
|
# bobject.scale.y *= -1
|
||||||
armature = bobject.find_armature()
|
armature = bobject.find_armature()
|
||||||
@ -3020,6 +3057,8 @@ class LeenkxExporter:
|
|||||||
|
|
||||||
if trait_prop.type.endswith("Object"):
|
if trait_prop.type.endswith("Object"):
|
||||||
value = lnx.utils.asset_name(trait_prop.value_object)
|
value = lnx.utils.asset_name(trait_prop.value_object)
|
||||||
|
elif trait_prop.type == "TSceneFormat":
|
||||||
|
value = lnx.utils.asset_name(trait_prop.value_scene)
|
||||||
else:
|
else:
|
||||||
value = trait_prop.get_value()
|
value = trait_prop.get_value()
|
||||||
|
|
||||||
|
@ -2,7 +2,10 @@ import importlib
|
|||||||
import os
|
import os
|
||||||
import queue
|
import queue
|
||||||
import sys
|
import sys
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
import types
|
import types
|
||||||
|
from typing import Dict, Tuple, Callable, Set
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
from bpy.app.handlers import persistent
|
from bpy.app.handlers import persistent
|
||||||
@ -30,6 +33,10 @@ if lnx.is_reload(__name__):
|
|||||||
else:
|
else:
|
||||||
lnx.enable_reload(__name__)
|
lnx.enable_reload(__name__)
|
||||||
|
|
||||||
|
# Module-level storage for active threads (eliminates re-queuing overhead)
|
||||||
|
_active_threads: Dict[threading.Thread, Callable] = {}
|
||||||
|
_last_poll_time = 0.0
|
||||||
|
_consecutive_empty_polls = 0
|
||||||
|
|
||||||
@persistent
|
@persistent
|
||||||
def on_depsgraph_update_post(self):
|
def on_depsgraph_update_post(self):
|
||||||
@ -135,35 +142,113 @@ def always() -> float:
|
|||||||
|
|
||||||
|
|
||||||
def poll_threads() -> float:
|
def poll_threads() -> float:
|
||||||
"""Polls the thread callback queue and if a thread has finished, it
|
|
||||||
is joined with the main thread and the corresponding callback is
|
|
||||||
executed in the main thread.
|
|
||||||
"""
|
"""
|
||||||
|
Improved thread polling with:
|
||||||
|
- No re-queuing overhead
|
||||||
|
- Batch processing of completed threads
|
||||||
|
- Adaptive timing based on activity
|
||||||
|
- Better memory management
|
||||||
|
- Simplified logic flow
|
||||||
|
"""
|
||||||
|
global _last_poll_time, _consecutive_empty_polls
|
||||||
|
current_time = time.time()
|
||||||
|
|
||||||
|
# Process all new threads from queue at once (batch processing)
|
||||||
|
new_threads_added = 0
|
||||||
try:
|
try:
|
||||||
|
while True:
|
||||||
thread, callback = make.thread_callback_queue.get(block=False)
|
thread, callback = make.thread_callback_queue.get(block=False)
|
||||||
|
_active_threads[thread] = callback
|
||||||
|
new_threads_added += 1
|
||||||
except queue.Empty:
|
except queue.Empty:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Early return if no active threads
|
||||||
|
if not _active_threads:
|
||||||
|
_consecutive_empty_polls += 1
|
||||||
|
# Adaptive timing: longer intervals when consistently empty
|
||||||
|
if _consecutive_empty_polls > 10:
|
||||||
|
return 0.5 # Back off when no activity
|
||||||
return 0.25
|
return 0.25
|
||||||
if thread.is_alive():
|
|
||||||
try:
|
# Reset empty poll counter when we have active threads
|
||||||
make.thread_callback_queue.put((thread, callback), block=False)
|
_consecutive_empty_polls = 0
|
||||||
except queue.Full:
|
|
||||||
return 0.5
|
# Find completed threads (single pass, no re-queuing)
|
||||||
return 0.1
|
completed_threads = []
|
||||||
|
for thread in list(_active_threads.keys()):
|
||||||
|
if not thread.is_alive():
|
||||||
|
completed_threads.append(thread)
|
||||||
|
|
||||||
|
# Batch process all completed threads
|
||||||
|
if completed_threads:
|
||||||
|
_process_completed_threads(completed_threads)
|
||||||
|
|
||||||
|
# Adaptive timing based on activity level
|
||||||
|
active_count = len(_active_threads)
|
||||||
|
if active_count == 0:
|
||||||
|
return 0.25
|
||||||
|
elif active_count <= 3:
|
||||||
|
return 0.05 # Medium frequency for low activity
|
||||||
else:
|
else:
|
||||||
|
return 0.01 # High frequency for high activity
|
||||||
|
|
||||||
|
def _process_completed_threads(completed_threads: list) -> None:
|
||||||
|
"""Process a batch of completed threads with robust error handling."""
|
||||||
|
for thread in completed_threads:
|
||||||
|
callback = _active_threads.pop(thread) # Remove from tracking
|
||||||
|
|
||||||
try:
|
try:
|
||||||
thread.join()
|
thread.join() # Should be instant since thread is dead
|
||||||
callback()
|
callback()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# If there is an exception, we can no longer return the time to
|
# Robust error recovery
|
||||||
# the next call to this polling function, so to keep it running
|
_handle_callback_error(e)
|
||||||
# we re-register it and then raise the original exception.
|
continue # Continue processing other threads
|
||||||
|
|
||||||
|
# Explicit cleanup for better memory management
|
||||||
|
del thread, callback
|
||||||
|
|
||||||
|
def _handle_callback_error(exception: Exception) -> None:
|
||||||
|
"""Centralized error handling with better recovery."""
|
||||||
|
try:
|
||||||
|
# Try to unregister existing timer
|
||||||
|
bpy.app.timers.unregister(poll_threads)
|
||||||
|
except ValueError:
|
||||||
|
pass # Timer wasn't registered, that's fine
|
||||||
|
|
||||||
|
# Re-register timer with slightly longer interval for stability
|
||||||
|
bpy.app.timers.register(poll_threads, first_interval=0.1, persistent=True)
|
||||||
|
|
||||||
|
# Re-raise the original exception after ensuring timer continuity
|
||||||
|
raise exception
|
||||||
|
|
||||||
|
def cleanup_polling_system() -> None:
|
||||||
|
"""Optional cleanup function for proper shutdown."""
|
||||||
|
global _active_threads, _consecutive_empty_polls
|
||||||
|
|
||||||
|
# Wait for remaining threads to complete (with timeout)
|
||||||
|
for thread in list(_active_threads.keys()):
|
||||||
|
if thread.is_alive():
|
||||||
|
thread.join(timeout=1.0) # 1 second timeout
|
||||||
|
|
||||||
|
# Clear tracking structures
|
||||||
|
_active_threads.clear()
|
||||||
|
_consecutive_empty_polls = 0
|
||||||
|
|
||||||
|
# Unregister timer
|
||||||
try:
|
try:
|
||||||
bpy.app.timers.unregister(poll_threads)
|
bpy.app.timers.unregister(poll_threads)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
bpy.app.timers.register(poll_threads, first_interval=0.01, persistent=True)
|
|
||||||
# Quickly check if another thread has finished
|
def get_polling_stats() -> dict:
|
||||||
return 0.01
|
"""Get statistics about the polling system for monitoring."""
|
||||||
|
return {
|
||||||
|
'active_threads': len(_active_threads),
|
||||||
|
'consecutive_empty_polls': _consecutive_empty_polls,
|
||||||
|
'thread_ids': [t.ident for t in _active_threads.keys()]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
loaded_py_libraries: dict[str, types.ModuleType] = {}
|
loaded_py_libraries: dict[str, types.ModuleType] = {}
|
||||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user