Compare commits

...

161 Commits

Author SHA1 Message Date
39091e8db3 Repe [T3DU] and Moises Jpelaez updates 2026-05-12 23:54:06 -07:00
6b404f9da6 Merge pull request 'main' (#114) from Onek8/LNXSDK:main into main
Reviewed-on: LeenkxTeam/LNXSDK#114
2026-04-30 07:45:08 +00:00
3845d554b1 merge upstream 2026-04-30 07:43:51 +00:00
53cdb7d2ff Update 2026-04-30 00:42:47 -07:00
9dbacb801d Merge pull request 'Update' (#113) from Onek8/LNXSDK:main into main
Reviewed-on: LeenkxTeam/LNXSDK#113
2026-04-29 07:00:26 +00:00
0245e2e1e6 Update 2026-04-28 23:54:34 -07:00
c9acef9798 Update 2026-04-28 19:07:04 -07:00
a3d5fa846b Update 2026-04-27 19:21:50 -07:00
98856b3f54 Update 2026-04-27 16:31:35 -07:00
669771fd58 Update 2026-04-27 15:28:18 -07:00
3c002e9f10 Update 2026-04-27 13:57:13 -07:00
5357b5ce25 Update leenkx/blender/lnx/material/node_meta.py 2026-03-05 01:32:33 +00:00
1372c687f0 Update leenkx/blender/lnx/material/cycles_nodes/nodes_texture.py 2026-03-05 01:31:22 +00:00
6a914782a5 Update leenkx/blender/lnx/lightmapper/utility/gui/Viewport.py 2026-03-05 01:28:23 +00:00
f4ada6ba50 Update leenkx/blender/lnx/lightmapper/utility/encoding.py 2026-03-05 01:27:45 +00:00
4211317c03 HaxeJolt 2026-03-04 00:50:15 -08:00
9126175569 patch 2026-03-04 00:14:45 -08:00
394ab38a80 patch 2026-02-27 00:54:58 -08:00
cc95912a7e Next patch 2026-02-24 23:46:31 -08:00
85a44b930d Node patch 2026-02-24 23:04:47 -08:00
cd3090817a Next patch 2026-02-24 21:30:00 -08:00
d45c632dcd Patch_2 2026-02-24 17:35:26 -08:00
1c3c30e6ce Patch_2 2026-02-24 11:44:01 -08:00
c9839c9be6 Merge pull request 'Patch_2' (#2) from e2002e_0 into main
Reviewed-on: #2
2026-02-24 18:54:11 +00:00
6a17251520 merge upstream 2026-02-24 07:40:25 +00:00
0adcafd697 Patch_1 2026-02-21 22:17:44 -08:00
423807c62f merge upstream 2026-02-21 22:47:25 +00:00
5598161b40 Upload files to "Krom" 2026-02-21 08:05:08 +00:00
e6ac30e57f Delete Krom/Krom.exe 2026-02-21 08:04:28 +00:00
5d1132b24c merge upstream 2026-02-21 07:52:44 +00:00
232ae3e7bc Upload files to "Krom" 2026-02-21 07:50:44 +00:00
a861665c98 Delete Krom/Krom.exe 2026-02-21 07:49:27 +00:00
4852a40848 Delete Krom/Krom 2026-02-21 07:49:09 +00:00
a00f3506ed Update leenkx/blender/lnx/material/cycles_nodes/nodes_vector.py 2026-01-05 07:14:08 +00:00
9aa01e6436 Update leenkx/blender/lnx/material/cycles_nodes/nodes_texture.py 2026-01-05 07:13:51 +00:00
18c564048f Update leenkx/blender/lnx/material/cycles_nodes/nodes_shader.py 2026-01-05 07:13:24 +00:00
da19096658 Update leenkx/blender/lnx/material/cycles_nodes/nodes_converter.py 2026-01-05 07:12:32 +00:00
06343dcca1 Update leenkx/blender/lnx/material/cycles_nodes/nodes_input.py 2026-01-05 07:11:59 +00:00
4375087d3a Update leenkx/blender/lnx/material/cycles_nodes/nodes_vector.py 2025-12-15 02:04:50 +00:00
825f783cbe Update leenkx/blender/lnx/material/cycles_nodes/nodes_texture.py 2025-12-15 02:04:30 +00:00
2f9a24753e Update leenkx/blender/lnx/material/cycles_nodes/nodes_shader.py 2025-12-15 02:04:10 +00:00
07efcaffdb Update leenkx/blender/lnx/material/cycles_nodes/nodes_input.py 2025-12-15 02:03:51 +00:00
a6bef6b35a Update leenkx/blender/lnx/material/cycles_nodes/nodes_color.py 2025-12-15 02:03:07 +00:00
b88f471f0f Update leenkx/blender/lnx/material/cycles_nodes/nodes_converter.py 2025-12-15 02:02:20 +00:00
7e31a73d28 Update leenkx/blender/lnx/lightmapper/utility/encoding.py 2025-12-14 20:30:49 +00:00
7fa4be0b07 Update leenkx/blender/lnx/lightmapper/utility/gui/Viewport.py 2025-12-14 20:29:53 +00:00
d5f3f05ab6 merge upstream 2025-11-14 17:48:37 +00:00
3bee97a560 Update leenkx/blender/lnx/material/cycles.py 2025-11-14 17:41:39 +00:00
4f4f28d62f Merge pull request 'main' (#111) from Onek8/LNXSDK:main into main
Reviewed-on: LeenkxTeam/LNXSDK#111
2025-11-06 16:32:50 +00:00
7076fb6b7e Update leenkx/blender/lnx/material/cycles.py 2025-11-06 16:29:46 +00:00
b72a22b5e9 Update leenkx/blender/lnx/props_ui.py 2025-11-06 16:17:18 +00:00
b265ab863c Update leenkx/blender/lnx/props.py 2025-11-06 16:13:39 +00:00
f5fa754e17 merge upstream 2025-10-03 22:16:29 +00:00
48f5575e4e Update leenkx/Sources/leenkx/logicnode/OnContactNode.hx 2025-10-03 07:56:12 +00:00
f2c4be6336 Update leenkx/Sources/leenkx/logicnode/HasContactNode.hx 2025-10-03 07:55:46 +00:00
2ddc938db8 Update leenkx/Sources/leenkx/logicnode/AnyContactNode.hx 2025-10-03 07:55:14 +00:00
5eb735ada2 Update leenkx/Sources/leenkx/trait/physics/bullet/PhysicsWorld.hx 2025-10-03 07:52:01 +00:00
9894cc20f2 Update leenkx/Sources/leenkx/trait/physics/PhysicsWorld.hx 2025-10-03 07:51:32 +00:00
dbe6d0829a Add leenkx/Sources/leenkx/trait/physics/PhysicsCache.hx 2025-10-03 07:51:07 +00:00
6f383e2ab2 Update leenkx/Sources/leenkx/trait/physics/PhysicsWorld.hx 2025-10-03 05:38:54 +00:00
5c2d29d7ce Update leenkx/Sources/leenkx/trait/physics/bullet/PhysicsWorld.hx 2025-10-03 05:37:50 +00:00
28579e14d7 Update leenkx/Sources/leenkx/logicnode/AnyContactNode.hx 2025-10-03 05:37:06 +00:00
2ec6f43cc5 Update leenkx/Sources/leenkx/logicnode/OnContactNode.hx 2025-10-03 05:36:14 +00:00
027021815a Update leenkx/Sources/leenkx/logicnode/HasContactNode.hx 2025-10-03 05:35:48 +00:00
b9b387803f Add leenkx/blender/lnx/logicnode/physics/LN_any_contact.py 2025-10-03 05:06:23 +00:00
e05d9d0237 Update leenkx/Sources/leenkx/logicnode/HasContactNode.hx 2025-10-03 05:04:48 +00:00
c908e6cad2 Update leenkx/Sources/leenkx/logicnode/OnContactNode.hx 2025-10-03 05:04:18 +00:00
506a0a0245 Add leenkx/Sources/leenkx/logicnode/AnyContactNode.hx 2025-10-03 05:03:29 +00:00
5cf33724e4 Update leenkx/Sources/leenkx/trait/physics/bullet/PhysicsWorld.hx 2025-10-03 05:02:28 +00:00
ac5aa3d19c Merge pull request 'Hashlink fix' (#110) from Onek8/LNXSDK:main into main
Reviewed-on: LeenkxTeam/LNXSDK#110
2025-10-03 03:21:56 +00:00
0c534ee632 Update leenkx/Sources/iron/object/ParticleSystem.hx 2025-10-01 01:42:51 +00:00
69a2bb1e7e merge upstream 2025-09-30 06:02:59 +00:00
e3e7855d26 Merge pull request 'main' (#109) from Onek8/LNXSDK:main into main
Reviewed-on: LeenkxTeam/LNXSDK#109
2025-09-30 05:59:23 +00:00
f7917974f8 Update leenkx/Sources/iron/object/ObjectAnimation.hx 2025-09-30 05:52:44 +00:00
fa2d8f05d5 Update leenkx/Sources/iron/object/ObjectAnimation.hx 2025-09-30 05:35:50 +00:00
5b86f32b51 merge upstream 2025-09-29 22:41:09 +00:00
73fcb55acc Update leenkx/blender/lnx/props_traits.py 2025-09-29 05:28:13 +00:00
c24baa3364 Update leenkx/blender/lnx/props_traits_props.py 2025-09-29 05:27:43 +00:00
4517c4863f Merge pull request 'Downward support to 2.8 LTS!!' (#108) from Onek8/LNXSDK:main into main
Reviewed-on: LeenkxTeam/LNXSDK#108
2025-09-28 20:02:58 +00:00
1299306e09 Update leenkx.py 2025-09-28 20:01:00 +00:00
f97d8fd846 Blender 2.8 - 4.5 Support 2025-09-28 12:44:04 -07:00
8f8d4b1376 Update leenkx/blender/lnx/props_traits.py 2025-09-28 00:09:57 +00:00
a926fa8dbb Update leenkx/blender/lnx/nodes_logic.py 2025-09-27 03:03:08 +00:00
6c3efa6c83 Update leenkx/blender/lnx/props_ui.py 2025-09-24 01:54:38 +00:00
21afad6d09 Update leenkx/blender/lnx/exporter.py 2025-09-24 01:53:43 +00:00
04c6983a09 Update leenkx/Sources/iron/data/SceneFormat.hx 2025-09-24 01:52:47 +00:00
45966ef0bb Update leenkx/blender/lnx/props.py 2025-09-24 01:51:11 +00:00
a72edc6203 Update leenkx/Sources/iron/object/ParticleSystem.hx 2025-09-24 01:50:03 +00:00
6af1ef2df1 Update leenkx/blender/lnx/props_ui.py 2025-09-24 01:33:47 +00:00
46e3047877 Update leenkx/blender/lnx/props.py 2025-09-23 19:57:53 +00:00
de74af215a Merge pull request 'main' (#107) from Onek8/LNXSDK:main into main
Reviewed-on: LeenkxTeam/LNXSDK#107
2025-09-23 17:54:11 +00:00
b6e96553c2 Update leenkx/blender/lnx/lightmapper/utility/build.py 2025-09-19 22:52:01 +00:00
58e009f709 Terrain Generation fix 2025-09-19 21:17:58 +00:00
e88f101ca6 t3du - Particle info random 2025-09-19 19:40:49 +00:00
d28d59b9e6 t3du - Particle info random 2025-09-19 19:38:12 +00:00
a4398c7279 t3du - Particle info random 2025-09-19 19:31:45 +00:00
abedfd799e t3du - Fix World Errors 2025-09-19 19:28:54 +00:00
4520422f6b t3du - Fix World Errors 2025-09-19 19:27:02 +00:00
88418c06c3 t3du - Fix World Errors 2025-09-19 19:25:30 +00:00
aedc2783ab t3du - Labels for finding nodes 2025-09-19 19:22:45 +00:00
1505414c4c t3du - Labels for finding nodes 2025-09-19 19:19:25 +00:00
fa818602c4 t3du - Labels for finding nodes 2025-09-19 19:18:05 +00:00
79dc458671 t3du - Labels for finding nodes 2025-09-19 19:15:41 +00:00
8e635fb1e9 t3du - Labels for finding nodes 2025-09-19 19:11:47 +00:00
4c2e6ab26a t3du - Probabilistic Index Node 2025-09-19 19:09:18 +00:00
2371e3777e t3du - Probabilistic Index Node 2025-09-19 19:08:03 +00:00
b458b77e5c moisesjpelaez - Include external blend files on build 2025-09-19 19:04:43 +00:00
9b76f8cca9 moisesjpelaez - Include external blend files on build 2025-09-19 19:03:22 +00:00
5f2acb209e moisesjpelaez - Include external blend files on build 2025-09-19 19:00:50 +00:00
6fc446e7a9 moisesjpelaez - General Fixes 2025-09-19 18:54:44 +00:00
71e57026e1 moisesjpelaez - General Fixes 2025-09-19 18:53:25 +00:00
5288a98440 moisesjpelaez - General Fixes 2025-09-19 18:49:09 +00:00
35e346be39 moisesjpelaez - General Fixes 2025-09-19 18:39:54 +00:00
843ef0b058 moisesjpelaez - General Fixes 2025-09-19 18:39:14 +00:00
177890bf39 moisesjpelaez - General Fixes 2025-09-19 18:37:01 +00:00
9ac37e6dc7 moisesjpelaez - General Fixes 2025-09-19 18:34:42 +00:00
e697437778 moisesjpelaez - General Fixes 2025-09-19 18:33:44 +00:00
c94fc0fd97 moisesjpelaez - General Fixes 2025-09-19 18:29:52 +00:00
cd0a6f6788 Update leenkx/Sources/iron/data/SceneFormat.hx 2025-09-19 18:28:19 +00:00
4400e0e9c8 moisesjpelaez - General Fixes 2025-09-19 18:27:22 +00:00
20cf07cfc3 moisesjpelaez - General Fixes 2025-09-19 18:25:54 +00:00
1939f19c05 moisesjpelaez - General Fixes 2025-09-19 18:24:19 +00:00
0d2b152ccb moisesjpelaez - General Fixes 2025-09-19 18:15:23 +00:00
7f58e0fc85 moisesjpelaez - General Fixes 2025-09-19 18:13:00 +00:00
0e4a6575c7 moisesjpelaez - General Material Updates 2025-09-19 18:09:04 +00:00
024676f43a moisesjpelaez - General Material Updates 2025-09-19 17:43:54 +00:00
8fe758862c moisesjpelaez - General Material Updates 2025-09-19 17:35:59 +00:00
1f3d1b47ae moisesjpelaez - General Material Updates 2025-09-19 17:34:27 +00:00
f659a3c2be moisesjpelaez - General Material Updates 2025-09-19 17:32:38 +00:00
6eeb9017d4 moisesjpelaez - General Material Updates 2025-09-19 17:30:42 +00:00
afe89c3834 Update leenkx/Sources/iron/data/ShaderData.hx 2025-09-19 17:27:14 +00:00
8b695f72bb moisesjpelaez - General Material Updates 2025-09-19 17:25:03 +00:00
3d99fa60c0 moisesjpelaez - General Material Updates 2025-09-19 17:23:42 +00:00
43be7729ba moisesjpelaez - Tween var 2025-09-19 17:17:41 +00:00
de0b1075c2 moisesjpelaez - Time Fix 2025-09-19 17:13:16 +00:00
c7aba23fa4 t3du - Fix DOF condition 2025-09-19 17:08:21 +00:00
881f3267cc t3du - Fix DOF condition 2025-09-19 17:06:10 +00:00
19b79d61c7 ObiNoWanKenobi - FirstPersonController Changes 2025-09-19 17:03:20 +00:00
fcbab54a0c moisesjpelaez - General Fixes 2025-09-19 16:57:49 +00:00
8fd05d5514 Update leenkx/Sources/leenkx/renderpath/RenderPathForward.hx 2025-08-28 19:21:48 +00:00
ad4013ed75 Update leenkx/Sources/leenkx/renderpath/RenderPathForward.hx 2025-08-28 19:11:31 +00:00
590e6219d5 merge upstream 2025-08-14 23:01:45 +00:00
8ac567b57b Merge pull request 'Update leenkx/Shaders/std/conetrace.glsl' (#104) from Onek8/LNXSDK:main into main
Reviewed-on: LeenkxTeam/LNXSDK#104
2025-08-14 23:01:04 +00:00
43b7ae7060 Update leenkx/Shaders/std/conetrace.glsl 2025-08-14 22:58:57 +00:00
662981fa03 Update leenkx/blender/lnx/material/make_mesh.py 2025-08-14 22:46:53 +00:00
a3866fb604 merge upstream 2025-08-14 21:32:32 +00:00
29e9e71a6a Merge pull request 'main' (#103) from Onek8/LNXSDK:main into main
Reviewed-on: LeenkxTeam/LNXSDK#103
2025-08-14 21:29:54 +00:00
bfb85b0a3b Update leenkx/Sources/iron/Scene.hx 2025-08-14 20:29:28 +00:00
ef99b800e0 Update leenkx/Sources/iron/App.hx 2025-08-14 20:27:20 +00:00
7cca955fc5 Update leenkx/Sources/iron/App.hx 2025-08-14 20:26:33 +00:00
7e7bbd5eae merge upstream 2025-08-14 20:24:23 +00:00
c31b2a18ad Update leenkx/blender/lnx/logicnode/draw/LN_draw_string.py 2025-08-14 19:03:28 +00:00
fb47bf2564 Update leenkx/blender/lnx/logicnode/draw/LN_draw_Text_Area_string.py 2025-08-14 19:01:59 +00:00
7ae6750620 Update leenkx/blender/lnx/logicnode/camera/LN_set_camera_start_end.py 2025-08-14 19:00:58 +00:00
5b87010f76 Update leenkx/Sources/leenkx/trait/internal/DebugConsole.hx 2025-08-14 18:58:52 +00:00
97e952fc15 Update leenkx/Sources/leenkx/logicnode/DrawStringNode.hx 2025-08-14 18:57:13 +00:00
b440539d65 Merge pull request 'main' (#102) from Onek8/LNXSDK:main into main
Reviewed-on: LeenkxTeam/LNXSDK#102
2025-07-23 17:34:02 +00:00
fbf63e4f17 Update leenkx/Shaders/std/brdf.glsl 2025-07-22 23:33:21 +00:00
a318e08072 Update leenkx/Shaders/sss_pass/sss_pass.frag.glsl 2025-07-22 23:20:35 +00:00
60a9db6459 Update api/api.hxml 2025-07-22 21:54:56 +00:00
3b5a93c92a Update leenkx/Sources/leenkx/trait/PhysicsBreak.hx 2025-07-22 21:51:25 +00:00
855 changed files with 141669 additions and 5839 deletions

2
.gitignore vendored
View File

@ -1,3 +1,5 @@
__pycache__/ __pycache__/
*.pyc *.pyc
*.DS_Store *.DS_Store
**/workspace.xml
**/vcs.xml

View File

@ -227,7 +227,7 @@ class SystemImpl {
} }
static inline var maxGamepads: Int = 4; static inline var maxGamepads: Int = 4;
static var frame: Framebuffer; public static var frame: Framebuffer;
static var keyboard: Keyboard = null; static var keyboard: Keyboard = null;
static var mouse: kha.input.Mouse; static var mouse: kha.input.Mouse;
static var surface: Surface; static var surface: Surface;
@ -388,7 +388,8 @@ class SystemImpl {
{ {
alpha: false, alpha: false,
antialias: options.framebuffer.samplesPerPixel > 1, antialias: options.framebuffer.samplesPerPixel > 1,
stencil: true stencil: true,
xrCompatible: true
}); // preserveDrawingBuffer: true } ); Warning: preserveDrawingBuffer can cause huge performance issues on mobile browsers }); // preserveDrawingBuffer: true } ); Warning: preserveDrawingBuffer can cause huge performance issues on mobile browsers
SystemImpl.gl.pixelStorei(GL.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 1); SystemImpl.gl.pixelStorei(GL.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 1);
@ -417,7 +418,8 @@ class SystemImpl {
{ {
alpha: false, alpha: false,
antialias: options.framebuffer.samplesPerPixel > 1, antialias: options.framebuffer.samplesPerPixel > 1,
stencil: true stencil: true,
xrCompatible: true
}); // preserveDrawingBuffer: true } ); WARNING: preserveDrawingBuffer causes huge performance issues (on mobile browser)! }); // preserveDrawingBuffer: true } ); WARNING: preserveDrawingBuffer causes huge performance issues (on mobile browser)!
SystemImpl.gl.pixelStorei(GL.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 1); SystemImpl.gl.pixelStorei(GL.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 1);
SystemImpl.gl.getExtension("OES_texture_float"); SystemImpl.gl.getExtension("OES_texture_float");
@ -547,6 +549,12 @@ class SystemImpl {
]; ];
function animate(timestamp) { function animate(timestamp) {
if (untyped Browser.window._khaSkipWindowRender == true) {
if (requestAnimationFrame != null)
requestAnimationFrame(animate);
return;
}
if (requestAnimationFrame == null) if (requestAnimationFrame == null)
Browser.window.setTimeout(animate, 1000.0 / 60.0); Browser.window.setTimeout(animate, 1000.0 / 60.0);
else else

View File

@ -47,6 +47,7 @@ class Graphics implements kha.graphics4.Graphics {
static var current: Graphics = null; static var current: Graphics = null;
static var useVertexAttributes: Int = 0; static var useVertexAttributes: Int = 0;
public static var vrFramebufferBound: Bool = false;
public function new(renderTarget: Canvas = null) { public function new(renderTarget: Canvas = null) {
this.renderTarget = renderTarget; this.renderTarget = renderTarget;
@ -89,8 +90,10 @@ class Graphics implements kha.graphics4.Graphics {
SystemImpl.gl.enable(GL.BLEND); SystemImpl.gl.enable(GL.BLEND);
SystemImpl.gl.blendFunc(GL.SRC_ALPHA, GL.ONE_MINUS_SRC_ALPHA); SystemImpl.gl.blendFunc(GL.SRC_ALPHA, GL.ONE_MINUS_SRC_ALPHA);
if (renderTarget == null) { if (renderTarget == null) {
SystemImpl.gl.bindFramebuffer(GL.FRAMEBUFFER, null); if (!vrFramebufferBound) {
SystemImpl.gl.viewport(0, 0, System.windowWidth(), System.windowHeight()); SystemImpl.gl.bindFramebuffer(GL.FRAMEBUFFER, null);
SystemImpl.gl.viewport(0, 0, System.windowWidth(), System.windowHeight());
}
} }
else { else {
SystemImpl.gl.bindFramebuffer(GL.FRAMEBUFFER, renderTargetFrameBuffer); SystemImpl.gl.bindFramebuffer(GL.FRAMEBUFFER, renderTargetFrameBuffer);

View File

@ -13,10 +13,29 @@ import kha.SystemImpl;
class VrInterface extends kha.vr.VrInterface { class VrInterface extends kha.vr.VrInterface {
var vrEnabled: Bool = false; var vrEnabled: Bool = false;
var isWebXR: Bool = false;
var vrDisplay: Dynamic; var vrDisplay: Dynamic;
var frameData: Dynamic; var frameData: Dynamic;
var xrSession: Dynamic;
var xrRefSpace: Dynamic;
public var xrGLLayer: Dynamic;
public var currentFrame: Dynamic;
public var currentViews: Dynamic;
public var currentViewerPose: Dynamic;
public var currentInputSources: Dynamic;
var xrAnimationFrameHandle: Int = -1;
public var _glContext: Dynamic;
public var _leftViewport: Dynamic;
public var _rightViewport: Dynamic;
public var _cachedViewsLength: Int = 0;
var savedCanvasWidth: Int = 0;
var savedCanvasHeight: Int = 0;
var browserRAFId: Int = -1;
var leftProjectionMatrix: FastMatrix4 = FastMatrix4.identity(); var leftProjectionMatrix: FastMatrix4 = FastMatrix4.identity();
var rightProjectionMatrix: FastMatrix4 = FastMatrix4.identity(); var rightProjectionMatrix: FastMatrix4 = FastMatrix4.identity();
var leftViewMatrix: FastMatrix4 = FastMatrix4.identity(); var leftViewMatrix: FastMatrix4 = FastMatrix4.identity();
@ -30,15 +49,29 @@ class VrInterface extends kha.vr.VrInterface {
public function new() { public function new() {
super(); super();
#if kha_webvr #if kha_webvr
var displayEnabled: Bool = Syntax.code("navigator.getVRDisplays"); var webXREnabled: Bool = Syntax.code("navigator.xr");
if (webXREnabled) {
isWebXR = true;
vrEnabled = true;
trace("WebXR API detected");
}
else {
var displayEnabled: Bool = Syntax.code("navigator.getVRDisplays");
if (displayEnabled) {
isWebXR = false;
vrEnabled = true;
getVRDisplays();
trace("WebVR 1.1 API detected");
}
}
#else #else
var displayEnabled = false; var displayEnabled = false;
#end #end
if (displayEnabled) { //if (displayEnabled) {
vrEnabled = true; // vrEnabled = true;
getVRDisplays(); // getVRDisplays();
trace("Display enabled."); // trace("Display enabled.");
} //}
} }
function getVRDisplays() { function getVRDisplays() {
@ -64,27 +97,475 @@ class VrInterface extends kha.vr.VrInterface {
} }
public override function onVRRequestPresent() { public override function onVRRequestPresent() {
if (isWebXR) {
requestWebXRSession();
} else {
// WebVR 1.1
try {
vrDisplay.requestPresent([{source: SystemImpl.khanvas}]).then(function() {
onResize();
vrDisplay.requestAnimationFrame(onAnimationFrame);
});
}
catch (err:Dynamic) {
trace("Failed to requestPresent WebVR: " + err);
}
}
}
function requestWebXRSession() {
var vrScaleFactor = 1.0;
#if lnx_vr
vrScaleFactor = leenkx.renderpath.Inc.getSuperSampling();
trace("[VR] Using renderpath superSample as framebufferScaleFactor: " + vrScaleFactor);
#end
try { try {
vrDisplay.requestPresent([{source: SystemImpl.khanvas}]).then(function() { Syntax.code("
onResize();
vrDisplay.requestAnimationFrame(onAnimationFrame);
}); let gl = null;
let canvas = null;
try {
if (typeof kha_SystemImpl !== 'undefined') {
gl = kha_SystemImpl.gl;
canvas = kha_SystemImpl.khanvas;
}
} catch (e) {
trace('kha_SystemImpl access failed: ' + e.message);
}
if (!canvas) {
canvas = document.querySelector('canvas');
}
if (canvas && !gl) {
const contextAttributes = { xrCompatible: true, antialias: true, alpha: false };
gl = canvas.getContext('webgl2', contextAttributes) ||
canvas.getContext('webgl', contextAttributes) ||
canvas.getContext('experimental-webgl', contextAttributes);
}
if (!canvas) {
canvas = document.getElementById('khanvas');
if (canvas && !gl) {
gl = canvas.getContext('webgl2') || canvas.getContext('webgl');
}
}
if (!gl) {
return;
}
const self = this;
const glContext = gl;
self._glContext = glContext;
");
Syntax.code("
self._vrRenderCallback = function() {
self.vrRenderCallback();
};
const checkAndRequestSession = async () => {
try {
const supported = await navigator.xr.isSessionSupported('immersive-vr');
if (!supported) {
trace('immersive-vr session not supported');
}
} catch (e) {
trace('WARN: isSessionSupported failed: ' + e.message);
// Continue anyway as some browsers do not support the check itself
}
return await navigator.xr.requestSession('immersive-vr', {
optionalFeatures: ['local-floor', 'hand-tracking', 'bounded-floor']
});
};
checkAndRequestSession().then(async (session) => {
self.xrSession = session;
if (typeof window !== 'undefined') {
window._khaSkipWindowRender = true;
}
const contextAttributes = glContext.getContextAttributes();
if (!contextAttributes || !contextAttributes.xrCompatible) {
await glContext.makeXRCompatible();
}
self.xrGLLayer = new XRWebGLLayer(session, glContext, {
depth: true, // Essential for depth testing
stencil: false, // Not needed, wastes memory
alpha: false, // Not needed in VR wastes
antialias: true, // Smooth rendering
framebufferScaleFactor: {0} // VR resolution quality from renderpath
});
if (self.xrGLLayer.framebufferWidth === 0 || self.xrGLLayer.framebufferHeight === 0) {
trace('XRWebGLLayer framebuffer has invalid dimensions');
}
session.updateRenderState({
baseLayer: self.xrGLLayer
});
const handlers = {};
handlers.end = () => {
self.onSessionEnd();
};
session.addEventListener('end', handlers.end);
handlers.select = (event) => {
if (self.onSelect) self.onSelect(event);
};
session.addEventListener('select', handlers.select);
handlers.selectstart = (event) => {
if (self.onSelectStart) self.onSelectStart(event);
};
session.addEventListener('selectstart', handlers.selectstart);
handlers.selectend = (event) => {
if (self.onSelectEnd) self.onSelectEnd(event);
};
session.addEventListener('selectend', handlers.selectend);
handlers.squeeze = (event) => {
if (self.onSqueeze) self.onSqueeze(event);
};
session.addEventListener('squeeze', handlers.squeeze);
handlers.squeezestart = (event) => {
if (self.onSqueezeStart) self.onSqueezeStart(event);
};
session.addEventListener('squeezestart', handlers.squeezestart);
handlers.squeezeend = (event) => {
if (self.onSqueezeEnd) self.onSqueezeEnd(event);
};
session.addEventListener('squeezeend', handlers.squeezeend);
session.addEventListener('inputsourceschange', handlers.inputsourceschange);
handlers.visibilitychange = (event) => {
const state = event.session.visibilityState;
};
session.addEventListener('visibilitychange', handlers.visibilitychange);
self._eventHandlers = handlers;
const requestRefSpace = async () => {
const spaces = ['local-floor', 'local'];
for (const space of spaces) {
try {
const refSpace = await session.requestReferenceSpace(space);
return refSpace;
} catch (e) {
trace(space + ' not supported');
}
}
trace('No reference space supported');
};
requestRefSpace().then((refSpace) => {
self.xrRefSpace = refSpace;
if (canvas && canvas.width) {
self.savedCanvasWidth = canvas.width;
self.savedCanvasHeight = canvas.height;
} else {
const canvasFallback = document.querySelector('canvas');
if (canvasFallback) {
self.savedCanvasWidth = canvasFallback.width;
self.savedCanvasHeight = canvasFallback.height;
}
}
const onFrame = (time, frame) => {
try {
if (self.xrSession) {
self.xrAnimationFrameHandle = self.xrSession.requestAnimationFrame(onFrame);
}
if (!self._lastFrameTime) self._lastFrameTime = time;
const deltaTime = time - self._lastFrameTime;
self._lastFrameTime = time;
if (!window._xrFrameCount) window._xrFrameCount = 0;
window._xrFrameCount++;
if (glContext && self.xrSession && self.xrSession.renderState && self.xrSession.renderState.baseLayer) {
const layer = self.xrSession.renderState.baseLayer;
if (layer.framebuffer) {
const pose = frame.getViewerPose(self.xrRefSpace);
if (pose && pose.views && pose.views.length > 0) {
glContext.bindFramebuffer(glContext.FRAMEBUFFER, layer.framebuffer);
let bgR = 0, bgG = 0, bgB = 0;
if (typeof iron !== 'undefined' && iron.Scene && iron.Scene.active && iron.Scene.active.world && iron.Scene.active.world.raw) {
const bgColor = iron.Scene.active.world.raw.background_color;
if (bgColor !== undefined) {
bgR = ((bgColor >> 16) & 255) / 255;
bgG = ((bgColor >> 8) & 255) / 255;
bgB = (bgColor & 255) / 255;
}
}
for (const view of pose.views) {
const vp = layer.getViewport(view);
glContext.viewport(vp.x, vp.y, vp.width, vp.height);
glContext.scissor(vp.x, vp.y, vp.width, vp.height);
glContext.enable(glContext.SCISSOR_TEST);
glContext.clearColor(bgR, bgG, bgB, 1.0);
glContext.clear(glContext.COLOR_BUFFER_BIT | glContext.DEPTH_BUFFER_BIT);
}
glContext.disable(glContext.SCISSOR_TEST);
}
}
}
if (!self.xrSession) {
return;
}
const pose = frame.getViewerPose(self.xrRefSpace);
if (!pose) {
return;
}
if (pose.emulatedPosition && !self._emulatedPosLogged) {
self._emulatedPosLogged = true;
}
const views = pose.views;
if (!self.xrSession.renderState || !self.xrSession.renderState.baseLayer) {
if (!self._noRenderStateLogged) {
self._noRenderStateLogged = true;
}
return;
}
const glLayer = self.xrSession.renderState.baseLayer;
if (!views || views.length === 0) {
return;
}
if (self.xrSession.visibilityState === 'hidden') {
return;
}
self.currentFrame = frame;
self.currentViews = views;
self.currentViewerPose = pose;
if (self.xrSession && self.xrSession.inputSources) {
self.currentInputSources = self.xrSession.inputSources;
}
if (glContext.isContextLost()) {
return;
}
if (!glContext || !glLayer || !glLayer.framebuffer) {
return;
}
if (glContext.bindVertexArray) {
glContext.bindVertexArray(null);
}
while (glContext.getError() !== glContext.NO_ERROR) {
// Drain error queue
}
glContext.bindFramebuffer(glContext.FRAMEBUFFER, glLayer.framebuffer);
const bindError = glContext.getError();
if (bindError !== glContext.NO_ERROR && !self._bindErrorLogged) {
self._bindErrorLogged = true;
}
const fbStatus = glContext.checkFramebufferStatus(glContext.FRAMEBUFFER);
if (fbStatus !== glContext.FRAMEBUFFER_COMPLETE) {
return;
}
glContext.enable(glContext.DEPTH_TEST);
glContext.depthFunc(glContext.LEQUAL);
glContext.depthMask(true);
glContext.colorMask(true, true, true, true);
glContext.disable(glContext.BLEND);
glContext.enable(glContext.CULL_FACE);
glContext.cullFace(glContext.BACK);
glContext.frontFace(glContext.CCW);
glContext.disable(glContext.STENCIL_TEST);
glContext.disable(glContext.POLYGON_OFFSET_FILL);
if (!self._fbLogged && p) {
const depthTest = glContext.isEnabled(glContext.DEPTH_TEST);
const cullFace = glContext.isEnabled(glContext.CULL_FACE);
const blend = glContext.isEnabled(glContext.BLEND);
self._fbLogged = true;
}
if (views.length === 0) {
return;
}
if (views.length >= 1) {
try {
self._leftViewport = glLayer.getViewport(views[0]);
self._rightViewport = views.length >= 2 ? glLayer.getViewport(views[1]) : null;
self._cachedViewsLength = views.length;
if (!self._leftViewport) {
return;
}
} catch (e) {
return;
}
}
if (views.length >= 1) {
self.leftProjectionMatrix = self.createMatrixFromArray(views[0].projectionMatrix);
self.leftViewMatrix = self.createMatrixFromArray(views[0].transform.inverse.matrix);
}
if (views.length >= 2) {
self.rightProjectionMatrix = self.createMatrixFromArray(views[1].projectionMatrix);
self.rightViewMatrix = self.createMatrixFromArray(views[1].transform.inverse.matrix);
} else if (views.length === 1) {
self.rightProjectionMatrix = self.leftProjectionMatrix;
self.rightViewMatrix = self.leftViewMatrix;
}
if (self._vrRenderCallback) {
self._vrRenderCallback();
}
} catch (err) {
console.error('XR Frame Error:', err);
} finally {
self.currentFrame = null;
self.currentViews = null;
self.currentInputSources = null;
}
};
self.xrAnimationFrameHandle = session.requestAnimationFrame(onFrame);
}).catch((err) => {
trace('REF SPACE FAILED: ' + err.message );
});
}).catch((err) => {
trace('SESSION FAILED: ' + err);
});
", vrScaleFactor);
} }
catch (err:Dynamic) { catch (err:Dynamic) {
trace("Failed to requestPresent."); trace("Failed to requestSession (WebXR).");
trace(err); trace(err);
} }
} }
public override function onVRExitPresent() { function onSessionEnd() {
try { var canvas = SystemImpl.khanvas;
vrDisplay.exitPresent([{source: SystemImpl.khanvas}]).then(function() { if (canvas == null) {
onResize(); canvas = Syntax.code("document.querySelector('canvas')");
});
} }
catch (err:Dynamic) {
trace("Failed to exitPresent."); if (canvas != null && savedCanvasWidth > 0 && savedCanvasHeight > 0) {
trace(err); canvas.width = savedCanvasWidth;
canvas.height = savedCanvasHeight;
}
if (xrSession != null) {
Syntax.code("
if (this.xrAnimationFrameHandle !== -1 && this.xrSession) {
this.xrSession.cancelAnimationFrame(this.xrAnimationFrameHandle);
this.xrAnimationFrameHandle = -1;
}
if (this._eventHandlers && this.xrSession) {
const handlers = this._eventHandlers;
const events = ['end', 'select', 'selectstart', 'selectend', 'squeeze', 'squeezestart', 'squeezeend', 'inputsourceschange', 'visibilitychange'];
for (const event of events) {
if (handlers[event]) {
this.xrSession.removeEventListener(event, handlers[event]);
}
}
this._eventHandlers = null;
}
");
}
Syntax.code("
const gl = this._glContext;
if (gl) {
gl.bindFramebuffer(gl.FRAMEBUFFER, null); // Restore default framebuffer
gl.disable(gl.SCISSOR_TEST);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
}
if (typeof window !== 'undefined') {
window._khaSkipWindowRender = false;
}
this.xrSession = null;
this.xrRefSpace = null;
this.xrGLLayer = null;
this.currentFrame = null;
this.currentViews = null;
this.currentInputSources = null;
this._lastFrameTime = null;
if (typeof window !== 'undefined') {
delete window._xrFrameCount;
delete window._ironRenderCount;
delete window._slowFrameCount;
}
");
}
public override function onVRExitPresent() {
if (isWebXR) {
try {
if (xrSession != null) {
Syntax.code("
if (this.xrSession) {
this.xrSession.end().then(() => {
trace('Session ended');
}).catch((err) => {
trace('Session.end() failed:', err);
});
}
");
xrSession = null;
xrRefSpace = null;
xrGLLayer = null;
}
}
catch (err:Dynamic) {
trace("Failed to exitPresent in WebXR");
trace(err);
}
}
else {
// WebVR 1.1
try {
vrDisplay.exitPresent([{source: SystemImpl.khanvas}]).then(function() {
onResize();
});
}
catch (err:Dynamic) {
trace("Failed to exitPresent");
trace(err);
}
} }
} }
@ -116,13 +597,25 @@ class VrInterface extends kha.vr.VrInterface {
} }
function onResize() { function onResize() {
if (vrDisplay != null && vrDisplay.isPresenting) { if (isWebXR) {
SystemImpl.khanvas.width = vrWidth; return;
SystemImpl.khanvas.height = vrHeight;
} }
else { else {
SystemImpl.khanvas.width = width; // WebVR 1.1
SystemImpl.khanvas.height = height; if (vrDisplay != null && vrDisplay.isPresenting) {
var canvas = SystemImpl.khanvas;
if (canvas != null) {
canvas.width = vrWidth;
canvas.height = vrHeight;
}
}
else {
var canvas = SystemImpl.khanvas;
if (canvas != null) {
canvas.width = width;
canvas.height = height;
}
}
} }
} }
@ -174,9 +667,11 @@ class VrInterface extends kha.vr.VrInterface {
} }
public override function IsPresenting(): Bool { public override function IsPresenting(): Bool {
if (vrDisplay != null) var presenting = false;
return vrDisplay.isPresenting; if (vrDisplay != null){
return false; presenting = vrDisplay.isPresenting;
}
return presenting;
} }
public override function IsVrEnabled(): Bool { public override function IsVrEnabled(): Bool {
@ -207,6 +702,10 @@ class VrInterface extends kha.vr.VrInterface {
function createMatrixFromArray(array: Float32Array): FastMatrix4 { function createMatrixFromArray(array: Float32Array): FastMatrix4 {
var matrix: FastMatrix4 = FastMatrix4.identity(); var matrix: FastMatrix4 = FastMatrix4.identity();
if (array == null || array.length < 16) {
trace("Warning: Invalid matrix array, using identity");
return matrix;
}
matrix._00 = array[0]; matrix._00 = array[0];
matrix._01 = array[1]; matrix._01 = array[1];
matrix._02 = array[2]; matrix._02 = array[2];
@ -246,4 +745,63 @@ class VrInterface extends kha.vr.VrInterface {
} }
return quaternion; return quaternion;
} }
public function vrRenderCallback(): Void {
var g4 = kha.SystemImpl.frame != null ? kha.SystemImpl.frame.g4 : null;
if (g4 != null && iron.Scene.active != null && iron.RenderPath.active != null) {
if (untyped window._vrUpdateStarted == null) {
untyped window._vrUpdateStarted = true;
}
iron.system.Time.update();
iron.Scene.active.updateFrame();
js.Syntax.code("
const App = iron.App;
if (App) {
const frame = window._vrCallbackCount;
const inits = App.traitInits;
if (inits && inits.length > 0) {
for (let i = 0; i < inits.length; i++) {
inits[i]();
}
inits.length = 0;
}
const fixedUpdates = App.traitFixedUpdates;
if (fixedUpdates) {
for (let i = 0; i < fixedUpdates.length; i++) {
fixedUpdates[i]();
}
}
const updates = App.traitUpdates;
if (updates) {
for (let i = 0; i < updates.length; i++) {
updates[i]();
}
}
const lateUpdates = App.traitLateUpdates;
if (lateUpdates) {
for (let i = 0; i < lateUpdates.length; i++) {
lateUpdates[i]();
}
}
}
");
iron.Scene.active.renderFrame(g4);
}
else {
if (untyped window._vrSkipLogged == null) {
untyped window._vrSkipLogged = true;
}
}
}
} }

View File

@ -157,4 +157,5 @@ extern class Krom {
static function getConstantLocationCompute(shader: Dynamic, name: String): Dynamic; static function getConstantLocationCompute(shader: Dynamic, name: String): Dynamic;
static function getTextureUnitCompute(shader: Dynamic, name: String): Dynamic; static function getTextureUnitCompute(shader: Dynamic, name: String): Dynamic;
static function compute(x: Int, y: Int, z: Int): Void; static function compute(x: Int, y: Int, z: Int): Void;
static function viewportSetCamera(posX: Float, posY: Float, posZ: Float, rotX: Float, rotY: Float, rotZ: Float, rotW: Float): Void;
} }

0
Krom/Krom Executable file → Normal file
View File

Binary file not shown.

View File

@ -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'])
@ -37,6 +39,8 @@
-D lnx_skin -D lnx_skin
-D lnx_morph_target -D lnx_morph_target
-D lnx_particles -D lnx_particles
-D lnx_cpu_particles
-D lnx_gpu_particles
-D sys_krom -D sys_krom
-D sys_g1 -D sys_g1
-D sys_g2 -D sys_g2

View File

@ -6,8 +6,8 @@ bl_info = {
"location": "Properties -> Render -> Leenkx Player", "location": "Properties -> Render -> Leenkx Player",
"description": "Full Stack SDK", "description": "Full Stack SDK",
"author": "Leenkx.com", "author": "Leenkx.com",
"version": (1, 0, 8), "version": (2026, 5, 0),
"blender": (4, 2, 1), "blender": (4, 5, 0),
"doc_url": "https://leenkx.com/", "doc_url": "https://leenkx.com/",
"tracker_url": "https://leenkx.com/support" "tracker_url": "https://leenkx.com/support"
} }
@ -24,7 +24,7 @@ import textwrap
import threading import threading
import traceback import traceback
import typing import typing
from typing import Callable, Optional from typing import Callable, Optional, List
import webbrowser import webbrowser
import bpy import bpy
@ -33,6 +33,12 @@ from bpy.props import *
from bpy.types import Operator, AddonPreferences from bpy.types import Operator, AddonPreferences
if bpy.app.version < (2, 90, 0):
ListType = List
else:
ListType = list
class SDKSource(IntEnum): class SDKSource(IntEnum):
PREFS = 0 PREFS = 0
LOCAL = 1 LOCAL = 1
@ -73,9 +79,10 @@ def detect_sdk_path():
area = win.screen.areas[0] area = win.screen.areas[0]
area_type = area.type area_type = area.type
area.type = "INFO" area.type = "INFO"
with bpy.context.temp_override(window=win, screen=win.screen, area=area): if bpy.app.version >= (2, 92, 0):
bpy.ops.info.select_all(action='SELECT') with bpy.context.temp_override(window=win, screen=win.screen, area=area):
bpy.ops.info.report_copy() bpy.ops.info.select_all(action='SELECT')
bpy.ops.info.report_copy()
area.type = area_type area.type = area_type
clipboard = bpy.context.window_manager.clipboard clipboard = bpy.context.window_manager.clipboard
@ -85,6 +92,7 @@ def detect_sdk_path():
if match: if match:
addon_prefs.sdk_path = os.path.dirname(match[-1]) addon_prefs.sdk_path = os.path.dirname(match[-1])
def get_link_web_server(self): def get_link_web_server(self):
return self.get('link_web_server', 'http://localhost/') return self.get('link_web_server', 'http://localhost/')
@ -310,7 +318,7 @@ class LeenkxAddonPreferences(AddonPreferences):
layout.label(text="Welcome to Leenkx!") layout.label(text="Welcome to Leenkx!")
# Compare version Blender and Leenkx (major, minor) # Compare version Blender and Leenkx (major, minor)
if bpy.app.version[:2] not in [(4, 4), (4, 2), (3, 6), (3, 3)]: if bpy.app.version[:2] not in [(4, 5), (4, 4), (4, 2), (3, 6), (3, 3)]:
box = layout.box().column() box = layout.box().column()
box.label(text="Warning: For Leenkx to work correctly, use a Blender LTS version") box.label(text="Warning: For Leenkx to work correctly, use a Blender LTS version")
@ -542,13 +550,13 @@ def apply_unix_permissions(sdk):
os.path.join(sdk, "nodejs/node-osx"), os.path.join(sdk, "nodejs/node-osx"),
os.path.join(sdk, "Krom/Krom.app/Contents/MacOS/Krom"), os.path.join(sdk, "Krom/Krom.app/Contents/MacOS/Krom"),
# Kha tools # Kha tools
os.path.join(sdk, "Kha/Tools/macos/haxe"), os.path.join(sdk, "Kha/Tools/macos_x64/haxe"),
os.path.join(sdk, "Kha/Tools/macos/lame"), os.path.join(sdk, "Kha/Tools/macos_x64/lame"),
os.path.join(sdk, "Kha/Tools/macos/oggenc"), os.path.join(sdk, "Kha/Tools/macos_x64/oggenc"),
# Kinc tools # Kinc tools
os.path.join(sdk, "Kha/Kinc/Tools/macos/kmake"), os.path.join(sdk, "Kha/Kinc/Tools/macos_x64/kmake"),
os.path.join(sdk, "Kha/Kinc/Tools/macos/kraffiti"), os.path.join(sdk, "Kha/Kinc/Tools/macos_x64/kraffiti"),
os.path.join(sdk, "Kha/Kinc/Tools/macos/krafix"), os.path.join(sdk, "Kha/Kinc/Tools/macos_x64/krafix"),
] ]
for path in paths: for path in paths:
os.chmod(path, 0o777) os.chmod(path, 0o777)
@ -558,7 +566,7 @@ def remove_readonly(func, path, excinfo):
func(path) func(path)
def run_proc(cmd: list[str], done: Optional[Callable[[bool], None]] = None): def run_proc(cmd: ListType[str], done: Optional[Callable[[bool], None]] = None):
def fn(p, done): def fn(p, done):
p.wait() p.wait()
if done is not None: if done is not None:
@ -840,7 +848,13 @@ def update_leenkx_py(sdk_path: str, force_relink=False):
else: else:
raise err raise err
else: else:
lnx_module_file.unlink(missing_ok=True) if bpy.app.version < (2, 92, 0):
try:
lnx_module_file.unlink()
except FileNotFoundError:
pass
else:
lnx_module_file.unlink(missing_ok=True)
shutil.copy(Path(sdk_path) / 'leenkx.py', lnx_module_file) shutil.copy(Path(sdk_path) / 'leenkx.py', lnx_module_file)

View File

@ -14,7 +14,7 @@ out vec4 fragColor;
vec2 barrelDistortion(vec2 coord, float amt) { vec2 barrelDistortion(vec2 coord, float amt) {
vec2 cc = coord - 0.5; vec2 cc = coord - 0.5;
float dist = dot(cc, cc); float dist = dot(cc, cc);
return coord + cc * dist * amt; return coord - cc * dist * amt;
} }
float sat(float value) float sat(float value)
{ {
@ -56,8 +56,6 @@ void main() {
if (CAType == 1) { if (CAType == 1) {
float reci_num_iter_f = 1.0 / float(num_iter); float reci_num_iter_f = 1.0 / float(num_iter);
vec2 resolution = vec2(1,1);
vec2 uv = (texCoord.xy/resolution.xy);
vec4 sumcol = vec4(0.0); vec4 sumcol = vec4(0.0);
vec4 sumw = vec4(0.0); vec4 sumw = vec4(0.0);
for (int i=0; i < num_iter; ++i) for (int i=0; i < num_iter; ++i)
@ -65,19 +63,21 @@ void main() {
float t = float(i) * reci_num_iter_f; float t = float(i) * reci_num_iter_f;
vec4 w = spectrum_offset(t); vec4 w = spectrum_offset(t);
sumw += w; sumw += w;
sumcol += w * texture(tex, barrelDistortion(uv, 0.6 * max_distort * t)); vec2 distortedUV = barrelDistortion(texCoord, 0.6 * max_distort * t);
sumcol += w * texture(tex, distortedUV);
} }
if (on == 1) fragColor = sumcol / sumw; else fragColor = texture(tex, texCoord); if (on == 1) fragColor = sumcol / sumw; else fragColor = texture(tex, texCoord);
} }
// Simple // inward sampling to avoid edge artifacts
else { else {
vec3 col = vec3(0.0); vec3 col = vec3(0.0);
col.x = texture(tex, texCoord + ((vec2(0.0, 1.0) * max_distort) / vec2(1000.0))).x; vec2 toCenter = (vec2(0.5) - texCoord) * max_distort / 500.0;
col.y = texture(tex, texCoord + ((vec2(-0.85, -0.5) * max_distort) / vec2(1000.0))).y; col.x = texture(tex, texCoord + toCenter * 0.0).x;
col.z = texture(tex, texCoord + ((vec2(0.85, -0.5) * max_distort) / vec2(1000.0))).z; col.y = texture(tex, texCoord + toCenter * 0.5).y;
if (on == 1) fragColor = vec4(col.x, col.y, col.z, fragColor.w); col.z = texture(tex, texCoord + toCenter * 1.0).z;
if (on == 1) fragColor = vec4(col.x, col.y, col.z, 1.0);
else fragColor = texture(tex, texCoord); else fragColor = texture(tex, texCoord);
} }
} }

View File

@ -358,6 +358,12 @@ void main() {
fragColor = textureLod(tex, texCo, 0.0); fragColor = textureLod(tex, texCo, 0.0);
#endif #endif
// TODO: re-investigate white artifacts
fragColor.rgb = clamp(fragColor.rgb, vec3(0.0), vec3(65504.0));
if (any(isnan(fragColor.rgb)) || any(isinf(fragColor.rgb))) {
fragColor.rgb = vec3(0.0);
}
#endif #endif
#ifdef _CSharpen #ifdef _CSharpen

View File

@ -57,10 +57,10 @@ uniform vec3 backgroundCol;
#ifdef _SSAO #ifdef _SSAO
uniform sampler2D ssaotex; uniform sampler2D ssaotex;
#else
#ifdef _SSGI
uniform sampler2D ssaotex;
#endif #endif
#ifdef _SSGI
uniform sampler2D ssgitex;
#endif #endif
#ifdef _SSS #ifdef _SSS
@ -102,8 +102,23 @@ uniform mat4 invVP;
#endif #endif
uniform vec2 cameraProj; uniform vec2 cameraProj;
#ifdef _VRStereo
uniform vec3 eye; // center camera position
uniform vec3 eyeLook; // center camera look
uniform vec3 eyeLeft;
uniform vec3 eyeRight;
uniform vec3 eyeLookLeft;
uniform vec3 eyeLookRight;
uniform mat4 invVPLeft;
uniform mat4 invVPRight;
#ifdef _SinglePoint
uniform vec3 pointPosLeft;
uniform vec3 pointPosRight;
#endif
#else
uniform vec3 eye; uniform vec3 eye;
uniform vec3 eyeLook; uniform vec3 eyeLook;
#endif
#ifdef _Clusters #ifdef _Clusters
uniform vec4 lightsArray[maxLights * 3]; uniform vec4 lightsArray[maxLights * 3];
@ -200,7 +215,9 @@ uniform vec3 sunCol;
#endif #endif
#ifdef _SinglePoint // Fast path for single light #ifdef _SinglePoint // Fast path for single light
#ifndef _VRStereo
uniform vec3 pointPos; uniform vec3 pointPos;
#endif
uniform vec3 pointCol; uniform vec3 pointCol;
#ifdef _ShadowMap #ifdef _ShadowMap
uniform float pointBias; uniform float pointBias;
@ -225,6 +242,8 @@ out vec4 fragColor;
void main() { void main() {
vec4 g0 = textureLod(gbuffer0, texCoord, 0.0); // Normal.xy, roughness, metallic/matid vec4 g0 = textureLod(gbuffer0, texCoord, 0.0); // Normal.xy, roughness, metallic/matid
vec4 g1 = textureLod(gbuffer1, texCoord, 0.0); // Basecolor.rgb, spec/occ
float depth = textureLod(gbufferD, texCoord, 0.0).r * 2.0 - 1.0;
vec3 n; vec3 n;
n.z = 1.0 - abs(g0.x) - abs(g0.y); n.z = 1.0 - abs(g0.x) - abs(g0.y);
@ -236,14 +255,28 @@ void main() {
uint matid; uint matid;
unpackFloatInt16(g0.a, metallic, matid); unpackFloatInt16(g0.a, metallic, matid);
vec4 g1 = textureLod(gbuffer1, texCoord, 0.0); // Basecolor.rgb, spec/occ
vec2 occspec = unpackFloat2(g1.a); vec2 occspec = unpackFloat2(g1.a);
vec3 albedo = surfaceAlbedo(g1.rgb, metallic); // g1.rgb - basecolor // re-investigate clamp basecolor to prevent extreme values causing glitches
vec3 f0 = surfaceF0(g1.rgb, metallic); vec3 basecolor = min(g1.rgb, vec3(2.0));
vec3 albedo = surfaceAlbedo(basecolor, metallic);
vec3 f0 = surfaceF0(basecolor, metallic);
float depth = textureLod(gbufferD, texCoord, 0.0).r * 2.0 - 1.0; #ifdef _VRStereo
bool isLeftEye = texCoord.x < 0.5;
vec3 eyePos = isLeftEye ? eyeLeft : eyeRight;
mat4 invVP_eye = isLeftEye ? invVPLeft : invVPRight;
vec2 eyeTexCoord = vec2(
isLeftEye ? texCoord.x * 2.0 : (texCoord.x - 0.5) * 2.0,
texCoord.y
);
vec3 p = getPos2(invVP_eye, depth, eyeTexCoord);
vec3 v = normalize(eyePos - p);
#else
vec3 p = getPos(eye, eyeLook, normalize(viewRay), depth, cameraProj); vec3 p = getPos(eye, eyeLook, normalize(viewRay), depth, cameraProj);
vec3 v = normalize(eye - p); vec3 v = normalize(eye - p);
#endif
float dotNV = max(dot(n, v), 0.0); float dotNV = max(dot(n, v), 0.0);
#ifdef _gbuffer2 #ifdef _gbuffer2
@ -287,6 +320,7 @@ void main() {
vec3 reflectionWorld = reflect(-v, n); vec3 reflectionWorld = reflect(-v, n);
float lod = getMipFromRoughness(roughness, envmapNumMipmaps); float lod = getMipFromRoughness(roughness, envmapNumMipmaps);
vec3 prefilteredColor = textureLod(senvmapRadiance, envMapEquirect(reflectionWorld), lod).rgb; vec3 prefilteredColor = textureLod(senvmapRadiance, envMapEquirect(reflectionWorld), lod).rgb;
prefilteredColor = min(prefilteredColor, vec3(20.0));
#endif #endif
#ifdef _EnvLDR #ifdef _EnvLDR
@ -340,15 +374,12 @@ void main() {
// fragColor.rgb = texture(ssaotex, texCoord).rrr; // fragColor.rgb = texture(ssaotex, texCoord).rrr;
#ifdef _SSAO #ifdef _SSAO
// #ifdef _RTGI
// fragColor.rgb *= textureLod(ssaotex, texCoord, 0.0).rgb;
// #else
fragColor.rgb *= textureLod(ssaotex, texCoord, 0.0).r; fragColor.rgb *= textureLod(ssaotex, texCoord, 0.0).r;
// #endif
#else
#ifdef _SSGI
fragColor.rgb += textureLod(ssaotex, texCoord, 0.0).rgb;
#endif #endif
#ifdef _SSGI
vec3 ssgiColor = textureLod(ssgitex, texCoord, 0.0).rgb;
fragColor.rgb += ssgiColor * albedo;
#endif #endif
#ifdef _EmissionShadeless #ifdef _EmissionShadeless
@ -381,62 +412,62 @@ void main() {
#ifdef _ShadowMap #ifdef _ShadowMap
#ifdef _CSM #ifdef _CSM
svisibility = shadowTestCascade( svisibility = shadowTestCascade(
#ifdef _ShadowMapAtlas #ifdef _ShadowMapAtlas
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
#ifndef _SingleAtlas #ifndef _SingleAtlas
shadowMapAtlasSun, shadowMapAtlasSunTransparent shadowMapAtlasSun, shadowMapAtlasSunTransparent
#else #else
shadowMapAtlas, shadowMapAtlasTransparent shadowMapAtlas, shadowMapAtlasTransparent
#endif #endif
#else #else
#ifndef _SingleAtlas #ifndef _SingleAtlas
shadowMapAtlasSun shadowMapAtlasSun
#else #else
shadowMapAtlas shadowMapAtlas
#endif #endif
#endif #endif
#else #else
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
shadowMap, shadowMapTransparent shadowMap, shadowMapTransparent
#else #else
shadowMap shadowMap
#endif #endif
#endif #endif
, eye, p + n * shadowsBias * 10, shadowsBias , eye, p + n * shadowsBias * 2, shadowsBias
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
, false , false
#endif #endif
); );
#else #else
vec4 lPos = LWVP * vec4(p + n * shadowsBias * 100, 1.0); vec4 lPos = LWVP * vec4(p + n * shadowsBias * 2, 1.0);
if (lPos.w > 0.0) { if (lPos.w > 0.0) {
svisibility = shadowTest( svisibility = shadowTest(
#ifdef _ShadowMapAtlas #ifdef _ShadowMapAtlas
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
#ifndef _SingleAtlas #ifndef _SingleAtlas
shadowMapAtlasSun, shadowMapAtlasSunTransparent shadowMapAtlasSun, shadowMapAtlasSunTransparent
#else #else
shadowMapAtlas, shadowMapAtlasTransparent shadowMapAtlas, shadowMapAtlasTransparent
#endif #endif
#else #else
#ifndef _SingleAtlas #ifndef _SingleAtlas
shadowMapAtlasSun shadowMapAtlasSun
#else #else
shadowMapAtlas shadowMapAtlas
#endif #endif
#endif #endif
#else #else
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
shadowMap, shadowMapTransparent shadowMap, shadowMapTransparent
#else #else
shadowMap shadowMap
#endif #endif
#endif #endif
, lPos.xyz / lPos.w, shadowsBias , lPos.xyz / lPos.w, shadowsBias
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
, false , false
#endif #endif
); );
} }
#endif #endif
#endif #endif
@ -498,8 +529,14 @@ void main() {
#ifdef _SinglePoint #ifdef _SinglePoint
#ifdef _VRStereo
vec3 lightPos = pointPosLeft;
#else
vec3 lightPos = pointPos;
#endif
fragColor.rgb += sampleLight( fragColor.rgb += sampleLight(
p, n, v, dotNV, pointPos, pointCol, albedo, roughness, occspec.y, f0 p, n, v, dotNV, lightPos, pointCol, albedo, roughness, occspec.y, f0
#ifdef _ShadowMap #ifdef _ShadowMap
, 0, pointBias, true , 0, pointBias, true
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
@ -522,7 +559,9 @@ void main() {
#ifdef _Spot #ifdef _Spot
#ifdef _SSS #ifdef _SSS
if (matid == 2) fragColor.rgb += fragColor.rgb * SSSSTransmittance(LWVPSpot[0], p, n, normalize(pointPos - p), lightPlane.y, shadowMapSpot[0]);//TODO implement transparent shadowmaps into the SSSSTransmittance() #ifdef _ShadowMap
if (matid == 2) fragColor.rgb += fragColor.rgb * SSSSTransmittance(LWVPSpot[0], p, n, normalize(lightPos - p), lightPlane.y, shadowMapSpot[0]);//TODO implement transparent shadowmaps into the SSSSTransmittance()
#endif
#endif #endif
#endif #endif
@ -582,5 +621,11 @@ void main() {
); );
} }
#endif // _Clusters #endif // _Clusters
fragColor.rgb = clamp(fragColor.rgb, vec3(0.0), vec3(65504.0));
if (any(isnan(fragColor.rgb)) || any(isinf(fragColor.rgb))) {
fragColor.rgb = vec3(0.0);
}
fragColor.a = 1.0; // Mark as opaque fragColor.a = 1.0; // Mark as opaque
} }

View File

@ -20,6 +20,36 @@
"name": "eyeLook", "name": "eyeLook",
"link": "_cameraLook" "link": "_cameraLook"
}, },
{
"name": "eyeLeft",
"link": "_eyeLeft",
"ifdef": ["_VRStereo"]
},
{
"name": "eyeRight",
"link": "_eyeRight",
"ifdef": ["_VRStereo"]
},
{
"name": "eyeLookLeft",
"link": "_eyeLookLeft",
"ifdef": ["_VRStereo"]
},
{
"name": "eyeLookRight",
"link": "_eyeLookRight",
"ifdef": ["_VRStereo"]
},
{
"name": "invVPLeft",
"link": "_inverseViewProjectionMatrixLeft",
"ifdef": ["_VRStereo"]
},
{
"name": "invVPRight",
"link": "_inverseViewProjectionMatrixRight",
"ifdef": ["_VRStereo"]
},
{ {
"name": "clipmaps", "name": "clipmaps",
"link": "_clipmaps", "link": "_clipmaps",
@ -176,8 +206,19 @@
{ {
"name": "pointPos", "name": "pointPos",
"link": "_pointPosition", "link": "_pointPosition",
"ifndef": ["_VRStereo"],
"ifdef": ["_SinglePoint"] "ifdef": ["_SinglePoint"]
}, },
{
"name": "pointPosLeft",
"link": "_pointPositionLeft",
"ifdef": ["_VRStereo", "_SinglePoint"]
},
{
"name": "pointPosRight",
"link": "_pointPositionRight",
"ifdef": ["_VRStereo", "_SinglePoint"]
},
{ {
"name": "pointCol", "name": "pointCol",
"link": "_pointColor", "link": "_pointColor",

View File

@ -97,6 +97,31 @@
"link": "_cascadeData", "link": "_cascadeData",
"ifdef": ["_Sun", "_ShadowMap", "_CSM"] "ifdef": ["_Sun", "_ShadowMap", "_CSM"]
}, },
{
"name": "eyeLookRight",
"link": "_eyeLookRight",
"ifdef": ["_VRStereo"]
},
{
"name": "invVPLeft",
"link": "_inverseViewProjectionMatrixLeft",
"ifdef": ["_VRStereo"]
},
{
"name": "invVPRight",
"link": "_inverseViewProjectionMatrixRight",
"ifdef": ["_VRStereo"]
},
{
"name": "invVP",
"link": "_viewProjectionMatrix",
"ifdef": ["_SSRS"]
},
{
"name": "smSizeUniform",
"link": "_shadowMapSize",
"ifdef": ["_SMSizeUniform"]
},
{ {
"name": "lightPlane", "name": "lightPlane",
"link": "_lightPlane", "link": "_lightPlane",
@ -108,8 +133,6 @@
"ifdef": ["_SSRS"] "ifdef": ["_SSRS"]
}, },
{ {
"name": "smSizeUniform",
"link": "_shadowMapSize",
"ifdef": ["_SMSizeUniform"] "ifdef": ["_SMSizeUniform"]
}, },
{ {
@ -120,8 +143,19 @@
{ {
"name": "pointPos", "name": "pointPos",
"link": "_pointPosition", "link": "_pointPosition",
"ifndef": ["_VRStereo"],
"ifdef": ["_SinglePoint"] "ifdef": ["_SinglePoint"]
}, },
{
"name": "pointPosLeft",
"link": "_pointPositionLeft",
"ifdef": ["_VRStereo", "_SinglePoint"]
},
{
"name": "pointPosRight",
"link": "_pointPositionRight",
"ifdef": ["_VRStereo", "_SinglePoint"]
},
{ {
"name": "pointCol", "name": "pointCol",
"link": "_pointColor", "link": "_pointColor",

View File

@ -0,0 +1,9 @@
https://gpuopen.com/manuals/fidelityfx_sdk/license/
Copyright © 2024 Advanced Micro Devices, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and /or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions :
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,157 @@
#version 450
// AMD FidelityFX Super Resolution 1.0.2 - EASU (Edge Adaptive Spatial Upsampling)
#include "compiled.inc"
uniform sampler2D tex;
uniform vec2 screenSize;
in vec2 texCoord;
out vec4 fragColor;
// Helper functions from AMD ffx_a.h
float APrxLoRcpF1(float a) {
return uintBitsToFloat(uint(0x7ef07ebb) - floatBitsToUint(a));
}
float AMax3F1(float x, float y, float z) {
return max(x, max(y, z));
}
float AMin3F1(float x, float y, float z) {
return min(x, min(y, z));
}
// Attempt to use textureGather for efficiency when available
#if __VERSION__ >= 400
void FsrEasuTap(
inout vec3 aC,
inout float aW,
vec2 off,
vec2 dir,
vec2 len,
float lob,
float clp,
vec3 c
) {
vec2 v = off * dir;
float d2 = v.x + v.y;
d2 = clamp(d2 * APrxLoRcpF1(max(abs(v.x), abs(v.y))), 0.0, 1.0);
d2 = d2 * d2;
d2 = d2 * len.x + len.y;
float wB = 2.0 / 5.0 * d2 - 1.0;
float wA = lob * d2 - 1.0;
wB *= wB;
wA *= wA;
float w = 25.0 / 16.0 * wA * wB;
w = min(w, clp);
w = max(w, 0.0);
aC += c * w;
aW += w;
}
vec3 FsrEasuF(vec2 ip) {
vec2 inputSize = textureSize(tex, 0);
vec2 inputRcp = 1.0 / inputSize;
// Position in input pixels
vec2 pp = ip * inputSize - 0.5;
vec2 fp = floor(pp);
pp -= fp;
// 12-tap kernel
// b c
// e f g h
// i j k l
// n o
ivec2 sp = ivec2(fp);
vec3 b = texelFetch(tex, sp + ivec2(0, -1), 0).rgb;
vec3 c = texelFetch(tex, sp + ivec2(1, -1), 0).rgb;
vec3 e = texelFetch(tex, sp + ivec2(-1, 0), 0).rgb;
vec3 f = texelFetch(tex, sp + ivec2(0, 0), 0).rgb;
vec3 g = texelFetch(tex, sp + ivec2(1, 0), 0).rgb;
vec3 h = texelFetch(tex, sp + ivec2(2, 0), 0).rgb;
vec3 i = texelFetch(tex, sp + ivec2(-1, 1), 0).rgb;
vec3 j = texelFetch(tex, sp + ivec2(0, 1), 0).rgb;
vec3 k = texelFetch(tex, sp + ivec2(1, 1), 0).rgb;
vec3 l = texelFetch(tex, sp + ivec2(2, 1), 0).rgb;
vec3 n = texelFetch(tex, sp + ivec2(0, 2), 0).rgb;
vec3 o = texelFetch(tex, sp + ivec2(1, 2), 0).rgb;
// Luma for edge detection (using green channel approximation)
float bL = b.g + 0.5 * (b.r + b.b);
float cL = c.g + 0.5 * (c.r + c.b);
float eL = e.g + 0.5 * (e.r + e.b);
float fL = f.g + 0.5 * (f.r + f.b);
float gL = g.g + 0.5 * (g.r + g.b);
float hL = h.g + 0.5 * (h.r + h.b);
float iL = i.g + 0.5 * (i.r + i.b);
float jL = j.g + 0.5 * (j.r + j.b);
float kL = k.g + 0.5 * (k.r + k.b);
float lL = l.g + 0.5 * (l.r + l.b);
float nL = n.g + 0.5 * (n.r + n.b);
float oL = o.g + 0.5 * (o.r + o.b);
// Gradient detection
float dirX = (cL - bL) + (gL - fL) + (kL - jL) + (oL - nL);
float dirY = (eL - iL) + (fL - jL) + (gL - kL) + (hL - lL);
// Normalize direction
float dirR = APrxLoRcpF1(max(abs(dirX), abs(dirY)));
dirX *= dirR;
dirY *= dirR;
// Calculate stretch based on edge direction
float len = length(vec2(dirX, dirY));
len = len * 0.5;
len *= len;
float stretch = (dirX * dirX + dirY * dirY) * APrxLoRcpF1(max(abs(dirX), abs(dirY)));
vec2 len2 = vec2(1.0 + (stretch - 1.0) * len, 1.0 - 0.5 * len);
float lob = 0.5 + (0.25 - 0.04 - 0.5) * len;
float clp = APrxLoRcpF1(lob);
// Accumulate samples
vec3 aC = vec3(0.0);
float aW = 0.0;
vec2 dir = vec2(dirX, dirY);
FsrEasuTap(aC, aW, vec2(0.0, -1.0) - pp, dir, len2, lob, clp, b);
FsrEasuTap(aC, aW, vec2(1.0, -1.0) - pp, dir, len2, lob, clp, c);
FsrEasuTap(aC, aW, vec2(-1.0, 0.0) - pp, dir, len2, lob, clp, e);
FsrEasuTap(aC, aW, vec2(0.0, 0.0) - pp, dir, len2, lob, clp, f);
FsrEasuTap(aC, aW, vec2(1.0, 0.0) - pp, dir, len2, lob, clp, g);
FsrEasuTap(aC, aW, vec2(2.0, 0.0) - pp, dir, len2, lob, clp, h);
FsrEasuTap(aC, aW, vec2(-1.0, 1.0) - pp, dir, len2, lob, clp, i);
FsrEasuTap(aC, aW, vec2(0.0, 1.0) - pp, dir, len2, lob, clp, j);
FsrEasuTap(aC, aW, vec2(1.0, 1.0) - pp, dir, len2, lob, clp, k);
FsrEasuTap(aC, aW, vec2(2.0, 1.0) - pp, dir, len2, lob, clp, l);
FsrEasuTap(aC, aW, vec2(0.0, 2.0) - pp, dir, len2, lob, clp, n);
FsrEasuTap(aC, aW, vec2(1.0, 2.0) - pp, dir, len2, lob, clp, o);
// Normalize
vec3 pix = aC / aW;
// Clamp to neighborhood min/max to prevent ringing
vec3 mn = min(min(min(f, g), j), k);
vec3 mx = max(max(max(f, g), j), k);
pix = clamp(pix, mn, mx);
return pix;
}
#else
// Fallback for older GLSL - simple bilinear
vec3 FsrEasuF(vec2 ip) {
return texture(tex, ip).rgb;
}
#endif
void main() {
vec3 col = FsrEasuF(texCoord);
fragColor = vec4(col, 1.0);
}

View File

@ -0,0 +1,19 @@
{
"contexts": [
{
"name": "fsr1_easu_pass",
"depth_write": false,
"compare_mode": "always",
"cull_mode": "none",
"links": [
{
"name": "screenSize",
"link": "_screenSize"
}
],
"texture_params": [],
"vertex_shader": "../include/pass.vert.glsl",
"fragment_shader": "fsr1_easu_pass.frag.glsl"
}
]
}

View File

@ -0,0 +1,9 @@
https://gpuopen.com/manuals/fidelityfx_sdk/license/
Copyright © 2024 Advanced Micro Devices, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and /or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions :
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,116 @@
#version 450
// AMD FidelityFX Super Resolution 1.0.2 - RCAS (Robust Contrast Adaptive Sharpening)
#include "compiled.inc"
uniform sampler2D tex;
// Sharpness in "stops": 0.0 = maximum sharpness, higher = less sharp
// Converted to linear via exp2(-sharpness)
#ifdef _FSR1_Ultra_Quality
const float SHARPNESS_STOPS = 0.0;
#elif defined(_FSR1_Balanced)
const float SHARPNESS_STOPS = 1.0;
#elif defined(_FSR1_Performance)
const float SHARPNESS_STOPS = 2.0;
#elif defined(_FSR1_Custom)
uniform vec4 PPComp15;
#define SHARPNESS_STOPS (PPComp15.x * 2.0)
#else
const float SHARPNESS_STOPS = 0.5; // Quality (default)
#endif
// FSR RCAS limit - prevents unnatural sharpening artifacts
#define FSR_RCAS_LIMIT (0.25 - (1.0 / 16.0))
in vec2 texCoord;
out vec4 fragColor;
// AMD helper functions from ffx_a.h
float AMin3F1(float x, float y, float z) { return min(x, min(y, z)); }
float AMax3F1(float x, float y, float z) { return max(x, max(y, z)); }
// High precision reciprocal (required for limiters per AMD docs)
// Added epsilon to prevent division by zero in dark areas
float ARcpF1(float a) {
return 1.0 / max(a, 1e-8);
}
// Medium precision reciprocal approximation (from AMD ffx_a.h)
// Only used for noise detection and final resolve
float APrxMedRcpF1(float a) {
return uintBitsToFloat(uint(0x7ef19fff) - floatBitsToUint(a));
}
void main() {
// Get texture size and texel offset
vec2 texSize = vec2(textureSize(tex, 0));
vec2 texelSize = 1.0 / texSize;
// Algorithm uses minimal 3x3 pixel neighborhood
// b
// d e f
// h
// Clamp inputs to [0,1] - FSR expects sRGB normalized input
vec3 b = clamp(texture(tex, texCoord + vec2(0.0, -texelSize.y)).rgb, 0.0, 1.0);
vec3 d = clamp(texture(tex, texCoord + vec2(-texelSize.x, 0.0)).rgb, 0.0, 1.0);
vec4 ee = texture(tex, texCoord);
vec3 e = clamp(ee.rgb, 0.0, 1.0);
vec3 f = clamp(texture(tex, texCoord + vec2(texelSize.x, 0.0)).rgb, 0.0, 1.0);
vec3 h = clamp(texture(tex, texCoord + vec2(0.0, texelSize.y)).rgb, 0.0, 1.0);
// Luma times 2 (AMD's luma calculation: B*0.5 + R*0.5 + G)
float bL = b.b * 0.5 + (b.r * 0.5 + b.g);
float dL = d.b * 0.5 + (d.r * 0.5 + d.g);
float eL = e.b * 0.5 + (e.r * 0.5 + e.g);
float fL = f.b * 0.5 + (f.r * 0.5 + f.g);
float hL = h.b * 0.5 + (h.r * 0.5 + h.g);
// Noise detection (official AMD algorithm with safety for flat areas)
float nz = 0.25 * bL + 0.25 * dL + 0.25 * fL + 0.25 * hL - eL;
float range = AMax3F1(AMax3F1(bL, dL, eL), fL, hL) - AMin3F1(AMin3F1(bL, dL, eL), fL, hL);
// Use safe division instead of APrxMedRcpF1 for range to avoid NaN in flat areas
nz = clamp(abs(nz) / max(range, 1e-5), 0.0, 1.0);
nz = -0.5 * nz + 1.0;
// Min and max of ring (per channel)
float mn4R = min(AMin3F1(b.r, d.r, f.r), h.r);
float mn4G = min(AMin3F1(b.g, d.g, f.g), h.g);
float mn4B = min(AMin3F1(b.b, d.b, f.b), h.b);
float mx4R = max(AMax3F1(b.r, d.r, f.r), h.r);
float mx4G = max(AMax3F1(b.g, d.g, f.g), h.g);
float mx4B = max(AMax3F1(b.b, d.b, f.b), h.b);
// Immediate constants for peak range
vec2 peakC = vec2(1.0, -4.0);
// Limiters - these need HIGH PRECISION reciprocals (per AMD docs)
float hitMinR = min(mn4R, e.r) * ARcpF1(4.0 * mx4R);
float hitMinG = min(mn4G, e.g) * ARcpF1(4.0 * mx4G);
float hitMinB = min(mn4B, e.b) * ARcpF1(4.0 * mx4B);
float hitMaxR = (peakC.x - max(mx4R, e.r)) * ARcpF1(4.0 * mn4R + peakC.y);
float hitMaxG = (peakC.x - max(mx4G, e.g)) * ARcpF1(4.0 * mn4G + peakC.y);
float hitMaxB = (peakC.x - max(mx4B, e.b)) * ARcpF1(4.0 * mn4B + peakC.y);
float lobeR = max(-hitMinR, hitMaxR);
float lobeG = max(-hitMinG, hitMaxG);
float lobeB = max(-hitMinB, hitMaxB);
// Apply sharpness (convert from stops to linear)
float sharpness = exp2(-SHARPNESS_STOPS);
float lobe = max(-FSR_RCAS_LIMIT, min(AMax3F1(lobeR, lobeG, lobeB), 0.0)) * sharpness;
// Apply noise removal
lobe *= nz;
// Resolve using safe reciprocal to avoid any edge case issues
float denom = 4.0 * lobe + 1.0;
float rcpL = 1.0 / max(denom, 0.25); // denom should be in [0.25, 1.0] range
vec3 pix;
pix.r = (lobe * b.r + lobe * d.r + lobe * h.r + lobe * f.r + e.r) * rcpL;
pix.g = (lobe * b.g + lobe * d.g + lobe * h.g + lobe * f.g + e.g) * rcpL;
pix.b = (lobe * b.b + lobe * d.b + lobe * h.b + lobe * f.b + e.b) * rcpL;
// Ensure output is clamped to valid range
fragColor = vec4(clamp(pix, 0.0, 1.0), ee.a);
}

View File

@ -0,0 +1,24 @@
{
"contexts": [
{
"name": "fsr1_rcas_pass",
"depth_write": false,
"compare_mode": "always",
"cull_mode": "none",
"links": [
{
"name": "screenSize",
"link": "_screenSize"
},
{
"name": "PPComp15",
"link": "_PPComp15",
"ifdef": ["_FSR1_Custom"]
}
],
"texture_params": [],
"vertex_shader": "../include/pass.vert.glsl",
"fragment_shader": "fsr1_rcas_pass.frag.glsl"
}
]
}

View File

@ -8,6 +8,7 @@ uniform sampler2D gbufferD;
uniform sampler2D gbuffer0; uniform sampler2D gbuffer0;
uniform sampler2D gbuffer1; uniform sampler2D gbuffer1;
uniform mat4 invVP; uniform mat4 invVP;
uniform mat4 invW;
uniform vec3 probep; uniform vec3 probep;
uniform vec3 eye; uniform vec3 eye;
@ -25,19 +26,27 @@ void main() {
float roughness = g0.b; float roughness = g0.b;
if (roughness > 0.95) { if (roughness > 0.95) {
fragColor.rgb = vec3(0.0); fragColor = vec4(0.0);
return; return;
} }
float spec = fract(textureLod(gbuffer1, texCoord, 0.0).a); float spec = fract(textureLod(gbuffer1, texCoord, 0.0).a);
if (spec == 0.0) { if (spec == 0.0) {
fragColor.rgb = vec3(0.0); fragColor = vec4(0.0);
return; return;
} }
float depth = textureLod(gbufferD, texCoord, 0.0).r * 2.0 - 1.0; float depth = textureLod(gbufferD, texCoord, 0.0).r * 2.0 - 1.0;
vec3 wp = getPos2(invVP, depth, texCoord); vec3 wp = getPos2(invVP, depth, texCoord);
vec3 localPos = (invW * vec4(wp, 1.0)).xyz;
// return if surface is inside probe volume bounds
if (abs(localPos.x) > 1.0 || abs(localPos.y) > 1.0 || abs(localPos.z) > 1.0) {
fragColor = vec4(0.0);
return;
}
vec2 enc = g0.rg; vec2 enc = g0.rg;
vec3 n; vec3 n;
n.z = 1.0 - abs(enc.x) - abs(enc.y); n.z = 1.0 - abs(enc.x) - abs(enc.y);
@ -50,5 +59,5 @@ void main() {
r.y = -r.y; r.y = -r.y;
#endif #endif
float intensity = clamp((1.0 - roughness) * dot(wp - probep, n), 0.0, 1.0); float intensity = clamp((1.0 - roughness) * dot(wp - probep, n), 0.0, 1.0);
fragColor.rgb = texture(probeTex, r).rgb * intensity; fragColor = vec4(texture(probeTex, r).rgb * intensity, 1.0);
} }

View File

@ -20,6 +20,10 @@
"name": "invVP", "name": "invVP",
"link": "_inverseViewProjectionMatrix" "link": "_inverseViewProjectionMatrix"
}, },
{
"name": "invW",
"link": "_inverseWorldMatrix"
},
{ {
"name": "probep", "name": "probep",
"link": "_probePosition" "link": "_probePosition"

View File

@ -25,13 +25,13 @@ void main() {
float roughness = g0.b; float roughness = g0.b;
if (roughness > 0.95) { if (roughness > 0.95) {
fragColor.rgb = vec3(0.0); fragColor = vec4(0.0);
return; return;
} }
float spec = fract(textureLod(gbuffer1, texCoord, 0.0).a); float spec = fract(textureLod(gbuffer1, texCoord, 0.0).a);
if (spec == 0.0) { if (spec == 0.0) {
fragColor.rgb = vec3(0.0); fragColor = vec4(0.0);
return; return;
} }
@ -50,5 +50,5 @@ void main() {
n = normalize(n); n = normalize(n);
float intensity = clamp((1.0 - roughness) * dot(n, proben), 0.0, 1.0); float intensity = clamp((1.0 - roughness) * dot(n, proben), 0.0, 1.0);
fragColor.rgb = texture(probeTex, tc).rgb * intensity; fragColor = vec4(texture(probeTex, tc).rgb * intensity, 1.0);
} }

View File

@ -5,42 +5,56 @@
uniform sampler2D tex; uniform sampler2D tex;
uniform sampler2D gbuffer0; uniform sampler2D gbuffer0;
uniform sampler2D gbufferD;
uniform vec2 dirInv; // texStep uniform vec2 dirInv; // texStep
in vec2 texCoord; in vec2 texCoord;
out float fragColor; out vec3 fragColor;
const float blurWeights[5] = float[] (0.227027, 0.1945946, 0.1216216, 0.054054, 0.016216); const int KERNEL_SIZE = 13;
// const float blurWeights[10] = float[] (0.132572, 0.125472, 0.106373, 0.08078, 0.05495, 0.033482, 0.018275, 0.008934, 0.003912, 0.001535); const float blurWeights[13] = float[](0.1, 0.09, 0.08, 0.07, 0.06, 0.05, 0.04, 0.03, 0.025, 0.02, 0.015, 0.01, 0.005);
const float discardThreshold = 0.95;
float doBlur(const float blurWeight, const int pos, const vec3 nor, const vec2 texCoord) {
const float posadd = pos + 0.5;
vec3 nor2 = getNor(textureLod(gbuffer0, texCoord + pos * dirInv, 0.0).rg);
float influenceFactor = step(discardThreshold, dot(nor2, nor));
float col = textureLod(tex, texCoord + posadd * dirInv, 0.0).r;
fragColor += col * blurWeight * influenceFactor;
float weight = blurWeight * influenceFactor;
nor2 = getNor(textureLod(gbuffer0, texCoord - pos * dirInv, 0.0).rg);
influenceFactor = step(discardThreshold, dot(nor2, nor));
col = textureLod(tex, texCoord - posadd * dirInv, 0.0).r;
fragColor += col * blurWeight * influenceFactor;
weight += blurWeight * influenceFactor;
return weight;
}
void main() { void main() {
vec3 nor = getNor(textureLod(gbuffer0, texCoord, 0.0).rg); vec3 centerNor = getNor(textureLod(gbuffer0, texCoord, 0.0).rg);
float centerDepth = textureLod(gbufferD, texCoord, 0.0).r;
fragColor = textureLod(tex, texCoord, 0.0).r * blurWeights[0]; // skip sky pixels
float weight = blurWeights[0]; if (centerDepth == 1.0) {
for (int i = 1; i < 5; i++) { fragColor = vec3(0.0);
weight += doBlur(blurWeights[i], i, nor, texCoord); return;
} }
fragColor = fragColor / weight; fragColor = textureLod(tex, texCoord, 0.0).rgb * blurWeights[0];
float totalWeight = blurWeights[0];
for (int i = 1; i < KERNEL_SIZE; i++) {
vec2 offset = float(i) * dirInv;
vec2 uvPos = texCoord + offset;
vec3 norPos = getNor(textureLod(gbuffer0, uvPos, 0.0).rg);
float depthPos = textureLod(gbufferD, uvPos, 0.0).r;
float normalWeight = max(0.0, dot(norPos, centerNor));
normalWeight = pow(normalWeight, 8.0); // Softer normal falloff for better blending
float depthWeight = 1.0 - smoothstep(0.0, 0.02, abs(depthPos - centerDepth));
float w = blurWeights[i] * normalWeight * depthWeight;
fragColor += textureLod(tex, uvPos, 0.0).rgb * w;
totalWeight += w;
vec2 uvNeg = texCoord - offset;
vec3 norNeg = getNor(textureLod(gbuffer0, uvNeg, 0.0).rg);
float depthNeg = textureLod(gbufferD, uvNeg, 0.0).r;
normalWeight = max(0.0, dot(norNeg, centerNor));
normalWeight = pow(normalWeight, 8.0);
depthWeight = 1.0 - smoothstep(0.0, 0.02, abs(depthNeg - centerDepth));
w = blurWeights[i] * normalWeight * depthWeight;
fragColor += textureLod(tex, uvNeg, 0.0).rgb * w;
totalWeight += w;
}
fragColor /= totalWeight;
} }

View File

@ -1,25 +1,8 @@
#version 450 #version 450
#include "compiled.inc" #include "compiled.inc"
#include "std/gbuffer.glsl"
#include "std/brdf.glsl"
#include "std/math.glsl" #include "std/math.glsl"
#ifdef _Clusters #include "std/gbuffer.glsl"
#include "std/clusters.glsl"
#endif
#ifdef _ShadowMap
#include "std/shadows.glsl"
#endif
#ifdef _LTC
#include "std/ltc.glsl"
#endif
#ifdef _LightIES
#include "std/ies.glsl"
#endif
#ifdef _Spot
#include "std/light_common.glsl"
#endif
#include "std/constants.glsl"
uniform sampler2D gbuffer0; uniform sampler2D gbuffer0;
uniform sampler2D gbuffer1; uniform sampler2D gbuffer1;
@ -27,480 +10,179 @@ uniform sampler2D gbufferD;
#ifdef _EmissionShaded #ifdef _EmissionShaded
uniform sampler2D gbufferEmission; uniform sampler2D gbufferEmission;
#endif #endif
uniform sampler2D sveloc;
uniform vec2 cameraProj;
uniform vec3 eye;
uniform vec3 eyeLook;
uniform vec2 screenSize;
uniform mat4 invVP;
in vec2 texCoord; uniform mat4 P;
in vec3 viewRay; uniform mat4 invP;
out vec3 fragColor; uniform mat3 V3;
float metallic;
uint matid;
#ifdef _SMSizeUniform
//!uniform vec2 smSizeUniform;
#endif
#ifdef _Clusters
uniform vec4 lightsArray[maxLights * 3];
#ifdef _Spot
uniform vec4 lightsArraySpot[maxLights * 2];
#endif
uniform sampler2D clustersData;
uniform vec2 cameraPlane;
#endif
#ifdef _SinglePoint // Fast path for single light
uniform vec3 pointPos;
uniform vec3 pointCol;
#ifdef _ShadowMap
uniform float pointBias;
#endif
#ifdef _Spot
uniform vec3 spotDir;
uniform vec3 spotRight;
uniform vec4 spotData;
#endif
#endif
#ifdef _CPostprocess
uniform vec3 PPComp12;
#endif
#ifdef _ShadowMap
#ifdef _SinglePoint
#ifdef _Spot
#ifndef _LTC
uniform sampler2DShadow shadowMapSpot[1];
uniform sampler2D shadowMapSpotTransparent[1];
uniform mat4 LWVPSpot[1];
#endif
#else
uniform samplerCubeShadow shadowMapPoint[1];
uniform samplerCube shadowMapPointTransparent[1];
uniform vec2 lightProj;
#endif
#endif
#ifdef _Clusters
#ifdef _SingleAtlas
uniform sampler2DShadow shadowMapAtlas;
uniform sampler2D shadowMapAtlasTransparent;
#endif
uniform vec2 lightProj;
#ifdef _ShadowMapAtlas
#ifndef _SingleAtlas
uniform sampler2DShadow shadowMapAtlasPoint;
uniform sampler2D shadowMapAtlasPointTransparent;
//!uniform vec4 pointLightDataArray[maxLightsCluster * 6];
#else
uniform samplerCubeShadow shadowMapPoint[4];
uniform samplerCube shadowMapPointTransparent[4];
#endif
#endif
#ifdef _Spot
#ifdef _ShadowMapAtlas
#ifndef _SingleAtlas
uniform sampler2DShadow shadowMapAtlasSpot;
uniform sampler2D shadowMapAtlasSpotTransparent;
#endif
#else
uniform sampler2DShadow shadowMapSpot[4];
uniform sampler2D shadowMapSpotTransparent[4];
#endif
uniform mat4 LWVPSpotArray[maxLightsCluster];
#endif
#endif
#endif
#ifdef _LTC
uniform vec3 lightArea0;
uniform vec3 lightArea1;
uniform vec3 lightArea2;
uniform vec3 lightArea3;
uniform sampler2D sltcMat;
uniform sampler2D sltcMag;
#ifdef _ShadowMap
#ifndef _Spot
#ifdef _SinglePoint
uniform sampler2DShadow shadowMapSpot[1];
uniform sampler2D shadowMapSpotTransparent[1];
uniform mat4 LWVPSpot[1];
#endif
#ifdef _Clusters
uniform sampler2DShadow shadowMapSpot[maxLightsCluster];
uniform mat4 LWVPSpotArray[maxLightsCluster];
#endif
#endif
#endif
#endif
#ifdef _Sun #ifdef _Sun
uniform vec3 sunDir; uniform vec3 sunDir;
uniform vec3 sunCol; uniform vec3 sunCol;
#ifdef _ShadowMap
#ifdef _ShadowMapAtlas
#ifndef _SingleAtlas
uniform sampler2DShadow shadowMapAtlasSun;
uniform sampler2D shadowMapAtlasSunTransparent;
#endif
#else
uniform sampler2DShadow shadowMap;
uniform sampler2D shadowMapTransparent;
#endif
uniform float shadowsBias;
#ifdef _CSM
//!uniform vec4 casData[shadowmapCascades * 4 + 4];
#else
uniform mat4 LWVP;
#endif
#endif // _ShadowMap
#endif #endif
vec3 sampleLight(const vec3 p, const vec3 n, const vec3 lp, const vec3 lightCol #ifdef _CPostprocess
#ifdef _ShadowMap uniform vec3 PPComp12;
, int index, float bias, bool receiveShadow, bool transparent
#endif
#ifdef _Spot
, const bool isSpot, const float spotSize, float spotBlend, vec3 spotDir, vec2 scale, vec3 right
#endif
) {
vec3 ld = lp - p;
vec3 l = normalize(ld);
vec3 visibility = lightCol;
visibility *= attenuate(distance(p, lp));
#ifdef _LTC
#ifdef _ShadowMap
if (receiveShadow) {
#ifdef _SinglePoint
vec4 lPos = LWVPSpotArray[0] * vec4(p + n * bias * 10, 1.0);
visibility *= shadowTest(shadowMapSpot[0],
shadowMapSpotTransparent[0],
lPos.xyz / lPos.w, bias, transparent);
#endif
#ifdef _Clusters
vec4 lPos = LWVPSpotArray[index] * vec4(p + n * bias * 10, 1.0);
if (index == 0) visibility *= shadowTest(shadowMapSpot[0],
shadowMapSpotTransparent[0],
lPos.xyz / lPos.w, bias, transparent);
else if (index == 1) visibility *= shadowTest(shadowMapSpot[1],
shadowMapSpotTransparent[1],
, lPos.xyz / lPos.w, bias, transparent);
else if (index == 2) visibility *= shadowTest(shadowMapSpot[2],
shadowMapSpotTransparent[2],
lPos.xyz / lPos.w, bias, transparent);
else if (index == 3) visibility *= shadowTest(shadowMapSpot[3],
shadowMapSpotTransparent[3],
lPos.xyz / lPos.w, bias, transparent);
#endif
}
#endif
return visibility;
#endif
#ifdef _Spot
if (isSpot) {
visibility *= spotlightMask(l, spotDir, right, scale, spotSize, spotBlend);
#ifdef _ShadowMap
if (receiveShadow) {
#ifdef _SinglePoint
vec4 lPos = LWVPSpot[0] * vec4(p + n * bias * 10, 1.0);
visibility *= shadowTest(shadowMapSpot[0],
shadowMapSpotTransparent[0],
lPos.xyz / lPos.w, bias, transparent);
#endif
#ifdef _Clusters
vec4 lPos = LWVPSpotArray[index] * vec4(p + n * bias * 10, 1.0);
#ifdef _ShadowMapAtlas
visibility *= shadowTest(
#ifndef _SingleAtlas
shadowMapAtlasSpot, shadowMapAtlasSpotTransparent
#else
shadowMapAtlas, shadowMapAtlasTransparent
#endif
, lPos.xyz / lPos.w, bias, transparent
);
#else
if (index == 0) visibility *= shadowTest(shadowMapSpot[0],
shadowMapSpotTransparent[0],
lPos.xyz / lPos.w, bias, transparent);
else if (index == 1) visibility *= shadowTest(shadowMapSpot[1],
shadowMapSpotTransparent[1],
lPos.xyz / lPos.w, bias, transparent);
else if (index == 2) visibility *= shadowTest(shadowMapSpot[2],
shadowMapSpotTransparent[2],
lPos.xyz / lPos.w, bias, transparent);
else if (index == 3) visibility *= shadowTest(shadowMapSpot[3],
shadowMapSpotTransparent[3],
lPos.xyz / lPos.w, bias, transparent);
#endif
#endif
}
#endif
return visibility;
}
#endif
#ifdef _LightIES
visibility *= iesAttenuation(-l);
#endif
#ifdef _ShadowMap
if (receiveShadow) {
#ifdef _SinglePoint
#ifndef _Spot
visibility *= PCFCube(shadowMapPoint[0],
shadowMapPointTransparent[0],
ld, -l, bias, lightProj, n, transparent);
#endif
#endif
#ifdef _Clusters
#ifdef _ShadowMapAtlas
visibility *= PCFFakeCube(
#ifndef _SingleAtlas
shadowMapAtlasPoint, shadowMapAtlasPointTransparent
#else
shadowMapAtlas, shadowMapAtlasTransparent
#endif
, ld, -l, bias, lightProj, n, index, transparent
);
#else
if (index == 0) visibility *= PCFCube(shadowMapPoint[0],
shadowMapPointTransparent[0],
ld, -l, bias, lightProj, n, transparent);
else if (index == 1) visibility *= PCFCube(shadowMapPoint[1],
shadowMapPointTransparent[1],
ld, -l, bias, lightProj, n, transparent);
else if (index == 2) visibility *= PCFCube(shadowMapPoint[2],
shadowMapPointTransparent[2],
ld, -l, bias, lightProj, n, transparent);
else if (index == 3) visibility *= PCFCube(shadowMapPoint[3],
shadowMapPointTransparent[3],
ld, -l, bias, lightProj, n, transparent);
#endif
#endif
}
#endif
return visibility;
}
vec3 getVisibility(vec3 p, vec3 n, float depth, vec2 uv) {
vec3 visibility = vec3(0.0);
#ifdef _Sun
#ifdef _ShadowMap
#ifdef _CSM
visibility = shadowTestCascade(
#ifdef _ShadowMapAtlas
#ifndef _SingleAtlas
shadowMapAtlasSun, shadowMapAtlasSunTransparent
#else
shadowMapAtlas, shadowMapAtlasTransparent
#endif
#else
shadowMap, shadowMapTransparent
#endif
, eye, p + n * shadowsBias * 10, shadowsBias, false
);
#else
vec4 lPos = LWVP * vec4(p + n * shadowsBias * 100, 1.0);
if (lPos.w > 0.0) {
visibility = shadowTest(
#ifdef _ShadowMapAtlas
#ifndef _SingleAtlas
shadowMapAtlasSun, shadowMapAtlasSunTransparent
#else
shadowMapAtlas, shadowMapAtlasTransparent
#endif
#else
shadowMap, shadowMapTransparent
#endif
, lPos.xyz / lPos.w, shadowsBias, false
);
}
#endif
#endif
#endif #endif
#ifdef _SinglePoint in vec2 texCoord;
visibility += sampleLight( out vec4 fragColor;
p, n, pointPos, pointCol
#ifdef _ShadowMap
, 0, pointBias, true, false
#endif
#ifdef _Spot
, true, spotData.x, spotData.y, spotDir, spotData.zw, spotRight
#endif
);
#endif
#ifdef _Clusters
float viewz = linearize(depth, cameraProj);
int clusterI = getClusterI(uv, viewz, cameraPlane);
int numLights = int(texelFetch(clustersData, ivec2(clusterI, 0), 0).r * 255);
#ifdef HLSL
viewz += textureLod(clustersData, vec2(0.0), 0.0).r * 1e-9; // TODO: krafix bug, needs to generate sampler
#endif
#ifdef _Spot
int numSpots = int(texelFetch(clustersData, ivec2(clusterI, 1 + maxLightsCluster), 0).r * 255);
int numPoints = numLights - numSpots;
#endif
for (int i = 0; i < min(numLights, maxLightsCluster); i++) {
int li = int(texelFetch(clustersData, ivec2(clusterI, i + 1), 0).r * 255);
visibility += sampleLight(
p,
n,
lightsArray[li * 3].xyz, // lp
lightsArray[li * 3 + 1].xyz // lightCol
#ifdef _ShadowMap
// light index, shadow bias, cast_shadows
, li, lightsArray[li * 3 + 2].x, lightsArray[li * 3 + 2].z != 0.0, false
#endif
#ifdef _Spot
, lightsArray[li * 3 + 2].y != 0.0
, lightsArray[li * 3 + 2].y // spot size (cutoff)
, lightsArraySpot[li * 2].w // spot blend (exponent)
, lightsArraySpot[li * 2].xyz // spotDir
, vec2(lightsArray[li * 3].w, lightsArray[li * 3 + 1].w) // scale
, lightsArraySpot[li * 2 + 1].xyz // right
#endif
);
}
#endif // _Clusters
return visibility;
}
vec3 getWorldPos(vec2 uv, float depth) {
vec4 pos = invVP * vec4(uv * 2.0 - 1.0, depth * 2.0 - 1.0, 1.0);
return pos.xyz / pos.w;
}
vec3 getNormal(vec2 uv) {
vec4 g0 = textureLod(gbuffer0, uv, 0.0);
vec2 enc = g0.rg;
vec3 n;
n.z = 1.0 - abs(enc.x) - abs(enc.y);
n.xy = n.z >= 0.0 ? enc.xy : octahedronWrap(enc.xy);
return normalize(n);
}
vec3 calculateIndirectLight(vec2 uv, vec3 pos, vec3 normal, float depth) {
// Simplified visibility - replace with your full visibility function if needed
vec3 sampleColor = textureLod(gbuffer1, uv, 0.0).rgb * getVisibility(pos, normal, depth, uv);
#ifdef _EmissionShadeless
if (matid == 1) { // pure emissive material, color stored in basecol
sampleColor += textureLod(gbuffer1, uv, 0.0).rgb;
}
#endif
#ifdef _EmissionShaded
#ifdef _EmissionShadeless
else {
#endif
vec3 sampleEmission = textureLod(gbufferEmission, uv, 0.0).rgb;
sampleColor += sampleEmission; // Emission should be added directly
#ifdef _EmissionShadeless
}
#endif
#endif
return sampleColor;
}
// Improved sampling parameters
const float GOLDEN_ANGLE = 2.39996323; const float GOLDEN_ANGLE = 2.39996323;
const float MAX_DEPTH_DIFFERENCE = 0.9; // More conservative depth threshold const int RAY_STEPS = 12;
const float SAMPLE_BIAS = 0.01; // Small offset to avoid self-occlusion
vec2 getProjectedCoord(const vec3 viewPos) {
vec4 projectedCoord = P * vec4(viewPos, 1.0);
projectedCoord.xy /= projectedCoord.w;
projectedCoord.xy = projectedCoord.xy * 0.5 + 0.5;
#ifdef _InvY
projectedCoord.y = 1.0 - projectedCoord.y;
#endif
return projectedCoord.xy;
}
vec3 cosineSampleHemisphere(vec3 n, vec2 rand) {
float phi = PI * 2.0 * rand.x;
float cosTheta = sqrt(1.0 - rand.y);
float sinTheta = sqrt(rand.y);
vec3 h = vec3(cos(phi) * sinTheta, sin(phi) * sinTheta, cosTheta);
vec3 tangent, bitangent;
vec3 absN = abs(n);
if (absN.x <= absN.y && absN.x <= absN.z) {
tangent = normalize(cross(n, vec3(1.0, 0.0, 0.0)));
} else if (absN.y <= absN.z) {
tangent = normalize(cross(n, vec3(0.0, 1.0, 0.0)));
} else {
tangent = normalize(cross(n, vec3(0.0, 0.0, 1.0)));
}
bitangent = cross(n, tangent);
return normalize(tangent * h.x + bitangent * h.y + n * h.z);
}
vec3 traceRay(vec3 origin, vec3 dir, float maxDist, float minDist) {
float stepSize = maxDist / float(RAY_STEPS);
vec3 pos = origin + dir * minDist;
float prevDepthDiff = 0.0;
float hadValidPrev = 0.0;
for (int i = 1; i <= RAY_STEPS; i++) {
pos += dir * stepSize;
vec2 uv = getProjectedCoord(pos);
vec2 sampleUV = clamp(uv, vec2(0.001), vec2(0.999));
float sampleDepth = textureLod(gbufferD, sampleUV, 0.0).r * 2.0 - 1.0;
if (sampleDepth == 1.0) {
hadValidPrev = 0.0;
continue;
}
vec3 sampleViewPos = getPosView2(invP, sampleDepth, sampleUV);
float depthDiff = pos.z - sampleViewPos.z;
float rayDist = length(pos - origin);
float thickness = 0.15 + rayDist * 0.25;
float crossed = hadValidPrev * step(0.0, prevDepthDiff) * step(depthDiff, 0.0);
float withinThickness = step(abs(depthDiff), thickness);
if (crossed > 0.5 || withinThickness > 0.5) {
float distWeight = 1.0 - (rayDist / maxDist);
distWeight = max(0.0, distWeight * distWeight);
return vec3(sampleUV, distWeight);
}
prevDepthDiff = depthDiff;
hadValidPrev = 1.0;
}
return vec3(-1.0);
}
void main() { void main() {
float depth = textureLod(gbufferD, texCoord, 0.0).r; float depth = textureLod(gbufferD, texCoord, 0.0).r * 2.0 - 1.0;
if (depth >= 1.0) { if (depth == 1.0) {
fragColor = vec3(0.0); fragColor = vec4(0.0, 0.0, 0.0, 1.0);
return; return;
}
vec4 g0 = textureLod(gbuffer0, texCoord, 0.0); // Normal.xy, roughness, metallic/matid
unpackFloatInt16(g0.a, metallic, matid);
vec2 velocity = -textureLod(sveloc, texCoord, 0.0).rg;
vec3 n;
n.z = 1.0 - abs(g0.x) - abs(g0.y);
n.xy = n.z >= 0.0 ? g0.xy : octahedronWrap(g0.xy);
n = normalize(n);
vec3 pos = getWorldPos(texCoord, depth);
vec3 normal = getNormal(texCoord);
vec3 centerColor = textureLod(gbuffer1, texCoord, 0.0).rgb;
float radius = ssaoRadius;
vec3 gi = vec3(0.0);
float totalWeight = 0.0;
float angle = fract(sin(dot(texCoord, vec2(12.9898, 78.233))) * 100.0);
for (int i = 0; i < ssgiSamples; i++) {
// Use quasi-random sequence for better coverage
float r = sqrt((float(i) + 0.5) / float(ssgiSamples)) * radius;
float a = (float(i) * GOLDEN_ANGLE) + angle;
vec2 offset = vec2(cos(a), sin(a)) * r * radius;
vec2 sampleUV = clamp(texCoord + offset * (BayerMatrix8[int(gl_FragCoord.x + velocity.x) % 8][int(gl_FragCoord.y + velocity.y) % 8] - 0.5) / screenSize, vec2(0.001), vec2(0.999));
float sampleDepth = textureLod(gbufferD, sampleUV, 0.0).r;
if (sampleDepth >= 1.0) continue;
vec3 samplePos = getWorldPos(sampleUV, sampleDepth);
vec3 sampleNormal = getNormal(sampleUV);
// Apply small bias to sample position to avoid self-occlusion
samplePos += sampleNormal * SAMPLE_BIAS;
vec3 dir = pos - samplePos;
float dist = length(dir);
if (abs(pos.z - samplePos.z) > MAX_DEPTH_DIFFERENCE) continue;;
vec3 sampleColor = calculateIndirectLight(sampleUV, samplePos, sampleNormal, sampleDepth);
float weight = 1.0 / (1.0 + dist * dist * 2.0) * max(dot(sampleNormal, n), 0.0);
gi += sampleColor * weight;
totalWeight += weight;
} }
// Normalize and apply intensity vec4 g0 = textureLod(gbuffer0, texCoord, 0.0);
if (totalWeight > 0.0) { vec2 enc = g0.rg;
gi /= totalWeight; vec3 n;
#ifdef _CPostprocess n.z = 1.0 - abs(enc.x) - abs(enc.y);
gi *= PPComp12.x; n.xy = n.z >= 0.0 ? enc.xy : octahedronWrap(enc.xy);
#else n = normalize(n);
gi *= ssaoStrength;
#endif
}
#ifdef _EmissionShadeless vec3 viewNormal = V3 * n;
if (matid == 1) { // pure emissive material, color stored in basecol vec3 viewPos = getPosView2(invP, depth, texCoord);
gi += textureLod(gbuffer1, texCoord, 0.0).rgb;
} #ifdef _CPostprocess
float radius = PPComp12.y;
float strength = PPComp12.x;
#else
float radius = ssgiRadius;
float strength = ssgiStrength;
#endif #endif
float noise = fract(52.9829189 * fract(0.06711056 * texCoord.x * 1000.0 + 0.00583715 * texCoord.y * 1000.0));
vec3 gi = vec3(0.0);
int validSamples = 0;
// min distance to avoid self shadowing artiffacts
float minDist = radius * 0.05;
for (int i = 0; i < ssgiSamples; i++) {
float fi = float(i) + noise;
vec2 rand = vec2(
fract(fi * 0.7548776662 + noise),
fract(fi * 0.5698402909 + noise * 1.5)
);
vec3 rayDir = cosineSampleHemisphere(viewNormal, rand);
vec3 hitResult = traceRay(viewPos, rayDir, radius, minDist);
if (hitResult.x < 0.0) continue;
vec2 hitUV = hitResult.xy;
float distWeight = hitResult.z;
vec3 hitAlbedo = textureLod(gbuffer1, hitUV, 1.0).rgb;
#ifdef _Sun
vec4 hitG0 = textureLod(gbuffer0, hitUV, 0.0);
vec2 hitEnc = hitG0.rg;
vec3 hitN;
hitN.z = 1.0 - abs(hitEnc.x) - abs(hitEnc.y);
hitN.xy = hitN.z >= 0.0 ? hitEnc.xy : octahedronWrap(hitEnc.xy);
hitN = normalize(hitN);
float hitNdotL = max(0.0, dot(hitN, sunDir));
vec3 hitRadiance = hitAlbedo * sunCol * hitNdotL;
#else
vec3 hitRadiance = hitAlbedo * 0.5;
#endif
#ifdef _EmissionShaded
hitRadiance += textureLod(gbufferEmission, hitUV, 0.0).rgb;
#endif
gi += hitRadiance * distWeight;
validSamples++;
}
if (validSamples > 0) {
gi /= float(validSamples);
}
gi *= strength;
#ifdef _EmissionShaded #ifdef _EmissionShaded
#ifdef _EmissionShadeless gi += textureLod(gbufferEmission, texCoord, 0.0).rgb * 0.3;
else {
#endif
gi += textureLod(gbufferEmission, texCoord, 0.0).rgb;
#ifdef _EmissionShadeless
}
#endif
#endif #endif
fragColor = gi / (gi + vec3(1.0)); // Reinhard tone mapping
fragColor = vec4(min(gi, vec3(2.0)), 1.0);
} }

View File

@ -6,60 +6,18 @@
"compare_mode": "always", "compare_mode": "always",
"cull_mode": "none", "cull_mode": "none",
"links": [ "links": [
{
"name": "invVP",
"link": "_inverseViewProjectionMatrix"
},
{ {
"name": "P", "name": "P",
"link": "_projectionMatrix" "link": "_projectionMatrix"
}, },
{
"name": "invP",
"link": "_inverseProjectionMatrix"
},
{ {
"name": "V3", "name": "V3",
"link": "_viewMatrix3" "link": "_viewMatrix3"
}, },
{
"name": "eye",
"link": "_cameraPosition"
},
{
"name": "eyeLook",
"link": "_cameraLook"
},
{
"name": "cameraProj",
"link": "_cameraPlaneProj"
},
{
"name": "screenSize",
"link": "_screenSize"
},
{
"name": "PPComp12",
"link": "_PPComp12",
"ifdef": ["_CPostprocess"]
},
{
"name": "lightsArraySpot",
"link": "_lightsArraySpot",
"ifdef": ["_Clusters", "_Spot"]
},
{
"name": "lightsArray",
"link": "_lightsArray",
"ifdef": ["_Clusters"]
},
{
"name": "clustersData",
"link": "_clustersData",
"ifdef": ["_Clusters"]
},
{
"name": "cameraPlane",
"link": "_cameraPlane",
"ifdef": ["_Clusters"]
},
{ {
"name": "sunDir", "name": "sunDir",
"link": "_sunDirection", "link": "_sunDirection",
@ -71,128 +29,13 @@
"ifdef": ["_Sun"] "ifdef": ["_Sun"]
}, },
{ {
"name": "shadowsBias", "name": "PPComp12",
"link": "_sunShadowsBias", "link": "_PPComp12",
"ifdef": ["_Sun", "_ShadowMap"] "ifdef": ["_CPostprocess"]
},
{
"name": "LWVP",
"link": "_biasLightWorldViewProjectionMatrixSun",
"ifndef": ["_CSM"],
"ifdef": ["_Sun", "_ShadowMap"]
},
{
"name": "casData",
"link": "_cascadeData",
"ifdef": ["_Sun", "_ShadowMap", "_CSM"]
},
{
"name": "lightArea0",
"link": "_lightArea0",
"ifdef": ["_LTC"]
},
{
"name": "lightArea1",
"link": "_lightArea1",
"ifdef": ["_LTC"]
},
{
"name": "lightArea2",
"link": "_lightArea2",
"ifdef": ["_LTC"]
},
{
"name": "lightArea3",
"link": "_lightArea3",
"ifdef": ["_LTC"]
},
{
"name": "sltcMat",
"link": "_ltcMat",
"ifdef": ["_LTC"]
},
{
"name": "sltcMag",
"link": "_ltcMag",
"ifdef": ["_LTC"]
},
{
"name": "smSizeUniform",
"link": "_shadowMapSize",
"ifdef": ["_SMSizeUniform"]
},
{
"name": "lightProj",
"link": "_lightPlaneProj",
"ifdef": ["_ShadowMap"]
},
{
"name": "pointPos",
"link": "_pointPosition",
"ifdef": ["_SinglePoint"]
},
{
"name": "pointCol",
"link": "_pointColor",
"ifdef": ["_SinglePoint"]
},
{
"name": "pointBias",
"link": "_pointShadowsBias",
"ifdef": ["_SinglePoint", "_ShadowMap"]
},
{
"name": "spotDir",
"link": "_spotDirection",
"ifdef": ["_SinglePoint", "_Spot"]
},
{
"name": "spotData",
"link": "_spotData",
"ifdef": ["_SinglePoint", "_Spot"]
},
{
"name": "spotRight",
"link": "_spotRight",
"ifdef": ["_SinglePoint", "_Spot"]
},
{
"name": "LWVPSpotArray",
"link": "_biasLightWorldViewProjectionMatrixSpotArray",
"ifdef": ["_Clusters", "_ShadowMap", "_Spot"]
},
{
"name": "pointLightDataArray",
"link": "_pointLightsAtlasArray",
"ifdef": ["_Clusters", "_ShadowMap", "_ShadowMapAtlas"]
},
{
"name": "LWVPSpot[0]",
"link": "_biasLightWorldViewProjectionMatrixSpot0",
"ifndef": ["_ShadowMapAtlas"],
"ifdef": ["_LTC", "_ShadowMap"]
},
{
"name": "LWVPSpot[1]",
"link": "_biasLightWorldViewProjectionMatrixSpot1",
"ifndef": ["_ShadowMapAtlas"],
"ifdef": ["_LTC", "_ShadowMap"]
},
{
"name": "LWVPSpot[2]",
"link": "_biasLightWorldViewProjectionMatrixSpot2",
"ifndef": ["_ShadowMapAtlas"],
"ifdef": ["_LTC", "_ShadowMap"]
},
{
"name": "LWVPSpot[3]",
"link": "_biasLightWorldViewProjectionMatrixSpot3",
"ifndef": ["_ShadowMapAtlas"],
"ifdef": ["_LTC", "_ShadowMap"]
} }
], ],
"texture_params": [], "texture_params": [],
"vertex_shader": "../include/pass_viewray.vert.glsl", "vertex_shader": "../include/pass.vert.glsl",
"fragment_shader": "ssgi_pass.frag.glsl" "fragment_shader": "ssgi_pass.frag.glsl"
} }
] ]

View File

@ -64,21 +64,26 @@ vec4 rayCast(vec3 dir) {
ddepth = getDeltaDepth(hitCoord); ddepth = getDeltaDepth(hitCoord);
if (ddepth > 0.0) return binarySearch(dir); if (ddepth > 0.0) return binarySearch(dir);
} }
return vec4(texCoord, 0.0, 1.0); return vec4(texCoord, 0.0, 0.0);
} }
void main() { void main() {
vec4 g0 = textureLod(gbuffer0, texCoord, 0.0);
float roughness = g0.z;
vec4 gr = textureLod(gbuffer_refraction, texCoord, 0.0); vec4 gr = textureLod(gbuffer_refraction, texCoord, 0.0);
float ior = gr.x; float ior = gr.x;
float opac = 1.0 - gr.y; float transmittance = gr.y;
float d = textureLod(gbufferD, texCoord, 0.0).r * 2.0 - 1.0; float surfaceDepth = gr.z;
if (d == 0.0 || d == 1.0 || opac == 1.0 || ior == 1.0) { float d = surfaceDepth * 2.0 - 1.0;
fragColor.rgb = textureLod(tex1, texCoord, 0.0).rgb;
fragColor.a = opac; vec4 sceneSample = textureLod(tex, texCoord, 0.0);
if (surfaceDepth == 0.0 || transmittance == 0.0 || ior == 1.0) {
vec3 background = textureLod(tex1, texCoord, 0.0).rgb;
fragColor.rgb = sceneSample.rgb + background * (1.0 - sceneSample.a);
fragColor.a = 1.0;
return; return;
} }
vec4 g0 = textureLod(gbuffer0, texCoord, 0.0);
float roughness = g0.z;
vec2 enc = g0.rg; vec2 enc = g0.rg;
vec3 n; vec3 n;
n.z = 1.0 - abs(enc.x) - abs(enc.y); n.z = 1.0 - abs(enc.x) - abs(enc.y);
@ -87,24 +92,32 @@ void main() {
vec3 viewNormal = V3 * n; vec3 viewNormal = V3 * n;
vec3 viewPos = getPosView(viewRay, d, cameraProj); vec3 viewPos = getPosView(viewRay, d, cameraProj);
vec3 refracted = refract(normalize(viewPos), viewNormal, 1.0 / ior); vec3 incident = normalize(viewPos);
vec3 refracted = refract(incident, viewNormal, 1.0 / ior);
if (length(refracted) < 0.001) {
vec3 background = textureLod(tex1, texCoord, 0.0).rgb;
fragColor.rgb = sceneSample.rgb + background * (1.0 - sceneSample.a);
fragColor.a = 1.0;
return;
}
hitCoord = viewPos; hitCoord = viewPos;
vec3 dir = refracted * (1.0 - rand(texCoord) * ss_refractionJitter * roughness) * 2.0; vec3 dir = refracted * (1.0 - rand(texCoord) * ss_refractionJitter * roughness) * 2.0;
vec4 coords = rayCast(dir); vec4 coords = rayCast(dir);
vec2 deltaCoords = abs(vec2(0.5, 0.5) - coords.xy);
float screenEdgeFactor = clamp(1.0 - (deltaCoords.x + deltaCoords.y), 0.0, 1.0); vec2 screenEdge = smoothstep(0.0, 0.1, coords.xy) * smoothstep(0.0, 0.1, 1.0 - coords.xy);
float screenEdgeFactor = screenEdge.x * screenEdge.y;
float refractivity = 1.0 - roughness; float refractivity = 1.0 - roughness;
float intensity = pow(refractivity, ss_refractionFalloffExp) * screenEdgeFactor * \
clamp(-refracted.z, 0.0, 1.0) * clamp((length(viewPos - hitCoord)), 0.0, 1.0) * coords.w; float intensity = pow(refractivity, ss_refractionFalloffExp) * screenEdgeFactor * coords.w;
intensity = clamp(intensity, 0.0, 1.0); intensity = clamp(intensity, 0.0, 1.0);
vec4 refractionCol = textureLod(tex1, coords.xy, 0.0).rgba; vec3 refractedBackground = textureLod(tex1, coords.xy, 0.0).rgb;
refractionCol.a = opac; vec3 straightBackground = textureLod(tex1, texCoord, 0.0).rgb;
//refractionCol *= intensity;
vec4 color = textureLod(tex, texCoord.xy, 0.0).rgba;
color.a = opac;
fragColor.rgba = mix(refractionCol, color, opac); vec3 behindColor = mix(straightBackground, refractedBackground, intensity);
fragColor.a = opac;
fragColor.rgb = sceneSample.rgb + behindColor * (1.0 - sceneSample.a);
fragColor.a = 1.0;
} }

View File

@ -5,12 +5,9 @@
"depth_write": false, "depth_write": false,
"compare_mode": "always", "compare_mode": "always",
"cull_mode": "none", "cull_mode": "none",
"blend_source": "source_alpha", "blend_source": "blend_one",
"blend_destination": "inverse_source_alpha", "blend_destination": "blend_zero",
"blend_operation": "add", "blend_operation": "add",
"alpha_blend_source": "blend_one",
"alpha_blend_destination": "blend_one",
"alpha_blend_operation": "add",
"links": [ "links": [
{ {
"name": "P", "name": "P",

View File

@ -36,6 +36,7 @@
#version 450 #version 450
#include "compiled.inc" #include "compiled.inc"
#include "std/gbuffer.glsl"
uniform sampler2D gbufferD; uniform sampler2D gbufferD;
uniform sampler2D gbuffer0; uniform sampler2D gbuffer0;
@ -47,69 +48,92 @@ uniform vec2 cameraProj;
in vec2 texCoord; in vec2 texCoord;
out vec4 fragColor; out vec4 fragColor;
const float SSSS_FOVY = 108.0; const vec3 SKIN_SSS_RADIUS = vec3(4.8, 2.4, 1.5);
const float SSS_DISTANCE_SCALE = 0.001;
// Temp hash func -
float hash13(vec3 p3) {
p3 = fract(p3 * vec3(0.1031, 0.1030, 0.0973));
p3 += dot(p3, p3.yzx + 33.33);
return fract((p3.x + p3.y) * p3.z);
}
// Separable SSS Reflectance
// const float sssWidth = 0.005;
vec4 SSSSBlur() { vec4 SSSSBlur() {
// Quality = 0 const int SSSS_N_SAMPLES = 15;
const int SSSS_N_SAMPLES = 11;
vec4 kernel[SSSS_N_SAMPLES]; vec4 kernel[SSSS_N_SAMPLES];
kernel[0] = vec4(0.560479, 0.669086, 0.784728, 0);
kernel[1] = vec4(0.00471691, 0.000184771, 5.07566e-005, -2); kernel[0] = vec4(0.233, 0.455, 0.649, 0.0); // Center sample
kernel[2] = vec4(0.0192831, 0.00282018, 0.00084214, -1.28); kernel[1] = vec4(0.100, 0.336, 0.344, 0.37); // +0.37mm
kernel[3] = vec4(0.03639, 0.0130999, 0.00643685, -0.72); kernel[2] = vec4(0.118, 0.198, 0.0, 0.97); // +0.97mm
kernel[4] = vec4(0.0821904, 0.0358608, 0.0209261, -0.32); kernel[3] = vec4(0.113, 0.007, 0.007, 1.93); // +1.93mm
kernel[5] = vec4(0.0771802, 0.113491, 0.0793803, -0.08); kernel[4] = vec4(0.358, 0.004, 0.0, 3.87); // +3.87mm
kernel[6] = vec4(0.0771802, 0.113491, 0.0793803, 0.08); kernel[5] = vec4(0.078, 0.0, 0.0, 6.53); // +6.53mm (red only)
kernel[7] = vec4(0.0821904, 0.0358608, 0.0209261, 0.32); kernel[6] = vec4(0.0, 0.0, 0.0, 0.0); // Unused
kernel[8] = vec4(0.03639, 0.0130999, 0.00643685, 0.72); kernel[7] = vec4(0.0, 0.0, 0.0, 0.0); // Unused
kernel[9] = vec4(0.0192831, 0.00282018, 0.00084214, 1.28); kernel[8] = vec4(0.100, 0.336, 0.344, -0.37); // -0.37mm
kernel[10] = vec4(0.00471691, 0.000184771, 5.07565e-005, 2); kernel[9] = vec4(0.118, 0.198, 0.0, -0.97); // -0.97mm
kernel[10] = vec4(0.113, 0.007, 0.007, -1.93); // -1.93mm
kernel[11] = vec4(0.358, 0.004, 0.0, -3.87); // -3.87mm
kernel[12] = vec4(0.078, 0.0, 0.0, -6.53); // -6.53mm (red only)
kernel[13] = vec4(0.0, 0.0, 0.0, 0.0); // Unused
kernel[14] = vec4(0.0, 0.0, 0.0, 0.0); // Unused
vec4 colorM = textureLod(tex, texCoord, 0.0); vec4 colorM = textureLod(tex, texCoord, 0.0);
// Fetch linear depth of current pixel
float depth = textureLod(gbufferD, texCoord, 0.0).r; float depth = textureLod(gbufferD, texCoord, 0.0).r;
float depthM = cameraProj.y / (depth - cameraProj.x); float depthM = cameraProj.y / (depth - cameraProj.x);
// Calculate the sssWidth scale (1.0 for a unit plane sitting on the projection window) float distanceScale = 1.0 / max(depthM, 0.1);
float distanceToProjectionWindow = 1.0 / tan(0.5 * radians(SSSS_FOVY));
float scale = distanceToProjectionWindow / depthM;
// Calculate the final step to fetch the surrounding pixels vec2 finalStep = sssWidth * distanceScale * dir * SSS_DISTANCE_SCALE;
vec2 finalStep = sssWidth * scale * dir;
finalStep *= 1.0;//SSSS_STREGTH_SOURCE; // Modulate it using the alpha channel.
finalStep *= 1.0 / 3.0; // Divide by 3 as the kernels range from -3 to 3.
finalStep *= 0.05; //
// Accumulate the center sample:
vec4 colorBlurred = colorM;
colorBlurred.rgb *= kernel[0].rgb;
// Accumulate the other samples vec3 jitterSeed = vec3(texCoord.xy * 1000.0, fract(cameraProj.x * 0.0001));
float jitterOffset = (hash13(jitterSeed) * 2.0 - 1.0) * 0.15;
finalStep *= (1.0 + jitterOffset);
vec3 colorBlurred = vec3(0.0);
vec3 weightSum = vec3(0.0);
colorBlurred += colorM.rgb * kernel[0].rgb;
weightSum += kernel[0].rgb;
for (int i = 1; i < SSSS_N_SAMPLES; i++) { for (int i = 1; i < SSSS_N_SAMPLES; i++) {
// Fetch color and depth for current sample float sampleJitter = hash13(vec3(texCoord.xy * 720.0, float(i) * 37.45)) * 0.1 - 0.05;
vec2 offset = texCoord + kernel[i].a * finalStep; vec2 offset = texCoord + (kernel[i].a + sampleJitter) * finalStep;
vec4 color = textureLod(tex, offset, 0.0); vec4 color = textureLod(tex, offset, 0.0);
//#if SSSS_FOLLOW_SURFACE == 1 const float DEPTH_THRESHOLD = 0.05;
// If the difference in depth is huge, we lerp color back to "colorM": float sampleDepth = textureLod(gbufferD, offset, 0.0).r;
//float depth = textureLod(tex, offset, 0.0).r; float sampleDepthM = cameraProj.y / (sampleDepth - cameraProj.x);
//float s = clamp(300.0f * distanceToProjectionWindow * sssWidth * abs(depthM - depth),0.0,1.0);
//color.rgb = mix(color.rgb, colorM.rgb, s);
//#endif
// Accumulate
colorBlurred.rgb += kernel[i].rgb * color.rgb;
}
return colorBlurred; float depthDiff = abs(depthM - sampleDepthM);
float depthWeight = exp(-depthDiff * 10.0);
if (depthDiff > DEPTH_THRESHOLD) {
color.rgb = mix(colorM.rgb, color.rgb, depthWeight);
}
colorBlurred += color.rgb * kernel[i].rgb;
weightSum += kernel[i].rgb;
}
vec3 normalizedColor = colorBlurred / max(weightSum, vec3(0.00001));
float dither = hash13(vec3(texCoord * 1333.0, 0.0)) * 0.003 - 0.0015;
normalizedColor = max(normalizedColor + vec3(dither), vec3(0.0));
return vec4(normalizedColor, colorM.a);
} }
void main() { void main() {
if (textureLod(gbuffer0, texCoord, 0.0).a == 8192.0) { vec4 g0 = textureLod(gbuffer0, texCoord, 0.0);
fragColor = clamp(SSSSBlur(), 0.0, 1.0); float metallic;
} uint matid;
else { unpackFloatInt16(g0.a, metallic, matid);
if (matid == 2u) {
vec4 originalColor = textureLod(tex, texCoord, 0.0);
vec4 blurredColor = SSSSBlur();
vec4 sssContribution = blurredColor - originalColor;
vec4 combined = originalColor + max(vec4(0.0), sssContribution) * 0.8;
fragColor = max(vec4(0.0), min(combined, vec4(10.0)));
} else {
fragColor = textureLod(tex, texCoord, 0.0); fragColor = textureLod(tex, texCoord, 0.0);
} }
} }

View File

@ -36,7 +36,8 @@ float d_ggx(const float nh, const float a) {
vec3 specularBRDF(const vec3 f0, const float roughness, const float nl, const float nh, const float nv, const float vh) { vec3 specularBRDF(const vec3 f0, const float roughness, const float nl, const float nh, const float nv, const float vh) {
float a = roughness * roughness; float a = roughness * roughness;
return d_ggx(nh, a) * g2_approx(nl, nv, a) * f_schlick(f0, vh) / max(4.0 * nv, 1e-5); //NdotL cancels out later vec3 result = d_ggx(nh, a) * g2_approx(nl, nv, a) * f_schlick(f0, vh) / max(4.0 * nv, 1e-5); //NdotL cancels out later
return result;
} }
// John Hable - Optimizing GGX Shaders // John Hable - Optimizing GGX Shaders
@ -78,7 +79,7 @@ vec3 orenNayarDiffuseBRDF(const vec3 albedo, const float roughness, const float
} }
vec3 lambertDiffuseBRDF(const vec3 albedo, const float nl) { vec3 lambertDiffuseBRDF(const vec3 albedo, const float nl) {
return albedo * nl; return albedo * (1.0 / 3.1415926535) * nl;
} }
vec3 surfaceAlbedo(const vec3 baseColor, const float metalness) { vec3 surfaceAlbedo(const vec3 baseColor, const float metalness) {
@ -86,7 +87,7 @@ vec3 surfaceAlbedo(const vec3 baseColor, const float metalness) {
} }
vec3 surfaceF0(const vec3 baseColor, const float metalness) { vec3 surfaceF0(const vec3 baseColor, const float metalness) {
return mix(vec3(0.04), baseColor * (2.0 + metalness * 1.2), metalness); return mix(vec3(0.04), baseColor, metalness);
} }
float getMipFromRoughness(const float roughness, const float numMipmaps) { float getMipFromRoughness(const float roughness, const float numMipmaps) {

View File

@ -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);
@ -202,9 +202,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);

View File

@ -24,7 +24,7 @@ const int DIFFUSE_CONE_COUNT = 16;
const float SHADOW_CONE_APERTURE = radians(15.0); const float SHADOW_CONE_APERTURE = radians(15.0);
const float DIFFUSE_CONE_APERTURE = 0.872665; // 50 degrees in radians const float DIFFUSE_CONE_APERTURE = 0.872665;
mat3 makeTangentBasis(const vec3 normal) { mat3 makeTangentBasis(const vec3 normal) {
// Create a tangent basis from normal vector // Create a tangent basis from normal vector

View File

@ -8,10 +8,10 @@
// const float compoDOFLength = 160.0; // Focal length in mm 18-200 // const float compoDOFLength = 160.0; // Focal length in mm 18-200
// const float compoDOFFstop = 128.0; // F-stop value // const float compoDOFFstop = 128.0; // F-stop value
const int samples = 6; // Samples on the first ring const int samples = 8; // Samples on the first ring
const int rings = 6; // Ring count const int rings = 6; // Ring count
const vec2 focus = vec2(0.5, 0.5); const vec2 focus = vec2(0.5, 0.5);
const float coc = 0.11; // Circle of confusion size in mm (35mm film = 0.03mm) const float coc = 0.03; // Circle of confusion size in mm (35mm film = 0.03mm)
const float maxblur = 1.0; const float maxblur = 1.0;
const float threshold = 0.5; // Highlight threshold const float threshold = 0.5; // Highlight threshold
const float gain = 2.0; // Highlight gain const float gain = 2.0; // Highlight gain
@ -55,21 +55,26 @@ vec3 dof(
float f = DOFLength; // Focal length in mm float f = DOFLength; // Focal length in mm
float d = fDepth * 1000.0; // Focal plane in mm float d = fDepth * 1000.0; // Focal plane in mm
float o = depth * 1000.0; // Depth in mm float o = depth * 1000.0; // Depth in mm
float a = (o * f) / (o - f); float a = (o > f) ? (o * f) / (o - f) : 0.0;
float b = (d * f) / (d - f); float b = (d > f) ? (d * f) / (d - f) : 0.0;
float c = (d - f) / (d * DOFFStop * coc); float sensorSize = max(DOFFStop, 10.0);
float c = (d - f) / (d * sensorSize * coc);
float blur = abs(a - b) * c; float blur = abs(a - b) * c;
blur = clamp(blur, 0.0, 1.0); blur = clamp(blur, 0.0, 1.0);
vec2 noise = rand2(texCoord) * namount * blur; vec2 noise = rand2(texCoord) * namount * blur;
float w = (texStep.x) * blur * maxblur + noise.x; float w = (texStep.x) * blur * maxblur + noise.x;
float h = (texStep.y) * blur * maxblur + noise.y; float h = (texStep.y) * blur * maxblur + noise.y;
vec3 col = vec3(0.0); vec3 sharpCol = textureLod(tex, texCoord, 0.0).rgb;
if (blur < 0.05) { vec3 col = sharpCol;
col = textureLod(tex, texCoord, 0.0).rgb; float blurThreshold = 0.02;
} float blurRange = 0.06;
else {
col = textureLod(tex, texCoord, 0.0).rgb; if (blur > blurThreshold) {
float blurAmount = smoothstep(blurThreshold, blurThreshold + blurRange, blur);
vec3 blurredCol = sharpCol;
float s = 1.0; float s = 1.0;
int ringsamples; int ringsamples;
@ -81,11 +86,12 @@ vec3 dof(
float ph = (sin(float(j) * step) * float(i)); float ph = (sin(float(j) * step) * float(i));
float p = 1.0; float p = 1.0;
// if (pentagon) p = penta(vec2(pw, ph)); // if (pentagon) p = penta(vec2(pw, ph));
col += color(texCoord + vec2(pw * w, ph * h), blur, tex, texStep) * mix(1.0, (float(i)) / (float(rings)), bias) * p; blurredCol += color(texCoord + vec2(pw * w, ph * h), blur, tex, texStep) * mix(1.0, (float(i)) / (float(rings)), bias) * p;
s += 1.0 * mix(1.0, (float(i)) / (float(rings)), bias) * p; s += 1.0 * mix(1.0, (float(i)) / (float(rings)), bias) * p;
} }
} }
col /= s; blurredCol /= s;
col = mix(sharpCol, blurredCol, blurAmount);
} }
return col; return col;
} }

View File

@ -1,11 +1,11 @@
#ifndef _GBUFFER_GLSL_ #ifndef _GBUFFER_GLSL_
#define _GBUFFER_GLSL_ #define _GBUFFER_GLSL_
vec2 octahedronWrap(const vec2 v) { vec2 octahedronWrap(vec2 v) {
return (1.0 - abs(v.yx)) * (vec2(v.x >= 0.0 ? 1.0 : -1.0, v.y >= 0.0 ? 1.0 : -1.0)); return (1.0 - abs(v.yx)) * (vec2(v.x >= 0.0 ? 1.0 : -1.0, v.y >= 0.0 ? 1.0 : -1.0));
} }
vec3 getNor(const vec2 enc) { vec3 getNor(vec2 enc) {
vec3 n; vec3 n;
n.z = 1.0 - abs(enc.x) - abs(enc.y); n.z = 1.0 - abs(enc.x) - abs(enc.y);
n.xy = n.z >= 0.0 ? enc.xy : octahedronWrap(enc.xy); n.xy = n.z >= 0.0 ? enc.xy : octahedronWrap(enc.xy);
@ -13,13 +13,13 @@ vec3 getNor(const vec2 enc) {
return n; return n;
} }
vec3 getPosView(const vec3 viewRay, const float depth, const vec2 cameraProj) { vec3 getPosView(vec3 viewRay, float depth, vec2 cameraProj) {
float linearDepth = cameraProj.y / (cameraProj.x - depth); float linearDepth = cameraProj.y / (cameraProj.x - depth);
//float linearDepth = cameraProj.y / ((depth * 0.5 + 0.5) - cameraProj.x); //float linearDepth = cameraProj.y / ((depth * 0.5 + 0.5) - cameraProj.x);
return viewRay * linearDepth; return viewRay * linearDepth;
} }
vec3 getPos(const vec3 eye, const vec3 eyeLook, const vec3 viewRay, const float depth, const vec2 cameraProj) { vec3 getPos(vec3 eye, vec3 eyeLook, vec3 viewRay, float depth, vec2 cameraProj) {
// eyeLook, viewRay should be normalized // eyeLook, viewRay should be normalized
float linearDepth = cameraProj.y / ((depth * 0.5 + 0.5) - cameraProj.x); float linearDepth = cameraProj.y / ((depth * 0.5 + 0.5) - cameraProj.x);
float viewZDist = dot(eyeLook, viewRay); float viewZDist = dot(eyeLook, viewRay);
@ -27,7 +27,7 @@ vec3 getPos(const vec3 eye, const vec3 eyeLook, const vec3 viewRay, const float
return wposition; return wposition;
} }
vec3 getPosNoEye(const vec3 eyeLook, const vec3 viewRay, const float depth, const vec2 cameraProj) { vec3 getPosNoEye(vec3 eyeLook, vec3 viewRay, float depth, vec2 cameraProj) {
// eyeLook, viewRay should be normalized // eyeLook, viewRay should be normalized
float linearDepth = cameraProj.y / ((depth * 0.5 + 0.5) - cameraProj.x); float linearDepth = cameraProj.y / ((depth * 0.5 + 0.5) - cameraProj.x);
float viewZDist = dot(eyeLook, viewRay); float viewZDist = dot(eyeLook, viewRay);
@ -36,10 +36,10 @@ vec3 getPosNoEye(const vec3 eyeLook, const vec3 viewRay, const float depth, cons
} }
#if defined(HLSL) || defined(METAL) #if defined(HLSL) || defined(METAL)
vec3 getPos2(const mat4 invVP, const float depth, vec2 coord) { vec3 getPos2(mat4 invVP, float depth, vec2 coord) {
coord.y = 1.0 - coord.y; coord.y = 1.0 - coord.y;
#else #else
vec3 getPos2(const mat4 invVP, const float depth, const vec2 coord) { vec3 getPos2(mat4 invVP, float depth, vec2 coord) {
#endif #endif
vec4 pos = vec4(coord * 2.0 - 1.0, depth, 1.0); vec4 pos = vec4(coord * 2.0 - 1.0, depth, 1.0);
pos = invVP * pos; pos = invVP * pos;
@ -48,10 +48,10 @@ vec3 getPos2(const mat4 invVP, const float depth, const vec2 coord) {
} }
#if defined(HLSL) || defined(METAL) #if defined(HLSL) || defined(METAL)
vec3 getPosView2(const mat4 invP, const float depth, vec2 coord) { vec3 getPosView2(mat4 invP, float depth, vec2 coord) {
coord.y = 1.0 - coord.y; coord.y = 1.0 - coord.y;
#else #else
vec3 getPosView2(const mat4 invP, const float depth, const vec2 coord) { vec3 getPosView2(mat4 invP, float depth, vec2 coord) {
#endif #endif
vec4 pos = vec4(coord * 2.0 - 1.0, depth, 1.0); vec4 pos = vec4(coord * 2.0 - 1.0, depth, 1.0);
pos = invP * pos; pos = invP * pos;
@ -60,10 +60,10 @@ vec3 getPosView2(const mat4 invP, const float depth, const vec2 coord) {
} }
#if defined(HLSL) || defined(METAL) #if defined(HLSL) || defined(METAL)
vec3 getPos2NoEye(const vec3 eye, const mat4 invVP, const float depth, vec2 coord) { vec3 getPos2NoEye(vec3 eye, mat4 invVP, float depth, vec2 coord) {
coord.y = 1.0 - coord.y; coord.y = 1.0 - coord.y;
#else #else
vec3 getPos2NoEye(const vec3 eye, const mat4 invVP, const float depth, const vec2 coord) { vec3 getPos2NoEye(vec3 eye, mat4 invVP, float depth, vec2 coord) {
#endif #endif
vec4 pos = vec4(coord * 2.0 - 1.0, depth, 1.0); vec4 pos = vec4(coord * 2.0 - 1.0, depth, 1.0);
pos = invVP * pos; pos = invVP * pos;
@ -71,24 +71,24 @@ vec3 getPos2NoEye(const vec3 eye, const mat4 invVP, const float depth, const vec
return pos.xyz - eye; return pos.xyz - eye;
} }
float packFloat(const float f1, const float f2) { float packFloat(float f1, float f2) {
return floor(f1 * 100.0) + min(f2, 1.0 - 1.0 / 100.0); return floor(f1 * 100.0) + min(f2, 1.0 - 1.0 / 100.0);
} }
vec2 unpackFloat(const float f) { vec2 unpackFloat(float f) {
return vec2(floor(f) / 100.0, fract(f)); return vec2(floor(f) / 100.0, fract(f));
} }
float packFloat2(const float f1, const float f2) { float packFloat2(float f1, float f2) {
// Higher f1 = less precise f2 // Higher f1 = less precise f2
return floor(f1 * 255.0) + min(f2, 1.0 - 1.0 / 100.0); return floor(f1 * 255.0) + min(f2, 1.0 - 1.0 / 100.0);
} }
vec2 unpackFloat2(const float f) { vec2 unpackFloat2(float f) {
return vec2(floor(f) / 255.0, fract(f)); return vec2(floor(f) / 255.0, fract(f));
} }
vec4 encodeRGBM(const vec3 rgb) { vec4 encodeRGBM(vec3 rgb) {
const float maxRange = 6.0; const float maxRange = 6.0;
float maxRGB = max(rgb.x, max(rgb.g, rgb.b)); float maxRGB = max(rgb.x, max(rgb.g, rgb.b));
float m = maxRGB / maxRange; float m = maxRGB / maxRange;
@ -96,7 +96,7 @@ vec4 encodeRGBM(const vec3 rgb) {
return vec4(rgb / (m * maxRange), m); return vec4(rgb / (m * maxRange), m);
} }
vec3 decodeRGBM(const vec4 rgbm) { vec3 decodeRGBM(vec4 rgbm) {
const float maxRange = 6.0; const float maxRange = 6.0;
return rgbm.rgb * rgbm.a * maxRange; return rgbm.rgb * rgbm.a * maxRange;
} }
@ -150,7 +150,7 @@ vec3 decNor(uint val) {
/** /**
Packs a float in [0, 1] and an integer in [0..15] into a single 16 bit float value. Packs a float in [0, 1] and an integer in [0..15] into a single 16 bit float value.
**/ **/
float packFloatInt16(const float f, const uint i) { float packFloatInt16(float f, uint i) {
const uint numBitFloat = 12; const uint numBitFloat = 12;
const float maxValFloat = float((1 << numBitFloat) - 1); const float maxValFloat = float((1 << numBitFloat) - 1);
@ -160,7 +160,7 @@ float packFloatInt16(const float f, const uint i) {
return float(bitsInt | bitsFloat); return float(bitsInt | bitsFloat);
} }
void unpackFloatInt16(const float val, out float f, out uint i) { void unpackFloatInt16(float val, out float f, out uint i) {
const uint numBitFloat = 12; const uint numBitFloat = 12;
const float maxValFloat = float((1 << numBitFloat) - 1); const float maxValFloat = float((1 << numBitFloat) - 1);

View File

@ -131,7 +131,7 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co
#ifdef _SSRS #ifdef _SSRS
, sampler2D gbufferD, mat4 invVP, vec3 eye , sampler2D gbufferD, mat4 invVP, vec3 eye
#endif #endif
) { ) {
vec3 ld = lp - p; vec3 ld = lp - p;
vec3 l = normalize(ld); vec3 l = normalize(ld);
vec3 h = normalize(v + l); vec3 h = normalize(v + l);
@ -148,9 +148,10 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co
vec3(1.0, 0.0, t.y), vec3(1.0, 0.0, t.y),
vec3(0.0, t.z, 0.0), vec3(0.0, t.z, 0.0),
vec3(t.w, 0.0, t.x)); vec3(t.w, 0.0, t.x));
float ltcspec = ltcEvaluate(n, v, dotNV, p, invM, lightArea0, lightArea1, lightArea2, lightArea3); const float PI = 3.1415926535;
float ltcspec = ltcEvaluate(n, v, dotNV, p, invM, lightArea0, lightArea1, lightArea2, lightArea3) / PI;
ltcspec *= textureLod(sltcMag, tuv, 0.0).a; ltcspec *= textureLod(sltcMag, tuv, 0.0).a;
float ltcdiff = ltcEvaluate(n, v, dotNV, p, mat3(1.0), lightArea0, lightArea1, lightArea2, lightArea3); float ltcdiff = ltcEvaluate(n, v, dotNV, p, mat3(1.0), lightArea0, lightArea1, lightArea2, lightArea3) / PI;
vec3 direct = albedo * ltcdiff + ltcspec * spec * 0.05; vec3 direct = albedo * ltcdiff + ltcspec * spec * 0.05;
#else #else
vec3 direct = lambertDiffuseBRDF(albedo, dotNL) + vec3 direct = lambertDiffuseBRDF(albedo, dotNL) +
@ -158,7 +159,7 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co
#endif #endif
direct *= attenuate(distance(p, lp)); direct *= attenuate(distance(p, lp));
direct *= lightCol; direct *= min(lightCol, vec3(100.0));
#ifdef _MicroShadowing #ifdef _MicroShadowing
direct *= clamp(dotNL + 2.0 * occ * occ - 1.0, 0.0, 1.0); direct *= clamp(dotNL + 2.0 * occ * occ - 1.0, 0.0, 1.0);
@ -183,53 +184,53 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co
#ifdef _SinglePoint #ifdef _SinglePoint
vec4 lPos = LWVPSpot[0] * vec4(p + n * bias * 10, 1.0); vec4 lPos = LWVPSpot[0] * vec4(p + n * bias * 10, 1.0);
direct *= shadowTest(shadowMapSpot[0], direct *= shadowTest(shadowMapSpot[0],
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
shadowMapSpotTransparent[0], shadowMapSpotTransparent[0],
#endif #endif
lPos.xyz / lPos.w, bias lPos.xyz / lPos.w, bias
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
, transparent , transparent
#endif #endif
); );
#endif #endif
#ifdef _Clusters #ifdef _Clusters
vec4 lPos = LWVPSpot[index] * vec4(p + n * bias * 10, 1.0); vec4 lPos = LWVPSpot[index] * vec4(p + n * bias * 10, 1.0);
if (index == 0) direct *= shadowTest(shadowMapSpot[0], if (index == 0) direct *= shadowTest(shadowMapSpot[0],
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
shadowMapSpotTransparent[0], shadowMapSpotTransparent[0],
#endif #endif
lPos.xyz / lPos.w, bias lPos.xyz / lPos.w, bias
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
, transparent , transparent
#endif #endif
); );
else if (index == 1) direct *= shadowTest(shadowMapSpot[1], else if (index == 1) direct *= shadowTest(shadowMapSpot[1],
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
shadowMapSpotTransparent[1], shadowMapSpotTransparent[1],
#endif #endif
lPos.xyz / lPos.w, bias lPos.xyz / lPos.w, bias
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
, transparent , transparent
#endif #endif
); );
else if (index == 2) direct *= shadowTest(shadowMapSpot[2], else if (index == 2) direct *= shadowTest(shadowMapSpot[2],
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
shadowMapSpotTransparent[2], shadowMapSpotTransparent[2],
#endif #endif
lPos.xyz / lPos.w, bias lPos.xyz / lPos.w, bias
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
, transparent , transparent
#endif #endif
); );
else if (index == 3) direct *= shadowTest(shadowMapSpot[3], else if (index == 3) direct *= shadowTest(shadowMapSpot[3],
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
shadowMapSpotTransparent[3], shadowMapSpotTransparent[3],
#endif #endif
lPos.xyz / lPos.w, bias lPos.xyz / lPos.w, bias
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
, transparent , transparent
#endif #endif
); );
#endif #endif
} }
#endif #endif
@ -243,76 +244,76 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co
#ifdef _ShadowMap #ifdef _ShadowMap
if (receiveShadow) { if (receiveShadow) {
#ifdef _SinglePoint #ifdef _SinglePoint
vec4 lPos = LWVPSpotArray[0] * vec4(p + n * bias * 10, 1.0); vec4 lPos = LWVPSpotArray[0] * vec4(p + n * bias * 2, 1.0);
direct *= shadowTest(shadowMapSpot[0], direct *= shadowTest(shadowMapSpot[0],
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
shadowMapSpotTransparent[0], shadowMapSpotTransparent[0],
#endif #endif
lPos.xyz / lPos.w, bias lPos.xyz / lPos.w, bias
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
, transparent , transparent
#endif #endif
); );
#endif #endif
#ifdef _Clusters #ifdef _Clusters
vec4 lPos = LWVPSpotArray[index] * vec4(p + n * bias * 10, 1.0); vec4 lPos = LWVPSpotArray[index] * vec4(p + n * bias * 2, 1.0);
#ifdef _ShadowMapAtlas #ifdef _ShadowMapAtlas
direct *= shadowTest( direct *= shadowTest(
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
#ifndef _SingleAtlas #ifndef _SingleAtlas
shadowMapAtlasSpot, shadowMapAtlasSpotTransparent shadowMapAtlasSpot, shadowMapAtlasSpotTransparent
#else #else
shadowMapAtlas, shadowMapAtlasTransparent shadowMapAtlas, shadowMapAtlasTransparent
#endif #endif
#else #else
#ifndef _SingleAtlas #ifndef _SingleAtlas
shadowMapAtlasSpot shadowMapAtlasSpot
#else #else
shadowMapAtlas shadowMapAtlas
#endif #endif
#endif #endif
, lPos.xyz / lPos.w, bias , lPos.xyz / lPos.w, bias
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
, transparent , transparent
#endif #endif
); );
#else #else
if (index == 0) direct *= shadowTest(shadowMapSpot[0], if (index == 0) direct *= shadowTest(shadowMapSpot[0],
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
shadowMapSpotTransparent[0], shadowMapSpotTransparent[0],
#endif #endif
lPos.xyz / lPos.w, bias lPos.xyz / lPos.w, bias
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
, transparent , transparent
#endif #endif
); );
else if (index == 1) direct *= shadowTest(shadowMapSpot[1], else if (index == 1) direct *= shadowTest(shadowMapSpot[1],
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
shadowMapSpotTransparent[1], shadowMapSpotTransparent[1],
#endif #endif
lPos.xyz / lPos.w, bias lPos.xyz / lPos.w, bias
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
, transparent , transparent
#endif #endif
); );
else if (index == 2) direct *= shadowTest(shadowMapSpot[2], else if (index == 2) direct *= shadowTest(shadowMapSpot[2],
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
shadowMapSpotTransparent[2], shadowMapSpotTransparent[2],
#endif #endif
lPos.xyz / lPos.w, bias lPos.xyz / lPos.w, bias
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
, transparent , transparent
#endif #endif
); );
else if (index == 3) direct *= shadowTest(shadowMapSpot[3], else if (index == 3) direct *= shadowTest(shadowMapSpot[3],
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
shadowMapSpotTransparent[3], shadowMapSpotTransparent[3],
#endif #endif
lPos.xyz / lPos.w, bias lPos.xyz / lPos.w, bias
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
, transparent , transparent
#endif #endif
); );
#endif #endif
#endif #endif
} }
@ -330,74 +331,74 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co
#ifdef _SinglePoint #ifdef _SinglePoint
#ifndef _Spot #ifndef _Spot
direct *= PCFCube(shadowMapPoint[0], direct *= PCFCube(shadowMapPoint[0],
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
shadowMapPointTransparent[0], shadowMapPointTransparent[0],
#endif #endif
ld, -l, bias, lightProj, n ld, -l, bias, lightProj, n
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
, transparent , transparent
#endif #endif
); );
#endif #endif
#endif #endif
#ifdef _Clusters #ifdef _Clusters
#ifdef _ShadowMapAtlas #ifdef _ShadowMapAtlas
direct *= PCFFakeCube( direct *= PCFFakeCube(
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
#ifndef _SingleAtlas #ifndef _SingleAtlas
shadowMapAtlasPoint, shadowMapAtlasPointTransparent shadowMapAtlasPoint, shadowMapAtlasPointTransparent
#else #else
shadowMapAtlas, shadowMapAtlasTransparent shadowMapAtlas, shadowMapAtlasTransparent
#endif #endif
#else #else
#ifndef _SingleAtlas #ifndef _SingleAtlas
shadowMapAtlasPoint shadowMapAtlasPoint
#else #else
shadowMapAtlas shadowMapAtlas
#endif #endif
#endif #endif
, ld, -l, bias, lightProj, n, index , ld, -l, bias, lightProj, n, index
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
, transparent , transparent
#endif #endif
); );
#else #else
if (index == 0) direct *= PCFCube(shadowMapPoint[0], if (index == 0) direct *= PCFCube(shadowMapPoint[0],
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
shadowMapPointTransparent[0], shadowMapPointTransparent[0],
#endif #endif
ld, -l, bias, lightProj, n ld, -l, bias, lightProj, n
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
, transparent , transparent
#endif #endif
); );
else if (index == 1) direct *= PCFCube(shadowMapPoint[1], else if (index == 1) direct *= PCFCube(shadowMapPoint[1],
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
shadowMapPointTransparent[1], shadowMapPointTransparent[1],
#endif #endif
ld, -l, bias, lightProj, n ld, -l, bias, lightProj, n
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
, transparent , transparent
#endif #endif
); );
else if (index == 2) direct *= PCFCube(shadowMapPoint[2], else if (index == 2) direct *= PCFCube(shadowMapPoint[2],
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
shadowMapPointTransparent[2], shadowMapPointTransparent[2],
#endif #endif
ld, -l, bias, lightProj, n ld, -l, bias, lightProj, n
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
, transparent , transparent
#endif #endif
); );
else if (index == 3) direct *= PCFCube(shadowMapPoint[3], else if (index == 3) direct *= PCFCube(shadowMapPoint[3],
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
shadowMapPointTransparent[3], shadowMapPointTransparent[3],
#endif #endif
ld, -l, bias, lightProj, n ld, -l, bias, lightProj, n
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
, transparent , transparent
#endif #endif
); );
#endif #endif
#endif #endif
} }
@ -435,9 +436,10 @@ vec3 sampleLightVoxels(const vec3 p, const vec3 n, const vec3 v, const float dot
vec3(1.0, 0.0, t.y), vec3(1.0, 0.0, t.y),
vec3(0.0, t.z, 0.0), vec3(0.0, t.z, 0.0),
vec3(t.w, 0.0, t.x)); vec3(t.w, 0.0, t.x));
float ltcspec = ltcEvaluate(n, v, dotNV, p, invM, lightArea0, lightArea1, lightArea2, lightArea3); const float PI = 3.1415926535;
float ltcspec = ltcEvaluate(n, v, dotNV, p, invM, lightArea0, lightArea1, lightArea2, lightArea3) / PI;
ltcspec *= textureLod(sltcMag, tuv, 0.0).a; ltcspec *= textureLod(sltcMag, tuv, 0.0).a;
float ltcdiff = ltcEvaluate(n, v, dotNV, p, mat3(1.0), lightArea0, lightArea1, lightArea2, lightArea3); float ltcdiff = ltcEvaluate(n, v, dotNV, p, mat3(1.0), lightArea0, lightArea1, lightArea2, lightArea3) / PI;
vec3 direct = albedo * ltcdiff + ltcspec * spec * 0.05; vec3 direct = albedo * ltcdiff + ltcspec * spec * 0.05;
#else #else
vec3 direct = lambertDiffuseBRDF(albedo, dotNL) + vec3 direct = lambertDiffuseBRDF(albedo, dotNL) +
@ -445,61 +447,62 @@ vec3 sampleLightVoxels(const vec3 p, const vec3 n, const vec3 v, const float dot
#endif #endif
direct *= attenuate(distance(p, lp)); direct *= attenuate(distance(p, lp));
direct *= lightCol; // CRITICAL: Clamp light color to prevent extreme HDR values causing white sphere artifacts
direct *= min(lightCol, vec3(100.0));
#ifdef _LTC #ifdef _LTC
#ifdef _ShadowMap #ifdef _ShadowMap
if (receiveShadow) { if (receiveShadow) {
#ifdef _SinglePoint #ifdef _SinglePoint
vec4 lPos = LWVPSpot[0] * vec4(p + n * bias * 10, 1.0); vec4 lPos = LWVPSpot[0] * vec4(p + n * bias * 2, 1.0);
direct *= shadowTest(shadowMapSpot[0], direct *= shadowTest(shadowMapSpot[0],
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
shadowMapSpotTransparent[0], shadowMapSpotTransparent[0],
#endif #endif
lPos.xyz / lPos.w, bias lPos.xyz / lPos.w, bias
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
, transparent , transparent
#endif #endif
); );
#endif #endif
#ifdef _Clusters #ifdef _Clusters
vec4 lPos = LWVPSpot[index] * vec4(p + n * bias * 10, 1.0); vec4 lPos = LWVPSpot[index] * vec4(p + n * bias * 2, 1.0);
if (index == 0) direct *= shadowTest(shadowMapSpot[0], if (index == 0) direct *= shadowTest(shadowMapSpot[0],
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
shadowMapSpotTransparent[0], shadowMapSpotTransparent[0],
#endif #endif
lPos.xyz / lPos.w, bias lPos.xyz / lPos.w, bias
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
, transparent , transparent
#endif #endif
); );
else if (index == 1) direct *= shadowTest(shadowMapSpot[1], else if (index == 1) direct *= shadowTest(shadowMapSpot[1],
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
shadowMapSpotTransparent[1], shadowMapSpotTransparent[1],
#endif #endif
lPos.xyz / lPos.w, bias lPos.xyz / lPos.w, bias
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
, transparent , transparent
#endif #endif
); );
else if (index == 2) direct *= shadowTest(shadowMapSpot[2], else if (index == 2) direct *= shadowTest(shadowMapSpot[2],
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
shadowMapSpotTransparent[2], shadowMapSpotTransparent[2],
#endif #endif
lPos.xyz / lPos.w, bias lPos.xyz / lPos.w, bias
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
, transparent , transparent
#endif #endif
); );
else if (index == 3) direct *= shadowTest(shadowMapSpot[3], else if (index == 3) direct *= shadowTest(shadowMapSpot[3],
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
shadowMapSpotTransparent[3], shadowMapSpotTransparent[3],
#endif #endif
lPos.xyz / lPos.w, bias lPos.xyz / lPos.w, bias
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
, transparent , transparent
#endif #endif
); );
#endif #endif
} }
#endif #endif
@ -513,76 +516,76 @@ vec3 sampleLightVoxels(const vec3 p, const vec3 n, const vec3 v, const float dot
#ifdef _ShadowMap #ifdef _ShadowMap
if (receiveShadow) { if (receiveShadow) {
#ifdef _SinglePoint #ifdef _SinglePoint
vec4 lPos = LWVPSpotArray[0] * vec4(p + n * bias * 10, 1.0); vec4 lPos = LWVPSpotArray[0] * vec4(p + n * bias * 2, 1.0);
direct *= shadowTest(shadowMapSpot[0], direct *= shadowTest(shadowMapSpot[0],
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
shadowMapSpotTransparent[0], shadowMapSpotTransparent[0],
#endif #endif
lPos.xyz / lPos.w, bias lPos.xyz / lPos.w, bias
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
, transparent , transparent
#endif #endif
); );
#endif #endif
#ifdef _Clusters #ifdef _Clusters
vec4 lPos = LWVPSpotArray[index] * vec4(p + n * bias * 10, 1.0); vec4 lPos = LWVPSpotArray[index] * vec4(p + n * bias * 2, 1.0);
#ifdef _ShadowMapAtlas #ifdef _ShadowMapAtlas
direct *= shadowTest( direct *= shadowTest(
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
#ifndef _SingleAtlas #ifndef _SingleAtlas
shadowMapAtlasSpot, shadowMapAtlasSpotTransparent shadowMapAtlasSpot, shadowMapAtlasSpotTransparent
#else #else
shadowMapAtlas, shadowMapAtlasTransparent shadowMapAtlas, shadowMapAtlasTransparent
#endif #endif
#else #else
#ifndef _SingleAtlas #ifndef _SingleAtlas
shadowMapAtlasSpot shadowMapAtlasSpot
#else #else
shadowMapAtlas shadowMapAtlas
#endif #endif
#endif #endif
, lPos.xyz / lPos.w, bias , lPos.xyz / lPos.w, bias
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
, transparent , transparent
#endif #endif
); );
#else #else
if (index == 0) direct *= shadowTest(shadowMapSpot[0], if (index == 0) direct *= shadowTest(shadowMapSpot[0],
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
shadowMapSpotTransparent[0], shadowMapSpotTransparent[0],
#endif #endif
lPos.xyz / lPos.w, bias lPos.xyz / lPos.w, bias
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
, transparent , transparent
#endif #endif
); );
else if (index == 1) direct *= shadowTest(shadowMapSpot[1], else if (index == 1) direct *= shadowTest(shadowMapSpot[1],
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
shadowMapSpotTransparent[1], shadowMapSpotTransparent[1],
#endif #endif
lPos.xyz / lPos.w, bias lPos.xyz / lPos.w, bias
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
, transparent , transparent
#endif #endif
); );
else if (index == 2) direct *= shadowTest(shadowMapSpot[2], else if (index == 2) direct *= shadowTest(shadowMapSpot[2],
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
shadowMapSpotTransparent[2], shadowMapSpotTransparent[2],
#endif #endif
lPos.xyz / lPos.w, bias lPos.xyz / lPos.w, bias
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
, transparent , transparent
#endif #endif
); );
else if (index == 3) direct *= shadowTest(shadowMapSpot[3], else if (index == 3) direct *= shadowTest(shadowMapSpot[3],
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
shadowMapSpotTransparent[3], shadowMapSpotTransparent[3],
#endif #endif
lPos.xyz / lPos.w, bias lPos.xyz / lPos.w, bias
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
, transparent , transparent
#endif #endif
); );
#endif #endif
#endif #endif
} }
@ -600,74 +603,74 @@ vec3 sampleLightVoxels(const vec3 p, const vec3 n, const vec3 v, const float dot
#ifdef _SinglePoint #ifdef _SinglePoint
#ifndef _Spot #ifndef _Spot
direct *= PCFCube(shadowMapPoint[0], direct *= PCFCube(shadowMapPoint[0],
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
shadowMapPointTransparent[0], shadowMapPointTransparent[0],
#endif #endif
ld, -l, bias, lightProj, n ld, -l, bias, lightProj, n
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
, transparent , transparent
#endif #endif
); );
#endif #endif
#endif #endif
#ifdef _Clusters #ifdef _Clusters
#ifdef _ShadowMapAtlas #ifdef _ShadowMapAtlas
direct *= PCFFakeCube( direct *= PCFFakeCube(
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
#ifndef _SingleAtlas #ifndef _SingleAtlas
shadowMapAtlasPoint, shadowMapAtlasPointTransparent shadowMapAtlasPoint, shadowMapAtlasPointTransparent
#else #else
shadowMapAtlas, shadowMapAtlasTransparent shadowMapAtlas, shadowMapAtlasTransparent
#endif #endif
#else #else
#ifndef _SingleAtlas #ifndef _SingleAtlas
shadowMapAtlasPoint shadowMapAtlasPoint
#else #else
shadowMapAtlas shadowMapAtlas
#endif #endif
#endif #endif
, ld, -l, bias, lightProj, n, index , ld, -l, bias, lightProj, n, index
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
, transparent , transparent
#endif #endif
); );
#else #else
if (index == 0) direct *= PCFCube(shadowMapPoint[0], if (index == 0) direct *= PCFCube(shadowMapPoint[0],
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
shadowMapPointTransparent[0], shadowMapPointTransparent[0],
#endif #endif
ld, -l, bias, lightProj, n ld, -l, bias, lightProj, n
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
, transparent , transparent
#endif #endif
); );
else if (index == 1) direct *= PCFCube(shadowMapPoint[1], else if (index == 1) direct *= PCFCube(shadowMapPoint[1],
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
shadowMapPointTransparent[1], shadowMapPointTransparent[1],
#endif #endif
ld, -l, bias, lightProj, n ld, -l, bias, lightProj, n
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
, transparent , transparent
#endif #endif
); );
else if (index == 2) direct *= PCFCube(shadowMapPoint[2], else if (index == 2) direct *= PCFCube(shadowMapPoint[2],
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
shadowMapPointTransparent[2], shadowMapPointTransparent[2],
#endif #endif
ld, -l, bias, lightProj, n ld, -l, bias, lightProj, n
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
, transparent , transparent
#endif #endif
); );
else if (index == 3) direct *= PCFCube(shadowMapPoint[3], else if (index == 3) direct *= PCFCube(shadowMapPoint[3],
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
shadowMapPointTransparent[3], shadowMapPointTransparent[3],
#endif #endif
ld, -l, bias, lightProj, n ld, -l, bias, lightProj, n
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
, transparent , transparent
#endif #endif
); );
#endif #endif
#endif #endif
} }

View File

@ -5,6 +5,7 @@ uniform vec2 morphDataDim;
uniform vec4 morphWeights[8]; uniform vec4 morphWeights[8];
void getMorphedVertex(vec2 uvCoord, inout vec3 A){ void getMorphedVertex(vec2 uvCoord, inout vec3 A){
vec3 totalDelta = vec3(0.0);
for(int i = 0; i<8; i++ ) for(int i = 0; i<8; i++ )
{ {
vec4 tempCoordY = vec4( uvCoord.y - (i * 4) * morphDataDim.y, vec4 tempCoordY = vec4( uvCoord.y - (i * 4) * morphDataDim.y,
@ -13,21 +14,28 @@ void getMorphedVertex(vec2 uvCoord, inout vec3 A){
uvCoord.y - (i * 4 + 3) * morphDataDim.y); uvCoord.y - (i * 4 + 3) * morphDataDim.y);
vec3 morph = texture(morphDataPos, vec2(uvCoord.x, tempCoordY.x)).rgb * morphScaleOffset.x + morphScaleOffset.y; vec3 morph = texture(morphDataPos, vec2(uvCoord.x, tempCoordY.x)).rgb * morphScaleOffset.x + morphScaleOffset.y;
A += morphWeights[i].x * morph; totalDelta += morphWeights[i].x * morph;
morph = texture(morphDataPos, vec2(uvCoord.x, tempCoordY.y)).rgb * morphScaleOffset.x + morphScaleOffset.y; morph = texture(morphDataPos, vec2(uvCoord.x, tempCoordY.y)).rgb * morphScaleOffset.x + morphScaleOffset.y;
A += morphWeights[i].y * morph; totalDelta += morphWeights[i].y * morph;
morph = texture(morphDataPos, vec2(uvCoord.x, tempCoordY.z)).rgb * morphScaleOffset.x + morphScaleOffset.y; morph = texture(morphDataPos, vec2(uvCoord.x, tempCoordY.z)).rgb * morphScaleOffset.x + morphScaleOffset.y;
A += morphWeights[i].z * morph; totalDelta += morphWeights[i].z * morph;
morph = texture(morphDataPos, vec2(uvCoord.x, tempCoordY.w)).rgb * morphScaleOffset.x + morphScaleOffset.y; morph = texture(morphDataPos, vec2(uvCoord.x, tempCoordY.w)).rgb * morphScaleOffset.x + morphScaleOffset.y;
A += morphWeights[i].w * morph; totalDelta += morphWeights[i].w * morph;
} }
//float deltaLength = length(totalDelta);
//if (deltaLength > 5.0) {
// clamp corrupted data
//totalDelta = normalize(totalDelta) * 5.0;
//}
A += totalDelta;
} }
void getMorphedNormal(vec2 uvCoord, vec3 oldNor, inout vec3 morphNor){ void getMorphedNormal(vec2 uvCoord, vec3 oldNor, inout vec3 morphNor){
vec3 normalDelta = vec3(0.0);
for(int i = 0; i<8; i++ ) for(int i = 0; i<8; i++ )
{ {
vec4 tempCoordY = vec4( uvCoord.y - (i * 4) * morphDataDim.y, vec4 tempCoordY = vec4( uvCoord.y - (i * 4) * morphDataDim.y,
@ -35,19 +43,11 @@ void getMorphedNormal(vec2 uvCoord, vec3 oldNor, inout vec3 morphNor){
uvCoord.y - (i * 4 + 2) * morphDataDim.y, uvCoord.y - (i * 4 + 2) * morphDataDim.y,
uvCoord.y - (i * 4 + 3) * morphDataDim.y); uvCoord.y - (i * 4 + 3) * morphDataDim.y);
vec3 norm = oldNor + morphWeights[i].x * (texture(morphDataNor, vec2(uvCoord.x, tempCoordY.x)).rgb * 2.0 - 1.0); normalDelta += morphWeights[i].x * (texture(morphDataNor, vec2(uvCoord.x, tempCoordY.x)).rgb * 2.0 - 1.0);
morphNor += norm; normalDelta += morphWeights[i].y * (texture(morphDataNor, vec2(uvCoord.x, tempCoordY.y)).rgb * 2.0 - 1.0);
normalDelta += morphWeights[i].z * (texture(morphDataNor, vec2(uvCoord.x, tempCoordY.z)).rgb * 2.0 - 1.0);
norm = oldNor + morphWeights[i].y * (texture(morphDataNor, vec2(uvCoord.x, tempCoordY.y)).rgb * 2.0 - 1.0); normalDelta += morphWeights[i].w * (texture(morphDataNor, vec2(uvCoord.x, tempCoordY.w)).rgb * 2.0 - 1.0);
morphNor += norm;
norm = oldNor + morphWeights[i].z * (texture(morphDataNor, vec2(uvCoord.x, tempCoordY.z)).rgb * 2.0 - 1.0);
morphNor += norm;
norm = oldNor + morphWeights[i].w * (texture(morphDataNor, vec2(uvCoord.x, tempCoordY.w)).rgb * 2.0 - 1.0);
morphNor += norm;
} }
morphNor = normalize(morphNor); morphNor = normalize(oldNor + normalDelta);
} }

View File

@ -0,0 +1 @@
<EFBFBD><EFBFBD>shader_datas<EFBFBD><EFBFBD><EFBFBD>name<EFBFBD>copy_pass<EFBFBD>contexts<EFBFBD><EFBFBD><EFBFBD>name<EFBFBD>copy_pass<EFBFBD>constants<EFBFBD><EFBFBD>texture_units<EFBFBD><EFBFBD><EFBFBD>name<EFBFBD>tex<EFBFBD>vertex_elements<EFBFBD><EFBFBD><EFBFBD>data<EFBFBD>float2<EFBFBD>name<EFBFBD>pos<EFBFBD>vertex_shader<EFBFBD>pass.vert<72>fragment_shader<65>pass_copy.frag<61>depth_write¬compare_mode<64>always<79>cull_mode<64>none<6E><65>name<6D>compositor_pass<73>contexts<74><73><EFBFBD>name<6D>compositor_pass<73>constants<74><73>texture_units<74><73><EFBFBD>name<6D>tex<65>vertex_elements<74><73><EFBFBD>data<74>float2<74>name<6D>pos<6F>vertex_shader<65>compositor_pass.vert<72>fragment_shader<65>compositor_pass.frag<61>depth_write¬compare_mode<64>always<79>cull_mode<64>none<6E><65>name<6D>deferred_light<68>contexts<74><73><EFBFBD>name<6D>deferred_light<68>constants<74><73><EFBFBD>type<70>mat4<74>name<6D>invVP<56>link<6E>_inverseViewProjectionMatrix<69><78>type<70>vec3<63>name<6D>eye<79>link<6E>_cameraPosition<6F><6E>type<70>float<61>name<6D>envmapStrength<74>link<6E>_envmapStrength<74><68>type<70>floats<74>name<6D>shirr<72>link<6E>_envmapIrradiance<63><65>type<70>int<6E>name<6D>envmapNumMipmaps<70>link<6E>_envmapNumMipmaps<70><73>type<70>vec2<63>name<6D>cameraProj<6F>link<6E>_cameraPlaneProj<6F><6A>type<70>vec3<63>name<6D>eyeLook<6F>link<6E>_cameraLook<6F><6B>type<70>vec2<63>name<6D>lightProj<6F>link<6E>_lightPlaneProj<6F><6A>type<70>vec3<63>name<6D>pointPos<6F>link<6E>_pointPosition<6F><6E>type<70>vec3<63>name<6D>pointCol<6F>link<6E>_pointColor<6F><72>type<70>float<61>name<6D>pointBias<61>link<6E>_pointShadowsBias<61>texture_units<74><73><EFBFBD>name<6D>gbufferD<72><44>name<6D>gbuffer0<72><30>name<6D>gbuffer1<72><31>name<6D>senvmapBrdf<64>link<6E>$brdf.png<6E><67>name<6D>senvmapRadiance<63>link<6E>_envmapRadiance<63><65>name<6D>shadowMapPoint[0]<5D>vertex_elements<74><73><EFBFBD>data<74>float2<74>name<6D>pos<6F>vertex_shader<65>pass_viewray.vert<72>fragment_shader<65>deferred_light.frag<61>color_attachments<74><73>RGBA64<36>depth_write¬compare_mode<64>always<79>cull_mode<64>none<6E><65>name<6D>water_pass<73>contexts<74><73><EFBFBD>name<6D>water_pass<73>constants<74><73><EFBFBD>type<70>mat4<74>name<6D>invVP<56><50>type<70>vec3<63>name<6D>eye<79>link<6E>_cameraPosition<6F><6E>type<70>float<61>name<6D>time<6D>link<6E>_time<6D><65>type<70>float<61>name<6D>holoOverallStrength<74><68>type<70>vec2<63>name<6D>cameraProj<6F>link<6E>_cameraPlaneProj<6F><6A>type<70>vec3<63>name<6D>eyeLook<6F>link<6E>_cameraLook<6F><6B>type<70>vec3<63>name<6D>ld<6C>link<6E>_lightDirection<6F>texture_units<74><73><EFBFBD>name<6D>tex<65><78>name<6D>gbufferD<72><44>name<6D>gbuffer0<72>vertex_elements<74><73><EFBFBD>data<74>float2<74>name<6D>pos<6F>vertex_shader<65>pass_viewray.vert<72>fragment_shader<65>water_pass.frag<61>depth_write¬compare_mode<64>always<79>cull_mode<64>none<6E>blend_source<63>source_alpha<68>blend_destination<6F>inverse_source_alpha<68>blend_operation<6F>add<64>alpha_blend_source<63>blend_one<6E>alpha_blend_destination<6F>blend_one<6E>alpha_blend_operation<6F>add<64><64>contexts<74><73><EFBFBD>name<6D>World_World<6C>depth_write¬compare_mode<64>less<73>cull_mode<64>clockwise<73>vertex_elements<74><73><EFBFBD>name<6D>pos<6F>data<74>float3<74><33>name<6D>nor<6F>data<74>float3<74>color_attachments<74><73>_HDR<44>texture_units<74><73>constants<74><73><EFBFBD>name<6D>SMVP<56>type<70>mat4<74>link<6E>_skydomeMatrix<69>vertex_shader<65>World_World.vert<72>fragment_shader<65>World_World.frag<61>name<6D>World_World

View File

@ -23,6 +23,59 @@ uniform vec2 smSizeUniform;
#endif #endif
#ifdef _ShadowMapAtlas #ifdef _ShadowMapAtlas
// PCF that clamps samples to tile boundaries to prevent bleeding
vec3 PCFTileAware(sampler2DShadow shadowMap,
#ifdef _ShadowMapTransparent
sampler2D shadowMapTransparent,
#endif
const vec2 uv, const float compare, const vec2 smSize,
const vec2 tileMin, const vec2 tileMax
#ifdef _ShadowMapTransparent
, const bool transparent
#endif
) {
vec3 result = vec3(0.0);
vec2 offset;
offset = vec2(-1.0, -1.0) / smSize;
result.x = texture(shadowMap, vec3(clamp(uv + offset, tileMin, tileMax), compare));
offset = vec2(-1.0, 0.0) / smSize;
result.x += texture(shadowMap, vec3(clamp(uv + offset, tileMin, tileMax), compare));
offset = vec2(-1.0, 1.0) / smSize;
result.x += texture(shadowMap, vec3(clamp(uv + offset, tileMin, tileMax), compare));
offset = vec2(0.0, -1.0) / smSize;
result.x += texture(shadowMap, vec3(clamp(uv + offset, tileMin, tileMax), compare));
result.x += texture(shadowMap, vec3(uv, compare));
offset = vec2(0.0, 1.0) / smSize;
result.x += texture(shadowMap, vec3(clamp(uv + offset, tileMin, tileMax), compare));
offset = vec2(1.0, -1.0) / smSize;
result.x += texture(shadowMap, vec3(clamp(uv + offset, tileMin, tileMax), compare));
offset = vec2(1.0, 0.0) / smSize;
result.x += texture(shadowMap, vec3(clamp(uv + offset, tileMin, tileMax), compare));
offset = vec2(1.0, 1.0) / smSize;
result.x += texture(shadowMap, vec3(clamp(uv + offset, tileMin, tileMax), compare));
result = result.xxx / 9.0;
#ifdef _ShadowMapTransparent
if (transparent == false) {
vec4 shadowmap_transparent = texture(shadowMapTransparent, uv);
if (shadowmap_transparent.a < compare)
result *= shadowmap_transparent.rgb;
}
#endif
return result;
}
// https://www.khronos.org/registry/OpenGL/specs/gl/glspec20.pdf // p:168 // https://www.khronos.org/registry/OpenGL/specs/gl/glspec20.pdf // p:168
// https://www.gamedev.net/forums/topic/687535-implementing-a-cube-map-lookup-function/5337472/ // https://www.gamedev.net/forums/topic/687535-implementing-a-cube-map-lookup-function/5337472/
vec2 sampleCube(vec3 dir, out int faceIndex) { vec2 sampleCube(vec3 dir, out int faceIndex) {
@ -251,7 +304,7 @@ vec3 PCFFakeCube(sampler2DShadow shadowMap,
#endif #endif
if (any(lessThan(uvtiled, vec2(0.0))) || any(greaterThan(uvtiled, vec2(1.0)))) { if (any(lessThan(uvtiled, vec2(0.0))) || any(greaterThan(uvtiled, vec2(1.0)))) {
return vec3(1.0); // Handle edge cases by returning full light return vec3(1.0);
} }
vec3 result = vec3(0.0); vec3 result = vec3(0.0);
@ -334,30 +387,55 @@ vec3 PCFFakeCube(sampler2DShadow shadowMap,
} }
#endif #endif
#ifdef _ShadowMapAtlas
uniform vec4 tileBounds;
#endif
vec3 shadowTest(sampler2DShadow shadowMap, vec3 shadowTest(sampler2DShadow shadowMap,
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
sampler2D shadowMapTransparent, sampler2D shadowMapTransparent,
#endif #endif
const vec3 lPos, const float shadowsBias const vec3 lPos, const float shadowsBias
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
, const bool transparent , const bool transparent
#endif #endif
) { ) {
if (lPos.x < 0.0 || lPos.y < 0.0 || lPos.x > 1.0 || lPos.y > 1.0) return vec3(1.0);
#ifdef _ShadowMapAtlas
// use tile PCF
#ifdef _SMSizeUniform
vec2 smSizeAtlas = smSizeUniform;
#else
const vec2 smSizeAtlas = shadowmapSize;
#endif
return PCFTileAware(shadowMap,
#ifdef _ShadowMapTransparent
shadowMapTransparent,
#endif
lPos.xy, lPos.z - shadowsBias, smSizeAtlas,
tileBounds.xy, tileBounds.zw
#ifdef _ShadowMapTransparent
, transparent
#endif
);
#else
// use PCF for non-atlas shadows
#ifdef _SMSizeUniform #ifdef _SMSizeUniform
vec2 smSize = smSizeUniform; vec2 smSize = smSizeUniform;
#else #else
const vec2 smSize = shadowmapSize; const vec2 smSize = shadowmapSize;
#endif #endif
if (lPos.x < 0.0 || lPos.y < 0.0 || lPos.x > 1.0 || lPos.y > 1.0) return vec3(1.0);
return PCF(shadowMap, return PCF(shadowMap,
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
shadowMapTransparent, shadowMapTransparent,
#endif #endif
lPos.xy, lPos.z - shadowsBias, smSize lPos.xy, lPos.z - shadowsBias, smSize
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
, transparent , transparent
#endif #endif
); );
#endif
} }
#ifdef _CSM #ifdef _CSM
@ -390,14 +468,14 @@ mat4 getCascadeMat(const float d, out int casi, out int casIndex) {
} }
vec3 shadowTestCascade(sampler2DShadow shadowMap, vec3 shadowTestCascade(sampler2DShadow shadowMap,
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
sampler2D shadowMapTransparent, sampler2D shadowMapTransparent,
#endif #endif
const vec3 eye, const vec3 p, const float shadowsBias const vec3 eye, const vec3 p, const float shadowsBias
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
, const bool transparent , const bool transparent
#endif #endif
) { ) {
#ifdef _SMSizeUniform #ifdef _SMSizeUniform
vec2 smSize = smSizeUniform; vec2 smSize = smSizeUniform;
#else #else
@ -413,14 +491,14 @@ vec3 shadowTestCascade(sampler2DShadow shadowMap,
vec3 visibility = vec3(1.0); vec3 visibility = vec3(1.0);
if (lPos.w > 0.0) visibility = PCF(shadowMap, if (lPos.w > 0.0) visibility = PCF(shadowMap,
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
shadowMapTransparent, shadowMapTransparent,
#endif #endif
lPos.xy, lPos.z - shadowsBias, smSize lPos.xy, lPos.z - shadowsBias, smSize
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
, transparent , transparent
#endif #endif
); );
// Blend cascade // Blend cascade
// https://github.com/TheRealMJP/Shadows // https://github.com/TheRealMJP/Shadows
@ -439,15 +517,16 @@ vec3 shadowTestCascade(sampler2DShadow shadowMap,
vec4 lPos2 = LWVP2 * vec4(p, 1.0); vec4 lPos2 = LWVP2 * vec4(p, 1.0);
lPos2.xyz /= lPos2.w; lPos2.xyz /= lPos2.w;
vec3 visibility2 = vec3(1.0); vec3 visibility2 = vec3(1.0);
// use lPos2 coordinates for second cascade, not lPos
if (lPos2.w > 0.0) visibility2 = PCF(shadowMap, if (lPos2.w > 0.0) visibility2 = PCF(shadowMap,
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
shadowMapTransparent, shadowMapTransparent,
#endif #endif
lPos.xy, lPos.z - shadowsBias, smSize lPos2.xy, lPos2.z - shadowsBias, smSize
#ifdef _ShadowMapTransparent #ifdef _ShadowMapTransparent
, transparent , transparent
#endif #endif
); );
float lerpAmt = smoothstep(0.0, blendThres, splitDist); float lerpAmt = smoothstep(0.0, blendThres, splitDist);
return mix(visibility2, visibility, lerpAmt); return mix(visibility2, visibility, lerpAmt);

View File

@ -0,0 +1,56 @@
/*
Copyright (c) 2024 Turánszki János
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
const int DIFFUSE_CONE_COUNT = 16;
const float DIFFUSE_CONE_APERTURE = radians(45.0);
const vec3 DIFFUSE_CONE_DIRECTIONS[16] = {
vec3(0.0000, 0.0000, 1.0000), // Central direction
vec3(0.3827, 0.0000, 0.9239), // Ring 1
vec3(-0.3827, 0.0000, 0.9239),
vec3(0.0000, 0.3827, 0.9239),
vec3(0.0000, -0.3827, 0.9239),
vec3(0.2706, 0.2706, 0.9239), // Ring 2
vec3(-0.2706, 0.2706, 0.9239),
vec3(0.2706, -0.2706, 0.9239),
vec3(-0.2706, -0.2706, 0.9239),
vec3(0.1802, 0.3604, 0.9239), // Ring 3
vec3(-0.1802, 0.3604, 0.9239),
vec3(0.1802, -0.3604, 0.9239),
vec3(-0.1802, -0.3604, 0.9239),
vec3(0.3604, 0.1802, 0.9239),
vec3(-0.3604, 0.1802, 0.9239),
vec3(0.3604, -0.1802, 0.9239)
};
const float BayerMatrix8[8][8] =
{
{ 1.0 / 65.0, 49.0 / 65.0, 13.0 / 65.0, 61.0 / 65.0, 4.0 / 65.0, 52.0 / 65.0, 16.0 / 65.0, 64.0 / 65.0 },
{ 33.0 / 65.0, 17.0 / 65.0, 45.0 / 65.0, 29.0 / 65.0, 36.0 / 65.0, 20.0 / 65.0, 48.0 / 65.0, 32.0 / 65.0 },
{ 9.0 / 65.0, 57.0 / 65.0, 5.0 / 65.0, 53.0 / 65.0, 12.0 / 65.0, 60.0 / 65.0, 8.0 / 65.0, 56.0 / 65.0 },
{ 41.0 / 65.0, 25.0 / 65.0, 37.0 / 65.0, 21.0 / 65.0, 44.0 / 65.0, 28.0 / 65.0, 40.0 / 65.0, 24.0 / 65.0 },
{ 3.0 / 65.0, 51.0 / 65.0, 15.0 / 65.0, 63.0 / 65.0, 2.0 / 65.0, 50.0 / 65.0, 14.0 / 65.0, 62.0 / 65.0 },
{ 35.0 / 65.0, 19.0 / 65.0, 47.0 / 65.0, 31.0 / 65.0, 34.0 / 65.0, 18.0 / 65.0, 46.0 / 65.0, 30.0 / 65.0 },
{ 11.0 / 65.0, 59.0 / 65.0, 7.0 / 65.0, 55.0 / 65.0, 10.0 / 65.0, 58.0 / 65.0, 6.0 / 65.0, 54.0 / 65.0 },
{ 43.0 / 65.0, 27.0 / 65.0, 39.0 / 65.0, 23.0 / 65.0, 42.0 / 65.0, 26.0 / 65.0, 38.0 / 65.0, 22.0 / 65.0 }
};

View File

@ -13,32 +13,80 @@ out vec4 fragColor;
const float SMAA_REPROJECTION_WEIGHT_SCALE = 30.0; const float SMAA_REPROJECTION_WEIGHT_SCALE = 30.0;
// TODO: Move to a utility
bool isInvalidValue(float v) {
return (v != v) || (v > 65000.0) || (v < -65000.0);
}
bool hasInvalidValues(vec3 v) {
return isInvalidValue(v.x) || isInvalidValue(v.y) || isInvalidValue(v.z);
}
void main() { void main() {
vec4 current = textureLod(tex, texCoord, 0.0); vec4 current = textureLod(tex, texCoord, 0.0);
current.rgb = clamp(current.rgb, vec3(0.0), vec3(10.0));
current.a = clamp(current.a, 0.0, 1.0);
if (hasInvalidValues(current.rgb)) {
current = vec4(0.0, 0.0, 0.0, 1.0);
}
#ifdef _Veloc #ifdef _Veloc
// Velocity is assumed to be calculated for motion blur, so we need to inverse it for reprojection // Velocity is assumed to be calculated for motion blur, so we need to inverse it for reprojection
vec2 velocity = -textureLod(sveloc, texCoord, 0.0).rg; vec2 velocity = -textureLod(sveloc, texCoord, 0.0).rg;
velocity = clamp(velocity, vec2(-1.0), vec2(1.0));
if (isInvalidValue(velocity.x) || isInvalidValue(velocity.y)) {
velocity = vec2(0.0);
}
#ifdef _InvY #ifdef _InvY
velocity.y = -velocity.y; velocity.y = -velocity.y;
#endif #endif
// Reproject current coordinates and fetch previous pixel // Reproject current coordinates and fetch previous pixel
vec4 previous = textureLod(tex2, texCoord + velocity, 0.0); vec2 prevCoord = texCoord + velocity;
prevCoord = clamp(prevCoord, vec2(0.0), vec2(1.0));
vec4 previous = textureLod(tex2, prevCoord, 0.0);
previous.rgb = clamp(previous.rgb, vec3(0.0), vec3(10.0));
previous.a = clamp(previous.a, 0.0, 1.0);
if (hasInvalidValues(previous.rgb)) {
previous = current; // Fallback to current frame if previous is corrupted
}
// Attenuate the previous pixel if the velocity is different
#ifdef _SMAA #ifdef _SMAA
float delta = abs(current.a * current.a - previous.a * previous.a) / 5.0; float currentAlpha = clamp(current.a, 0.0, 1.0);
float previousAlpha = clamp(previous.a, 0.0, 1.0);
float delta = abs(currentAlpha * currentAlpha - previousAlpha * previousAlpha) / 5.0;
delta = clamp(delta, 0.0, 1.0); // Ensure delta is in valid range
#else #else
const float delta = 0.0; const float delta = 0.0;
#endif #endif
float weight = 0.5 * clamp(1.0 - sqrt(delta) * SMAA_REPROJECTION_WEIGHT_SCALE, 0.0, 1.0);
// Blend the pixels according to the calculated weight: float weight = 0.5 * clamp(1.0 - sqrt(max(delta, 0.0)) * SMAA_REPROJECTION_WEIGHT_SCALE, 0.0, 1.0);
fragColor = vec4(mix(current.rgb, previous.rgb, weight), 1.0);
vec3 blended = mix(current.rgb, previous.rgb, weight);
blended = clamp(blended, vec3(0.0), vec3(10.0));
if (hasInvalidValues(blended)) {
blended = current.rgb;
}
fragColor = vec4(blended, 1.0);
#else #else
vec4 previous = textureLod(tex2, texCoord, 0.0); vec4 previous = textureLod(tex2, texCoord, 0.0);
fragColor = vec4(mix(current.rgb, previous.rgb, 0.5), 1.0);
previous.rgb = clamp(previous.rgb, vec3(0.0), vec3(10.0));
if (hasInvalidValues(previous.rgb)) {
previous.rgb = current.rgb;
}
vec3 blended = mix(current.rgb, previous.rgb, 0.5);
blended = clamp(blended, vec3(0.0), vec3(10.0));
if (hasInvalidValues(blended)) {
blended = current.rgb;
}
fragColor = vec4(blended, 1.0);
#endif #endif
} }

View File

@ -0,0 +1,79 @@
/*
Copyright (c) 2024 Turánszki János
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#version 450
layout (local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
#include "compiled.inc"
#include "std/math.glsl"
#include "std/gbuffer.glsl"
#include "std/imageatomic.glsl"
#include "std/conetrace.glsl"
uniform sampler2D gbufferD;
uniform sampler2D gbuffer0;
uniform sampler3D voxels;
uniform sampler3D voxelsSDF;
uniform sampler2D gbuffer_refraction;
uniform layout(rgba8) image2D voxels_refraction;
uniform float clipmaps[voxelgiClipmapCount * 10];
uniform mat4 InvVP;
uniform vec2 cameraProj;
uniform vec3 eye;
uniform vec3 eyeLook;
uniform vec2 postprocess_resolution;
void main() {
const vec2 pixel = gl_GlobalInvocationID.xy;
vec2 uv = (pixel + 0.5) / postprocess_resolution;
#ifdef _InvY
uv.y = 1.0 - uv.y
#endif
float depth = textureLod(gbufferD, uv, 0.0).r * 2.0 - 1.0;
if (depth == 0) return;
vec2 ior_opac = textureLod(gbuffer_refraction, uv, 0.0).xy;
float x = uv.x * 2 - 1;
float y = uv.y * 2 - 1;
vec4 v = vec4(x, y, 1.0, 1.0);
v = vec4(InvVP * v);
v.xyz /= v.w;
vec3 viewRay = v.xyz - eye;
vec3 P = getPos(eye, eyeLook, normalize(viewRay), depth, cameraProj);
vec4 g0 = textureLod(gbuffer0, uv, 0.0);
vec3 n;
n.z = 1.0 - abs(g0.x) - abs(g0.y);
n.xy = n.z >= 0.0 ? g0.xy : octahedronWrap(g0.xy);
n = normalize(n);
vec3 color = vec3(0.0);
if(ior_opac.y < 1.0)
color = traceRefraction(P, n, voxels, voxelsSDF, normalize(eye - P), ior_opac.x, g0.b, clipmaps, pixel).rgb;
imageStore(voxels_refraction, ivec2(pixel), vec4(color, 1.0));
}

View File

@ -0,0 +1,75 @@
/*
Copyright (c) 2024 Turánszki János
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#version 450
layout (local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
#include "compiled.inc"
#include "std/math.glsl"
#include "std/gbuffer.glsl"
#include "std/imageatomic.glsl"
#include "std/conetrace.glsl"
uniform sampler3D voxels;
uniform sampler3D voxelsSDF;
uniform sampler2D gbufferD;
uniform sampler2D gbuffer0;
uniform layout(r16) image2D voxels_shadows;
uniform float clipmaps[voxelgiClipmapCount * 10];
uniform mat4 InvVP;
uniform vec2 cameraProj;
uniform vec3 eye;
uniform vec3 eyeLook;
uniform vec2 postprocess_resolution;
uniform vec3 lPos;
void main() {
const vec2 pixel = gl_GlobalInvocationID.xy;
vec2 uv = (pixel + 0.5) / postprocess_resolution;
#ifdef _InvY
uv.y = 1.0 - uv.y;
#endif
float depth = textureLod(gbufferD, uv, 0.0).r * 2.0 - 1.0;
if (depth == 0) return;
float x = uv.x * 2 - 1;
float y = uv.y * 2 - 1;
vec4 v = vec4(x, y, 1.0, 1.0);
v = vec4(InvVP * v);
v.xyz /= v.w;
vec3 viewRay = v.xyz - eye;
vec3 P = getPos(eye, eyeLook, normalize(viewRay), depth, cameraProj);
vec4 g0 = textureLod(gbuffer0, uv, 0.0);
vec3 n;
n.z = 1.0 - abs(g0.x) - abs(g0.y);
n.xy = n.z >= 0.0 ? g0.xy : octahedronWrap(g0.xy);
n = normalize(n);
float occ = 1.0 - traceShadow(P, n, voxels, voxelsSDF, normalize(lPos - P), clipmaps, pixel);
imageStore(voxels_shadows, ivec2(pixel), vec4(occ));
}

View File

@ -75,17 +75,16 @@ vec4 binarySearch(vec3 dir) {
} }
vec4 rayCast(vec3 dir) { vec4 rayCast(vec3 dir) {
float ddepth; #ifdef _CPostprocess
dir *= ss_refractionRayStep; dir *= PPComp9.x;
for (int i = 0; i < maxSteps; i++) { #else
hitCoord += dir; dir *= ssrRayStep;
ddepth = getDeltaDepth(hitCoord); #endif
if (ddepth > 0.0) for (int i = 0; i < maxSteps; i++) {
return binarySearch(dir); hitCoord += dir;
} if (getDeltaDepth(hitCoord) > 0.0) return binarySearch(dir);
// No hit — fallback to projecting the ray to UV space }
vec2 fallbackUV = getProjectedCoord(hitCoord); return vec4(0.0);
return vec4(fallbackUV, 0.0, 0.5); // We set .w lower to indicate fallback
} }
#endif //SSR #endif //SSR

View File

@ -24,11 +24,11 @@ class App {
public static var renderPathTime: Float; public static var renderPathTime: Float;
public static var endFrameCallbacks: Array<Void->Void> = []; public static var endFrameCallbacks: Array<Void->Void> = [];
#end #end
static var last = 0.0;
static var time = 0.0; static var time = 0.0;
static var lastw = -1; static var lastw = -1;
static var lasth = -1; static var lasth = -1;
public static var onResize: Void->Void = null; public static var onResize: Void->Void = null; // TODO: deprecate this. Use leenkx.system.Signal 'resized' instead.
public static var resized: leenkx.system.Signal = new leenkx.system.Signal(); // args: (w: Int, h: Int)
public static function init(done: Void->Void) { public static function init(done: Void->Void) {
new App(done); new App(done);
@ -53,7 +53,39 @@ class App {
static function update() { static function update() {
if (Scene.active == null || !Scene.active.ready) return; if (Scene.active == null || !Scene.active.ready) return;
// VR is handling it so we prevent double updates
// TODO: avoid js.Syntax
#if (kha_webgl && lnx_vr)
var vrActive = false;
js.Syntax.code("
if (typeof kha !== 'undefined' && kha.vr && kha.vr.VrInterface) {
const vr = kha.vr.VrInterface.instance;
if (vr && vr.IsPresenting && vr.IsPresenting()) {
{0} = true;
}
}
", vrActive);
if (vrActive) return;
#end
iron.system.Time.update(); iron.system.Time.update();
if (lastw == -1) {
lastw = App.w();
lasth = App.h();
}
if (lastw != App.w() || lasth != App.h()) {
resized.emit(App.w(), 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
@ -62,7 +94,6 @@ class App {
Scene.active.updateFrame(); Scene.active.updateFrame();
time += iron.system.Time.delta; time += iron.system.Time.delta;
while (time >= iron.system.Time.fixedStep) { while (time >= iron.system.Time.fixedStep) {
@ -70,16 +101,19 @@ class App {
time -= iron.system.Time.fixedStep; time -= iron.system.Time.fixedStep;
} }
@:privateAccess iron.system.Time._fixedStepInterpolation = time / iron.system.Time.fixedStep;
var i = 0; var i = 0;
var l = traitUpdates.length; var l = traitUpdates.length;
while (i < l) { while (i < l) {
if (traitInits.length > 0) { while (traitInits.length > 0) {
for (f in traitInits) { var f = traitInits.shift();
traitInits.length > 0 ? f() : break; if (f != null) f();
} }
traitInits.splice(0, traitInits.length); // Re-check bounds after processing inits (scene switch may have removed traits)
if (i < traitUpdates.length) {
traitUpdates[i]();
} }
traitUpdates[i]();
// Account for removed traits // Account for removed traits
l <= traitUpdates.length ? i++ : l = traitUpdates.length; l <= traitUpdates.length ? i++ : l = traitUpdates.length;
} }
@ -98,22 +132,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>) {
@ -131,13 +149,26 @@ class App {
startTime = kha.Scheduler.realTime(); startTime = kha.Scheduler.realTime();
#end #end
if (traitInits.length > 0) { while (traitInits.length > 0) {
for (f in traitInits) { var f = traitInits.shift();
traitInits.length > 0 ? f() : break; if (f != null) f();
}
traitInits.splice(0, traitInits.length);
} }
// skip for XR callback to handle rendering
// TODO: avoid js Syntax
#if (kha_webgl && lnx_vr)
var vrActive = false;
js.Syntax.code("
if (typeof kha !== 'undefined' && kha.vr && kha.vr.VrInterface) {
const vr = kha.vr.VrInterface.instance;
if (vr && vr.IsPresenting && vr.IsPresenting()) {
{0} = true;
}
}
", vrActive);
if (!vrActive) {
#end
Scene.active.renderFrame(frame.g4); Scene.active.renderFrame(frame.g4);
for (f in traitRenders) { for (f in traitRenders) {
@ -146,6 +177,10 @@ class App {
render2D(frame); render2D(frame);
#if (kha_webgl && lnx_vr)
}
#end
#if lnx_debug #if lnx_debug
renderPathTime = kha.Scheduler.realTime() - startTime; renderPathTime = kha.Scheduler.realTime() - startTime;
#end #end

View File

@ -18,10 +18,43 @@ import iron.object.LightObject;
import iron.object.MeshObject; import iron.object.MeshObject;
import iron.object.Uniforms; import iron.object.Uniforms;
import iron.object.Clipmap; import iron.object.Clipmap;
#if lnx_vr
import iron.math.Vec4;
import iron.math.Mat4;
import iron.math.Quat;
#end
class RenderPath { class RenderPath {
public static var active: RenderPath; public static var active: RenderPath;
#if lnx_vr
static var vrSimulateMode: Bool = false;
static var vrCameraOffsetSet:Bool = false;
static var vrCameraOffset:Vec4 = new Vec4();
static var wasVRPresenting:Bool = false;
public static var vrCalibrationPosition:Vec4 = null;
public static var vrCalibrationRotation:iron.math.Quat = null;
public static var vrCalibrationSaved:Bool = false;
public static var vrCenterCameraWorld:Mat4 = null;
static var vrOriginalSuperSample:Float = -1.0;
public static inline function isVRPresenting(): Bool {
#if (kha_webgl && lnx_vr)
return kha.vr.VrInterface.instance != null && kha.vr.VrInterface.instance.IsPresenting();
#else
return false;
#end
}
public static inline function isVRSimulateMode(): Bool {
return vrSimulateMode;
}
// TODO: done remove safely
public static inline function debugLog(msg: String, once: Bool = true): Void {
return;
}
#end
public var frameScissor = false; public var frameScissor = false;
public var frameScissorX = 0; public var frameScissorX = 0;
@ -43,9 +76,13 @@ class RenderPath {
public var isProbe = false; public var isProbe = false;
public var currentG: Graphics = null; public var currentG: Graphics = null;
public var frameG: Graphics; public var frameG: Graphics;
#if lnx_vr
var beginCalled = false;
var renderToXRFramebuffer = false;
#end
public var drawOrder = DrawOrder.Distance; public var drawOrder = DrawOrder.Distance;
public var paused = false; public var paused = false;
public var ready(get, null): Bool; public var ready(get, never): Bool;
function get_ready(): Bool { return loading == 0; } function get_ready(): Bool { return loading == 0; }
public var commands: Void->Void = null; public var commands: Void->Void = null;
public var setupDepthTexture: Void->Void = null; public var setupDepthTexture: Void->Void = null;
@ -123,9 +160,93 @@ class RenderPath {
public function renderFrame(g: Graphics) { public function renderFrame(g: Graphics) {
if (!ready || paused || iron.App.w() == 0 || iron.App.h() == 0) return; if (!ready || paused || iron.App.w() == 0 || iron.App.h() == 0) return;
if (lastW > 0 && (lastW != iron.App.w() || lastH != iron.App.h())) resize(); var appW = iron.App.w();
lastW = iron.App.w(); var appH = iron.App.h();
lastH = iron.App.h();
// use native XR framebuffer dimensions
#if (kha_webgl && lnx_vr)
if (kha.vr.VrInterface.instance != null) {
var vr = kha.vr.VrInterface.instance;
var isPresenting = vr != null && vr.IsPresenting();
// save/restore camera position between modes
if (!wasVRPresenting && isPresenting) {
if (Scene.active != null && Scene.active.camera != null) {
if (vrCalibrationPosition == null) vrCalibrationPosition = new Vec4();
if (vrCalibrationRotation == null) vrCalibrationRotation = new Quat();
vrCalibrationPosition.setFrom(Scene.active.camera.transform.loc);
vrCalibrationRotation.setFrom(Scene.active.camera.transform.rot);
vrCalibrationSaved = true;
}
// save original super sampling for later
vrOriginalSuperSample = leenkx.renderpath.Inc.superSample;
// compositeToXR function handles blitting to VR framebuffer
var xrVr: kha.js.vr.VrInterface = cast vr;
if (xrVr.xrGLLayer != null) {
var vrWidth = untyped xrVr.xrGLLayer.framebufferWidth;
var vrHeight = untyped xrVr.xrGLLayer.framebufferHeight;
}
}
else if (wasVRPresenting && !isPresenting) {
// reset VR frame time before anything else
#if (kha_webgl && lnx_vr)
iron.system.Time.vrFrameTime = -1.0;
#end
if (vrCalibrationSaved && Scene.active != null && Scene.active.camera != null) {
Scene.active.camera.transform.loc.setFrom(vrCalibrationPosition);
Scene.active.camera.transform.rot.setFrom(vrCalibrationRotation);
Scene.active.camera.buildMatrix();
Scene.active.camera.buildProjection();
}
// restore original super sampling from simulate mode
if (vrOriginalSuperSample >= 0.0) {
leenkx.renderpath.Inc.superSample = vrOriginalSuperSample;
for (rt in renderTargets) {
if (rt.raw.width == 0 && rt.raw.scale != null) {
rt.raw.scale = vrOriginalSuperSample;
}
}
resize();
vrOriginalSuperSample = -1.0;
}
// reset offset for next session
vrCameraOffsetSet = false;
vrCameraOffset = null;
}
wasVRPresenting = isPresenting;
if (isPresenting) {
// TODO: re-investigate using super sampling to avoid pixelation in simulate mode while giving max quality in headset
if (vrOriginalSuperSample >= 0.0 && leenkx.renderpath.Inc.superSample != 4.0) {
leenkx.renderpath.Inc.superSample = 4.0;
for (rt in renderTargets) {
if (rt.raw.width == 0 && rt.raw.scale != null) {
rt.raw.scale = 4.0;
}
}
resize();
}
var xrVr: kha.js.vr.VrInterface = cast vr;
if (xrVr.xrGLLayer != null) {
appW = xrVr.xrGLLayer.framebufferWidth;
appH = xrVr.xrGLLayer.framebufferHeight;
}
}
}
#end
if (lastW > 0 && (lastW != appW || lastH != appH)) resize();
lastW = appW;
lastH = appH;
frameTime = Time.time() - lastFrameTime; frameTime = Time.time() - lastFrameTime;
lastFrameTime = Time.time(); lastFrameTime = Time.time();
@ -191,7 +312,9 @@ class RenderPath {
} }
light = Scene.active.lights[0]; light = Scene.active.lights[0];
commands(); if (commands != null) {
commands();
}
if (!isProbe) frame++; if (!isProbe) frame++;
} }
@ -207,13 +330,13 @@ class RenderPath {
begin(frameG, Scene.active.camera.currentFace); begin(frameG, Scene.active.camera.currentFace);
} }
else { // Screen, planar probe else { // Screen, planar probe
currentW = iron.App.w(); currentW = kha.System.windowWidth();
currentH = iron.App.h(); currentH = kha.System.windowHeight();
if (frameScissor) setFrameScissor(); if (frameScissor) setFrameScissor();
begin(frameG); begin(frameG);
if (!isProbe) { if (!isProbe) {
setCurrentViewport(iron.App.w(), iron.App.h()); setCurrentViewport(kha.System.windowWidth(), kha.System.windowHeight());
setCurrentScissor(iron.App.w(), iron.App.h()); setCurrentScissor(kha.System.windowWidth(), kha.System.windowHeight());
} }
} }
} }
@ -258,16 +381,42 @@ class RenderPath {
if (currentG != null) end(); if (currentG != null) end();
currentG = g; currentG = g;
additionalTargets = additionalRenderTargets; additionalTargets = additionalRenderTargets;
face >= 0 ? g.beginFace(face) : g.begin(additionalRenderTargets);
// we still bind but skip begin() when explicitly rendering to XR framebuffer (renderToXRFramebuffer flag)
#if lnx_vr
if (!renderToXRFramebuffer) {
face >= 0 ? g.beginFace(face) : g.begin(additionalRenderTargets);
beginCalled = true;
} else {
// XR framebuffer is already bound by VrInterface so we dont rebind
beginCalled = false;
}
#else
face >= 0 ? g.beginFace(face) : g.begin(additionalRenderTargets);
#end
} }
inline function end() { inline function end() {
if (currentG == null) return;
if (scissorSet) { if (scissorSet) {
currentG.disableScissor(); currentG.disableScissor();
scissorSet = false; scissorSet = false;
} }
#if lnx_vr
if (beginCalled) {
currentG.end();
beginCalled = false;
}
// persist for rendering both eyes
if (!isVRPresenting()) {
currentG = null;
additionalTargets = null;
}
#else
currentG.end(); currentG.end();
currentG = null; currentG = null;
#end
bindParams = null; bindParams = null;
} }
@ -296,7 +445,9 @@ class RenderPath {
public function clearTarget(colorFlag: Null<Int> = null, depthFlag: Null<Float> = null) { public function clearTarget(colorFlag: Null<Int> = null, depthFlag: Null<Float> = null) {
if (colorFlag == -1) { // -1 == 0xffffffff if (colorFlag == -1) { // -1 == 0xffffffff
if (Scene.active.world != null) { if (Scene.active.world != null) {
colorFlag = Scene.active.world.raw.background_color; var col = Scene.active.world.raw.background_color;
var strength = Scene.active.world.probe != null ? Scene.active.world.probe.raw.strength : 1.0;
colorFlag = Color.fromFloats(((col >> 16) & 0xff) / 255 * strength, ((col >> 8) & 0xff) / 255 * strength, (col & 0xff) / 255 * strength);
} }
else if (Scene.active.camera != null) { else if (Scene.active.camera != null) {
var cc = Scene.active.camera.data.raw.clear_color; var cc = Scene.active.camera.data.raw.clear_color;
@ -327,18 +478,24 @@ class RenderPath {
if (depthDiff != 0) return depthDiff; if (depthDiff != 0) return depthDiff;
#end #end
return a.cameraDistance >= b.cameraDistance ? 1 : -1; if (a.cameraDistance == b.cameraDistance) return 0;
return a.cameraDistance > b.cameraDistance ? 1 : -1;
}); });
} }
public static function sortMeshesShader(meshes: Array<MeshObject>) { public static function sortMeshesIndex(meshes: Array<MeshObject>) {
meshes.sort(function(a, b): Int { meshes.sort(function(a, b): Int {
#if rp_depth_texture #if rp_depth_texture
var depthDiff = boolToInt(a.depthRead) - boolToInt(b.depthRead); var depthDiff = boolToInt(a.depthRead) - boolToInt(b.depthRead);
if (depthDiff != 0) return depthDiff; if (depthDiff != 0) return depthDiff;
#end #end
return a.materials[0].name >= b.materials[0].name ? 1 : -1; if (a.data.sortingIndex != b.data.sortingIndex) {
return a.data.sortingIndex > b.data.sortingIndex ? 1 : -1;
}
if (a.data.name == b.data.name) return 0;
return a.data.name > b.data.name ? 1 : -1;
}); });
} }
@ -399,7 +556,7 @@ class RenderPath {
#if lnx_batch #if lnx_batch
sortMeshesDistance(Scene.active.meshBatch.nonBatched); sortMeshesDistance(Scene.active.meshBatch.nonBatched);
#else #else
drawOrder == DrawOrder.Shader ? sortMeshesShader(meshes) : sortMeshesDistance(meshes); drawOrder == DrawOrder.Index ? sortMeshesIndex(meshes) : sortMeshesDistance(meshes);
#end #end
meshesSorted = true; meshesSorted = true;
} }
@ -514,48 +671,214 @@ class RenderPath {
end(); end();
} }
#if (rp_voxels != "Off")
public function getComputeShader(handle: String): kha.compute.Shader { public function getComputeShader(handle: String): kha.compute.Shader {
return Reflect.field(kha.Shaders, handle + "_comp"); return Reflect.field(kha.Shaders, handle + "_comp");
} }
#end
#if lnx_vr
// blits to each eyes viewport in the XR framebuffer.
public function compositeToXR(sourceTarget: String) {
#if (kha_webgl && lnx_vr)
var vr: kha.js.vr.VrInterface = cast kha.vr.VrInterface.instance;
if (vr == null || vr._glContext == null || vr.xrGLLayer == null) {
return;
}
var gl: js.html.webgl.WebGL2RenderingContext = cast vr._glContext;
var source = renderTargets.get(sourceTarget);
if (source == null) {
return;
}
var sourceFB: js.html.webgl.Framebuffer = untyped source.image.g4.renderTargetFrameBuffer;
if (sourceFB == null) {
return;
}
// trace('Framebuffer OK');
renderToXRFramebuffer = true;
gl.bindFramebuffer(js.html.webgl.WebGL2RenderingContext.DRAW_FRAMEBUFFER, vr.xrGLLayer.framebuffer);
gl.bindFramebuffer(js.html.webgl.WebGL2RenderingContext.READ_FRAMEBUFFER, sourceFB);
var readStatus = gl.checkFramebufferStatus(js.html.webgl.WebGL2RenderingContext.READ_FRAMEBUFFER);
var drawStatus = gl.checkFramebufferStatus(js.html.webgl.WebGL2RenderingContext.DRAW_FRAMEBUFFER);
if (readStatus != js.html.webgl.WebGL2RenderingContext.FRAMEBUFFER_COMPLETE ||
drawStatus != js.html.webgl.WebGL2RenderingContext.FRAMEBUFFER_COMPLETE) {
return;
}
var halfWidth = Std.int(source.image.width / 2);
var fullHeight = source.image.height;
if (vr._leftViewport != null) {
var vp = vr._leftViewport;
gl.blitFramebuffer(
0, 0, halfWidth, fullHeight,
vp.x, vp.y, vp.x + vp.width, vp.y + vp.height,
js.html.webgl.WebGL2RenderingContext.COLOR_BUFFER_BIT,
js.html.webgl.WebGL2RenderingContext.LINEAR
);
}
if (vr._rightViewport != null) {
var vp = vr._rightViewport;
gl.blitFramebuffer(
halfWidth, 0, source.image.width, fullHeight,
vp.x, vp.y, vp.x + vp.width, vp.y + vp.height,
js.html.webgl.WebGL2RenderingContext.COLOR_BUFFER_BIT,
js.html.webgl.WebGL2RenderingContext.LINEAR
);
}
gl.bindFramebuffer(js.html.webgl.WebGL2RenderingContext.FRAMEBUFFER, null);
renderToXRFramebuffer = false;
#end
}
#end
#if lnx_vr #if lnx_vr
public function drawStereo(drawMeshes: Void->Void) { public function drawStereo(drawMeshes: Void->Void) {
var vr = kha.vr.VrInterface.instance; vrSimulateMode = false;
if (currentG == null && frameG != null) {
currentG = frameG;
}
var appw = iron.App.w(); var appw = iron.App.w();
var apph = iron.App.h(); var apph = iron.App.h();
var halfw = Std.int(appw / 2);
var g = currentG; var g = currentG;
if (vr != null && vr.IsPresenting()) { // get render target dimensions not App.w/h gbuffer is scaled in simulate mode with supersampling
// Left eye
Scene.active.camera.V.setFrom(Scene.active.camera.leftV); var gbuffer0 = renderTargets.get("gbuffer0");
var actualWidth = (gbuffer0 != null && gbuffer0.image != null) ? gbuffer0.image.width : appw;
var actualHeight = (gbuffer0 != null && gbuffer0.image != null) ? gbuffer0.image.height : apph;
var actualHalfWidth = Std.int(actualWidth / 2);
var vrFBWidth = actualWidth;
var vrFBHeight = actualHeight;
var vrHalfWidth = actualHalfWidth;
var isVRPresenting = false;
vrSimulateMode = false;
var vr:Dynamic = null;
var vrExists = false;
#if (kha_webgl && lnx_vr)
if (kha.vr.VrInterface.instance != null) {
vr = kha.vr.VrInterface.instance;
vrExists = true;
}
#end
if (vrExists && vr != null && vr.IsPresenting()) {
vrSimulateMode = false;
isVRPresenting = true;
// get framebuffer dimensions from XR layer
#if (kha_webgl && lnx_vr)
var xrVr: kha.js.vr.VrInterface = cast vr;
if (xrVr.xrGLLayer != null) {
vrFBWidth = untyped xrVr.xrGLLayer.framebufferWidth;
vrFBHeight = untyped xrVr.xrGLLayer.framebufferHeight;
vrHalfWidth = Std.int(vrFBWidth / 2);
}
#end
if (Scene.active == null || Scene.active.camera == null) {
return;
}
#if (kha_webgl && lnx_vr)
if (vrCenterCameraWorld == null) vrCenterCameraWorld = Mat4.identity();
vrCenterCameraWorld.setFrom(Scene.active.camera.transform.world);
#end
// LEFT EYE
// HMD center for room scale position tracking
#if (kha_webgl && lnx_vr)
var xrVr: kha.js.vr.VrInterface = cast vr;
if (xrVr.currentViewerPose != null) {
var viewerTransform = untyped xrVr.currentViewerPose.transform;
if (viewerTransform != null && viewerTransform.position != null) {
// VR present calibration is used to position objects in world space not the camera
var pos = viewerTransform.position;
// camera follows headset directly in local floor space
Scene.active.camera.transform.loc.set(pos.x, pos.y, pos.z);
if (viewerTransform.orientation != null) {
Scene.active.camera.transform.rot.set(
viewerTransform.orientation.x,
viewerTransform.orientation.y,
viewerTransform.orientation.z,
viewerTransform.orientation.w
);
}
Scene.active.camera.transform.buildMatrix();
}
}
iron.system.VRController.updatePoses();
#end
Scene.active.camera.V.self = vr.GetViewMatrix(0);
Scene.active.camera.P.self = vr.GetProjectionMatrix(0); Scene.active.camera.P.self = vr.GetProjectionMatrix(0);
g.viewport(0, 0, halfw, apph); Scene.active.camera.VP.setFrom(Scene.active.camera.P);
Scene.active.camera.VP.multmat(Scene.active.camera.V);
Scene.active.camera.buildMatrix(); // update frustum for culling
var renderWidth = actualWidth;
var renderHeight = actualHeight;
var renderHalfWidth = actualHalfWidth;
// left half of render target
g.viewport(0, 0, renderHalfWidth, renderHeight);
g.scissor(0, 0, renderHalfWidth, renderHeight);
drawMeshes(); drawMeshes();
// Right eye // RIGHT EYE
begin(g, additionalTargets); Scene.active.camera.V.self = vr.GetViewMatrix(1);
Scene.active.camera.V.setFrom(Scene.active.camera.rightV);
Scene.active.camera.P.self = vr.GetProjectionMatrix(1); Scene.active.camera.P.self = vr.GetProjectionMatrix(1);
g.viewport(halfw, 0, halfw, apph); Scene.active.camera.VP.setFrom(Scene.active.camera.P);
Scene.active.camera.VP.multmat(Scene.active.camera.V);
Scene.active.camera.buildMatrix();
// right half of render target
g.viewport(renderHalfWidth, 0, renderHalfWidth, renderHeight);
g.scissor(renderHalfWidth, 0, renderHalfWidth, renderHeight);
drawMeshes(); drawMeshes();
// restore for post-processing
g.disableScissor();
g.viewport(0, 0, renderWidth, renderHeight);
} }
else { // Simulate else { // Simulate
Scene.active.camera.buildProjection(halfw / apph); vrSimulateMode = true;
var ipd_offset = 0.032 * 35.0;
// Left eye #if (kha_webgl && lnx_vr)
g.viewport(0, 0, halfw, apph); if (vrCenterCameraWorld == null) vrCenterCameraWorld = Mat4.identity();
vrCenterCameraWorld.setFrom(Scene.active.camera.transform.world);
#end
Scene.active.camera.buildProjection(actualHalfWidth / actualHeight);
Scene.active.camera.transform.move(Scene.active.camera.right(), -ipd_offset);
Scene.active.camera.buildMatrix();
g.viewport(0, 0, actualHalfWidth, actualHeight);
g.scissor(0, 0, actualHalfWidth, actualHeight);
drawMeshes(); drawMeshes();
// Right eye
begin(g, additionalTargets); begin(g, additionalTargets);
Scene.active.camera.transform.move(Scene.active.camera.right(), 0.032); Scene.active.camera.transform.move(Scene.active.camera.right(), ipd_offset * 2.0);
Scene.active.camera.buildMatrix(); Scene.active.camera.buildMatrix();
g.viewport(halfw, 0, halfw, apph); g.viewport(actualHalfWidth, 0, actualHalfWidth, actualHeight);
g.scissor(actualHalfWidth, 0, actualHalfWidth, actualHeight);
drawMeshes(); drawMeshes();
Scene.active.camera.transform.move(Scene.active.camera.right(), -0.032); Scene.active.camera.transform.move(Scene.active.camera.right(), -ipd_offset);
Scene.active.camera.buildMatrix(); Scene.active.camera.buildMatrix();
g.disableScissor();
g.viewport(0, 0, actualWidth, actualHeight);
} }
} }
#end #end
@ -912,8 +1235,8 @@ class CachedShaderContext {
public function new() {} public function new() {}
} }
@:enum abstract DrawOrder(Int) from Int { enum abstract DrawOrder(Int) from Int {
var Distance = 0; // Early-z var Distance = 0; // Early-z
var Shader = 1; // Less state changes var Index = 1; // Less state changes
// var Mix = 2; // Distance buckets sorted by shader // var Mix = 2; // Distance buckets sorted by shader
} }

View File

@ -64,7 +64,6 @@ class Scene {
#end #end
public var empties: Array<Object>; public var empties: Array<Object>;
public var animations: Array<Animation>; public var animations: Array<Animation>;
public var tilesheets: Array<Tilesheet>;
#if lnx_skin #if lnx_skin
public var armatures: Array<Armature>; public var armatures: Array<Armature>;
#end #end
@ -78,6 +77,9 @@ class Scene {
public var traitRemoves: Array<Void->Void> = []; public var traitRemoves: Array<Void->Void> = [];
var initializing: Bool; // Is the scene in its initialization phase? var initializing: Bool; // Is the scene in its initialization phase?
var spawnDepth: Int = 0; // Nested spawn counter (defer trait creation while > 0)
var spawning(get, never): Bool;
inline function get_spawning(): Bool return spawnDepth > 0;
public function new() { public function new() {
uid = uidCounter++; uid = uidCounter++;
@ -101,7 +103,6 @@ class Scene {
#end #end
empties = []; empties = [];
animations = []; animations = [];
tilesheets = [];
#if lnx_skin #if lnx_skin
armatures = []; armatures = [];
#end #end
@ -124,9 +125,8 @@ class Scene {
// Startup scene // Startup scene
active.addScene(format.name, null, function(sceneObject: Object) { active.addScene(format.name, null, function(sceneObject: Object) {
for (object in sceneObject.getChildren(true)) { // Create traits bottom-up (children first, then parents)
createTraits(object.raw.traits, object); createTraitsBottomUp(sceneObject);
}
#if lnx_terrain #if lnx_terrain
if (format.terrain_ref != null) { if (format.terrain_ref != null) {
@ -141,17 +141,29 @@ class Scene {
active.camera = active.getCamera(format.camera_ref); active.camera = active.getCamera(format.camera_ref);
active.sceneParent = sceneObject; active.sceneParent = sceneObject;
active.ready = true;
for (f in active.traitInits) f(); for (f in active.traitInits) f();
active.traitInits = []; active.traitInits = [];
active.ready = true;
active.initializing = false; active.initializing = false;
done(sceneObject); done(sceneObject);
}); });
}); });
} }
// Create traits in post-order (bottom-up): children's traits are created before parents.
// This ensures that when a parent's notifyOnInit runs, children are already initialized.
static function createTraitsBottomUp(object: Object) {
// First, recursively process all children
for (child in object.children) {
createTraitsBottomUp(child);
}
// Then create traits for this object
if (object.raw != null) {
createTraits(object.raw.traits, object);
}
}
#if lnx_patch #if lnx_patch
public static var getRenderPath: Void->RenderPath; public static var getRenderPath: Void->RenderPath;
public static function patch() { public static function patch() {
@ -212,6 +224,8 @@ class Scene {
Data.getSceneRaw(sceneName, function(format: TSceneFormat) { Data.getSceneRaw(sceneName, function(format: TSceneFormat) {
Scene.create(format, function(o: Object) { Scene.create(format, function(o: Object) {
framePassed = true;
if (done != null) done(o); if (done != null) done(o);
#if (rp_background == "World") #if (rp_background == "World")
@ -242,10 +256,6 @@ class Scene {
if (!ready || RenderPath.active == null) return; if (!ready || RenderPath.active == null) return;
framePassed = true; framePassed = true;
for (tilesheet in tilesheets) {
tilesheet.update();
}
// Render probes // Render probes
#if rp_probes #if rp_probes
var activeCamera = camera; var activeCamera = camera;
@ -289,33 +299,39 @@ class Scene {
return root.children.length > 0 ? root.children[0].getTrait(c) : null; return root.children.length > 0 ? root.children[0].getTrait(c) : null;
} }
// TODO: solve name referencing for linked objects
public function getMesh(name: String): MeshObject { public function getMesh(name: String): MeshObject {
for (m in meshes) if (m.name == name) return m; for (m in meshes) if (m.name == name) return m;
return null; return null;
} }
// TODO: solve name referencing for linked objects
public function getLight(name: String): LightObject { public function getLight(name: String): LightObject {
for (l in lights) if (l.name == name) return l; for (l in lights) if (l.name == name) return l;
return null; return null;
} }
// TODO: solve name referencing for linked objects
public function getCamera(name: String): CameraObject { public function getCamera(name: String): CameraObject {
for (c in cameras) if (c.name == name) return c; for (c in cameras) if (c.name == name) return c;
return null; return null;
} }
#if lnx_audio #if lnx_audio
// TODO: solve name referencing for linked objects
public function getSpeaker(name: String): SpeakerObject { public function getSpeaker(name: String): SpeakerObject {
for (s in speakers) if (s.name == name) return s; for (s in speakers) if (s.name == name) return s;
return null; return null;
} }
#end #end
// TODO: solve name referencing for linked objects
public function getEmpty(name: String): Object { public function getEmpty(name: String): Object {
for (e in empties) if (e.name == name) return e; for (e in empties) if (e.name == name) return e;
return null; return null;
} }
// TODO: solve name referencing for linked objects
public function getGroup(name: String): Array<Object> { public function getGroup(name: String): Array<Object> {
if (groups == null) groups = new Map(); if (groups == null) groups = new Map();
var g = groups.get(name); var g = groups.get(name);
@ -391,6 +407,7 @@ class Scene {
#end #end
var objectsCount = getObjectsCount(format.objects); var objectsCount = getObjectsCount(format.objects);
spawnDepth++; // Defer trait creation until all objects are ready
function traverseObjects(parent: Object, objects: Array<TObj>, parentObject: TObj, done: Void->Void) { function traverseObjects(parent: Object, objects: Array<TObj>, parentObject: TObj, done: Void->Void) {
if (objects == null) return; if (objects == null) return;
for (i in 0...objects.length) { for (i in 0...objects.length) {
@ -408,11 +425,16 @@ class Scene {
} }
if (format.objects == null || format.objects.length == 0) { if (format.objects == null || format.objects.length == 0) {
spawnDepth--;
createTraits(format.traits, parent); // Scene traits createTraits(format.traits, parent); // Scene traits
done(parent); done(parent);
} }
else { else {
traverseObjects(parent, format.objects, null, function() { // Scene objects traverseObjects(parent, format.objects, null, function() { // Scene objects
spawnDepth--;
if (!initializing) {
createTraitsBottomUp(parent);
}
createTraits(format.traits, parent); // Scene traits createTraits(format.traits, parent); // Scene traits
done(parent); done(parent);
}); });
@ -426,7 +448,7 @@ class Scene {
var result = objects.length; var result = objects.length;
for (o in objects) { for (o in objects) {
if (discardNoSpawn && o.spawn != null && o.spawn == false) continue; // Do not count children of non-spawned objects if (discardNoSpawn && o.spawn != null && o.spawn == false) continue; // Do not count children of non-spawned objects
if (o.children != null) result += getObjectsCount(o.children); if (o.children != null) result += getObjectsCount(o.children, discardNoSpawn);
} }
return result; return result;
} }
@ -440,11 +462,16 @@ class Scene {
@param srcRaw If not `null`, spawn the object from the given scene data instead of using the scene this function is called on. Useful to spawn objects from other scenes. @param srcRaw If not `null`, spawn the object from the given scene data instead of using the scene this function is called on. Useful to spawn objects from other scenes.
**/ **/
public function spawnObject(name: String, parent: Null<Object>, done: Null<Object->Void>, spawnChildren = true, srcRaw: Null<TSceneFormat> = null) { public function spawnObject(name: String, parent: Null<Object>, done: Null<Object->Void>, spawnChildren = true, srcRaw: Null<TSceneFormat> = null) {
spawnObjectInternal(name, parent, done, spawnChildren, srcRaw, true);
}
function spawnObjectInternal(name: String, parent: Null<Object>, done: Null<Object->Void>, spawnChildren: Bool, srcRaw: Null<TSceneFormat>, createTraits: Bool) {
if (srcRaw == null) srcRaw = raw; if (srcRaw == null) srcRaw = raw;
var objectsTraversed = 0; var objectsTraversed = 0;
var obj = getRawObjectByName(srcRaw, name); var obj = getRawObjectByName(srcRaw, name);
var objectsCount = spawnChildren ? getObjectsCount([obj], false) : 1; var objectsCount = spawnChildren ? getObjectsCount([obj], false) : 1;
var rootId = -1; var rootId = -1;
spawnDepth++; // Defer trait creation until all objects are ready
function spawnObjectTree(obj: TObj, parent: Object, parentObject: TObj, done: Object->Void) { function spawnObjectTree(obj: TObj, parent: Object, parentObject: TObj, done: Object->Void) {
createObject(obj, srcRaw, parent, parentObject, function(object: Object) { createObject(obj, srcRaw, parent, parentObject, function(object: Object) {
if (rootId == -1) { if (rootId == -1) {
@ -453,20 +480,27 @@ class Scene {
if (spawnChildren && obj.children != null) { if (spawnChildren && obj.children != null) {
for (child in obj.children) spawnObjectTree(child, object, obj, done); for (child in obj.children) spawnObjectTree(child, object, obj, done);
} }
if (++objectsTraversed == objectsCount && done != null) { if (++objectsTraversed == objectsCount) {
// Retrieve the originally spawned object from the current // Retrieve the originally spawned object from the current
// child object to ensure done() is called with the right // child object to ensure done() is called with the right
// object // object
while (object.uid != rootId) { while (object.uid != rootId) {
object = object.parent; object = object.parent;
} }
done(object); // Create traits bottom-up after all objects are ready
spawnDepth--;
if (createTraits) {
createTraitsBottomUp(object);
}
// Then call user callback
if (done != null) done(object);
} }
}); });
} }
spawnObjectTree(obj, parent, null, done); spawnObjectTree(obj, parent, null, done);
} }
// TODO: solve name referencing for linked objects
public function parseObject(sceneName: String, objectName: String, parent: Object, done: Object->Void) { public function parseObject(sceneName: String, objectName: String, parent: Object, done: Object->Void) {
Data.getSceneRaw(sceneName, function(format: TSceneFormat) { Data.getSceneRaw(sceneName, function(format: TSceneFormat) {
var o: TObj = getRawObjectByName(format, objectName); var o: TObj = getRawObjectByName(format, objectName);
@ -495,6 +529,10 @@ class Scene {
static function traverseObjs(children: Array<TObj>, name: String): TObj { static function traverseObjs(children: Array<TObj>, name: String): TObj {
for (o in children) { for (o in children) {
if (o.name == name) return o; if (o.name == name) return o;
else if (o.filename != "") {
var n: String = name + "_" + o.filename;
if (o.name == n) return o;
}
if (o.children != null) { if (o.children != null) {
var res = traverseObjs(o.children, name); var res = traverseObjs(o.children, name);
if (res != null) return res; if (res != null) return res;
@ -592,7 +630,7 @@ class Scene {
else { else {
for (object_ref in object_refs) { for (object_ref in object_refs) {
// Spawn top-level collection objects and their children // Spawn top-level collection objects and their children
spawnObject(object_ref, groupOwner, function(spawnedObject: Object) { spawnObjectInternal(object_ref, groupOwner, function(spawnedObject: Object) {
// Apply collection/group instance offset to all // Apply collection/group instance offset to all
// top-level parents of that group // top-level parents of that group
if (!isObjectInGroup(groupRef, spawnedObject.parent, format)) { if (!isObjectInGroup(groupRef, spawnedObject.parent, format)) {
@ -610,9 +648,10 @@ class Scene {
} }
if (++spawned == object_refs.length) { if (++spawned == object_refs.length) {
groupOwner.transform.reset(); groupOwner.transform.reset();
groupOwner.transform.buildMatrix();
done(); done();
} }
}, true, format); }, true, format, false);
} }
} }
} }
@ -764,6 +803,7 @@ class Scene {
#end #end
} }
// TODO: solve name referencing for linked objects
public function returnMeshObject(object_file: String, data_ref: String, sceneName: String, armature: #if lnx_skin Armature #else Null<Int> #end, materials: Vector<MaterialData>, parent: Object, parentObject: TObj, o: TObj, done: Object->Void) { public function returnMeshObject(object_file: String, data_ref: String, sceneName: String, armature: #if lnx_skin Armature #else Null<Int> #end, materials: Vector<MaterialData>, parent: Object, parentObject: TObj, o: TObj, done: Object->Void) {
Data.getMesh(object_file, data_ref, function(mesh: MeshData) { Data.getMesh(object_file, data_ref, function(mesh: MeshData) {
var object = addMeshObject(mesh, materials, parent); var object = addMeshObject(mesh, materials, parent);
@ -779,9 +819,9 @@ class Scene {
for (ref in o.particle_refs) cast(object, MeshObject).setupParticleSystem(sceneName, ref); for (ref in o.particle_refs) cast(object, MeshObject).setupParticleSystem(sceneName, ref);
} }
#end #end
// Attach tilesheet // Attach tilesheet from embedded object data
if (o.tilesheet_ref != null) { if (o.tilesheet != null) {
cast(object, MeshObject).setupTilesheet(sceneName, o.tilesheet_ref, o.tilesheet_action_ref); cast(object, MeshObject).setupTilesheet(o.tilesheet);
} }
if (o.camera_list != null){ if (o.camera_list != null){
@ -820,6 +860,7 @@ class Scene {
if (object != null) { if (object != null) {
object.raw = o; object.raw = o;
object.name = o.name; object.name = o.name;
if (o.filename != null) object.filename = o.filename;
if (o.visible != null) object.visible = o.visible; if (o.visible != null) object.visible = o.visible;
if (o.visible_mesh != null) object.visibleMesh = o.visible_mesh; if (o.visible_mesh != null) object.visibleMesh = o.visible_mesh;
if (o.visible_shadow != null) object.visibleShadow = o.visible_shadow; if (o.visible_shadow != null) object.visibleShadow = o.visible_shadow;
@ -848,9 +889,9 @@ class Scene {
} }
} }
// If the scene is still initializing, traits will be created later // If the scene is still initializing or spawning, traits will be created later
// to ensure that object references for trait properties are valid // to ensure that object references for trait properties are valid
if (!active.initializing) createTraits(o.traits, object); if (!active.initializing && !active.spawning) createTraits(o.traits, object);
} }
done(object); done(object);
} }
@ -882,17 +923,15 @@ class Scene {
// Set trait properties // Set trait properties
if (t.props != null) { if (t.props != null) {
var traitFields = Type.getInstanceFields(Type.getClass(traitInst));
for (i in 0...Std.int(t.props.length / 3)) { for (i in 0...Std.int(t.props.length / 3)) {
var pname: String = t.props[i * 3]; var pname: String = t.props[i * 3];
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 (traitFields.indexOf(pname) == -1) continue;
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) {
@ -954,7 +993,14 @@ class Scene {
static function createTraitClassInstance(traitName: String, args: Array<Dynamic>): Dynamic { static function createTraitClassInstance(traitName: String, args: Array<Dynamic>): Dynamic {
var cname = Type.resolveClass(traitName); var cname = Type.resolveClass(traitName);
if (cname == null) return null; if (cname == null) return null;
return Type.createInstance(cname, args); var trait:Dynamic;
try {
trait = Type.createInstance(cname, args);
} catch(e) {
trace("Error creating trait: " + traitName + " - " + e);
trait = null;
}
return trait;
} }
function loadEmbeddedData(datas: Array<String>, done: Void->Void) { function loadEmbeddedData(datas: Array<String>, done: Void->Void) {

View File

@ -14,9 +14,9 @@ class Trait {
var _add: Array<Void->Void> = null; var _add: Array<Void->Void> = null;
var _init: Array<Void->Void> = null; var _init: Array<Void->Void> = null;
var _remove: Array<Void->Void> = null; var _remove: Array<Void->Void> = null;
var _fixedUpdate: Array<Void->Void> = null;
var _update: Array<Void->Void> = null; var _update: Array<Void->Void> = null;
var _lateUpdate: Array<Void->Void> = null; var _lateUpdate: Array<Void->Void> = null;
var _fixedUpdate: Array<Void->Void> = null;
var _render: Array<kha.graphics4.Graphics->Void> = null; var _render: Array<kha.graphics4.Graphics->Void> = null;
var _render2D: Array<kha.graphics2.Graphics->Void> = null; var _render2D: Array<kha.graphics2.Graphics->Void> = null;

View File

@ -37,7 +37,9 @@ class Armature {
} }
public function getAction(name: String): TAction { public function getAction(name: String): TAction {
for (a in actions) if (a.name == name) return a; for (a in actions) {
if (a.name == name) return a;
}
return null; return null;
} }

View File

@ -416,7 +416,7 @@ class Data {
var loading = loadingBlobs.get(file); // Is already being loaded var loading = loadingBlobs.get(file); // Is already being loaded
if (loading != null) { if (loading != null) {
loading.push(done); loading.push(done);
return; //return;
} }
loadingBlobs.set(file, [done]); // Start loading loadingBlobs.set(file, [done]); // Start loading

View File

@ -9,6 +9,7 @@ import iron.data.SceneFormat;
class MeshData { class MeshData {
public var name: String; public var name: String;
public var sortingIndex: Int;
public var raw: TMeshData; public var raw: TMeshData;
public var format: TSceneFormat; public var format: TSceneFormat;
public var geom: Geometry; public var geom: Geometry;
@ -23,6 +24,7 @@ class MeshData {
public function new(raw: TMeshData, done: MeshData->Void) { public function new(raw: TMeshData, done: MeshData->Void) {
this.raw = raw; this.raw = raw;
this.name = raw.name; this.name = raw.name;
this.sortingIndex = raw.sorting_index;
if (raw.scale_pos != null) scalePos = raw.scale_pos; if (raw.scale_pos != null) scalePos = raw.scale_pos;
if (raw.scale_tex != null) scaleTex = raw.scale_tex; if (raw.scale_tex != null) scaleTex = raw.scale_tex;

View File

@ -31,7 +31,6 @@ typedef TSceneFormat = {
@:optional public var speaker_datas: Array<TSpeakerData>; @:optional public var speaker_datas: Array<TSpeakerData>;
@:optional public var world_datas: Array<TWorldData>; @:optional public var world_datas: Array<TWorldData>;
@:optional public var world_ref: String; @:optional public var world_ref: String;
@:optional public var tilesheet_datas: Array<TTilesheetData>;
@:optional public var objects: Array<TObj>; @:optional public var objects: Array<TObj>;
@:optional public var groups: Array<TGroup>; @:optional public var groups: Array<TGroup>;
@:optional public var gravity: Float32Array; @:optional public var gravity: Float32Array;
@ -49,6 +48,7 @@ typedef TMeshData = {
@:structInit class TMeshData { @:structInit class TMeshData {
#end #end
public var name: String; public var name: String;
public var sorting_index: Int;
public var vertex_arrays: Array<TVertexArray>; public var vertex_arrays: Array<TVertexArray>;
public var index_arrays: Array<TIndexArray>; public var index_arrays: Array<TIndexArray>;
@:optional public var dynamic_usage: Null<Bool>; @:optional public var dynamic_usage: Null<Bool>;
@ -168,6 +168,7 @@ typedef TShaderOverride = {
@:structInit class TShaderOverride { @:structInit class TShaderOverride {
#end #end
@:optional public var cull_mode: String; @:optional public var cull_mode: String;
@:optional public var compare_mode: String;
@:optional public var addressing: String; @:optional public var addressing: String;
@:optional public var filter: String; @:optional public var filter: String;
@:optional public var shared_sampler: String; @:optional public var shared_sampler: String;
@ -222,6 +223,7 @@ typedef TShaderData = {
@:structInit class TShaderData { @:structInit class TShaderData {
#end #end
public var name: String; public var name: String;
public var next_pass: String;
public var contexts: Array<TShaderContext>; public var contexts: Array<TShaderContext>;
} }
@ -362,18 +364,6 @@ typedef TProbeData = {
@:optional public var radiance_mipmaps: Null<Int>; @:optional public var radiance_mipmaps: Null<Int>;
} }
#if js
typedef TTilesheetData = {
#else
@:structInit class TTilesheetData {
#end
public var name: String;
public var tilesx: Int;
public var tilesy: Int;
public var framerate: Int;
public var actions: Array<TTilesheetAction>;
}
#if js #if js
typedef TTilesheetAction = { typedef TTilesheetAction = {
#else #else
@ -383,6 +373,31 @@ typedef TTilesheetAction = {
public var start: Int; public var start: Int;
public var end: Int; public var end: Int;
public var loop: Bool; public var loop: Bool;
public var tilesx: Int;
public var tilesy: Int;
public var framerate: Int;
@:optional public var mesh: String; // Optional mesh to swap to when playing this action
@:optional public var events: Array<TTilesheetEvent>; // Optional events triggered on specific frames
}
#if js
typedef TTilesheetEvent = {
#else
@:structInit class TTilesheetEvent {
#end
public var name: String; // Event name
public var frame: Int; // Frame number when event triggers
}
#if js
typedef TTilesheetData = {
#else
@:structInit class TTilesheetData {
#end
public var actions: Array<TTilesheetAction>;
public var start_action: String;
public var flipx: Bool;
public var flipy: Bool;
} }
#if js #if js
@ -390,25 +405,47 @@ typedef TParticleData = {
#else #else
@:structInit class TParticleData { @:structInit class TParticleData {
#end #end
// Format
public var fps: Int;
public var name: String; public var name: String;
public var type: Int; // 0 - Emitter, Hair public var type: Int; // 0 - Emitter, Hair
// Lnx
public var auto_start: Bool; public var auto_start: Bool;
public var dynamic_emitter: Bool;
public var is_unique: Bool; public var is_unique: Bool;
public var local_coords: Bool;
public var loop: Bool; public var loop: Bool;
// Emission
public var count: Int; public var count: Int;
// public var hair_length: FastFloat; TODO
public var frame_start: FastFloat; public var frame_start: FastFloat;
public var frame_end: FastFloat; public var frame_end: FastFloat;
public var lifetime: FastFloat; public var lifetime: FastFloat;
public var lifetime_random: FastFloat; public var lifetime_random: FastFloat;
public var emit_from: Int; // 0 - Vert, 1 - Face, 2 - Volume public var emit_from: Int; // 0 - Vert, 1 - Face, 2 - Volume
// Velocity
public var object_align_factor: Float32Array; public var object_align_factor: Float32Array;
public var factor_random: FastFloat; public var factor_random: FastFloat;
// Rotation
public var use_rotations: Bool;
public var rotation_mode: Int; // 0 - None, 1 - Normal, 2 - Normal-Tangent, 3 - Velocity/Hair, 4 - Global X, 5 - Global Y, 6 - Global Z, 7 - Object X, 8 - Object Y, 9 - Object Z
public var rotation_factor_random: Float;
public var phase_factor: Float;
public var phase_factor_random: Float;
public var use_dynamic_rotation: Bool;
// Physics
public var physics_type: Int; // 0 - No, 1 - Newton public var physics_type: Int; // 0 - No, 1 - Newton
public var mass: FastFloat;
// Render
public var particle_size: FastFloat; // Object scale public var particle_size: FastFloat; // Object scale
public var size_random: FastFloat; // Random scale public var size_random: FastFloat; // Random scale
public var mass: FastFloat; public var show_emitter: Bool;
public var instance_object: String; // Object reference public var instance_object: String; // Object reference
// Field Weights
public var weight_gravity: FastFloat; public var weight_gravity: FastFloat;
public var weight_texture: FastFloat;
// Textures
public var texture_slots: Dynamic;
} }
#if js #if js
@ -430,6 +467,7 @@ typedef TObj = {
public var name: String; public var name: String;
public var data_ref: String; public var data_ref: String;
public var transform: TTransform; public var transform: TTransform;
@:optional public var filename: String; // For objects instanced from external files
@:optional public var material_refs: Array<String>; @:optional public var material_refs: Array<String>;
@:optional public var particle_refs: Array<TParticleReference>; @:optional public var particle_refs: Array<TParticleReference>;
@:optional public var render_emitter: Bool; @:optional public var render_emitter: Bool;
@ -459,8 +497,7 @@ typedef TObj = {
@:optional public var mobile: Null<Bool>; @:optional public var mobile: Null<Bool>;
@:optional public var spawn: Null<Bool>; // Auto add object when creating scene @:optional public var spawn: Null<Bool>; // Auto add object when creating scene
@:optional public var local_only: Null<Bool>; // Apply parent matrix @:optional public var local_only: Null<Bool>; // Apply parent matrix
@:optional public var tilesheet_ref: String; @:optional public var tilesheet: TTilesheetData; // Embedded tilesheet data
@:optional public var tilesheet_action_ref: String;
@:optional public var sampled: Null<Bool>; // Object action @:optional public var sampled: Null<Bool>; // Object action
@:optional public var is_ik_fk_only: Null<Bool>; // Bone IK or FK only @:optional public var is_ik_fk_only: Null<Bool>; // Bone IK or FK only
@:optional public var bone_layers: Array<Bool>; // Bone Layer @:optional public var bone_layers: Array<Bool>; // Bone Layer

View File

@ -22,6 +22,7 @@ using StringTools;
class ShaderData { class ShaderData {
public var name: String; public var name: String;
public var nextPass: String;
public var raw: TShaderData; public var raw: TShaderData;
public var contexts: Array<ShaderContext> = []; public var contexts: Array<ShaderContext> = [];
@ -33,6 +34,7 @@ class ShaderData {
public function new(raw: TShaderData, done: ShaderData->Void, overrideContext: TShaderOverride = null) { public function new(raw: TShaderData, done: ShaderData->Void, overrideContext: TShaderOverride = null) {
this.raw = raw; this.raw = raw;
this.name = raw.name; this.name = raw.name;
this.nextPass = raw.next_pass;
for (c in raw.contexts) contexts.push(null); for (c in raw.contexts) contexts.push(null);
var contextsLoaded = 0; var contextsLoaded = 0;
@ -228,6 +230,9 @@ class ShaderContext {
if (overrideContext.cull_mode != null) { if (overrideContext.cull_mode != null) {
pipeState.cullMode = getCullMode(overrideContext.cull_mode); pipeState.cullMode = getCullMode(overrideContext.cull_mode);
} }
if (overrideContext.compare_mode != null) {
pipeState.depthMode = getCompareMode(overrideContext.compare_mode);
}
} }
pipeState.compile(); pipeState.compile();

View File

@ -117,7 +117,8 @@ class TerrainStream {
vertex_arrays: [pos, nor, tex], vertex_arrays: [pos, nor, tex],
index_arrays: [ind], index_arrays: [ind],
scale_pos: scalePos, scale_pos: scalePos,
scale_tex: 1.0 scale_tex: 1.0,
sorting_index: 0
}; };
new MeshData(rawmeshData, function(data: MeshData) { new MeshData(rawmeshData, function(data: MeshData) {

View File

@ -14,7 +14,7 @@ class Animation {
public var isSkinned: Bool; public var isSkinned: Bool;
public var isSampled: Bool; public var isSampled: Bool;
public var action = ""; @:isVar public var action(get, default) = "";
#if lnx_skin #if lnx_skin
public var armature: iron.data.Armature; // Bone public var armature: iron.data.Armature; // Bone
#end #end
@ -57,6 +57,10 @@ class Animation {
play(); play();
} }
function get_action(): String {
return action;
}
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.blendTime = blendTime;
@ -98,7 +102,7 @@ class Animation {
else { else {
sampler.timeOld = sampler.time; sampler.timeOld = sampler.time;
sampler.offsetOld = sampler.offset; sampler.offsetOld = sampler.offset;
sampler.setTimeOnly(sampler.time + delta * sampler.speed); sampler.setTimeOnly(sampler.time + delta * sampler.speed * iron.system.Time.scale);
updateActionTrack(sampler); updateActionTrack(sampler);
} }
} }
@ -141,6 +145,7 @@ class Animation {
sampler.cacheSet = false; sampler.cacheSet = false;
sampler.trackEnd = false; sampler.trackEnd = false;
if (anim == null || anim.tracks == null || anim.tracks.length == 0) return;
var track = anim.tracks[0]; var track = anim.tracks[0];
if (frameIndex == -1) { if (frameIndex == -1) {
@ -442,7 +447,12 @@ class ActionSampler {
*/ */
public inline function setObjectAction(actionData: TObj) { public inline function setObjectAction(actionData: TObj) {
this.actionData = [actionData]; this.actionData = [actionData];
this.totalFrames = actionData.anim.tracks[0].frames.length; if (actionData != null && actionData.anim != null && actionData.anim.tracks != null && actionData.anim.tracks.length > 0) {
this.totalFrames = actionData.anim.tracks[0].frames.length;
}
else {
this.totalFrames = 0;
}
actionDataInit = true; actionDataInit = true;
} }

View File

@ -14,6 +14,8 @@ import iron.data.Armature;
import iron.data.Data; import iron.data.Data;
import iron.math.Ray; import iron.math.Ray;
import StringTools;
class BoneAnimation extends Animation { class BoneAnimation extends Animation {
public static var skinMaxBones = 128; public static var skinMaxBones = 128;
@ -84,6 +86,15 @@ class BoneAnimation extends Animation {
} }
} }
override function get_action(): String {
var an: String = action; // an -> action name
if (an != "" && object != null && object.filename != "") {
var sufix = "_" + object.filename;
if (an.indexOf(sufix) != -1) an = StringTools.replace(an, sufix, "");
}
return an;
}
public function initMatsEmpty(): Array<Mat4> { public function initMatsEmpty(): Array<Mat4> {
var mats = []; var mats = [];
@ -108,9 +119,11 @@ class BoneAnimation extends Animation {
object.transform.rot.set(0, 0, 0, 1); object.transform.rot.set(0, 0, 0, 1);
object.transform.buildMatrix(); object.transform.buildMatrix();
var refs = mo.parent.raw.bone_actions; if (mo.parent != null && mo.parent.raw != null && mo.parent.raw.bone_actions != null) {
if (refs != null && refs.length > 0) { var refs = mo.parent.raw.bone_actions;
Data.getSceneRaw(refs[0], function(action: TSceneFormat) { play(action.name); }); if (refs.length > 0) {
Data.getSceneRaw(refs[0], function(action: TSceneFormat) { play(action.name); });
}
} }
} }
if (armatureObject.raw.relative_bone_constraints) relativeBoneConstraints = true; if (armatureObject.raw.relative_bone_constraints) relativeBoneConstraints = true;
@ -134,6 +147,15 @@ class BoneAnimation extends Animation {
} }
} }
function getName(n: String): String {
var fn: String = n; // fn -> final name
if (fn != "" && object != null && object.filename != "") {
var sufix = "_" + object.filename;
if (fn.indexOf(sufix) == -1) fn += sufix;
}
return fn;
}
@:access(iron.object.Transform) @:access(iron.object.Transform)
function updateBoneChildren(bone: TObj, bm: Mat4) { function updateBoneChildren(bone: TObj, bm: Mat4) {
var ar = boneChildren.get(bone.name); var ar = boneChildren.get(bone.name);
@ -183,8 +205,10 @@ class BoneAnimation extends Animation {
} }
function setAction(action: String) { function setAction(action: String) {
if (armature == null) return;
armature.initMats(); armature.initMats();
var a = armature.getAction(action); var a = armature.getAction(action);
if (a == null) return;
skeletonBones = a.bones; skeletonBones = a.bones;
skeletonMats = a.mats; skeletonMats = a.mats;
if(! rootMotionCacheInit) skeletonMats.push(Mat4.identity()); if(! rootMotionCacheInit) skeletonMats.push(Mat4.identity());
@ -193,8 +217,11 @@ class BoneAnimation extends Animation {
} }
function getAction(action: String): Array<TObj> { function getAction(action: String): Array<TObj> {
if (armature == null) return null;
armature.initMats(); armature.initMats();
return armature.getAction(action).bones; var a = armature.getAction(action);
if (a == null) return null;
return a.bones;
} }
function multParent(i: Int, fasts: Array<Mat4>, bones: Array<TObj>, mats: Array<Mat4>) { function multParent(i: Int, fasts: Array<Mat4>, bones: Array<TObj>, mats: Array<Mat4>) {
@ -225,10 +252,11 @@ class BoneAnimation extends Animation {
} }
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) {
super.play(action, onComplete, blendTime, speed, loop); var actionName: String = getName(action);
if (action != "") { if (actionName != "") {
setAction(action); setAction(actionName);
var tempAnimParam = new ActionSampler(action); super.play(actionName, onComplete, blendTime, speed, loop);
var tempAnimParam = new ActionSampler(actionName);
registerAction("tempAction", tempAnimParam); registerAction("tempAction", tempAnimParam);
updateAnimation = function(mats){ updateAnimation = function(mats){
sampleAction(tempAnimParam, mats); sampleAction(tempAnimParam, mats);
@ -239,6 +267,10 @@ class BoneAnimation extends Animation {
override public function update(delta: FastFloat) { override public function update(delta: FastFloat) {
this.delta = delta; this.delta = delta;
if (!isSkinned && skeletonBones == null) setAction(armature.actions[0].name); if (!isSkinned && skeletonBones == null) setAction(armature.actions[0].name);
// TODO: double check skip culling for skinned meshes if they need animation updates for bounds
// if (object != null && !object.visible) return;
if (object != null && (!object.visible || object.culled)) return; if (object != null && (!object.visible || object.culled)) return;
if (skeletonBones == null || skeletonBones.length == 0) return; if (skeletonBones == null || skeletonBones.length == 0) return;
@ -248,7 +280,6 @@ class BoneAnimation extends Animation {
super.update(delta); super.update(delta);
if(updateAnimation != null) { if(updateAnimation != null) {
updateAnimation(skeletonMats); updateAnimation(skeletonMats);
} }
@ -401,6 +432,7 @@ class BoneAnimation extends Animation {
} }
var bones = sampler.getBoneAction(); var bones = sampler.getBoneAction();
if (bones == null) return;
for(b in bones){ for(b in bones){
if (b.anim != null) { if (b.anim != null) {
updateTrack(b.anim, sampler); updateTrack(b.anim, sampler);
@ -410,13 +442,14 @@ class BoneAnimation extends Animation {
} }
public function sampleAction(sampler: ActionSampler, actionMats: Array<Mat4>) { public function sampleAction(sampler: ActionSampler, actionMats: Array<Mat4>) {
if(! sampler.actionDataInit) { if(! sampler.actionDataInit) {
var bones = getAction(sampler.action); var bones = getAction(sampler.action);
sampler.setBoneAction(bones); sampler.setBoneAction(bones);
} }
var bones = sampler.getBoneAction(); var bones = sampler.getBoneAction();
if (bones == null) return;
actionMats[skeletonBones.length].setIdentity(); actionMats[skeletonBones.length].setIdentity();
var rootMotionEnabled = sampler.rootMotionPos || sampler.rootMotionRot; var rootMotionEnabled = sampler.rootMotionPos || sampler.rootMotionRot;
for (i in 0...bones.length) { for (i in 0...bones.length) {
@ -427,7 +460,6 @@ class BoneAnimation extends Animation {
updateAnimSampled(bones[i].anim, actionMats[i], sampler); updateAnimSampled(bones[i].anim, actionMats[i], sampler);
} }
} }
} }
function updateAnimSampled(anim: TAnimation, mm: Mat4, sampler: ActionSampler) { function updateAnimSampled(anim: TAnimation, mm: Mat4, sampler: ActionSampler) {
@ -588,6 +620,9 @@ class BoneAnimation extends Animation {
public override function getTotalFrames(sampler: ActionSampler): Int { public override function getTotalFrames(sampler: ActionSampler): Int {
var bones = getAction(sampler.action); var bones = getAction(sampler.action);
if (bones == null){
return 0;
}
var track = bones[0].anim.tracks[0]; var track = bones[0].anim.tracks[0];
return Std.int(track.frames[track.frames.length - 1] - track.frames[0]); return Std.int(track.frames[track.frames.length - 1] - track.frames[0]);
} }
@ -1048,9 +1083,9 @@ class BoneAnimation extends Animation {
var rootLen = root.bone_length * rootMat.getScale().x; var rootLen = root.bone_length * rootMat.getScale().x;
// Get distance form root to goal // Get distance form root to goal
var goalLen = Math.abs(Vec4.distance(rootMat.getLoc(), goal)); var goalLen: FastFloat = Math.abs(Vec4.distance(rootMat.getLoc(), goal));
var totalLength = effectorLen + rootLen; var totalLength: FastFloat = effectorLen + rootLen;
// Get tip location of effector bone // Get tip location of effector bone
var effectorTipPos = new Vec4().setFrom(effectorMat.look()).normalize(); var effectorTipPos = new Vec4().setFrom(effectorMat.look()).normalize();

View File

@ -42,9 +42,10 @@ class CameraObject extends Object {
this.data = data; this.data = data;
#if lnx_vr // dont just auto initialize VR button - headset trait controls VR
iron.system.VR.initButton(); // #if lnx_vr
#end // iron.system.VR.initButton();
// #end
buildProjection(); buildProjection();
@ -85,7 +86,14 @@ class CameraObject extends Object {
projectionJitter(); projectionJitter();
#end #end
// matrices are set by VR system so avoid rebuilding transforms unless its in preview/not presenting
#if (kha_webgl && lnx_vr)
if (@:privateAccess !RenderPath.isVRPresenting()) {
buildMatrix();
}
#else
buildMatrix(); buildMatrix();
#end
RenderPath.active.renderFrame(g); RenderPath.active.renderFrame(g);

View File

@ -59,6 +59,9 @@ class LightObject extends Object {
public static var clustersData: kha.Image = null; public static var clustersData: kha.Image = null;
static var lpos = new Vec4(); static var lpos = new Vec4();
public static var LWVPMatrixArray: Float32Array = null; public static var LWVPMatrixArray: Float32Array = null;
#if lnx_vr
static var originalLightPositions: Float32Array = null;
#end
#end // lnx_clusters #end // lnx_clusters
public var V: Mat4 = Mat4.identity(); public var V: Mat4 = Mat4.identity();
@ -367,8 +370,6 @@ class LightObject extends Object {
} }
#end // lnx_csm #end // lnx_csm
#if lnx_clusters
// Centralize discarding conditions when iterating over lights // Centralize discarding conditions when iterating over lights
// Important to avoid issues later with "misaligned" data in uniforms (lightsArray, clusterData, LWVPSpotArray) // Important to avoid issues later with "misaligned" data in uniforms (lightsArray, clusterData, LWVPSpotArray)
public inline static function discardLight(light: LightObject) { public inline static function discardLight(light: LightObject) {
@ -379,6 +380,8 @@ class LightObject extends Object {
return #if lnx_shadowmap_atlas light.culledLight || #end discardLight(light); return #if lnx_shadowmap_atlas light.culledLight || #end discardLight(light);
} }
#if lnx_clusters
#if (lnx_shadowmap_atlas && lnx_shadowmap_atlas_lod) #if (lnx_shadowmap_atlas && lnx_shadowmap_atlas_lod)
// Arbitrary function to map from [0-16] to [1.0-0.0] // Arbitrary function to map from [0-16] to [1.0-0.0]
public inline static function zToShadowMapScale(z: Int, max: Int): Float { public inline static function zToShadowMapScale(z: Int, max: Int): Float {
@ -419,7 +422,8 @@ class LightObject extends Object {
#if lnx_spot // Point lamps first #if lnx_spot // Point lamps first
lights.sort(function(a, b): Int { lights.sort(function(a, b): Int {
return a.data.raw.type >= b.data.raw.type ? 1 : -1; if (a.data.raw.type == b.data.raw.type) return 0;
return a.data.raw.type > b.data.raw.type ? 1 : -1;
}); });
#end #end
@ -491,6 +495,13 @@ class LightObject extends Object {
continue; continue;
} }
#end #end
if (minX < 0) minX = 0;
if (maxX >= slicesX) maxX = slicesX - 1;
if (minY < 0) minY = 0;
if (maxY >= slicesY) maxY = slicesY - 1;
if (minZ < 0) minZ = 0;
if (maxZ >= slicesZ) maxZ = slicesZ - 1;
// Mark affected clusters // Mark affected clusters
for (z in minZ...maxZ + 1) { for (z in minZ...maxZ + 1) {
for (y in minY...maxY + 1) { for (y in minY...maxY + 1) {
@ -519,7 +530,7 @@ class LightObject extends Object {
updateLightsArray(); // TODO: only update on light change updateLightsArray(); // TODO: only update on light change
} }
static function updateLightsArray() { public static function updateLightsArray() {
if (lightsArray == null) { // vec4x3 - 1: pos, a, color, b, 2: dir, c if (lightsArray == null) { // vec4x3 - 1: pos, a, color, b, 2: dir, c
lightsArray = new Float32Array(maxLights * 4 * 3); lightsArray = new Float32Array(maxLights * 4 * 3);
#if lnx_spot #if lnx_spot
@ -578,6 +589,49 @@ class LightObject extends Object {
} }
} }
// VR deferred stereo we save original light positions before adjusting for per-eye rendering
#if lnx_vr
public static function saveOriginalLightPositions() {
if (lightsArray == null) return;
if (originalLightPositions == null) {
originalLightPositions = new Float32Array(lightsArray.length);
}
for (i in 0...lightsArray.length) {
originalLightPositions[i] = lightsArray[i];
}
}
// negative for left eye, positive for right eye
public static function adjustLightPositionsForVREye(offsetX: Float, rightVec: Vec4) {
if (lightsArray == null) return;
var lights = Scene.active.lights;
var n = lights.length > maxLights ? maxLights : lights.length;
var i = 0;
for (l in lights) {
if (discardLightCulled(l)) continue;
if (i >= n) break;
lightsArray[i * 12 ] = originalLightPositions[i * 12 ] + rightVec.x * offsetX;
lightsArray[i * 12 + 1] = originalLightPositions[i * 12 + 1] + rightVec.y * offsetX;
lightsArray[i * 12 + 2] = originalLightPositions[i * 12 + 2] + rightVec.z * offsetX;
i++;
}
}
public static function restoreOriginalLightPositions() {
if (lightsArray == null || originalLightPositions == null) return;
for (i in 0...lightsArray.length) {
lightsArray[i] = originalLightPositions[i];
}
}
#end
public static function updateLWVPMatrixArray(object: Object, type: String) { public static function updateLWVPMatrixArray(object: Object, type: String) {
if (LWVPMatrixArray == null) { if (LWVPMatrixArray == null) {
LWVPMatrixArray = new Float32Array(maxLightsCluster * 16); LWVPMatrixArray = new Float32Array(maxLightsCluster * 16);
@ -629,8 +683,8 @@ class LightObject extends Object {
LWVPMatrixArray[i * 16 + 13] = m._31; LWVPMatrixArray[i * 16 + 13] = m._31;
LWVPMatrixArray[i * 16 + 14] = m._32; LWVPMatrixArray[i * 16 + 14] = m._32;
LWVPMatrixArray[i * 16 + 15] = m._33; LWVPMatrixArray[i * 16 + 15] = m._33;
i++; // only increment in light type
} }
i++;
} }
return LWVPMatrixArray; return LWVPMatrixArray;
} }

View File

@ -18,17 +18,18 @@ class MeshObject extends Object {
public var depthRead(default, null) = false; public var depthRead(default, null) = false;
#if lnx_particles #if lnx_particles
public var particleSystems: Array<ParticleSystem> = null; // Particle owner public var particleSystems: Array<ParticleSystem> = null; // Particle owner
public var render_emitter = true;
#end
#if lnx_gpu_particles
public var particleChildren: Array<MeshObject> = null; public var particleChildren: Array<MeshObject> = null;
public var particleOwner: MeshObject = null; // Particle object public var particleOwner: MeshObject = null; // Particle object
public var particleIndex = -1; public var particleIndex = -1;
public var render_emitter = true;
#end #end
public var cameraDistance: Float; public var cameraDistance: Float;
public var cameraList: Array<String> = null; 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 tilesheet: Tilesheet = null;
public var tilesheets: Array<Tilesheet> = null;
public var skip_context: String = null; // Do not draw this context public var skip_context: String = null; // Do not draw this context
public var force_context: String = null; // Draw only this context public var force_context: String = null; // Draw only this context
static var lastPipeline: PipelineState = null; static var lastPipeline: PipelineState = null;
@ -72,18 +73,22 @@ class MeshObject extends Object {
#if lnx_batch #if lnx_batch
Scene.active.meshBatch.removeMesh(this); Scene.active.meshBatch.removeMesh(this);
#end #end
#if lnx_particles #if lnx_gpu_particles
if (particleChildren != null) { if (particleChildren != null) {
for (c in particleChildren) c.remove(); for (c in particleChildren) c.remove();
particleChildren = null; particleChildren = null;
} }
#end
#if lnx_particles
if (particleSystems != null) { if (particleSystems != null) {
for (psys in particleSystems) psys.remove(); for (psys in particleSystems) {
#if lnx_cpu_particles psys.stop(); #end
psys.remove();
}
particleSystems = null; particleSystems = null;
} }
#end #end
if (activeTilesheet != null) activeTilesheet.remove(); if (tilesheet != null) tilesheet.remove();
if (tilesheets != null) for (ts in tilesheets) { ts.remove(); }
if (Scene.active != null) Scene.active.meshes.remove(this); if (Scene.active != null) Scene.active.meshes.remove(this);
data.refcount--; data.refcount--;
super.remove(); super.remove();
@ -113,35 +118,19 @@ class MeshObject extends Object {
#if lnx_particles #if lnx_particles
public function setupParticleSystem(sceneName: String, pref: TParticleReference) { public function setupParticleSystem(sceneName: String, pref: TParticleReference) {
if (particleSystems == null) particleSystems = []; if (particleSystems == null) particleSystems = [];
var psys = new ParticleSystem(sceneName, pref); var psys = new ParticleSystem(sceneName, pref, this);
particleSystems.push(psys); particleSystems.push(psys);
} }
#end #end
public function setupTilesheet(sceneName: String, tilesheet_ref: String, tilesheet_action_ref: String) { public function setupTilesheet(tilesheetData: iron.data.SceneFormat.TTilesheetData) {
activeTilesheet = new Tilesheet(sceneName, tilesheet_ref, tilesheet_action_ref); tilesheet = new Tilesheet(tilesheetData, this);
if(tilesheets == null) tilesheets = new Array<Tilesheet>();
tilesheets.push(activeTilesheet);
} }
public function setActiveTilesheet(sceneName: String, tilesheet_ref: String, tilesheet_action_ref: String) { public function setTilesheetAction(actionRef: String) {
var set = false; if (tilesheet != null) {
// Check if tilesheet already created tilesheet.play(actionRef);
if (tilesheets != null) {
for (ts in tilesheets) {
if (ts.raw.name == tilesheet_ref) {
activeTilesheet = ts;
activeTilesheet.play(tilesheet_action_ref);
set = true;
break;
}
}
} }
// If not already created
if (!set) {
setupTilesheet(sceneName, tilesheet_ref, tilesheet_action_ref);
}
} }
inline function isLodMaterial(): Bool { inline function isLodMaterial(): Bool {
@ -179,7 +168,7 @@ class MeshObject extends Object {
// Scale radius for skinned mesh and particle system // Scale radius for skinned mesh and particle system
// TODO: define skin & particle bounds // TODO: define skin & particle bounds
var radiusScale = data.isSkinned ? 2.0 : 1.0; var radiusScale = data.isSkinned ? 2.0 : 1.0;
#if lnx_particles #if lnx_gpu_particles
// particleSystems for update, particleOwner for render // particleSystems for update, particleOwner for render
if (particleSystems != null || particleOwner != null) radiusScale *= 1000; if (particleSystems != null || particleOwner != null) radiusScale *= 1000;
#end #end
@ -236,9 +225,14 @@ 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;
// Update tilesheet
if (tilesheet != null && meshContext) {
tilesheet.update();
}
if (cameraList != null && cameraList.indexOf(Scene.active.camera.name) < 0) return; if (cameraList != null && cameraList.indexOf(Scene.active.camera.name) < 0) return;
#if lnx_particles #if lnx_gpu_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) {
if (particleChildren == null) { if (particleChildren == null) {
@ -257,9 +251,11 @@ class MeshObject extends Object {
} }
} }
for (i in 0...particleSystems.length) { for (i in 0...particleSystems.length) {
particleSystems[i].update(particleChildren[i], this); particleSystems[i].update(particleChildren[i]);
} }
} }
#end
#if lnx_particles
if (particleSystems != null && particleSystems.length > 0 && !render_emitter) return; if (particleSystems != null && particleSystems.length > 0 && !render_emitter) return;
if (particleSystems == null && cullMaterial(context)) return; if (particleSystems == null && cullMaterial(context)) return;
#else #else
@ -302,6 +298,10 @@ class MeshObject extends Object {
// Render mesh // Render mesh
var ldata = lod.data; var ldata = lod.data;
// Next pass rendering first (inverse order)
renderNextPass(g, context, bindParams, lod);
for (i in 0...ldata.geom.indexBuffers.length) { for (i in 0...ldata.geom.indexBuffers.length) {
var mi = ldata.geom.materialIndices[i]; var mi = ldata.geom.materialIndices[i];
@ -405,4 +405,85 @@ class MeshObject extends Object {
} }
} }
} }
function renderNextPass(g: Graphics, context: String, bindParams: Array<String>, lod: MeshObject) {
var ldata = lod.data;
for (i in 0...ldata.geom.indexBuffers.length) {
var mi = ldata.geom.materialIndices[i];
if (mi >= materials.length) continue;
var currentMaterial: MaterialData = materials[mi];
if (currentMaterial == null || currentMaterial.shader == null) continue;
var nextPassName: String = currentMaterial.shader.nextPass;
if (nextPassName == null || nextPassName == "") continue;
var nextMaterial: MaterialData = null;
for (mat in materials) {
// First try exact match
if (mat.name == nextPassName) {
nextMaterial = mat;
break;
}
// If no exact match, try to match base name for linked materials
if (mat.name.indexOf("_") > 0 && mat.name.substr(mat.name.length - 6) == ".blend") {
var baseName = mat.name.substring(0, mat.name.indexOf("_"));
if (baseName == nextPassName) {
nextMaterial = mat;
break;
}
}
}
if (nextMaterial == null) continue;
var nextMaterialContext: MaterialContext = null;
var nextShaderContext: ShaderContext = null;
for (j in 0...nextMaterial.raw.contexts.length) {
if (nextMaterial.raw.contexts[j].name.substr(0, context.length) == context) {
nextMaterialContext = nextMaterial.contexts[j];
nextShaderContext = nextMaterial.shader.getContext(context);
break;
}
}
if (nextShaderContext == null) continue;
if (skipContext(context, nextMaterial)) continue;
var elems = nextShaderContext.raw.vertex_elements;
// Uniforms
if (nextShaderContext.pipeState != lastPipeline) {
g.setPipeline(nextShaderContext.pipeState);
lastPipeline = nextShaderContext.pipeState;
}
Uniforms.setContextConstants(g, nextShaderContext, bindParams);
Uniforms.setObjectConstants(g, nextShaderContext, this);
Uniforms.setMaterialConstants(g, nextShaderContext, nextMaterialContext);
// VB / IB
#if lnx_deinterleaved
g.setVertexBuffers(ldata.geom.get(elems));
#else
if (ldata.geom.instancedVB != null) {
g.setVertexBuffers([ldata.geom.get(elems), ldata.geom.instancedVB]);
}
else {
g.setVertexBuffer(ldata.geom.get(elems));
}
#end
g.setIndexBuffer(ldata.geom.indexBuffers[i]);
// Draw next pass for this specific geometry section
if (ldata.geom.instanced) {
g.drawIndexedVerticesInstanced(ldata.geom.instanceCount, ldata.geom.start, ldata.geom.count);
}
else {
g.drawIndexedVertices(ldata.geom.start, ldata.geom.count);
}
}
}
} }

View File

@ -57,6 +57,10 @@ class MorphTarget {
morphWeights.set(i, value); morphWeights.set(i, value);
} }
} }
public inline function setMorphValueDirect(index: Int, value: Float) {
morphWeights.set(index, value);
}
} }
#end #end

View File

@ -11,6 +11,7 @@ class Object {
public var raw: TObj = null; public var raw: TObj = null;
public var name: String = ""; public var name: String = "";
public var filename: String = "";
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> = [];
@ -111,12 +112,15 @@ class Object {
**/ **/
public function getChild(name: String): Object { public function getChild(name: String): Object {
if (this.name == name) return this; if (this.name == name) return this;
else { else if (this.filename != "") {
for (c in children) { if (this.name == name + "_" + this.filename) return this;
var r = c.getChild(name);
if (r != null) return r;
}
} }
for (c in children) {
var r = c.getChild(name);
if (r != null) return r;
}
return null; return null;
} }
@ -209,15 +213,40 @@ class Object {
return null; return null;
} }
public function getTraitFromChildren<T: Trait>(c: Class<T>): T {
var t: T = getTrait(c);
if (t != null) return t;
for (child in getChildren(true)) {
t = child.getTraitFromChildren(c);
if (t != null) return t;
}
return null;
}
#if lnx_skin #if lnx_skin
public function getBoneAnimation(armatureUid): BoneAnimation { public function getBoneAnimation(armatureUid: Int): BoneAnimation {
for (a in Scene.active.animations) if (a.armature != null && a.armature.uid == armatureUid) return cast a; for (a in Scene.active.animations) {
if (a.armature != null && a.armature.uid == armatureUid) {
return cast a;
}
}
return null;
}
public function getParentArmature(name: String): BoneAnimation {
for (a in Scene.active.animations) {
if (a.armature != null && a.armature.name == name) return cast a;
}
return null; return null;
} }
#else #else
public function getBoneAnimation(armatureUid): Animation { public function getBoneAnimation(armatureUid): Animation {
return null; return null;
} }
public function getParentArmature(name: String): Animation {
return null;
}
#end #end
public function getObjectAnimation(): ObjectAnimation { public function getObjectAnimation(): ObjectAnimation {
@ -225,6 +254,15 @@ class Object {
return null; return null;
} }
public function getAnimation(): Null<Animation> {
if (animation != null) return animation;
for (c in getChildren(true)) {
var a = c.getAnimation();
if (a != null) return a;
}
return null;
}
public function setupAnimation(oactions: Array<TSceneFormat> = null) { public function setupAnimation(oactions: Array<TSceneFormat> = null) {
// Parented to bone // Parented to bone
#if lnx_skin #if lnx_skin

View File

@ -9,6 +9,7 @@ import iron.math.Vec4;
import iron.math.Mat4; import iron.math.Mat4;
import iron.math.Quat; import iron.math.Quat;
import iron.data.SceneFormat; import iron.data.SceneFormat;
import StringTools;
class ObjectAnimation extends Animation { class ObjectAnimation extends Animation {
@ -24,6 +25,9 @@ class ObjectAnimation extends Animation {
public var transformMap: Map<String, FastFloat>; public var transformMap: Map<String, FastFloat>;
var defaultSampler: ActionSampler = null;
static inline var DEFAULT_SAMPLER_ID = "__object_default_action__";
public static var trackNames: Array<String> = [ "xloc", "yloc", "zloc", public static var trackNames: Array<String> = [ "xloc", "yloc", "zloc",
"xrot", "yrot", "zrot", "xrot", "yrot", "zrot",
"qwrot", "qxrot", "qyrot", "qzrot", "qwrot", "qxrot", "qyrot", "qzrot",
@ -40,17 +44,46 @@ class ObjectAnimation extends Animation {
super(); super();
} }
override function get_action(): String {
var an: String = action; // an -> action name
if (an != "" && object != null && object.filename != "") {
var sufix = "_" + object.filename;
if (an.indexOf(sufix) != -1) an = StringTools.replace(an, sufix, "");
}
return an;
}
function getAction(action: String): TObj { function getAction(action: String): TObj {
for (a in oactions) if (a != null && a.objects[0].name == action) return a.objects[0]; for (a in oactions) if (a != null && a.objects[0].name == action) return a.objects[0];
return null; return null;
} }
override public function play(action = "", onComplete: Void->Void = null, blendTime = 0.0, speed = 1.0, loop = true) { override public function play(action = "", onComplete: Void->Void = null, blendTime = 0.0, speed = 1.0, loop = true) {
super.play(action, onComplete, blendTime, speed, loop); var actionName: String = object != null && object.filename != "" ? action + "_" + object.filename : action;
if (this.action == "" && oactions[0] != null) this.action = oactions[0].objects[0].name; super.play(actionName, onComplete, blendTime, speed, loop);
if (this.action == "" && oactions != null && oactions[0] != null){
this.action = oactions[0].objects[0].name;
}
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) {
deRegisterAction(DEFAULT_SAMPLER_ID);
}
var callbacks = onComplete != null ? [onComplete] : null;
defaultSampler = new ActionSampler(this.action, speed, loop, false, callbacks);
registerAction(DEFAULT_SAMPLER_ID, defaultSampler);
if (paused) defaultSampler.paused = true;
updateAnimation = function(map: Map<String, FastFloat>) {
sampleAction(defaultSampler, map);
};
}
else {
if (defaultSampler != null) {
deRegisterAction(DEFAULT_SAMPLER_ID);
defaultSampler = null;
}
updateAnimation = null;
} }
} }
@ -61,12 +94,13 @@ class ObjectAnimation extends Animation {
Animation.beginProfile(); Animation.beginProfile();
#end #end
if(transformMap == null) transformMap = new Map(); if (transformMap == null) transformMap = new Map();
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();
#if lnx_debug #if lnx_debug
@ -75,7 +109,9 @@ class ObjectAnimation extends Animation {
} }
public override function getTotalFrames(sampler: ActionSampler): Int { public override function getTotalFrames(sampler: ActionSampler): Int {
var track = getAction(sampler.action).anim.tracks[0]; var action = getAction(sampler.action);
if (action == null || action.anim == null || action.anim.tracks == null || action.anim.tracks.length == 0) return 0;
var track = action.anim.tracks[0];
return Std.int(track.frames[track.frames.length - 1] - track.frames[0]); return Std.int(track.frames[track.frames.length - 1] - track.frames[0]);
} }

View File

@ -1,309 +1,9 @@
package iron.object; package iron.object;
#if lnx_particles #if lnx_gpu_particles
typedef ParticleSystem = ParticleSystemGPU;
import kha.FastFloat; #elseif lnx_cpu_particles
import kha.graphics4.Usage; typedef ParticleSystem = ParticleSystemCPU;
import kha.arrays.Float32Array; #else
import iron.data.Data; class ParticleSystem { public function new() { } }
import iron.data.ParticleData;
import iron.data.SceneFormat;
import iron.system.Time;
import iron.math.Mat4;
import iron.math.Quat;
import iron.math.Vec3;
import iron.math.Vec4;
class ParticleSystem {
public var data: ParticleData;
public var speed = 1.0;
var currentSpeed = 0.0;
var particles: Array<Particle>;
var ready: Bool;
var frameRate = 24;
var lifetime = 0.0;
var looptime = 0.0;
var animtime = 0.0;
var time = 0.0;
var spawnRate = 0.0;
var seed = 0;
var r: TParticleData;
var gx: Float;
var gy: Float;
var gz: Float;
var alignx: Float;
var aligny: Float;
var alignz: Float;
var dimx: Float;
var dimy: Float;
var tilesx: Int;
var tilesy: Int;
var tilesFramerate: Int;
var count = 0;
var lap = 0;
var lapTime = 0.0;
var m = Mat4.identity();
var ownerLoc = new Vec4();
var ownerRot = new Quat();
var ownerScl = new Vec4();
var random = 0.0;
public function new(sceneName: String, pref: TParticleReference) {
seed = pref.seed;
currentSpeed = speed;
speed = 0;
particles = [];
ready = false;
Data.getParticle(sceneName, pref.particle, function(b: ParticleData) {
data = b;
r = data.raw;
if (Scene.active.raw.gravity != null) {
gx = Scene.active.raw.gravity[0] * r.weight_gravity;
gy = Scene.active.raw.gravity[1] * r.weight_gravity;
gz = Scene.active.raw.gravity[2] * r.weight_gravity;
}
else {
gx = 0;
gy = 0;
gz = -9.81 * r.weight_gravity;
}
alignx = r.object_align_factor[0];
aligny = r.object_align_factor[1];
alignz = r.object_align_factor[2];
looptime = (r.frame_end - r.frame_start) / frameRate;
lifetime = r.lifetime / frameRate;
animtime = r.loop ? looptime : looptime + lifetime;
spawnRate = ((r.frame_end - r.frame_start) / r.count) / frameRate;
for (i in 0...r.count) {
particles.push(new Particle(i));
}
ready = true;
if (r.auto_start){
start();
}
});
}
public function start() {
if (r.is_unique) random = Math.random();
lifetime = r.lifetime / frameRate;
time = 0;
lap = 0;
lapTime = 0;
speed = currentSpeed;
}
public function pause() {
speed = 0;
}
public function resume() {
lifetime = r.lifetime / frameRate;
speed = currentSpeed;
}
// TODO: interrupt smoothly
public function stop() {
end();
}
function end() {
lifetime = 0;
speed = 0;
lap = 0;
}
public function update(object: MeshObject, owner: MeshObject) {
if (!ready || object == null || speed == 0.0) return;
if (iron.App.pauseUpdates) return;
var prevLap = lap;
// Copy owner world transform but discard scale
owner.transform.world.decompose(ownerLoc, ownerRot, ownerScl);
object.transform.loc = ownerLoc;
object.transform.rot = ownerRot;
// Set particle size per particle system
object.transform.scale = new Vec4(r.particle_size, r.particle_size, r.particle_size, 1);
object.transform.buildMatrix();
owner.transform.buildMatrix();
object.transform.dim.setFrom(owner.transform.dim);
dimx = object.transform.dim.x;
dimy = object.transform.dim.y;
if (object.activeTilesheet != null) {
tilesx = object.activeTilesheet.raw.tilesx;
tilesy = object.activeTilesheet.raw.tilesy;
tilesFramerate = object.activeTilesheet.raw.framerate;
}
// Animate
time += Time.renderDelta * speed; // realDelta to renderDelta
lap = Std.int(time / animtime);
lapTime = time - lap * animtime;
count = Std.int(lapTime / spawnRate);
if (lap > prevLap && !r.loop) {
end();
}
updateGpu(object, owner);
}
public function getData(): Mat4 {
var hair = r.type == 1;
m._00 = animtime;
m._01 = hair ? 1 / particles.length : spawnRate;
m._02 = hair ? 1 : lifetime;
m._03 = particles.length;
m._10 = hair ? 0 : alignx;
m._11 = hair ? 0 : aligny;
m._12 = hair ? 0 : alignz;
m._13 = hair ? 0 : r.factor_random;
m._20 = hair ? 0 : gx;
m._21 = hair ? 0 : gy;
m._22 = hair ? 0 : gz;
m._23 = hair ? 0 : r.lifetime_random;
m._30 = tilesx;
m._31 = tilesy;
m._32 = 1 / tilesFramerate;
m._33 = hair ? 1 : lapTime;
return m;
}
public function getSizeRandom(): FastFloat {
return r.size_random;
}
public function getRandom(): FastFloat {
return random;
}
public function getSize(): FastFloat {
return r.particle_size;
}
function updateGpu(object: MeshObject, owner: MeshObject) {
if (!object.data.geom.instanced) setupGeomGpu(object, owner);
// GPU particles transform is attached to owner object
}
function setupGeomGpu(object: MeshObject, owner: MeshObject) {
var instancedData = new Float32Array(particles.length * 3);
var i = 0;
var normFactor = 1 / 32767; // pa.values are not normalized
var scalePosOwner = owner.data.scalePos;
var scalePosParticle = object.data.scalePos;
var particleSize = r.particle_size;
var scaleFactor = new Vec4().setFrom(owner.transform.scale);
scaleFactor.mult(scalePosOwner / (particleSize * scalePosParticle));
switch (r.emit_from) {
case 0: // Vert
var pa = owner.data.geom.positions;
for (p in particles) {
var j = Std.int(fhash(i) * (pa.values.length / pa.size));
instancedData.set(i, pa.values[j * pa.size ] * normFactor * scaleFactor.x); i++;
instancedData.set(i, pa.values[j * pa.size + 1] * normFactor * scaleFactor.y); i++;
instancedData.set(i, pa.values[j * pa.size + 2] * normFactor * scaleFactor.z); i++;
}
case 1: // Face
var positions = owner.data.geom.positions.values;
for (p in particles) {
// Choose random index array (there is one per material) and random face
var ia = owner.data.geom.indices[Std.random(owner.data.geom.indices.length)];
var faceIndex = Std.random(Std.int(ia.length / 3));
var i0 = ia[faceIndex * 3 + 0];
var i1 = ia[faceIndex * 3 + 1];
var i2 = ia[faceIndex * 3 + 2];
var v0 = new Vec3(positions[i0 * 4], positions[i0 * 4 + 1], positions[i0 * 4 + 2]);
var v1 = new Vec3(positions[i1 * 4], positions[i1 * 4 + 1], positions[i1 * 4 + 2]);
var v2 = new Vec3(positions[i2 * 4], positions[i2 * 4 + 1], positions[i2 * 4 + 2]);
var pos = randomPointInTriangle(v0, v1, v2);
instancedData.set(i, pos.x * normFactor * scaleFactor.x); i++;
instancedData.set(i, pos.y * normFactor * scaleFactor.y); i++;
instancedData.set(i, pos.z * normFactor * scaleFactor.z); i++;
}
case 2: // Volume
var scaleFactorVolume = new Vec4().setFrom(object.transform.dim);
scaleFactorVolume.mult(0.5 / (particleSize * scalePosParticle));
for (p in particles) {
instancedData.set(i, (Math.random() * 2.0 - 1.0) * scaleFactorVolume.x); i++;
instancedData.set(i, (Math.random() * 2.0 - 1.0) * scaleFactorVolume.y); i++;
instancedData.set(i, (Math.random() * 2.0 - 1.0) * scaleFactorVolume.z); i++;
}
}
object.data.geom.setupInstanced(instancedData, 1, Usage.StaticUsage);
}
function fhash(n: Int): Float {
var s = n + 1.0;
s *= 9301.0 % s;
s = (s * 9301.0 + 49297.0) % 233280.0;
return s / 233280.0;
}
public function remove() {}
/**
Generates a random point in the triangle with vertex positions abc.
Please note that the given position vectors are changed in-place by this
function and can be considered garbage afterwards, so make sure to clone
them first if needed.
**/
public static inline function randomPointInTriangle(a: Vec3, b: Vec3, c: Vec3): Vec3 {
// Generate a random point in a square where (0, 0) <= (x, y) < (1, 1)
var x = Math.random();
var y = Math.random();
if (x + y > 1) {
// We're in the upper right triangle in the square, mirror to lower left
x = 1 - x;
y = 1 - y;
}
// Transform the point to the triangle abc
var u = b.sub(a);
var v = c.sub(a);
return a.add(u.mult(x).add(v.mult(y)));
}
}
class Particle {
public var i: Int;
public var x = 0.0;
public var y = 0.0;
public var z = 0.0;
public var cameraDistance: Float;
public function new(i: Int) {
this.i = i;
}
}
#end #end

View File

@ -0,0 +1,551 @@
package iron.object;
#if lnx_cpu_particles
import iron.Scene;
import iron.data.Data;
import iron.data.ParticleData;
import iron.data.SceneFormat;
import iron.math.Quat;
import iron.math.Vec3;
import iron.math.Vec4;
import iron.object.MeshObject;
import iron.object.Object;
import iron.system.Time;
import iron.system.Tween;
import kha.FastFloat;
import kha.arrays.Int16Array;
import kha.arrays.Uint32Array;
class ParticleSystemCPU {
public var data: ParticleData;
public var speed: FastFloat = 1.0; // Not used yet. Added to go in hand with `ParticleSystemGPU`
var r: TParticleData;
// Format
final baseFrameRate: FastFloat = 24.0;
var frameRate: FastFloat = 24.0;
var type: Int = 0; // type: 0 - Emission, 1 - Hair
// Emission
var count: Int = 10; // count
var frameStart: FastFloat = 1; // frame_start
var frameEnd: FastFloat = 10.0; // frame_end
var lifetime: FastFloat = 24.0; // lifetime
var lifetimeRandom: FastFloat = 0.0; // lifetime_random
var emitFrom: Int = 1; // emit_from: 0 - Vert, 1 - Face, 2 - Volume // TODO: fully integrate Blender's properties
// Velocity
var velocity: Vec3 = new Vec3(0.0, 0.0, 1.0); // object_align_factor: Float32Array
var velocityRandom: FastFloat = 0.0; // factor_random
// Rotation
var rotation: Bool = false; // use_rotations
var orientationAxis: Int = 0; // rotation_mode: 0 - None, 1 - Normal, 2 - Normal-Tangent, 3 - Velocity/Hair, 4 - Global X, 5 - Global Y, 6 - Global Z, 7 - Object X, 8 - Object Y, 9 - Object Z
var rotationRandom: FastFloat = 0.0; // rotation_factor_random
var phase: FastFloat = 0.0; // phase_factor
var phaseRandom: FastFloat = 0.0; // phase_factor_random
var dynamicRotation: Bool = false; // use_dynamic_rotation
// Render
var instanceObject: String; // instance_object
var scale: FastFloat = 1.0; // particle_size
var scaleRandom: FastFloat = 0.0; // size_random
// Field weights
var gravity: Vec3 = new Vec3(0, 0, -9.8);
var gravityFactor: FastFloat = 1.0; // weight_gravity
var textureFactor: FastFloat = 1.0; // weight_texture
// Textures
var textureSlots: Map<String, Dynamic> = [];
// Lnx props
var autoStart: Bool = true; // auto_start
var localCoords: Bool = false; // local_coords
var loop: Bool = false; // loop
// Internal logic
var owner: MeshObject;
var lifetimeSeconds: FastFloat = 0.0;
var spawnRate: FastFloat = 0.0;
var spawnFactor: Int = 1;
var spawnedParticles: Int = 0;
var particleScale: FastFloat = 1.0;
var loopAnim: TAnim;
var spawnTime: FastFloat = 0;
var randQuat: Quat;
var phaseQuat: Quat;
// Tween scaling
var scaleElementsCount: Int = 0;
var scaleRampSizeFactor: FastFloat = 0;
var rampPositions: Array<FastFloat> = [];
var rampColors: Array<FastFloat> = [];
// Optimization
var particlePool: Array<Object> = [];
var particlePhysics: Map<Object, TParticlePhysics> = [];
public function new(sceneName: String, pref: TParticleReference, mo: MeshObject) {
Data.getParticle(sceneName, pref.particle, function (b: ParticleData) {
data = b;
r = data.raw;
owner = mo;
frameRate = r.fps;
type = r.type;
count = r.count;
frameStart = r.frame_start;
frameEnd = r.frame_end;
lifetime = r.lifetime;
lifetimeRandom = r.lifetime_random;
emitFrom = r.emit_from;
rotation = r.use_rotations;
orientationAxis = r.rotation_mode;
rotationRandom = r.rotation_factor_random;
phase = r.phase_factor;
phaseRandom = r.phase_factor_random;
dynamicRotation = r.use_dynamic_rotation;
instanceObject = r.instance_object;
scale = r.particle_size;
scaleRandom = r.size_random;
velocity = new Vec3(r.object_align_factor[0], r.object_align_factor[1], r.object_align_factor[2]).mult(frameRate / baseFrameRate).mult(1 / scale);
velocityRandom = r.factor_random * (frameRate / baseFrameRate);
if (Scene.active.raw.gravity != null) {
gravity = new Vec3(Scene.active.raw.gravity[0], Scene.active.raw.gravity[1], Scene.active.raw.gravity[2]).mult(frameRate / baseFrameRate).mult(1 / scale);
}
gravityFactor = r.weight_gravity * (frameRate / baseFrameRate);
textureFactor = r.weight_texture;
if (r.texture_slots != null) {
for (slot in Reflect.fields(r.texture_slots)) {
textureSlots[slot] = Reflect.field(r.texture_slots, slot);
}
}
autoStart = r.auto_start;
localCoords = r.local_coords;
loop = r.loop;
spawnRate = ((frameEnd - frameStart) / count) / frameRate;
lifetimeSeconds = lifetime / frameRate;
scaleElementsCount = getRampElementsLength();
scaleRampSizeFactor = getRampSizeFactor();
Scene.active.notifyOnInit(function () {
var i: Int;
for (i in 0...count) addToPool();
});
switch (type) {
case 0: // Emission
loopAnim = {
tick: function () {
spawnTime += Time.delta * Time.scale;
var expected: Int = Math.floor(spawnTime / spawnRate);
while (spawnedParticles < expected && spawnedParticles < count) {
spawnParticle();
spawnedParticles++;
}
updateParticles();
},
target: null,
props: null,
duration: loop ? lifetimeSeconds : lifetimeSeconds * 2,
done: function () {
if (loop) start();
}
}
case 1: // Hair
Scene.active.notifyOnInit(function () {
var i: Int;
for (i in 0...count) spawnParticle();
});
default:
}
Scene.active.notifyOnInit(function () {
if (autoStart) start();
});
});
}
public function start() {
if (type != 0) return;
spawnTime = 0;
spawnedParticles = 0;
Tween.to(loopAnim);
}
// TODO
public function pause() {
}
// TODO
public function resume() {
}
public function stop() {
if (type != 0) return;
spawnTime = 0;
spawnedParticles = 0;
Tween.stop(loopAnim);
for (particle => physics in particlePhysics) releaseParticle(particle);
particlePhysics.clear();
}
function addToPool() {
Scene.active.spawnObject(instanceObject, localCoords ? owner : null, function (o: Object) {
o.visible = false;
particlePool.push(o);
});
}
function getFreeParticle(): Object {
for (particle in particlePool) {
if (!particle.visible) {
particle.visible = true;
return particle;
}
}
return null;
}
function releaseParticle(o: Object) {
o.visible = false;
o.transform.loc = new Vec4();
o.transform.rot = new Quat();
o.transform.scale = new Vec4(1, 1, 1, 1);
}
function spawnParticle() {
var o: Object = getFreeParticle();
if (o == null) {
addToPool();
o = getFreeParticle();
}
owner.transform.buildMatrix();
var objectPos: Vec4 = new Vec4();
var objectRot: Quat = new Quat();
var objectScale: Vec4 = new Vec4();
owner.transform.world.decompose(objectPos, objectRot, objectScale);
o.visible = true;
var normFactor: FastFloat = 1 / 32767;
var scalePos: FastFloat = owner.data.scalePos;
var scalePosParticle: FastFloat = cast(o, MeshObject).data.scalePos;
// TODO: add all properties from Blender's UI
switch (emitFrom) {
case 0: // Vertices
var pa: TVertexArray = owner.data.geom.positions;
var i: Int = Std.int(Math.random() * (pa.values.length / pa.size));
var loc: Vec4 = new Vec4(pa.values[i * pa.size] * normFactor, pa.values[i * pa.size + 1] * normFactor, pa.values[i * pa.size + 2] * normFactor, 1);
if (!localCoords) {
loc.applyQuat(objectRot);
loc.add(objectPos);
}
o.transform.loc.setFrom(loc);
case 1: // Faces
var positions: Int16Array = owner.data.geom.positions.values;
var ia: Uint32Array = owner.data.geom.indices[Std.random(owner.data.geom.indices.length)];
var faceIndex: Int = Std.random(Std.int(ia.length / 3));
var i0 = ia[faceIndex * 3 + 0];
var i1 = ia[faceIndex * 3 + 1];
var i2 = ia[faceIndex * 3 + 2];
var v0: Vec3 = new Vec3(positions[i0 * 4], positions[i0 * 4 + 1], positions[i0 * 4 + 2]);
var v1: Vec3 = new Vec3(positions[i1 * 4], positions[i1 * 4 + 1], positions[i1 * 4 + 2]);
var v2: Vec3 = new Vec3(positions[i2 * 4], positions[i2 * 4 + 1], positions[i2 * 4 + 2]);
var pos: Vec3 = randomPointInTriangle(v0, v1, v2);
var loc: Vec4 = new Vec4(pos.x, pos.y, pos.z, 1).mult(normFactor);
if (!localCoords) {
loc.applyQuat(objectRot);
loc.add(objectPos);
}
o.transform.loc.setFrom(loc);
case 2: // Volume
var scaleFactorVolume: Vec4 = new Vec4().setFrom(owner.transform.dim);
scaleFactorVolume.mult(0.5);
var loc: Vec4 = new Vec4((Math.random() * 2.0 - 1.0) * scaleFactorVolume.x, (Math.random() * 2.0 - 1.0) * scaleFactorVolume.y, (Math.random() * 2.0 - 1.0) * scaleFactorVolume.z, 1);
if (!localCoords) {
loc.applyQuat(objectRot);
loc.add(objectPos);
}
o.transform.loc.setFrom(loc);
}
particleScale = 1 - scaleRandom * Math.random();
var localFactor: Vec3 = localCoords ? new Vec3(objectScale.x, objectScale.y, objectScale.z) : new Vec3(1, 1, 1);
var sc: Vec4 = new Vec4(o.transform.scale.x / localFactor.x, o.transform.scale.y / localFactor.y, o.transform.scale.z / localFactor.z, 1.0).mult(scale).mult(particleScale);
var randomLifetime: FastFloat = lifetimeSeconds * (1 - Math.random() * lifetimeRandom);
if (scaleElementsCount != 0) {
rampPositions = getRampPositions();
rampColors = getRampColors();
} else {
o.transform.scale.setFrom(sc);
}
o.transform.buildMatrix();
var randomX: FastFloat = (Math.random() * 2 / (scale * particleScale) - 1 / (scale * particleScale)) * velocityRandom;
var randomY: FastFloat = (Math.random() * 2 / (scale * particleScale) - 1 / (scale * particleScale)) * velocityRandom;
var randomZ: FastFloat = (Math.random() * 2 / (scale * particleScale) - 1 / (scale * particleScale)) * velocityRandom;
var g: Vec3 = new Vec3();
var rotatedVelocity: Vec4 = new Vec4(velocity.x + randomX, velocity.y + randomY, velocity.z + randomZ, 1);
if (!localCoords) rotatedVelocity.applyQuat(objectRot);
if (rotation) {
// Rotation phase and randomness. Wrap values between -1 and 1.
randQuat = new Quat().fromEuler((Math.random() * 2 - 1) * Math.PI * rotationRandom, (Math.random() * 2 - 1) * Math.PI * rotationRandom, (Math.random() * 2 - 1) * Math.PI * rotationRandom);
var phaseRand: FastFloat = (Math.random() * 2 - 1) * phaseRandom;
var phaseValue: FastFloat = phase + phaseRand;
while (phaseValue > 1) phaseValue -= 2;
while (phaseValue < -1) phaseValue += 2;
var dirQuat: Quat = new Quat();
phaseQuat = new Quat().fromEuler(0, phaseValue * Math.PI, 0);
switch (orientationAxis) {
case 0: // None
o.transform.rotate(new Vec4(0, 0, 1, 1), -Math.PI * 0.5);
case 1: // Normal
case 2: // Normal-Tangent
case 3: // Velocity/Hair
setVelocityHair(o, rotatedVelocity, randQuat, phaseQuat);
case 4: // Global X
o.transform.rot.fromEuler(0, 0, -Math.PI * 0.5).mult(phaseQuat).mult(randQuat);
case 5: // Global Y
o.transform.rot.fromEuler(0, 0, 0).mult(phaseQuat).mult(randQuat);
case 6: // Global Z
o.transform.rot.fromEuler(0, -Math.PI * 0.5, -Math.PI * 0.5).mult(phaseQuat).mult(randQuat);
case 7: // Object X
o.transform.rot.setFrom(objectRot);
dirQuat.fromEuler(0, 0, -Math.PI * 0.5);
o.transform.rot.mult(dirQuat).mult(phaseQuat).mult(randQuat);
case 8: // Object Y
o.transform.rot.setFrom(objectRot);
o.transform.rot.mult(phaseQuat).mult(randQuat);
case 9: // Object Z
o.transform.rot.setFrom(objectRot);
dirQuat.fromEuler(0, -Math.PI * 0.5, 0).mult(new Quat().fromEuler(0, 0, -Math.PI * 0.5));
o.transform.rot.mult(dirQuat).mult(phaseQuat).mult(randQuat);
default:
}
} else {
o.transform.rotate(new Vec4(0, 0, 1, 1), -Math.PI * 0.5);
}
var physics: TParticlePhysics = {
velocity: rotatedVelocity.clone(),
gravity: gravity.clone().mult(gravityFactor),
lifetime: randomLifetime,
age: 0.0,
hasScaleRamp: scaleElementsCount != 0,
baseScale: sc.clone(),
rampPositions: rampPositions.copy(),
rampColors: rampColors.copy(),
scaleRampSizeFactor: scaleRampSizeFactor
};
particlePhysics.set(o, physics);
o.transform.buildMatrix();
}
}
function setVelocityHair(object: Object, velocity: Vec4, randQuat: Quat, phaseQuat: Quat) {
var dir: Vec4 = velocity.clone().normalize();
var yaw: FastFloat = Math.atan2(-dir.x, dir.y);
var pitch: FastFloat = Math.asin(dir.z);
var targetRot: Quat = new Quat().fromEuler(pitch, 0, yaw);
targetRot.mult(randQuat);
object.transform.rot.setFrom(targetRot.mult(phaseQuat));
}
function updateParticles() {
for (particle => physics in particlePhysics) {
physics.age += Time.delta * Time.scale;
if (physics.age >= physics.lifetime) {
particlePhysics.remove(particle);
releaseParticle(particle);
continue;
}
physics.velocity.x += physics.gravity.x * Time.delta * Time.scale;
physics.velocity.y += physics.gravity.y * Time.delta * Time.scale;
physics.velocity.z += physics.gravity.z * Time.delta * Time.scale;
particle.transform.translate(
physics.velocity.x * Time.delta * Time.scale,
physics.velocity.y * Time.delta * Time.scale,
physics.velocity.z * Time.delta * Time.scale
);
if (rotation && dynamicRotation && orientationAxis == 3) setVelocityHair(particle, physics.velocity, randQuat, phaseQuat);
if (physics.hasScaleRamp && physics.rampPositions.length > 1) {
var normalizedAge: FastFloat = physics.age / physics.lifetime;
var scaleMultiplier: FastFloat = interpolateRampValue(normalizedAge, physics.rampPositions, physics.rampColors);
var finalScale: FastFloat = scale * (particleScale * (1 - physics.scaleRampSizeFactor) + scaleMultiplier * physics.scaleRampSizeFactor);
particle.transform.scale.setFrom(physics.baseScale.clone().mult(finalScale));
}
particle.transform.buildMatrix();
}
}
// Linear interpolation
function interpolateRampValue(normalizedAge: FastFloat, positions: Array<FastFloat>, colors: Array<FastFloat>): FastFloat {
if (positions.length == 0) return 1.0;
if (normalizedAge <= positions[0]) return colors[0];
if (normalizedAge >= positions[positions.length - 1]) return colors[colors.length - 1];
var i: Int;
for (i in 0...(positions.length - 1)) {
if (normalizedAge >= positions[i] && normalizedAge <= positions[i + 1]) {
var t: FastFloat = (normalizedAge - positions[i]) / (positions[i + 1] - positions[i]);
return colors[i] + t * (colors[i + 1] - colors[i]);
}
}
return colors[colors.length - 1];
}
function getRampSizeFactor(): FastFloat {
// Just using the first slot for now: 1 texture slot
// TODO: use all available slots ?
for (slot in textureSlots.keys()) {
var s: Dynamic = textureSlots[slot];
if (s != null && s.use_map_size) {
var sizeFactor: FastFloat = s.size_factor;
return sizeFactor * textureFactor;
}
}
return 0.0;
}
function getRampElementsLength(): Int {
for (slot in textureSlots.keys()) {
var s: Dynamic = textureSlots[slot];
if (s == null) continue;
var tex: Dynamic = s.texture;
if (tex == null) continue;
if (tex.use_color_ramp) {
var ramp: Dynamic = tex.color_ramp;
if (ramp == null) continue;
var elems: Dynamic = ramp.elements;
if (elems == null) continue;
return elems.length;
}
}
return 0;
}
function getRampPositions(): Array<FastFloat> {
// Just using the first slot for now: 1 texture slot
// TODO: use all available slots ?
for (slot in textureSlots.keys()) {
var s: Dynamic = textureSlots[slot];
if (s == null) continue;
var tex: Dynamic = s.texture;
if (tex == null) continue;
if (tex.use_color_ramp) {
var ramp: Dynamic = tex.color_ramp;
if (ramp == null) continue;
var elems: Dynamic = ramp.elements;
if (elems == null) continue;
var positions: Array<FastFloat> = [];
for (i in 0...elems.length) {
positions.push(elems[i].position);
}
return positions;
}
}
return [];
}
function getRampColors(): Array<FastFloat> {
// Just using the first slot for now: 1 texture slot
// TODO: use all available slots ?
for (slot in textureSlots.keys()) {
var s: Dynamic = textureSlots[slot];
if (s == null) continue;
var tex: Dynamic = s.texture;
if (tex == null) continue;
if (tex.use_color_ramp) {
var ramp: Dynamic = tex.color_ramp;
if (ramp == null) continue;
var elems: Dynamic = ramp.elements;
if (elems == null) continue;
var colors: Array<FastFloat> = [];
for (i in 0...elems.length) {
colors.push(elems[i].color.b); // Just need R, G or B for black and white images. Using B as it can be interpreted as V with HSV
}
return colors;
}
}
return [];
}
public function remove() {
for (particle in particlePool) particle.remove();
}
/**
Generates a random point in the triangle with vertex positions abc.
Please note that the given position vectors are changed in-place by this
function and can be considered garbage afterwards, so make sure to clone
them first if needed.
**/
public static inline function randomPointInTriangle(a: Vec3, b: Vec3, c: Vec3): Vec3 {
// Generate a random point in a square where (0, 0) <= (x, y) < (1, 1)
var x = Math.random();
var y = Math.random();
if (x + y > 1) {
// We're in the upper right triangle in the square, mirror to lower left
x = 1 - x;
y = 1 - y;
}
// Transform the point to the triangle abc
var u = b.sub(a);
var v = c.sub(a);
return a.add(u.mult(x).add(v.mult(y)));
}
typedef TParticlePhysics = {
var velocity: Vec4;
var gravity: Vec3;
var lifetime: Float;
var age: Float;
var hasScaleRamp: Bool;
var baseScale: Vec4;
var rampPositions: Array<FastFloat>;
var rampColors: Array<FastFloat>;
var scaleRampSizeFactor: FastFloat;
}
}
#end

View File

@ -0,0 +1,306 @@
package iron.object;
#if lnx_gpu_particles
import kha.FastFloat;
import kha.graphics4.Usage;
import kha.arrays.Float32Array;
import iron.data.Data;
import iron.data.ParticleData;
import iron.data.SceneFormat;
import iron.system.Time;
import iron.math.Mat4;
import iron.math.Quat;
import iron.math.Vec3;
import iron.math.Vec4;
class ParticleSystemGPU {
public var data: ParticleData;
public var speed = 1.0;
var currentSpeed = 0.0;
var particles: Array<Particle>;
var ready: Bool;
var frameRate = 24;
var lifetime = 0.0;
var looptime = 0.0;
var animtime = 0.0;
var time = 0.0;
var spawnRate = 0.0;
var seed = 0;
var r: TParticleData;
var gx: Float;
var gy: Float;
var gz: Float;
var alignx: Float;
var aligny: Float;
var alignz: Float;
var dimx: Float;
var dimy: Float;
var tilesx: Int;
var tilesy: Int;
var tilesFramerate: Int;
var count = 0;
var lap = 0;
var lapTime = 0.0;
var m = Mat4.identity();
var owner: MeshObject;
var ownerLoc = new Vec4();
var ownerRot = new Quat();
var ownerScl = new Vec4();
var random = 0.0;
public function new(sceneName: String, pref: TParticleReference, mo: MeshObject) {
seed = pref.seed;
currentSpeed = speed;
speed = 0;
particles = [];
ready = false;
Data.getParticle(sceneName, pref.particle, function(b: ParticleData) {
data = b;
r = data.raw;
owner = mo;
if (Scene.active.raw.gravity != null) {
gx = Scene.active.raw.gravity[0] * r.weight_gravity;
gy = Scene.active.raw.gravity[1] * r.weight_gravity;
gz = Scene.active.raw.gravity[2] * r.weight_gravity;
}
else {
gx = 0;
gy = 0;
gz = -9.81 * r.weight_gravity;
}
alignx = r.object_align_factor[0];
aligny = r.object_align_factor[1];
alignz = r.object_align_factor[2];
looptime = (r.frame_end - r.frame_start) / frameRate;
lifetime = r.lifetime / frameRate;
animtime = r.loop ? looptime : looptime + lifetime;
spawnRate = ((r.frame_end - r.frame_start) / r.count) / frameRate;
for (i in 0...r.count) particles.push(new Particle(i));
ready = true;
if (r.auto_start) start();
});
}
public function start() {
if (r.is_unique) random = Math.random();
lifetime = r.lifetime / frameRate;
time = 0;
lap = 0;
lapTime = 0;
speed = currentSpeed;
}
public function pause() {
speed = 0;
}
public function resume() {
lifetime = r.lifetime / frameRate;
speed = currentSpeed;
}
// TODO: interrupt smoothly
public function stop() {
end();
}
function end() {
lifetime = 0;
speed = 0;
lap = 0;
}
public function update(object: MeshObject) {
if (!ready || object == null || speed == 0.0) return;
if (iron.App.pauseUpdates) return;
var prevLap = lap;
// Copy owner world transform but discard scale
owner.transform.world.decompose(ownerLoc, ownerRot, ownerScl);
object.transform.loc = ownerLoc;
object.transform.rot = ownerRot;
// Set particle size per particle system
object.transform.scale = new Vec4(r.particle_size, r.particle_size, r.particle_size, 1);
object.transform.buildMatrix();
owner.transform.buildMatrix();
object.transform.dim.setFrom(owner.transform.dim);
dimx = object.transform.dim.x;
dimy = object.transform.dim.y;
if (object.tilesheet != null) {
tilesx = object.tilesheet.getTilesX();
tilesy = object.tilesheet.getTilesY();
tilesFramerate = object.tilesheet.action.framerate;
}
// Animate
time += Time.renderDelta * speed;
lap = Std.int(time / animtime);
lapTime = time - lap * animtime;
count = Std.int(lapTime / spawnRate);
if (lap > prevLap && !r.loop) {
end();
}
updateGpu(object);
}
public function getData(): Mat4 {
var hair = r.type == 1;
m._00 = animtime;
m._01 = hair ? 1 / particles.length : spawnRate;
m._02 = hair ? 1 : lifetime;
m._03 = particles.length;
m._10 = hair ? 0 : alignx;
m._11 = hair ? 0 : aligny;
m._12 = hair ? 0 : alignz;
m._13 = hair ? 0 : r.factor_random;
m._20 = hair ? 0 : gx;
m._21 = hair ? 0 : gy;
m._22 = hair ? 0 : gz;
m._23 = hair ? 0 : r.lifetime_random;
m._30 = tilesx;
m._31 = tilesy;
m._32 = 1 / tilesFramerate;
m._33 = hair ? 1 : lapTime;
return m;
}
public function getSizeRandom(): FastFloat {
return r.size_random;
}
public function getRandom(): FastFloat {
return random;
}
public function getSize(): FastFloat {
return r.particle_size;
}
function updateGpu(object: MeshObject) {
if (!object.data.geom.instanced) setupGeomGpu(object);
// GPU particles transform is attached to owner object
}
function setupGeomGpu(object: MeshObject) {
var instancedData = new Float32Array(particles.length * 3);
var i = 0;
var normFactor = 1 / 32767; // pa.values are not normalized
var scalePosOwner = owner.data.scalePos;
var scalePosParticle = object.data.scalePos;
var particleSize = r.particle_size;
var scaleFactor = new Vec4().setFrom(owner.transform.scale);
scaleFactor.mult(scalePosOwner / (particleSize * scalePosParticle));
switch (r.emit_from) {
case 0: // Vert
var pa = owner.data.geom.positions;
for (p in particles) {
var j = Std.int(fhash(i) * (pa.values.length / pa.size));
instancedData.set(i, pa.values[j * pa.size ] * normFactor * scaleFactor.x); i++;
instancedData.set(i, pa.values[j * pa.size + 1] * normFactor * scaleFactor.y); i++;
instancedData.set(i, pa.values[j * pa.size + 2] * normFactor * scaleFactor.z); i++;
}
case 1: // Face
var positions = owner.data.geom.positions.values;
for (p in particles) {
// Choose random index array (there is one per material) and random face
var ia = owner.data.geom.indices[Std.random(owner.data.geom.indices.length)];
var faceIndex = Std.random(Std.int(ia.length / 3));
var i0 = ia[faceIndex * 3 + 0];
var i1 = ia[faceIndex * 3 + 1];
var i2 = ia[faceIndex * 3 + 2];
var v0 = new Vec3(positions[i0 * 4], positions[i0 * 4 + 1], positions[i0 * 4 + 2]);
var v1 = new Vec3(positions[i1 * 4], positions[i1 * 4 + 1], positions[i1 * 4 + 2]);
var v2 = new Vec3(positions[i2 * 4], positions[i2 * 4 + 1], positions[i2 * 4 + 2]);
var pos = randomPointInTriangle(v0, v1, v2);
instancedData.set(i, pos.x * normFactor * scaleFactor.x); i++;
instancedData.set(i, pos.y * normFactor * scaleFactor.y); i++;
instancedData.set(i, pos.z * normFactor * scaleFactor.z); i++;
}
case 2: // Volume
var scaleFactorVolume = new Vec4().setFrom(object.transform.dim);
scaleFactorVolume.mult(0.5 / (particleSize * scalePosParticle));
for (p in particles) {
instancedData.set(i, (Math.random() * 2.0 - 1.0) * scaleFactorVolume.x); i++;
instancedData.set(i, (Math.random() * 2.0 - 1.0) * scaleFactorVolume.y); i++;
instancedData.set(i, (Math.random() * 2.0 - 1.0) * scaleFactorVolume.z); i++;
}
}
object.data.geom.setupInstanced(instancedData, 1, Usage.StaticUsage);
}
function fhash(n: Int): Float {
var s = n + 1.0;
s *= 9301.0 % s;
s = (s * 9301.0 + 49297.0) % 233280.0;
return s / 233280.0;
}
public function remove() {}
/**
Generates a random point in the triangle with vertex positions abc.
Please note that the given position vectors are changed in-place by this
function and can be considered garbage afterwards, so make sure to clone
them first if needed.
**/
public static inline function randomPointInTriangle(a: Vec3, b: Vec3, c: Vec3): Vec3 {
// Generate a random point in a square where (0, 0) <= (x, y) < (1, 1)
var x = Math.random();
var y = Math.random();
if (x + y > 1) {
// We're in the upper right triangle in the square, mirror to lower left
x = 1 - x;
y = 1 - y;
}
// Transform the point to the triangle abc
var u = b.sub(a);
var v = c.sub(a);
return a.add(u.mult(x).add(v.mult(y)));
}
}
class Particle {
public var i: Int;
public var x = 0.0;
public var y = 0.0;
public var z = 0.0;
public var cameraDistance: Float;
public function new(i: Int) {
this.i = i;
}
}
#end

View File

@ -1,53 +1,258 @@
package iron.object; package iron.object;
import iron.Scene; import iron.App;
import iron.data.Data;
import iron.data.SceneFormat; import iron.data.SceneFormat;
import iron.system.Time; import iron.system.Time;
import haxe.ds.Map;
@:allow(iron.Scene)
class Tilesheet { class Tilesheet {
public var tileX = 0.0; // Tile offset on tilesheet texture 0-1 public var tileX: Float = 0.0;
public var tileY = 0.0; public var tileY: Float = 0.0;
public var flipX: Bool = false;
public var raw: TTilesheetData; public var flipY: Bool = false;
public var paused: Bool = false;
public var frame: Int = 0;
public var actions: Array<TTilesheetAction>;
public var action: TTilesheetAction = null; public var action: TTilesheetAction = null;
var ready: Bool;
public var paused = false; public var ready: Bool = false;
public var frame = 0; var time: Float = 0.0;
var time = 0.0;
var onActionComplete: Void->Void = null; var onActionComplete: Void->Void = null;
var onReady: Void->Void = null;
var onEvent: String->Void = null; // Callback for tilesheet events
var prevFrame: Int = -1; // Track previous frame to detect changes
var owner: MeshObject = null;
var currentMesh: MeshObject = null;
var meshCache: Map<String, MeshObject> = new Map();
var pendingAction: String = null;
var pendingOnComplete: Void->Void = null;
public function new(sceneName: String, tilesheet_ref: String, tilesheet_action_ref: String) { public function new(tilesheetData: TTilesheetData, ownerObject: MeshObject = null) {
ready = false; owner = ownerObject;
Data.getSceneRaw(sceneName, function(format: TSceneFormat) { actions = tilesheetData.actions;
for (ts in format.tilesheet_datas) {
if (ts.name == tilesheet_ref) { pendingAction = tilesheetData.start_action;
raw = ts; if ((pendingAction == null || pendingAction == "") && actions.length > 0) {
Scene.active.tilesheets.push(this); pendingAction = actions[0].name;
play(tilesheet_action_ref); }
ready = true;
break; flipX = tilesheetData.flipx;
flipY = tilesheetData.flipy;
// If no actions need mesh swapping, ready immediately
var hasMeshActions: Bool = false;
for (a in actions) {
if (a.mesh != null && a.mesh != "") {
hasMeshActions = true;
break;
}
}
if (!hasMeshActions) {
ready = true;
if (pendingAction != null) {
playAction(pendingAction);
pendingAction = null;
}
if (onReady != null) onReady();
}
}
public function update() {
if (App.pauseUpdates) return;
if (!ready) {
if (tryInitialize()) {
ready = true;
if (pendingAction != null) {
playAction(pendingAction, pendingOnComplete);
pendingAction = null;
pendingOnComplete = null;
}
if (onReady != null) onReady();
}
return;
}
if (paused || action == null || action.start >= action.end) return;
time += Time.renderDelta;
var frameTime = 1 / action.framerate;
var framesToAdvance = 0;
while (time >= frameTime) {
time -= frameTime;
framesToAdvance++;
}
if (framesToAdvance > 0) {
setFrame(frame + framesToAdvance);
}
}
function tryInitialize(): Bool {
if (owner == null) return false;
// If no children, use the owner mesh itself
if (owner.children == null || owner.children.length == 0) {
if (owner.data != null && !meshCache.exists(owner.data.name)) {
meshCache.set(owner.data.name, owner);
// Also cache by object name for flexible lookup
if (owner.name != owner.data.name) {
meshCache.set(owner.name, owner);
} }
} }
}); } else {
// Use child meshes for mesh swapping
for (child in owner.children) {
if (Std.isOfType(child, MeshObject)) {
var meshChild = cast(child, MeshObject);
if (meshChild.data != null && !meshCache.exists(meshChild.data.name)) {
meshCache.set(meshChild.data.name, meshChild);
meshChild.visible = false;
// Also cache by object name for flexible lookup
if (meshChild.name != meshChild.data.name) {
meshCache.set(meshChild.name, meshChild);
}
}
}
}
}
for (a in actions) {
if (a.mesh != null && a.mesh != "" && !meshCache.exists(a.mesh)) {
if (findMatchingMesh(a.mesh) == null) return false;
}
}
return true;
}
/** Find mesh by base name pattern (handles linked objects with different suffixes). */
function findMatchingMesh(actionMeshName: String): MeshObject {
var baseName = actionMeshName;
// Strip "Mesh" prefix if present
if (StringTools.startsWith(baseName, "Mesh")) {
baseName = baseName.substr(4);
}
// Strip suffix after underscore (e.g., "_character.blend")
var idx = baseName.indexOf("_");
if (idx > 0) baseName = baseName.substr(0, idx);
for (meshName in meshCache.keys()) {
if (meshName.indexOf(baseName) != -1) {
var mesh = meshCache.get(meshName);
meshCache.set(actionMeshName, mesh); // Cache alias
return mesh;
}
}
return null;
} }
public function play(action_ref: String, onActionComplete: Void->Void = null) { public function play(action_ref: String, onActionComplete: Void->Void = null) {
this.onActionComplete = onActionComplete; if (actions == null) return;
for (a in raw.actions) {
if (!ready) {
pendingAction = action_ref;
pendingOnComplete = onActionComplete;
return;
}
playAction(action_ref, onActionComplete);
}
public function notifyOnReady(callback: Void->Void) {
onReady = callback;
if (ready) onReady();
}
public function notifyOnEvent(callback: String->Void) {
onEvent = callback;
}
function playAction(action_ref: String, onComplete: Void->Void = null) {
if (action != null && action.name == action_ref) {
paused = false;
return;
}
onActionComplete = onComplete;
for (a in actions) {
if (a.name == action_ref) { if (a.name == action_ref) {
action = a; action = a;
break; break;
} }
} }
if (action == null) return;
if (action.mesh != null && action.mesh != "") {
var targetMesh = meshCache.get(action.mesh);
if (targetMesh != null && targetMesh != currentMesh) {
swapMesh(targetMesh);
}
}
prevFrame = -1; // Reset previous frame for new action
setFrame(action.start); setFrame(action.start);
paused = false; paused = false;
time = 0.0; time = 0.0;
} }
function swapMesh(meshObj: MeshObject) {
if (owner == null || meshObj == null) return;
currentMesh = meshObj;
if (meshObj.data != null) owner.setData(meshObj.data);
if (meshObj.materials != null) owner.materials = meshObj.materials;
}
function setFrame(f: Int) {
frame = f;
if (frame > action.end && action.start < action.end) {
// Check for events on last frame before completing
checkEvents(prevFrame, action.end);
if (onActionComplete != null) onActionComplete();
if (action.loop) {
prevFrame = -1; // Reset for loop
setFrame(action.start);
} else {
paused = true;
}
return;
}
// Check for events between previous frame and current frame
checkEvents(prevFrame, frame);
prevFrame = frame;
var tx = frame % action.tilesx;
var ty = Std.int(frame / action.tilesx);
tileX = tx / action.tilesx;
tileY = ty / action.tilesy;
}
/** Check and fire events for frames between fromFrame (exclusive) and toFrame (inclusive). */
function checkEvents(fromFrame: Int, toFrame: Int) {
if (onEvent == null || action == null || action.events == null) return;
// Convert to action-relative frame numbers
var relativeFrom = fromFrame - action.start;
var relativeTo = toFrame - action.start;
for (evt in action.events) {
// Fire event if it falls in the range (fromFrame, toFrame]
if (evt.frame > relativeFrom && evt.frame <= relativeTo) {
onEvent(evt.name);
}
}
}
public function pause() { public function pause() {
paused = true; paused = true;
} }
@ -57,61 +262,33 @@ class Tilesheet {
} }
public function remove() { public function remove() {
Scene.active.tilesheets.remove(this); ready = false;
action = null;
actions = null;
owner = null;
currentMesh = null;
pendingAction = null;
pendingOnComplete = null;
onEvent = null;
prevFrame = -1;
meshCache.clear();
} }
/**
* Set the frame of the current active tilesheet action. Automatically un-pauses action.
* @param frame Frame offset with 0 as the first frame of the active action.
**/
public function setFrameOffset(frame: Int) { public function setFrameOffset(frame: Int) {
if (action == null) return;
setFrame(action.start + frame); setFrame(action.start + frame);
paused = false; paused = false;
} }
/**
* Returns the current frame.
* @return Frame offset with 0 as the first frame of the active action.
*/
public function getFrameOffset(): Int { public function getFrameOffset(): Int {
return frame - action.start; return action != null ? frame - action.start : 0;
} }
function update() { public function getTilesX(): Int {
if (!ready || paused || action.start >= action.end) return; return action != null ? action.tilesx : 1;
time += Time.renderDelta;
var frameTime = 1 / raw.framerate;
var framesToAdvance = 0;
// Check how many animation frames passed during the last render frame
// and catch up if required. The remaining `time` that couldn't fit in
// another animation frame will be used in the next `update()`.
while (time >= frameTime) {
time -= frameTime;
framesToAdvance++;
}
if (framesToAdvance != 0) {
setFrame(frame + framesToAdvance);
}
} }
function setFrame(f: Int) { public function getTilesY(): Int {
frame = f; return action != null ? action.tilesy : 1;
// Action end
if (frame > action.end && action.start < action.end) {
if (onActionComplete != null) onActionComplete();
if (action.loop) setFrame(action.start);
else paused = true;
return;
}
var tx = frame % raw.tilesx;
var ty = Std.int(frame / raw.tilesx);
tileX = tx * (1 / raw.tilesx);
tileY = ty * (1 / raw.tilesy);
} }
} }

View File

@ -286,7 +286,7 @@ class Transform {
public function applyParentInverse() { public function applyParentInverse() {
var pt = object.parent.transform; var pt = object.parent.transform;
pt.buildMatrix(); pt.buildMatrix();
temp.getInverse(pt.world); temp.getInverse(pt.local);
this.local.multmat(temp); this.local.multmat(temp);
this.decompose(); this.decompose();
this.buildMatrix(); this.buildMatrix();
@ -295,7 +295,7 @@ class Transform {
public function applyParent() { public function applyParent() {
var pt = object.parent.transform; var pt = object.parent.transform;
pt.buildMatrix(); pt.buildMatrix();
this.local.multmat(pt.world); this.local.multmat(pt.local);
this.decompose(); this.decompose();
this.buildMatrix(); this.buildMatrix();
} }

View File

@ -7,16 +7,20 @@ import kha.graphics4.TextureFilter;
import kha.graphics4.MipMapFilter; import kha.graphics4.MipMapFilter;
import kha.arrays.Float32Array; import kha.arrays.Float32Array;
import iron.math.Vec4; import iron.math.Vec4;
import iron.math.Mat4;
import iron.math.Quat; import iron.math.Quat;
import iron.math.Mat3; import iron.math.Mat3;
import iron.math.Mat4;
import iron.data.WorldData;
import iron.data.MaterialData; import iron.data.MaterialData;
import iron.data.ShaderData; import iron.data.ShaderData;
import iron.data.SceneFormat; import iron.data.WorldData;
import iron.data.SceneFormat.TShaderConstant;
import iron.data.SceneFormat.TBindConstant;
import iron.object.Transform;
import iron.object.LightObject;
import iron.Scene;
import iron.RenderPath;
import iron.system.Input; import iron.system.Input;
import iron.system.Time; import iron.system.Time;
import iron.RenderPath;
using StringTools; using StringTools;
// Structure for setting shader uniforms // Structure for setting shader uniforms
@ -38,6 +42,7 @@ class Uniforms {
public static var helpMat = Mat4.identity(); public static var helpMat = Mat4.identity();
public static var helpMat2 = Mat4.identity(); public static var helpMat2 = Mat4.identity();
public static var helpMat3 = Mat3.identity(); public static var helpMat3 = Mat3.identity();
public static var helpMat4 = Mat4.identity();
public static var helpVec = new Vec4(); public static var helpVec = new Vec4();
public static var helpVec2 = new Vec4(); public static var helpVec2 = new Vec4();
public static var helpQuat = new Quat(); // Keep at identity public static var helpQuat = new Quat(); // Keep at identity
@ -47,6 +52,10 @@ class Uniforms {
public static var externalVec4Links: Array<Object->MaterialData->String->Vec4> = null; public static var externalVec4Links: Array<Object->MaterialData->String->Vec4> = null;
public static var externalVec3Links: Array<Object->MaterialData->String->Vec4> = null; public static var externalVec3Links: Array<Object->MaterialData->String->Vec4> = null;
public static var externalVec2Links: Array<Object->MaterialData->String->Vec4> = null; public static var externalVec2Links: Array<Object->MaterialData->String->Vec4> = null;
public static var eyeLeftCallCount = 0;
public static var lastFrameChecked = -1;
public static var externalFloatLinks: Array<Object->MaterialData->String->Null<kha.FastFloat>> = null; public static var externalFloatLinks: Array<Object->MaterialData->String->Null<kha.FastFloat>> = null;
public static var externalFloatsLinks: Array<Object->MaterialData->String->Float32Array> = null; public static var externalFloatsLinks: Array<Object->MaterialData->String->Float32Array> = null;
public static var externalIntLinks: Array<Object->MaterialData->String->Null<Int>> = null; public static var externalIntLinks: Array<Object->MaterialData->String->Null<Int>> = null;
@ -290,6 +299,89 @@ class Uniforms {
helpMat.getInverse(helpMat); helpMat.getInverse(helpMat);
m = helpMat; m = helpMat;
} }
#if lnx_vr
case "_inverseViewProjectionMatrixLeft": {
var vr = kha.vr.VrInterface.instance;
if (vr != null && vr.IsPresenting()) {
var leftView = vr.GetViewMatrix(0);
var leftProj = vr.GetProjectionMatrix(0);
helpMat._00 = leftView._00; helpMat._01 = leftView._01; helpMat._02 = leftView._02; helpMat._03 = leftView._03;
helpMat._10 = leftView._10; helpMat._11 = leftView._11; helpMat._12 = leftView._12; helpMat._13 = leftView._13;
helpMat._20 = leftView._20; helpMat._21 = leftView._21; helpMat._22 = leftView._22; helpMat._23 = leftView._23;
helpMat._30 = leftView._30; helpMat._31 = leftView._31; helpMat._32 = leftView._32; helpMat._33 = leftView._33;
helpMat2._00 = leftProj._00; helpMat2._01 = leftProj._01; helpMat2._02 = leftProj._02; helpMat2._03 = leftProj._03;
helpMat2._10 = leftProj._10; helpMat2._11 = leftProj._11; helpMat2._12 = leftProj._12; helpMat2._13 = leftProj._13;
helpMat2._20 = leftProj._20; helpMat2._21 = leftProj._21; helpMat2._22 = leftProj._22; helpMat2._23 = leftProj._23;
helpMat2._30 = leftProj._30; helpMat2._31 = leftProj._31; helpMat2._32 = leftProj._32; helpMat2._33 = leftProj._33;
helpMat.multmat(helpMat2);
helpMat.getInverse(helpMat);
} else if (iron.RenderPath.isVRSimulateMode()) {
var ipd_offset = 0.032 * 35.0; // Match eye offset
var rightVec = camera.rightWorld();
var eyeLeftX = camera.transform.worldx() - rightVec.x * ipd_offset;
var eyeLeftY = camera.transform.worldy() - rightVec.y * ipd_offset;
var eyeLeftZ = camera.transform.worldz() - rightVec.z * ipd_offset;
helpMat.setFrom(camera.transform.world);
helpMat._30 = eyeLeftX;
helpMat._31 = eyeLeftY;
helpMat._32 = eyeLeftZ;
helpMat.getInverse(helpMat); // Now it's a view matrix
helpMat.multmat(camera.P);
helpMat.getInverse(helpMat);
} else {
helpMat.setFrom(camera.V);
helpMat.multmat(camera.P);
helpMat.getInverse(helpMat);
}
m = helpMat;
}
case "_inverseViewProjectionMatrixRight": {
var vr = kha.vr.VrInterface.instance;
if (vr != null && vr.IsPresenting()) {
var rightView = vr.GetViewMatrix(1);
var rightProj = vr.GetProjectionMatrix(1);
// kha.math.FastMatrix4 to iron.math.Mat4
helpMat2._00 = rightView._00; helpMat2._01 = rightView._01; helpMat2._02 = rightView._02; helpMat2._03 = rightView._03;
helpMat2._10 = rightView._10; helpMat2._11 = rightView._11; helpMat2._12 = rightView._12; helpMat2._13 = rightView._13;
helpMat2._20 = rightView._20; helpMat2._21 = rightView._21; helpMat2._22 = rightView._22; helpMat2._23 = rightView._23;
helpMat2._30 = rightView._30; helpMat2._31 = rightView._31; helpMat2._32 = rightView._32; helpMat2._33 = rightView._33;
helpMat4._00 = rightProj._00; helpMat4._01 = rightProj._01; helpMat4._02 = rightProj._02; helpMat4._03 = rightProj._03;
helpMat4._10 = rightProj._10; helpMat4._11 = rightProj._11; helpMat4._12 = rightProj._12; helpMat4._13 = rightProj._13;
helpMat4._20 = rightProj._20; helpMat4._21 = rightProj._21; helpMat4._22 = rightProj._22; helpMat4._23 = rightProj._23;
helpMat4._30 = rightProj._30; helpMat4._31 = rightProj._31; helpMat4._32 = rightProj._32; helpMat4._33 = rightProj._33;
helpMat2.multmat(helpMat4);
helpMat2.getInverse(helpMat2);
m = helpMat2;
} else if (iron.RenderPath.isVRSimulateMode()) {
var ipd_offset = 0.032 * 35.0;
var rightVec = camera.rightWorld();
// calculate right eye position in world space
var eyeRightX = camera.transform.worldx() + rightVec.x * ipd_offset;
var eyeRightY = camera.transform.worldy() + rightVec.y * ipd_offset;
var eyeRightZ = camera.transform.worldz() + rightVec.z * ipd_offset;
helpMat2.setFrom(camera.transform.world);
helpMat2._30 = eyeRightX;
helpMat2._31 = eyeRightY;
helpMat2._32 = eyeRightZ;
helpMat2.getInverse(helpMat2);
helpMat2.multmat(camera.P);
helpMat2.getInverse(helpMat2);
m = helpMat2;
} else {
// fallback to center camera
helpMat2.setFrom(camera.V);
helpMat2.multmat(camera.P);
helpMat2.getInverse(helpMat2);
m = helpMat2;
}
}
#end
case "_viewProjectionMatrix": { case "_viewProjectionMatrix": {
#if lnx_centerworld #if lnx_centerworld
m = vmat(camera.V); m = vmat(camera.V);
@ -402,6 +494,28 @@ class Uniforms {
v = helpVec; v = helpVec;
} }
} }
#if lnx_vr
case "_pointPositionLeft": {
var point = RenderPath.active.point;
if (point != null) {
var lightWorldX = point.transform.worldx();
var lightWorldY = point.transform.worldy();
var lightWorldZ = point.transform.worldz();
helpVec.set(lightWorldX, lightWorldY, lightWorldZ);
v = helpVec;
}
}
case "_pointPositionRight": {
var point = RenderPath.active.point;
if (point != null) {
var lightWorldX = point.transform.worldx();
var lightWorldY = point.transform.worldy();
var lightWorldZ = point.transform.worldz();
helpVec.set(lightWorldX, lightWorldY, lightWorldZ);
v = helpVec;
}
}
#end
#if lnx_spot #if lnx_spot
case "_spotDirection": { case "_spotDirection": {
var point = RenderPath.active.point; var point = RenderPath.active.point;
@ -488,8 +602,90 @@ class Uniforms {
helpVec = camera.rightWorld().normalize(); helpVec = camera.rightWorld().normalize();
v = helpVec; v = helpVec;
} }
#if lnx_vr
case "_eyeLeft": {
var currentFrame = iron.RenderPath.active.frame;
if (currentFrame != lastFrameChecked) {
eyeLeftCallCount = 0;
lastFrameChecked = currentFrame;
}
eyeLeftCallCount++;
var vr = kha.vr.VrInterface.instance;
if (vr != null && vr.IsPresenting()) {
var leftViewMatrix = vr.GetViewMatrix(0);
var invLeft = leftViewMatrix.inverse();
helpVec.set(invLeft._30, invLeft._31, invLeft._32);
// trace("eyeLeft: " + helpVec.x + ", " + helpVec.y + ", " + helpVec.z);
} else if (iron.RenderPath.isVRSimulateMode()) {
var ipd_offset = 0.032 * 35.0;
var rightVec = camera.rightWorld();
var centerX = camera.transform.worldx();
var centerY = camera.transform.worldy();
var centerZ = camera.transform.worldz();
helpVec.set(
centerX - rightVec.x * ipd_offset,
centerY - rightVec.y * ipd_offset,
centerZ - rightVec.z * ipd_offset
);
} else {
helpVec.set(camera.transform.worldx(), camera.transform.worldy(), camera.transform.worldz());
}
v = helpVec;
}
case "_eyeRight": {
var vr = kha.vr.VrInterface.instance;
if (vr != null && vr.IsPresenting()) {
var rightViewMatrix = vr.GetViewMatrix(1);
var invRight = rightViewMatrix.inverse();
helpVec.set(invRight._30, invRight._31, invRight._32);
} else if (iron.RenderPath.isVRSimulateMode()) {
var ipd_offset = 0.032 * 35.0;
var rightVec = camera.rightWorld();
var centerX = camera.transform.worldx();
var centerY = camera.transform.worldy();
var centerZ = camera.transform.worldz();
helpVec.set(
centerX + rightVec.x * ipd_offset,
centerY + rightVec.y * ipd_offset,
centerZ + rightVec.z * ipd_offset
);
} else {
helpVec.set(camera.transform.worldx(), camera.transform.worldy(), camera.transform.worldz());
}
v = helpVec;
}
case "_eyeLookLeft": {
var vr = kha.vr.VrInterface.instance;
if (vr != null && vr.IsPresenting()) {
var leftViewMatrix = vr.GetViewMatrix(0);
var invLeft = leftViewMatrix.inverse();
helpVec.set(-invLeft._20, -invLeft._21, -invLeft._22);
helpVec.normalize();
} else {
helpVec = camera.lookWorld().normalize();
}
v = helpVec;
}
case "_eyeLookRight": {
var vr = kha.vr.VrInterface.instance;
if (vr != null && vr.IsPresenting()) {
var rightViewMatrix = vr.GetViewMatrix(1);
var invRight = rightViewMatrix.inverse();
helpVec.set(-invRight._20, -invRight._21, -invRight._22);
helpVec.normalize();
} else {
helpVec = camera.lookWorld().normalize();
}
v = helpVec;
}
#end
case "_backgroundCol": { case "_backgroundCol": {
if (camera.data.raw.clear_color != null) helpVec.set(camera.data.raw.clear_color[0], camera.data.raw.clear_color[1], camera.data.raw.clear_color[2]); if (Scene.active.world != null) {
var col = Scene.active.world.raw.background_color;
helpVec.set(((col >> 16) & 0xff) / 255, ((col >> 8) & 0xff) / 255, (col & 0xff) / 255);
}
else if (camera.data.raw.clear_color != null) helpVec.set(camera.data.raw.clear_color[0], camera.data.raw.clear_color[1], camera.data.raw.clear_color[2]);
v = helpVec; v = helpVec;
} }
case "_hosekSunDirection": { case "_hosekSunDirection": {
@ -672,7 +868,8 @@ class Uniforms {
f = iron.App.w() / iron.App.h(); f = iron.App.w() / iron.App.h();
} }
case "_frameScale": { case "_frameScale": {
f = RenderPath.active.frameTime / Time.delta; var d = Time.delta;
f = d > 0.0001 ? Math.min(RenderPath.active.frameTime / d, 2.0) : 1.0;
} }
case "_fieldOfView": { case "_fieldOfView": {
f = camera.data.raw.fov; f = camera.data.raw.fov;
@ -902,7 +1099,7 @@ class Uniforms {
m = helpMat; m = helpMat;
} }
#end #end
#if lnx_particles #if lnx_gpu_particles
case "_particleData": { case "_particleData": {
var mo = cast(object, MeshObject); var mo = cast(object, MeshObject);
if (mo.particleOwner != null && mo.particleOwner.particleSystems != null) { if (mo.particleOwner != null && mo.particleOwner.particleSystems != null) {
@ -913,18 +1110,9 @@ class Uniforms {
} }
if (m == null) { if (m == null) {
#if lnx_spot #if (!lnx_clusters && lnx_spot)
if (c.link.startsWith("_biasLightWorldViewProjectionMatrixSpot")) {
var light = getSpot(c.link.charCodeAt(c.link.length - 1) - "0".code);
if (light != null) {
object == null ? helpMat.setIdentity() : helpMat.setFrom(object.transform.worldUnpack);
helpMat.multmat(light.VP);
helpMat.multmat(biasMat);
m = helpMat;
}
}
if (c.link.startsWith("_biasLightViewProjectionMatrixSpot")) { if (c.link.startsWith("_biasLightViewProjectionMatrixSpot")) {
var light = getSpot(c.link.charCodeAt(c.link.length - 1) - "0".code); var light = getSpot(0);
if (light != null) { if (light != null) {
helpMat.setFrom(light.VP); helpMat.setFrom(light.VP);
helpMat.multmat(biasMat); helpMat.multmat(biasMat);
@ -1058,14 +1246,19 @@ class Uniforms {
var vy: Null<kha.FastFloat> = null; var vy: Null<kha.FastFloat> = null;
switch (c.link) { switch (c.link) {
case "_tilesheetOffset": { case "_tilesheetOffset": {
var ts = cast(object, MeshObject).activeTilesheet; var ts = cast(object, MeshObject).tilesheet;
vx = ts.tileX; vx = ts.tileX;
vy = ts.tileY; vy = ts.tileY;
} }
case "_tilesheetFlip": {
var ts = cast(object, MeshObject).tilesheet;
vx = ts.flipX ? 1.0 : 0.0;
vy = ts.flipY ? 1.0 : 0.0;
}
case "_tilesheetTiles": { case "_tilesheetTiles": {
var ts = cast(object, MeshObject).activeTilesheet; var ts = cast(object, MeshObject).tilesheet;
vx = ts.raw.tilesx; vx = ts.getTilesX();
vy = ts.raw.tilesy; vy = ts.getTilesY();
} }
#if lnx_morph_target #if lnx_morph_target
case "_morphScaleOffset": { case "_morphScaleOffset": {
@ -1113,7 +1306,7 @@ class Uniforms {
case "_texUnpack": { case "_texUnpack": {
f = texUnpack != null ? texUnpack : 1.0; f = texUnpack != null ? texUnpack : 1.0;
} }
#if lnx_particles #if lnx_gpu_particles
case "_particleSizeRandom": { case "_particleSizeRandom": {
var mo = cast(object, MeshObject); var mo = cast(object, MeshObject);
if (mo.particleOwner != null && mo.particleOwner.particleSystems != null) { if (mo.particleOwner != null && mo.particleOwner.particleSystems != null) {
@ -1168,16 +1361,16 @@ class Uniforms {
fa = cast(object, MeshObject).morphTarget.morphWeights; fa = cast(object, MeshObject).morphTarget.morphWeights;
} }
#end #end
} }
if (fa == null && externalFloatsLinks != null) { if (fa == null && externalFloatsLinks != null) {
for (fn in externalFloatsLinks) { for (fn in externalFloatsLinks) {
fa = fn(object, currentMat(object), c.link); fa = fn(object, currentMat(object), c.link);
if (fa != null) break; if (fa != null) break;
} }
} }
if (fa == null) return; if (fa == null) return;
g.setFloats(location, fa); g.setFloats(location, fa);
} }
else if (c.type == "int") { else if (c.type == "int") {
@ -1207,6 +1400,7 @@ class Uniforms {
if (materialContext.raw.bind_constants != null) { if (materialContext.raw.bind_constants != null) {
for (i in 0...materialContext.raw.bind_constants.length) { for (i in 0...materialContext.raw.bind_constants.length) {
var matc = materialContext.raw.bind_constants[i]; var matc = materialContext.raw.bind_constants[i];
if (matc == null) continue;
var pos = -1; var pos = -1;
for (i in 0...context.raw.constants.length) { for (i in 0...context.raw.constants.length) {
if (context.raw.constants[i].name == matc.name) { if (context.raw.constants[i].name == matc.name) {

View File

@ -601,8 +601,10 @@ class Keyboard extends VirtualInput {
function downListener(code: KeyCode) { function downListener(code: KeyCode) {
var s = keyCode(code); var s = keyCode(code);
keysFrame.push(s); if (!keysDown.get(s)) {
keysStarted.set(s, true); keysFrame.push(s);
keysStarted.set(s, true);
}
keysDown.set(s, true); keysDown.set(s, true);
repeatTime = kha.Scheduler.time() + 0.4; repeatTime = kha.Scheduler.time() + 0.4;
@ -618,8 +620,10 @@ class Keyboard extends VirtualInput {
function upListener(code: KeyCode) { function upListener(code: KeyCode) {
var s = keyCode(code); var s = keyCode(code);
keysFrame.push(s); if (keysDown.get(s)) {
keysReleased.set(s, true); keysFrame.push(s);
keysReleased.set(s, true);
}
keysDown.set(s, false); keysDown.set(s, false);
#if kha_android_rmb #if kha_android_rmb
@ -746,7 +750,11 @@ class Gamepad extends VirtualInput {
} }
else if (axis == 1 || axis == 3) { // Y else if (axis == 1 || axis == 3) { // Y
stick.lastY = stick.y; stick.lastY = stick.y;
#if (kha_html5 || lnx_debug_html5)
stick.y = -value;
#else
stick.y = value; stick.y = value;
#end
stick.movementY = stick.y - stick.lastY; stick.movementY = stick.y - stick.lastY;
} }
stick.moved = true; stick.moved = true;
@ -765,13 +773,12 @@ class Gamepad extends VirtualInput {
} }
class Sensor { class Sensor {
public var x = 0.0; public var x = 0.0;
public var y = 0.0; public var y = 0.0;
public var z = 0.0; public var z = 0.0;
public function new() { public function new(sensorType: kha.input.SensorType = kha.input.SensorType.Accelerometer) {
kha.input.Sensor.get(kha.input.SensorType.Accelerometer).notify(listener); kha.input.Sensor.get(sensorType).notify(listener);
} }
function listener(x: Float, y: Float, z: Float) { function listener(x: Float, y: Float, z: Float) {

View File

@ -111,12 +111,18 @@ class LnxPack {
#if js #if js
var out = {}; var out = {};
#else #else
var out = Type.createEmptyInstance(getClass(key, parentKey)); var cls = getClass(key, parentKey);
var out: Dynamic = cls != null ? Type.createEmptyInstance(cls) : {};
var fields: Array<String> = cls != null ? Type.getInstanceFields(cls) : null;
#end #end
for (n in 0...length) { for (n in 0...length) {
var k = Std.string(read(i)); var raw = read(i);
var k = Std.string(raw);
var v = read(i, k, key); var v = read(i, k, key);
Reflect.setField(out, k, v); #if !js
if (fields == null || fields.indexOf(k) != -1)
#end
Reflect.setField(out, k, v);
} }
return out; return out;
} }
@ -161,7 +167,9 @@ class LnxPack {
case "tracks": TTrack; case "tracks": TTrack;
case "morph_target": TMorphTarget; case "morph_target": TMorphTarget;
case "vertex_groups": TVertex_groups; case "vertex_groups": TVertex_groups;
case _: TSceneFormat; case "tilesheet": TTilesheetData;
case "events": TTilesheetEvent;
case _: null;
} }
} }
#end #end

View File

@ -14,7 +14,7 @@ class Time {
return 1 / frequency; return 1 / frequency;
} }
static var _fixedStep: Null<Float> = 1/60; 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;
@ -24,6 +24,20 @@ class Time {
_fixedStep = value; _fixedStep = value;
} }
static var _fixedStepInterpolation: Float = 0.0;
public static var fixedStepInterpolation(get, never): Float;
static function get_fixedStepInterpolation(): Float {
return _fixedStepInterpolation;
}
// TODO: VR Frame Time Override - used to sync physics with VR headset refresh rate
#if lnx_vr
public static var vrFrameTime: Float = -1.0; // VR frame time in seconds (-1 = not in VR)
static var lastVRFrameTime: Float = 0.0;
static var vrFrameCount: Int = 0;
static var normalModeLogged: Bool = false;
#end
static var lastTime = 0.0; static var lastTime = 0.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;
@ -47,6 +61,24 @@ class Time {
} }
public static function update() { public static function update() {
#if lnx_vr
// TODO: use VR frame time when in VR present mode to sync physics with VR headset refresh
if (vrFrameTime >= 0.0) {
if (lastVRFrameTime > 0.0) {
_delta = vrFrameTime - lastVRFrameTime;
} else {
_delta = 1.0 / 90.0; // Default to 90Hz for first VR frame
}
lastVRFrameTime = vrFrameTime;
return;
} else {
if (!normalModeLogged) {
normalModeLogged = true;
}
}
#end
_delta = realTime() - lastTime; _delta = realTime() - lastTime;
lastTime = realTime(); lastTime = realTime();
} }

View File

@ -94,34 +94,34 @@ class Tween {
// Way too much Reflect trickery.. // Way too much Reflect trickery..
var ps = Reflect.fields(a.props); var ps = Reflect.fields(a.props);
for (i in 0...ps.length) { for (j in 0...ps.length) {
var p = ps[i]; var p = ps[j];
var k = a._time / a.duration; var k = a._time / a.duration;
if (k > 1) k = 1; if (k > 1) k = 1;
if (a._comps[i] == 1) { if (a._comps[j] == 1) {
var fromVal: Float = a._x[i]; var fromVal: Float = a._x[j];
var toVal: Float = Reflect.getProperty(a.props, p); var toVal: Float = Reflect.getProperty(a.props, p);
var val: Float = fromVal + (toVal - fromVal) * eases[a.ease](k); var val: Float = fromVal + (toVal - fromVal) * eases[a.ease](k);
Reflect.setProperty(a.target, p, val); Reflect.setProperty(a.target, p, val);
} }
else { // _comps[i] == 4 else { // _comps[j] == 4
var obj = Reflect.getProperty(a.props, p); var obj = Reflect.getProperty(a.props, p);
var toX: Float = Reflect.getProperty(obj, "x"); var toX: Float = Reflect.getProperty(obj, "x");
var toY: Float = Reflect.getProperty(obj, "y"); var toY: Float = Reflect.getProperty(obj, "y");
var toZ: Float = Reflect.getProperty(obj, "z"); var toZ: Float = Reflect.getProperty(obj, "z");
var toW: Float = Reflect.getProperty(obj, "w"); var toW: Float = Reflect.getProperty(obj, "w");
if (a._normalize[i]) { if (a._normalize[j]) {
var qdot = (a._x[i] * toX) + (a._y[i] * toY) + (a._z[i] * toZ) + (a._w[i] * toW); var qdot = (a._x[j] * toX) + (a._y[j] * toY) + (a._z[j] * toZ) + (a._w[j] * toW);
if (qdot < 0.0) { if (qdot < 0.0) {
toX = -toX; toY = -toY; toZ = -toZ; toW = -toW; toX = -toX; toY = -toY; toZ = -toZ; toW = -toW;
} }
} }
var x: Float = a._x[i] + (toX - a._x[i]) * eases[a.ease](k); var x: Float = a._x[j] + (toX - a._x[j]) * eases[a.ease](k);
var y: Float = a._y[i] + (toY - a._y[i]) * eases[a.ease](k); var y: Float = a._y[j] + (toY - a._y[j]) * eases[a.ease](k);
var z: Float = a._z[i] + (toZ - a._z[i]) * eases[a.ease](k); var z: Float = a._z[j] + (toZ - a._z[j]) * eases[a.ease](k);
var w: Float = a._w[i] + (toW - a._w[i]) * eases[a.ease](k); var w: Float = a._w[j] + (toW - a._w[j]) * eases[a.ease](k);
if (a._normalize[i]) { if (a._normalize[j]) {
var l = Math.sqrt(x * x + y * y + z * z + w * w); var l = Math.sqrt(x * x + y * y + z * z + w * w);
if (l > 0.0) { if (l > 0.0) {
l = 1.0 / l; l = 1.0 / l;
@ -255,7 +255,7 @@ typedef TAnim = {
@:optional var _normalize: Array<Bool>; @:optional var _normalize: Array<Bool>;
} }
@:enum abstract Ease(Int) from Int to Int { enum abstract Ease(Int) from Int to Int {
var Linear = 0; var Linear = 0;
var SineIn = 1; var SineIn = 1;
var SineOut = 2; var SineOut = 2;

View File

@ -0,0 +1,138 @@
package iron.system;
#if lnx_vr
import iron.math.Vec4;
import iron.math.Quat;
class VRController {
public static var leftHandPosition: Vec4 = new Vec4();
public static var leftHandRotation: Quat = new Quat();
public static var rightHandPosition: Vec4 = new Vec4();
public static var rightHandRotation: Quat = new Quat();
public static var leftHandActive: Bool = false;
public static var rightHandActive: Bool = false;
public static var leftThumbstickX: Float = 0.0;
public static var leftThumbstickY: Float = 0.0;
public static var rightThumbstickX: Float = 0.0;
public static var rightThumbstickY: Float = 0.0;
public static var leftTrigger: Float = 0.0;
public static var rightTrigger: Float = 0.0;
public static var leftGrip: Float = 0.0;
public static var rightGrip: Float = 0.0;
public static var leftButtonX: Bool = false;
public static var leftButtonY: Bool = false;
public static var rightButtonA: Bool = false;
public static var rightButtonB: Bool = false;
public static var debugLog:Bool = false;
public static function enableDebug() {
debugLog = true;
}
public static function disableDebug() {
debugLog = false;
}
public static function updatePoses() {
var vr: kha.js.vr.VrInterface = cast kha.vr.VrInterface.instance;
if (vr == null || !vr.IsPresenting()) {
if (debugLog) trace("[VRController] Not presenting or VR null");
leftHandActive = false;
rightHandActive = false;
return;
}
untyped window._vrControllerFrame = (untyped window._vrControllerFrame || 0) + 1;
leftHandActive = false;
rightHandActive = false;
leftButtonX = false;
leftButtonY = false;
rightButtonA = false;
rightButtonB = false;
var refSpace = untyped vr.xrRefSpace;
if (vr.currentInputSources == null || vr.currentFrame == null || refSpace == null) {
return;
}
var inputSources = vr.currentInputSources;
var frame = vr.currentFrame;
var sourceCount:Int = untyped inputSources.length;
for (i in 0...sourceCount) {
var inputSource = untyped inputSources[i];
if (inputSource == null) continue;
var handedness = untyped inputSource.handedness; // "left", "right", or "none"
var gripSpace = untyped inputSource.gripSpace;
var targetRaySpace = untyped inputSource.targetRaySpace;
// use targetRaySpace first laser/pointer and fall back to gripSpace
var space = (targetRaySpace != null) ? targetRaySpace : gripSpace;
if (space == null) {
continue;
}
var pose = untyped frame.getPose(space, refSpace);
if (pose == null || pose.transform == null) {
continue;
}
var transform = pose.transform;
var pos = transform.position;
var orient = transform.orientation;
if (handedness == "left") {
leftHandPosition.set(pos.x, pos.y, pos.z);
leftHandRotation.set(orient.x, orient.y, orient.z, orient.w);
leftHandActive = true;
var gamepad = untyped inputSource.gamepad;
if (gamepad != null) {
// [0]=thumbstickX [1]=thumbstickY [2]=touchpadX [3]=touchpadY
if (gamepad.axes != null && gamepad.axes.length >= 2) {
leftThumbstickX = gamepad.axes[0];
leftThumbstickY = gamepad.axes[1];
}
// [0]=trigger [1]=grip [4]=X [5]=Y
if (gamepad.buttons != null) {
if (gamepad.buttons.length > 0) leftTrigger = gamepad.buttons[0].value;
if (gamepad.buttons.length > 1) leftGrip = gamepad.buttons[1].value;
if (gamepad.buttons.length > 4) leftButtonX = gamepad.buttons[4].pressed;
if (gamepad.buttons.length > 5) leftButtonY = gamepad.buttons[5].pressed;
}
}
}
else if (handedness == "right") {
rightHandPosition.set(pos.x, pos.y, pos.z);
rightHandRotation.set(orient.x, orient.y, orient.z, orient.w);
rightHandActive = true;
var gamepad = untyped inputSource.gamepad;
if (gamepad != null) {
if (gamepad.axes != null && gamepad.axes.length >= 2) {
rightThumbstickX = gamepad.axes[0];
rightThumbstickY = gamepad.axes[1];
}
if (gamepad.buttons != null) {
if (gamepad.buttons.length > 0) rightTrigger = gamepad.buttons[0].value;
if (gamepad.buttons.length > 1) rightGrip = gamepad.buttons[1].value;
if (gamepad.buttons.length > 4) rightButtonA = gamepad.buttons[4].pressed;
if (gamepad.buttons.length > 5) rightButtonB = gamepad.buttons[5].pressed;
}
}
}
}
}
}
#end

View File

@ -44,6 +44,7 @@ typedef TConfig = {
@:optional var rp_supersample: Null<Float>; @:optional var rp_supersample: Null<Float>;
@:optional var rp_shadowmap_cube: Null<Int>; // size @:optional var rp_shadowmap_cube: Null<Int>; // size
@:optional var rp_shadowmap_cascade: Null<Int>; // size for single cascade @:optional var rp_shadowmap_cascade: Null<Int>; // size for single cascade
@:optional var rp_ssao: Null<Bool>;
@:optional var rp_ssgi: Null<Bool>; @:optional var rp_ssgi: Null<Bool>;
@:optional var rp_ssr: Null<Bool>; @:optional var rp_ssr: Null<Bool>;
@:optional var rp_ssrefr: Null<Bool>; @:optional var rp_ssrefr: Null<Bool>;

View File

@ -13,7 +13,7 @@ class AddParticleToObjectNode extends LogicNode {
} }
override function run(from: Int) { override function run(from: Int) {
#if lnx_particles #if lnx_gpu_particles
if (property0 == 'Scene Active'){ if (property0 == 'Scene Active'){
var objFrom: Object = inputs[1].get(); var objFrom: Object = inputs[1].get();
@ -47,7 +47,7 @@ class AddParticleToObjectNode extends LogicNode {
var oslot: Int = mobjTo.particleSystems.length-1; var oslot: Int = mobjTo.particleSystems.length-1;
var opsys = mobjTo.particleSystems[oslot]; var opsys = mobjTo.particleSystems[oslot];
@:privateAccess opsys.setupGeomGpu(mobjTo.particleChildren[oslot], mobjTo); @:privateAccess opsys.setupGeomGpu(mobjTo.particleChildren[oslot]);
} else { } else {
var sceneName: String = inputs[1].get(); var sceneName: String = inputs[1].get();
@ -82,7 +82,7 @@ class AddParticleToObjectNode extends LogicNode {
var oslot: Int = mobjTo.particleSystems.length-1; var oslot: Int = mobjTo.particleSystems.length-1;
var opsys = mobjTo.particleSystems[oslot]; var opsys = mobjTo.particleSystems[oslot];
@:privateAccess opsys.setupGeomGpu(mobjTo.particleChildren[oslot], mobjTo); @:privateAccess opsys.setupGeomGpu(mobjTo.particleChildren[oslot]);
break; break;
} }

View File

@ -2,13 +2,17 @@ package leenkx.logicnode;
import iron.object.Object; import iron.object.Object;
#if lnx_bullet #if lnx_physics
import leenkx.trait.physics.PhysicsConstraint; import leenkx.trait.physics.PhysicsConstraint;
import leenkx.trait.physics.bullet.PhysicsConstraint.ConstraintType; import leenkx.trait.physics.PhysicsConstraint.ConstraintAxis;
#elseif lnx_oimo #if lnx_bullet
// TODO import leenkx.trait.physics.bullet.PhysicsConstraint.ConstraintType;
#elseif lnx_jolt
import leenkx.trait.physics.jolt.PhysicsConstraint.ConstraintType;
#else
import leenkx.trait.physics.oimo.PhysicsConstraint.ConstraintType;
#end
#end #end
class AddPhysicsConstraintNode extends LogicNode { class AddPhysicsConstraintNode extends LogicNode {
public var property0: String;//Type public var property0: String;//Type
@ -27,8 +31,7 @@ class AddPhysicsConstraintNode extends LogicNode {
if (pivotObject == null || rb1 == null || rb2 == null) return; if (pivotObject == null || rb1 == null || rb2 == null) return;
#if lnx_bullet #if lnx_physics
var disableCollisions: Bool = inputs[4].get(); var disableCollisions: Bool = inputs[4].get();
var breakable: Bool = inputs[5].get(); var breakable: Bool = inputs[5].get();
var breakingThreshold: Float = inputs[6].get(); var breakingThreshold: Float = inputs[6].get();
@ -110,8 +113,6 @@ class AddPhysicsConstraintNode extends LogicNode {
} }
pivotObject.addTrait(con); pivotObject.addTrait(con);
} }
#elseif lnx_oimo
// TODO
#end #end
runOutput(0); runOutput(0);
} }

View File

@ -0,0 +1,48 @@
package leenkx.logicnode;
import iron.object.Object;
#if lnx_physics
import leenkx.trait.physics.PhysicsCache;
import leenkx.trait.physics.RigidBody;
#end
class AnyContactNode extends LogicNode {
public var property0: String;
var lastContact = false;
public function new(tree: LogicTree) {
super(tree);
tree.notifyOnUpdate(update);
}
function update() {
var object1: Object = inputs[0].get();
if (object1 == null) object1 = tree.object;
if (object1 == null) return;
var contact = false;
#if lnx_physics
var rb1 = PhysicsCache.getCachedRigidBody(object1);
if (rb1 != null) {
var rbs = PhysicsCache.getCachedContacts(rb1);
contact = (rbs != null && rbs.length > 0);
}
#end
var shouldTrigger = false;
switch (property0) {
case "begin":
shouldTrigger = contact && !lastContact;
case "overlap":
shouldTrigger = contact;
case "end":
shouldTrigger = !contact && lastContact;
}
lastContact = contact;
if (shouldTrigger) runOutput(0);
}
}

View File

@ -37,86 +37,90 @@ class CreateLeenkxNode extends LogicNode {
function onEvent() { function onEvent() {
#if js #if js
var window:haxe.DynamicAccess<Dynamic> = untyped js.Browser.window; var lnxjs:Dynamic = js.Lib.global;
var lxCxNew = window.get('lxNew'); var lnxCxNew = lnxjs.lnxNew;
var lxCn:Dynamic = lxCxNew(net_Url); if (lnxCxNew == null) {
trace("ERROR: lnxNew not available");
return;
}
lxCn.on("connections", function(c) { var lnxCn:Dynamic = lnxCxNew(net_Url);
leenkx.network.Leenkx.data.set(net_Url, c + 1);
leenkx.network.Leenkx.connections[net_Url].onconnections();
});
lxCn.on("message", function(address,message) {
leenkx.network.Leenkx.data.set(net_Url, message);
leenkx.network.Leenkx.id.set(net_Url, address);
leenkx.network.Leenkx.connections[net_Url].onmessage();
});
lxCn.on("seen", function(address) {
leenkx.network.Leenkx.id.set(net_Url, address);
leenkx.network.Leenkx.connections[net_Url].onseen();
});
lxCn.on("left", function(address) {
leenkx.network.Leenkx.id.set(net_Url, address);
leenkx.network.Leenkx.connections[net_Url].onleft();
});
lxCn.on("server", function(address) {
leenkx.network.Leenkx.id.set(net_Url, address);
leenkx.network.Leenkx.connections[net_Url].onserver();
});
lxCn.on("ping", function(address) {
leenkx.network.Leenkx.id.set(net_Url, address);
leenkx.network.Leenkx.connections[net_Url].onping();
});
lxCn.on("timeout", function(address) {
leenkx.network.Leenkx.id.set(net_Url, address);
leenkx.network.Leenkx.connections[net_Url].ontimeout();
});
lxCn.on("rpc", function(address, call, args, nonce) {
leenkx.network.Leenkx.data.set(net_Url, call);
leenkx.network.Leenkx.id.set(net_Url, address);
call(args);
leenkx.network.Leenkx.connections[net_Url].onrpc();
});
lxCn.on("rpc-response", function(address, nonce, response) {
leenkx.network.Leenkx.id.set(net_Url, address);
leenkx.network.Leenkx.connections[net_Url].onrpcresponse();
});
lxCn.on("wireleft", function(wirecount, wire) {
leenkx.network.Leenkx.data.set(net_Url, wirecount);
leenkx.network.Leenkx.id.set(net_Url, wire.peerId);
leenkx.network.Leenkx.connections[net_Url].onwireleft();
});
lxCn.on("wireseen", function(wirecount, wire) {
leenkx.network.Leenkx.data.set(net_Url, wirecount);
leenkx.network.Leenkx.id.set(net_Url, wire.peerId);
leenkx.network.Leenkx.connections[net_Url].onwireseen();
});
lxCn.on("torrent", function(identifier, torrent) {
leenkx.network.Leenkx.data.set(net_Url, torrent);
leenkx.network.Leenkx.id.set(net_Url, identifier);
leenkx.network.Leenkx.connections[net_Url].ontorrent();
});
lxCn.on("tracker", function(identifier) { lnxCn.on("connections", function(c) {
leenkx.network.Leenkx.id.set(net_Url, identifier); leenkx.network.Leenkx.data.set(net_Url, c + 1);
leenkx.network.Leenkx.connections[net_Url].ontracker(); leenkx.network.Leenkx.connections[net_Url].onconnections();
}); });
lnxCn.on("message", function(address, message) {
leenkx.network.Leenkx.data.set(net_Url, message);
leenkx.network.Leenkx.id.set(net_Url, address);
leenkx.network.Leenkx.connections[net_Url].onmessage();
});
lnxCn.on("seen", function(address) {
leenkx.network.Leenkx.id.set(net_Url, address);
leenkx.network.Leenkx.connections[net_Url].onseen();
});
lnxCn.on("left", function(address) {
leenkx.network.Leenkx.id.set(net_Url, address);
leenkx.network.Leenkx.connections[net_Url].onleft();
});
lnxCn.on("server", function(address) {
leenkx.network.Leenkx.id.set(net_Url, address);
leenkx.network.Leenkx.connections[net_Url].onserver();
});
lnxCn.on("ping", function(address) {
leenkx.network.Leenkx.id.set(net_Url, address);
leenkx.network.Leenkx.connections[net_Url].onping();
});
lnxCn.on("timeout", function(address) {
leenkx.network.Leenkx.id.set(net_Url, address);
leenkx.network.Leenkx.connections[net_Url].ontimeout();
});
lnxCn.on("rpc", function(address, call, args, nonce) {
leenkx.network.Leenkx.data.set(net_Url, call);
leenkx.network.Leenkx.id.set(net_Url, address);
call(args);
leenkx.network.Leenkx.connections[net_Url].onrpc();
});
lnxCn.on("rpc-response", function(address, nonce, response) {
leenkx.network.Leenkx.id.set(net_Url, address);
leenkx.network.Leenkx.connections[net_Url].onrpcresponse();
});
lnxCn.on("wireleft", function(wirecount, wire) {
leenkx.network.Leenkx.data.set(net_Url, wirecount);
leenkx.network.Leenkx.id.set(net_Url, wire.peerId);
leenkx.network.Leenkx.connections[net_Url].onwireleft();
});
lnxCn.on("wireseen", function(wirecount, wire) {
leenkx.network.Leenkx.data.set(net_Url, wirecount);
leenkx.network.Leenkx.id.set(net_Url, wire.peerId);
leenkx.network.Leenkx.connections[net_Url].onwireseen();
});
lnxCn.on("torrent", function(identifier, torrent) {
leenkx.network.Leenkx.data.set(net_Url, torrent);
leenkx.network.Leenkx.id.set(net_Url, identifier);
leenkx.network.Leenkx.connections[net_Url].ontorrent();
});
lnxCn.on("tracker", function(identifier) {
leenkx.network.Leenkx.id.set(net_Url, identifier);
leenkx.network.Leenkx.connections[net_Url].ontracker();
});
lnxCn.on("announce", function(identifier) {
leenkx.network.Leenkx.id.set(net_Url, identifier);
leenkx.network.Leenkx.connections[net_Url].onannounce();
});
lxCn.on("announce", function(identifier) { Reflect.setField(lnxjs, "lnx_" + net_Url, lnxCn);
leenkx.network.Leenkx.id.set(net_Url, identifier); Leenkx.connections[net_Url].client = lnxCn;
leenkx.network.Leenkx.connections[net_Url].onannounce();
}); var script = 'globalThis.addEventListener("beforeunload", function (e) {
window.set("lx_" + net_Url, lxCn); leenkx.network.Leenkx.connections.h["' + net_Url + '"].client.destroy();
Leenkx.connections[net_Url].client = lxCn; delete e["returnValue"];
var script = ' });';
window.addEventListener("beforeunload", function (e) { js.Syntax.code('(1, eval)({0})', script);
leenkx.network.Leenkx.connections.h["' + net_Url + '"].client.destroy();
delete e["returnValue"]; runOutput(0);
}); #end
';
js.Syntax.code('(1, eval)({0})', script.toString());
runOutput(0);
#end
} }

View File

@ -31,7 +31,7 @@ class DrawImageNode extends LogicNode {
RenderToTexture.g.rotate(angle, x, y); RenderToTexture.g.rotate(angle, x, y);
if (imgName != lastImgName) { if (imgName != lastImgName || img == null) {
// Load new image // Load new image
lastImgName = imgName; lastImgName = imgName;
iron.data.Data.getImage(imgName, (image: Image) -> { iron.data.Data.getImage(imgName, (image: Image) -> {

View File

@ -0,0 +1,107 @@
package leenkx.logicnode;
import iron.math.Vec4;
import kha.Image;
import kha.Color;
import leenkx.renderpath.RenderToTexture;
class DrawImageRenderNode extends LogicNode {
var img: Image;
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
if (from == 1)
tree.notifyOnRender(render);
else {
RenderToTexture.ensure2DContext("DrawImageRenderNode");
final colorVec: Vec4 = inputs[3].get();
final anchorH: Int = inputs[4].get();
final anchorV: Int = inputs[5].get();
final x: Float = inputs[6].get();
final y: Float = inputs[7].get();
final width: Float = inputs[8].get();
final height: Float = inputs[9].get();
final sx: Float = inputs[10].get();
final sy: Float = inputs[11].get();
final swidth: Float = inputs[12].get();
final sheight: Float = inputs[13].get();
final angle: Float = inputs[14].get();
final drawx = x - 0.5 * width * anchorH;
final drawy = y - 0.5 * height * anchorV;
RenderToTexture.g.rotate(angle, x, y);
if (img != null){
RenderToTexture.g.color = 0xff000000;
RenderToTexture.g.fillRect(drawx, drawy, width, height);
RenderToTexture.g.color = RenderToTexture.g.color = Color.fromFloats(colorVec.x, colorVec.y, colorVec.z, colorVec.w);
RenderToTexture.g.drawScaledSubImage(img, sx, sy, swidth, sheight, drawx, drawy, width, height);
}
RenderToTexture.g.rotate(-angle, x, y);
runOutput(0);
}
}
function render(g: kha.graphics4.Graphics) {
var camera = inputs[2].get();
img = kha.Image.createRenderTarget(iron.App.w(), iron.App.h(),
kha.graphics4.TextureFormat.RGBA32,
kha.graphics4.DepthStencilFormat.NoDepthAndStencil);
final sceneCam = iron.Scene.active.camera;
final oldRT = camera.renderTarget;
iron.Scene.active.camera = camera;
camera.renderTarget = img;
camera.renderFrame(g);
img = camera.renderTarget;
if (inputs[15].get() || kha.Image.renderTargetsInvertedY()) {
img = kha.Image.createRenderTarget(iron.App.w(), iron.App.h(),
kha.graphics4.TextureFormat.RGBA32,
kha.graphics4.DepthStencilFormat.NoDepthAndStencil);
img.g2.begin(true, Color.Transparent);
img.g2.color = Color.White;
if (kha.Image.renderTargetsInvertedY()) {
img.g2.drawScaledImage(camera.renderTarget, 0, iron.App.h(), iron.App.w(), -iron.App.h());
} else {
img.g2.drawImage(camera.renderTarget, 0, 0);
}
if (inputs[15].get()) {
for (f in @:privateAccess iron.App.traitRenders2D) {
f(img.g2);
}
}
img.g2.end();
}
camera.renderTarget = oldRT;
iron.Scene.active.camera = sceneCam;
tree.removeRender(render);
}
}

View File

@ -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);
} }
} }

View File

@ -14,7 +14,7 @@ class DrawSubImageNode extends LogicNode {
} }
override function run(from: Int) { override function run(from: Int) {
RenderToTexture.ensure2DContext("DrawImageNode"); RenderToTexture.ensure2DContext("DrawSubImageNode");
final imgName: String = inputs[1].get(); final imgName: String = inputs[1].get();
final colorVec: Vec4 = inputs[2].get(); final colorVec: Vec4 = inputs[2].get();
@ -32,12 +32,10 @@ class DrawSubImageNode extends LogicNode {
final drawx = x - 0.5 * width * anchorH; final drawx = x - 0.5 * width * anchorH;
final drawy = y - 0.5 * height * anchorV; final drawy = y - 0.5 * height * anchorV;
final sdrawx = sx - 0.5 * swidth * anchorH;
final sdrawy = sy - 0.5 * sheight * anchorV;
RenderToTexture.g.rotate(angle, x, y); RenderToTexture.g.rotate(angle, x, y);
if (imgName != lastImgName) { if (imgName != lastImgName || img == null) {
// Load new image // Load new image
lastImgName = imgName; lastImgName = imgName;
iron.data.Data.getImage(imgName, (image: Image) -> { iron.data.Data.getImage(imgName, (image: Image) -> {
@ -51,7 +49,7 @@ class DrawSubImageNode extends LogicNode {
} }
RenderToTexture.g.color = Color.fromFloats(colorVec.x, colorVec.y, colorVec.z, colorVec.w); RenderToTexture.g.color = Color.fromFloats(colorVec.x, colorVec.y, colorVec.z, colorVec.w);
RenderToTexture.g.drawScaledSubImage(img, sdrawx, sdrawy, swidth, sheight, drawx, drawy, width, height); RenderToTexture.g.drawScaledSubImage(img, sx, sy, swidth, sheight, drawx, drawy, width, height);
RenderToTexture.g.rotate(-angle, x, y); RenderToTexture.g.rotate(-angle, x, y);
runOutput(0); runOutput(0);

View File

@ -0,0 +1,71 @@
package leenkx.logicnode;
import kha.Color;
import iron.math.Vec4;
import leenkx.renderpath.RenderToTexture;
class DrawToImageNode extends LogicNode {
var img: kha.Image = null;
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
var file: String = inputs[1].get();
var colorVec: Vec4 = inputs[2].get();
img = kha.Image.createRenderTarget(inputs[3].get(), inputs[4].get(),
kha.graphics4.TextureFormat.RGBA32,
kha.graphics4.DepthStencilFormat.NoDepthAndStencil);
RenderToTexture.ensureEmptyRenderTarget("DrawToImageNode");
img.g2.begin();
RenderToTexture.g = img.g2;
RenderToTexture.g.color = Color.fromFloats(colorVec.x, colorVec.y, colorVec.z, colorVec.w);
RenderToTexture.g.fillRect(0, 0, img.width, img.height);
runOutput(0);
RenderToTexture.g = null;
img.g2.end();
var pixels = img.getPixels();
var tx = inputs[5].get();
var ty = inputs[6].get();
var tw = inputs[7].get();
var th = inputs[8].get();
var bo = new haxe.io.BytesOutput();
var rgb = haxe.io.Bytes.alloc(tw * th * 4);
for (j in ty...ty + th) {
for (i in tx...tx + tw) {
var l = j * img.width + i;
var m = (j - ty) * tw + i - tx;
//ARGB 0xff
rgb.set(m * 4 + 0, pixels.get(l * 4 + 3));
rgb.set(m * 4 + 1, pixels.get(l * 4 + 0));
rgb.set(m * 4 + 2, pixels.get(l * 4 + 1));
rgb.set(m * 4 + 3, pixels.get(l * 4 + 2));
}
}
var imgwriter = new iron.format.bmp.Writer(bo);
imgwriter.write(iron.format.bmp.Tools.buildFromARGB(tw, th, rgb));
#if kha_krom
Krom.fileSaveBytes(Krom.getFilesLocation() + "/" + file, bo.getBytes().getData());
#elseif kha_html5
var blob = new js.html.Blob([bo.getBytes().getData()], {type: "application"});
var url = js.html.URL.createObjectURL(blob);
var a = cast(js.Browser.document.createElement("a"), js.html.AnchorElement);
a.href = url;
a.download = file;
a.click();
js.html.URL.revokeObjectURL(url);
#end
}
}

View File

@ -0,0 +1,80 @@
package leenkx.logicnode;
import iron.math.Vec4;
import kha.Image;
import kha.Color;
import leenkx.renderpath.RenderToTexture;
class DrawToScreenNode extends LogicNode {
var img: Image;
var colorVec: Vec4;
var anchorH: Int;
var anchorV: Int;
var x: Float;
var y: Float;
var width: Float;
var height: Float;
var sx: Float;
var sy: Float;
var swidth: Float;
var sheight: Float;
var angle: Float;
var drawx: Float;
var drawy: Float;
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
if (from == 0){
if (img == null){
runOutput(0);
return;
}
colorVec = inputs[4].get();
anchorH = inputs[5].get();
anchorV = inputs[6].get();
x = inputs[7].get();
y = inputs[8].get();
width = inputs[9].get();
height = inputs[10].get();
sx = inputs[11].get();
sy = inputs[12].get();
swidth = inputs[13].get();
sheight = inputs[14].get();
angle = inputs[15].get();
drawx = x - 0.5 * width * anchorH;
drawy = y - 0.5 * height * anchorV;
RenderToTexture.g.rotate(angle, x, y);
RenderToTexture.g.color = Color.fromFloats(colorVec.x, colorVec.y, colorVec.z, colorVec.w);
RenderToTexture.g.drawScaledSubImage(img, sx, sy, swidth, sheight, drawx, drawy, width, height);
RenderToTexture.g.rotate(-angle, x, y);
runOutput(0);
} else {
if (img == null)
img = kha.Image.createRenderTarget(inputs[2].get(), inputs[3].get(),
kha.graphics4.TextureFormat.RGBA32,
kha.graphics4.DepthStencilFormat.NoDepthAndStencil);
RenderToTexture.ensureEmptyRenderTarget("DrawToScreenNode");
img.g2.begin(inputs[16].get(), Color.Transparent);
RenderToTexture.g = img.g2;
runOutput(1);
RenderToTexture.g = null;
img.g2.end();
}
}
}

View File

@ -0,0 +1,23 @@
package leenkx.logicnode;
import leenkx.system.Signal;
class EmitSignalNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
var signal: Signal = inputs[1].get();
if (signal == null)
return;
var args: Array<Any> = [];
for (i in 2...inputs.length) {
args.push(inputs[i].get());
}
Reflect.callMethod(signal, Reflect.field(signal, "emit"), args);
runOutput(0);
}
}

View File

@ -0,0 +1,96 @@
package leenkx.logicnode;
import iron.math.Vec4;
import kha.Color;
import kha.Image;
class GetImageColorNode extends LogicNode {
public var property0: String;
var renderTarget: Image = null;
public function new(tree: LogicTree) {
super(tree);
renderTarget = Image.createRenderTarget(iron.App.w(), iron.App.h(), kha.graphics4.TextureFormat.RGBA32,
kha.graphics4.DepthStencilFormat.NoDepthAndStencil);
}
override function get(from: Int): Dynamic {
var i: Int;
var j: Int;
if (property0 == 'Image'){
i = inputs[1].get();
j = inputs[2].get();
} else {
i = inputs[0].get();
j = inputs[1].get();
}
if (i < 0 || j < 0) return null;
if (property0 != 'Image')
if (i > renderTarget.width || j > renderTarget.height) return null;
renderTarget.g2.begin(true, Color.Transparent);
renderTarget.g2.color = Color.White;
if (property0 == 'Render' || property0 == 'Render&Render2D'){
if (leenkx.renderpath.RenderPathCreator.finalTarget != null){
var img: Image = iron.RenderPath.active.renderTargets.get("buf").image;
renderTarget.g2.drawScaledImage(img, 0, 0, iron.App.w(), iron.App.h());
}
}
if (Image.renderTargetsInvertedY()){
renderTarget.g2.scale(1, -1);
renderTarget.g2.translate(0, renderTarget.height);
}
if (property0 == 'Image'){
var img: Image;
iron.data.Data.getImage(inputs[0].get(), (image: Image) -> {
img = image;
});
if (img == null || i > img.width || j > img.height){
renderTarget.g2.end();
return null;
} else
renderTarget.g2.drawScaledImage(img, 0, 0, img.width, img.height);
}
if (property0.indexOf('2D') > 0)
for (f in @:privateAccess iron.App.traitRenders2D)
f(renderTarget.g2);
if (Image.renderTargetsInvertedY()){
renderTarget.g2.scale(1, -1);
renderTarget.g2.translate(0, renderTarget.height);
}
renderTarget.g2.end();
var pixels = renderTarget.getPixels();
var k = j * renderTarget.width + i;
#if kha_krom
var l = k;
#elseif kha_html5
var l = (renderTarget.height - j) * renderTarget.width + i;
#end
var r = pixels.get(l * 4 + 0)/255;
var g = pixels.get(l * 4 + 1)/255;
var b = pixels.get(l * 4 + 2)/255;
var a = pixels.get(l * 4 + 3)/255;
var v = new Vec4(r, g, b, a);
return v;
}
}

View File

@ -14,7 +14,7 @@ class GetParticleDataNode extends LogicNode {
if (object == null) return null; if (object == null) return null;
#if lnx_particles #if lnx_gpu_particles
var mo = cast(object, iron.object.MeshObject); var mo = cast(object, iron.object.MeshObject);

View File

@ -13,7 +13,7 @@ class GetParticleNode extends LogicNode {
if (object == null) return null; if (object == null) return null;
#if lnx_particles #if lnx_gpu_particles
var mo = cast(object, iron.object.MeshObject); var mo = cast(object, iron.object.MeshObject);

View File

@ -0,0 +1,21 @@
package leenkx.logicnode;
import iron.object.MeshObject;
class GetTilesheetFlipNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function get(from: Int): Dynamic {
var object: MeshObject = inputs[0].get();
if (object == null) return null;
if (object.tilesheet == null) return null;
if (from == 0) return object.tilesheet.flipX;
if (from == 1) return object.tilesheet.flipY;
return null;
}
}

View File

@ -11,13 +11,13 @@ class GetTilesheetStateNode extends LogicNode {
override function get(from: Int): Dynamic { override function get(from: Int): Dynamic {
var object: MeshObject = inputs[0].get(); var object: MeshObject = inputs[0].get();
if (object == null) return null; if (object == null || object.tilesheet == null) return null;
var tilesheet = object.activeTilesheet; var tilesheet = object.tilesheet;
return switch (from) { return switch (from) {
case 0: tilesheet.raw.name; case 0: object.name; // Return object name since tilesheet is embedded
case 1: tilesheet.action.name; case 1: tilesheet.action != null ? tilesheet.action.name : null;
case 2: tilesheet.getFrameOffset(); case 2: tilesheet.getFrameOffset();
case 3: tilesheet.frame; case 3: tilesheet.frame;
case 4: tilesheet.paused; case 4: tilesheet.paused;

View File

@ -0,0 +1,24 @@
package leenkx.logicnode;
import leenkx.system.Signal;
class GlobalSignalNode extends LogicNode {
public static var signals: Map<String, Signal> = new Map<String, Signal>();
public function new(tree: LogicTree) {
super(tree);
}
override function get(from: Int): Null<Signal> {
var name: String = inputs[0].get();
if (name == null || name == "")
return null;
var signal: Signal = signals.get(name);
if (signal == null) {
signal = new Signal();
signals.set(name, signal);
}
return signal;
}
}

View File

@ -1,7 +1,10 @@
package leenkx.logicnode; package leenkx.logicnode;
import iron.object.Object; import iron.object.Object;
#if lnx_physics
import leenkx.trait.physics.PhysicsCache;
import leenkx.trait.physics.RigidBody; import leenkx.trait.physics.RigidBody;
#end
class HasContactNode extends LogicNode { class HasContactNode extends LogicNode {
@ -15,12 +18,15 @@ class HasContactNode extends LogicNode {
if (object1 == null || object2 == null) return false; if (object1 == null || object2 == null) return false;
#if lnx_physics #if lnx_physics
var physics = leenkx.trait.physics.PhysicsWorld.active; var rb1 = PhysicsCache.getCachedRigidBody(object1);
var rb2 = object2.getTrait(RigidBody); var rb2 = PhysicsCache.getCachedRigidBody(object2);
var rbs = physics.getContacts(object1.getTrait(RigidBody));
if (rbs != null) for (rb in rbs) if (rb == rb2) return true; if (rb1 != null && rb2 != null) {
#end var rbs = PhysicsCache.getCachedContacts(rb1);
return PhysicsCache.hasContactWith(rbs, rb2);
}
#end
return false; return false;
} }
} }

View File

@ -32,10 +32,15 @@ class LeenkxCloseConnectionNode extends LogicNode {
} else { } else {
var script = ' var script = '
try{ try{
leenkx.network.Leenkx.connections.h["1008"].client.torrent._peers[p].conn._pc.close(); var lnxConn = leenkx.network.Leenkx.connections.h["' + connection._url + '"];
leenkx.network.Leenkx.connections.h["1008"].client.torrent._peers[p].conn.destroy(); if (lnxConn && lnxConn.client && lnxConn.client.torrent && lnxConn.client.torrent._peers) {
leenkx.network.Leenkx.id.set("1008",p); for (var p in lnxConn.client.torrent._peers) {
leenkx.network.Leenkx.connections.h["1008"].onclose(); lnxConn.client.torrent._peers[p].conn._pc.close();
lnxConn.client.torrent._peers[p].conn.destroy();
leenkx.network.Leenkx.id.set("' + connection._url + '", p);
lnxConn.onclose();
}
}
}catch(error){ }catch(error){
console.log("Error: " + error); console.log("Error: " + error);
} }

View File

@ -40,7 +40,7 @@ class LeenkxEventNode extends LogicNode {
default: throw "Failed to set client event type."; default: throw "Failed to set client event type.";
} }
} else if (property0 == "host") { } else if (property0 == "host") {
#if sys #if (sys || kha_krom)
var net_Domain = inputs[0].get(); var net_Domain = inputs[0].get();
var net_Port = inputs[1].get(); var net_Port = inputs[1].get();
net_Url = "ws://" + net_Domain + ":" + Std.string(net_Port); net_Url = "ws://" + net_Domain + ":" + Std.string(net_Port);
@ -53,7 +53,7 @@ class LeenkxEventNode extends LogicNode {
} }
#end #end
} else if (property0 == "securehost"){ } else if (property0 == "securehost"){
#if sys #if (sys || kha_krom)
var net_Domain = inputs[0].get(); var net_Domain = inputs[0].get();
var net_Port = inputs[1].get(); var net_Port = inputs[1].get();
net_Url = "wss://" + net_Domain + ":" + Std.string(net_Port); net_Url = "wss://" + net_Domain + ":" + Std.string(net_Port);

View File

@ -27,9 +27,9 @@ class LeenkxSendMessageNode extends LogicNode {
try { try {
if(inputs[5].get() == true){ if(inputs[5].get() == true){
var script = 'for (p in lx_' + connection._url +'.torrent._peers){ var script = 'for (p in lnx_' + connection._url +'.torrent._peers){
try{ try{
lx_' + connection._url +'.torrent._peers[p].conn.send(`'+ api + message + '`); lnx_' + connection._url +'.torrent._peers[p].conn.send(`'+ api + message + '`);
}catch(error){ }catch(error){
console.log("Error: " + error); console.log("Error: " + error);
} }
@ -37,7 +37,7 @@ class LeenkxSendMessageNode extends LogicNode {
js.Syntax.code('(1, eval)({0})', script.toString()); js.Syntax.code('(1, eval)({0})', script.toString());
runOutput(0); runOutput(0);
} else { } else {
var script = 'lx_' + connection._url +'.send(`' + inputs[4].get() + '`, `'+ api + message + '` );'; var script = 'lnx_' + connection._url +'.send(`' + inputs[4].get() + '`, `'+ api + message + '` );';
js.Syntax.code('(1, eval)({0})', script.toString()); js.Syntax.code('(1, eval)({0})', script.toString());
runOutput(0); runOutput(0);
} }
@ -58,9 +58,9 @@ class LeenkxSendMessageNode extends LogicNode {
try { try {
connection.buffer = buffer; connection.buffer = buffer;
if(inputs[5].get() == true){ if(inputs[5].get() == true){
var script = 'for (p in lx_' + connection._url +'.torrent._peers){ var script = 'for (p in lnx_' + connection._url +'.torrent._peers){
try{ try{
lx_' + connection._url +'.torrent._peers[p].conn.send(leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer ); lnx_' + connection._url +'.torrent._peers[p].conn.send(leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );
}catch(error){ }catch(error){
console.log("Error: " + error); console.log("Error: " + error);
} }
@ -68,7 +68,7 @@ class LeenkxSendMessageNode extends LogicNode {
js.Syntax.code('(1, eval)({0})', script.toString()); js.Syntax.code('(1, eval)({0})', script.toString());
runOutput(0); runOutput(0);
} else { } else {
var script = 'lx_' + connection._url +'.send("' + inputs[4].get() + '",leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );'; var script = 'lnx_' + connection._url +'.send("' + inputs[4].get() + '",leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );';
js.Syntax.code('(1, eval)({0})', script.toString()); js.Syntax.code('(1, eval)({0})', script.toString());
runOutput(0); runOutput(0);
} }
@ -77,15 +77,15 @@ class LeenkxSendMessageNode extends LogicNode {
} }
} }
} else { } else {
var window:haxe.DynamicAccess<Dynamic> = untyped js.Browser.window; if(inputs[5].get() == true){
var lxCn = window.get('lx_' + connection._url); var script = 'lnx_' + connection._url + '.send(`' + api + message + '`);';
if(inputs[5].get() == true){ js.Syntax.code('(1, eval)({0})', script);
lxCn.send(api+message); }else{
}else{ var script = 'lnx_' + connection._url + '.send(`' + inputs[4].get() + '`, `' + api + message + '`);';
lxCn.send(inputs[4].get(), api+message); js.Syntax.code('(1, eval)({0})', script);
} }
runOutput(0); runOutput(0);
return; return;
} }
case "vector": case "vector":
if(property0 == "client"){ if(property0 == "client"){
@ -103,9 +103,9 @@ class LeenkxSendMessageNode extends LogicNode {
try { try {
connection.buffer = buffer; connection.buffer = buffer;
if(inputs[5].get() == true){ if(inputs[5].get() == true){
var script = 'for (p in lx_' + connection._url +'.torrent._peers){ var script = 'for (p in lnx_' + connection._url +'.torrent._peers){
try{ try{
lx_' + connection._url +'.torrent._peers[p].conn.send("' + api + message + '"); lnx_' + connection._url +'.torrent._peers[p].conn.send("' + api + message + '");
}catch(error){ }catch(error){
console.log("Error: " + error); console.log("Error: " + error);
} }
@ -113,7 +113,7 @@ class LeenkxSendMessageNode extends LogicNode {
js.Syntax.code('(1, eval)({0})', script.toString()); js.Syntax.code('(1, eval)({0})', script.toString());
runOutput(0); runOutput(0);
} else { } else {
var script = 'lx_' + connection._url +'.send("' + inputs[4].get() + '",leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );'; var script = 'lnx_' + connection._url +'.send("' + inputs[4].get() + '",leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );';
js.Syntax.code('(1, eval)({0})', script.toString()); js.Syntax.code('(1, eval)({0})', script.toString());
runOutput(0); runOutput(0);
} }
@ -121,12 +121,12 @@ class LeenkxSendMessageNode extends LogicNode {
trace("Error: " + error); trace("Error: " + error);
} }
} else { } else {
var window:haxe.DynamicAccess<Dynamic> = untyped js.Browser.window;
var lxCn = window.get('lx_' + connection._url);
if(inputs[5].get() == true){ if(inputs[5].get() == true){
lxCn.send(api+message); var script = 'lnx_' + connection._url + '.send(`' + api + message + '`);';
js.Syntax.code('(1, eval)({0})', script);
}else{ }else{
lxCn.send(inputs[4].get(), api+message); var script = 'lnx_' + connection._url + '.send(`' + inputs[4].get() + '`, `' + api + message + '`);';
js.Syntax.code('(1, eval)({0})', script);
} }
runOutput(0); runOutput(0);
return; return;
@ -143,9 +143,9 @@ class LeenkxSendMessageNode extends LogicNode {
try { try {
connection.buffer = buffer; connection.buffer = buffer;
if(inputs[5].get() == true){ if(inputs[5].get() == true){
var script = 'for (p in lx_' + connection._url +'.torrent._peers){ var script = 'for (p in lnx_' + connection._url +'.torrent._peers){
try{ try{
lx_' + connection._url +'.torrent._peers[p].conn.send(leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer ); lnx_' + connection._url +'.torrent._peers[p].conn.send(leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );
}catch(error){ }catch(error){
console.log("Error: " + error); console.log("Error: " + error);
} }
@ -153,7 +153,7 @@ class LeenkxSendMessageNode extends LogicNode {
js.Syntax.code('(1, eval)({0})', script.toString()); js.Syntax.code('(1, eval)({0})', script.toString());
runOutput(0); runOutput(0);
} else { } else {
var script = 'lx_' + connection._url +'.send("' + inputs[4].get() + '",leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );'; var script = 'lnx_' + connection._url +'.send("' + inputs[4].get() + '",leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );';
js.Syntax.code('(1, eval)({0})', script.toString()); js.Syntax.code('(1, eval)({0})', script.toString());
runOutput(0); runOutput(0);
} }
@ -161,12 +161,12 @@ class LeenkxSendMessageNode extends LogicNode {
trace("Error: " + error); trace("Error: " + error);
} }
} else { } else {
var window:haxe.DynamicAccess<Dynamic> = untyped js.Browser.window;
var lxCn = window.get('lx_' + connection._url);
if(inputs[5].get() == true){ if(inputs[5].get() == true){
lxCn.send(api+message); var script = 'lnx_' + connection._url + '.send(`' + api + message + '`);';
js.Syntax.code('(1, eval)({0})', script);
}else{ }else{
lxCn.send(inputs[4].get(), api+message); var script = 'lnx_' + connection._url + '.send(`' + inputs[4].get() + '`, `' + api + message + '`);';
js.Syntax.code('(1, eval)({0})', script);
} }
runOutput(0); runOutput(0);
return; return;
@ -183,9 +183,9 @@ class LeenkxSendMessageNode extends LogicNode {
try { try {
connection.buffer = buffer; connection.buffer = buffer;
if(inputs[5].get() == true){ if(inputs[5].get() == true){
var script = 'for (p in lx_' + connection._url +'.torrent._peers){ var script = 'for (p in lnx_' + connection._url +'.torrent._peers){
try{ try{
lx_' + connection._url +'.torrent._peers[p].conn.send(leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer ); lnx_' + connection._url +'.torrent._peers[p].conn.send(leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );
}catch(error){ }catch(error){
console.log("Error: " + error); console.log("Error: " + error);
} }
@ -193,7 +193,7 @@ class LeenkxSendMessageNode extends LogicNode {
js.Syntax.code('(1, eval)({0})', script.toString()); js.Syntax.code('(1, eval)({0})', script.toString());
runOutput(0); runOutput(0);
} else { } else {
var script = 'lx_' + connection._url +'.send("' + inputs[4].get() + '",leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );'; var script = 'lnx_' + connection._url +'.send("' + inputs[4].get() + '",leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );';
js.Syntax.code('(1, eval)({0})', script.toString()); js.Syntax.code('(1, eval)({0})', script.toString());
runOutput(0); runOutput(0);
} }
@ -201,12 +201,12 @@ class LeenkxSendMessageNode extends LogicNode {
trace("Error: " + error); trace("Error: " + error);
} }
} else { } else {
var window:haxe.DynamicAccess<Dynamic> = untyped js.Browser.window;
var lxCn = window.get('lx_' + connection._url);
if(inputs[5].get() == true){ if(inputs[5].get() == true){
lxCn.send(api+message); var script = 'lnx_' + connection._url + '.send(`' + api + message + '`);';
js.Syntax.code('(1, eval)({0})', script);
}else{ }else{
lxCn.send(inputs[4].get(), api+message); var script = 'lnx_' + connection._url + '.send(`' + inputs[4].get() + '`, `' + api + message + '`);';
js.Syntax.code('(1, eval)({0})', script);
} }
runOutput(0); runOutput(0);
return; return;
@ -225,9 +225,9 @@ class LeenkxSendMessageNode extends LogicNode {
try { try {
connection.buffer = buffer; connection.buffer = buffer;
if(inputs[5].get() == true){ if(inputs[5].get() == true){
var script = 'for (p in lx_' + connection._url +'.torrent._peers){ var script = 'for (p in lnx_' + connection._url +'.torrent._peers){
try{ try{
lx_' + connection._url +'.torrent._peers[p].conn.send(leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer ); lnx_' + connection._url +'.torrent._peers[p].conn.send(leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );
}catch(error){ }catch(error){
console.log("Error: " + error); console.log("Error: " + error);
} }
@ -235,7 +235,7 @@ class LeenkxSendMessageNode extends LogicNode {
js.Syntax.code('(1, eval)({0})', script.toString()); js.Syntax.code('(1, eval)({0})', script.toString());
runOutput(0); runOutput(0);
} else { } else {
var script = 'lx_' + connection._url +'.send("' + inputs[4].get() + '",leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );'; var script = 'lnx_' + connection._url +'.send("' + inputs[4].get() + '",leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );';
js.Syntax.code('(1, eval)({0})', script.toString()); js.Syntax.code('(1, eval)({0})', script.toString());
runOutput(0); runOutput(0);
} }
@ -243,12 +243,12 @@ class LeenkxSendMessageNode extends LogicNode {
trace("Error: " + error); trace("Error: " + error);
} }
} else { } else {
var window:haxe.DynamicAccess<Dynamic> = untyped js.Browser.window;
var lxCn = window.get('lx_' + connection._url);
if(inputs[5].get() == true){ if(inputs[5].get() == true){
lxCn.send(api+message); var script = 'lnx_' + connection._url + '.send(`' + api + message + '`);';
js.Syntax.code('(1, eval)({0})', script);
}else{ }else{
lxCn.send(inputs[4].get(), api+message); var script = 'lnx_' + connection._url + '.send(`' + inputs[4].get() + '`, `' + api + message + '`);';
js.Syntax.code('(1, eval)({0})', script);
} }
runOutput(0); runOutput(0);
return; return;
@ -283,10 +283,10 @@ class LeenkxSendMessageNode extends LogicNode {
connection.buffer = buffer; connection.buffer = buffer;
if(inputs[5].get() == true){ if(inputs[5].get() == true){
var script = 'for (p in lx_' + connection._url +'.torrent._peers){ var script = 'for (p in lnx_' + connection._url +'.torrent._peers){
try{ try{
//console.log("Mine: " + lx_8001.torrent.discovery.peerId + " || Incomming: " + p); //console.log("Mine: " + lx_8001.torrent.discovery.peerId + " || Incomming: " + p);
lx_' + connection._url +'.torrent._peers[p].conn.send(leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer ); lnx_' + connection._url +'.torrent._peers[p].conn.send(leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );
}catch(error){ }catch(error){
console.log("Error: " + error); console.log("Error: " + error);
} }
@ -294,8 +294,8 @@ class LeenkxSendMessageNode extends LogicNode {
js.Syntax.code('(1, eval)({0})', script.toString()); js.Syntax.code('(1, eval)({0})', script.toString());
runOutput(0); runOutput(0);
} else { } else {
//var script = 'lx_' + connection._url +'.send("' + inputs[4].get() + '",leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );'; //var script = 'lnx_' + connection._url +'.send("' + inputs[4].get() + '",leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );';
var script = 'lx_' + connection._url +'.send(leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );'; var script = 'lnx_' + connection._url +'.send(leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );';
js.Syntax.code('(1, eval)({0})', script.toString()); js.Syntax.code('(1, eval)({0})', script.toString());
runOutput(0); runOutput(0);
} }
@ -319,9 +319,9 @@ class LeenkxSendMessageNode extends LogicNode {
try { try {
connection.buffer = buffer; connection.buffer = buffer;
if(inputs[5].get() == true){ if(inputs[5].get() == true){
var script = 'for (p in lx_' + connection._url +'.torrent._peers){ var script = 'for (p in lnx_' + connection._url +'.torrent._peers){
try{ try{
lx_' + connection._url +'.torrent._peers[p].conn.send(leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer ); lnx_' + connection._url +'.torrent._peers[p].conn.send(leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );
}catch(error){ }catch(error){
console.log("Error: " + error); console.log("Error: " + error);
} }
@ -329,7 +329,7 @@ class LeenkxSendMessageNode extends LogicNode {
js.Syntax.code('(1, eval)({0})', script.toString()); js.Syntax.code('(1, eval)({0})', script.toString());
runOutput(0); runOutput(0);
} else { } else {
var script = 'lx_' + connection._url +'.send("' + inputs[4].get() + '",leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );'; var script = 'lnx_' + connection._url +'.send("' + inputs[4].get() + '",leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );';
js.Syntax.code('(1, eval)({0})', script.toString()); js.Syntax.code('(1, eval)({0})', script.toString());
runOutput(0); runOutput(0);
} }
@ -337,12 +337,12 @@ class LeenkxSendMessageNode extends LogicNode {
trace("Error: " + error); trace("Error: " + error);
} }
} else { } else {
var window:haxe.DynamicAccess<Dynamic> = untyped js.Browser.window;
var lxCn = window.get('lx_' + connection._url);
if(inputs[5].get() == true){ if(inputs[5].get() == true){
lxCn.send(api+message); var script = 'lnx_' + connection._url + '.send(`' + api + message + '`);';
js.Syntax.code('(1, eval)({0})', script);
}else{ }else{
lxCn.send(inputs[4].get(), api+message); var script = 'lnx_' + connection._url + '.send(`' + inputs[4].get() + '`, `' + api + message + '`);';
js.Syntax.code('(1, eval)({0})', script);
} }
runOutput(0); runOutput(0);
return; return;
@ -360,9 +360,9 @@ class LeenkxSendMessageNode extends LogicNode {
try { try {
if(inputs[5].get() == true){ if(inputs[5].get() == true){
var script = 'for (p in lx_' + connection._url +'.torrent._peers){ var script = 'for (p in lnx_' + connection._url +'.torrent._peers){
try{ try{
lx_' + connection._url +'.torrent._peers[p].conn.send("' + api + message + '"); lnx_' + connection._url +'.torrent._peers[p].conn.send("' + api + message + '");
}catch(error){ }catch(error){
console.log("Error: " + error); console.log("Error: " + error);
} }
@ -370,7 +370,7 @@ class LeenkxSendMessageNode extends LogicNode {
js.Syntax.code('(1, eval)({0})', script.toString()); js.Syntax.code('(1, eval)({0})', script.toString());
runOutput(0); runOutput(0);
} else { } else {
var script = 'for (p in lx_' + connection._url +'.torrent._peers){ var script = 'for (p in lnx_' + connection._url +'.torrent._peers){
try{ try{
lx_' + connection._url +'.torrent._peers[' + inputs[4].get() + '].conn.send("' + api + message + '"); lx_' + connection._url +'.torrent._peers[' + inputs[4].get() + '].conn.send("' + api + message + '");

View File

@ -33,6 +33,7 @@ class LogicTree extends iron.Trait {
if (paused) return; if (paused) return;
paused = true; paused = true;
if (_fixedUpdate != null) for (f in _fixedUpdate) iron.App.removeFixedUpdate(f);
if (_update != null) for (f in _update) iron.App.removeUpdate(f); if (_update != null) for (f in _update) iron.App.removeUpdate(f);
if (_lateUpdate != null) for (f in _lateUpdate) iron.App.removeLateUpdate(f); if (_lateUpdate != null) for (f in _lateUpdate) iron.App.removeLateUpdate(f);
} }
@ -41,6 +42,7 @@ class LogicTree extends iron.Trait {
if (!paused) return; if (!paused) return;
paused = false; paused = false;
if (_fixedUpdate != null) for (f in _fixedUpdate) iron.App.notifyOnFixedUpdate(f);
if (_update != null) for (f in _update) iron.App.notifyOnUpdate(f); if (_update != null) for (f in _update) iron.App.notifyOnUpdate(f);
if (_lateUpdate != null) for (f in _lateUpdate) iron.App.notifyOnLateUpdate(f); if (_lateUpdate != null) for (f in _lateUpdate) iron.App.notifyOnLateUpdate(f);
} }

View File

@ -16,7 +16,7 @@ class NetworkCloseConnectionNode extends LogicNode {
if(property1 == "client") { if(property1 == "client") {
var connection = cast(inputs[1].get(), leenkx.network.WebSocket); var connection = cast(inputs[1].get(), leenkx.network.WebSocket);
if (connection == null) return; if (connection == null) return;
#if sys #if (sys || kha_krom)
try{ try{
var net_Url = connection._protocol + "://" + connection._host + ":" + connection._port; var net_Url = connection._protocol + "://" + connection._host + ":" + connection._port;
connection.close(); connection.close();
@ -44,7 +44,7 @@ class NetworkCloseConnectionNode extends LogicNode {
} }
#end #end
} else if(property1 == "securehost"){ } else if(property1 == "securehost"){
#if sys #if (sys || kha_krom)
var connection = cast(inputs[1].get(), leenkx.network.WebSocketSecureServer<leenkx.network.Connect.HostHandler>); var connection = cast(inputs[1].get(), leenkx.network.WebSocketSecureServer<leenkx.network.Connect.HostHandler>);
if (connection == null) return; if (connection == null) return;
var net_Url = "wss://" + @:privateAccess connection._host + ":" + @:privateAccess connection._port; var net_Url = "wss://" + @:privateAccess connection._host + ":" + @:privateAccess connection._port;
@ -56,7 +56,7 @@ class NetworkCloseConnectionNode extends LogicNode {
} }
#end #end
} else { } else {
#if sys #if (sys || kha_krom)
var connection = cast(inputs[1].get(), leenkx.network.WebSocketServer<leenkx.network.Connect.HostHandler>); var connection = cast(inputs[1].get(), leenkx.network.WebSocketServer<leenkx.network.Connect.HostHandler>);
if (connection == null) return; if (connection == null) return;
var net_Url = "ws://" + @:privateAccess connection._host + ":" + @:privateAccess connection._port; var net_Url = "ws://" + @:privateAccess connection._host + ":" + @:privateAccess connection._port;

View File

@ -28,7 +28,7 @@ class NetworkEventNode extends LogicNode {
default: throw "Failed to set client event type."; default: throw "Failed to set client event type.";
} }
} else if (property0 == "host") { } else if (property0 == "host") {
#if sys #if (sys || kha_krom)
var net_Domain = inputs[0].get(); var net_Domain = inputs[0].get();
var net_Port = inputs[1].get(); var net_Port = inputs[1].get();
net_Url = "ws://" + net_Domain + ":" + Std.string(net_Port); net_Url = "ws://" + net_Domain + ":" + Std.string(net_Port);
@ -41,7 +41,7 @@ class NetworkEventNode extends LogicNode {
} }
#end #end
} else if (property0 == "securehost"){ } else if (property0 == "securehost"){
#if sys #if (sys || kha_krom)
var net_Domain = inputs[0].get(); var net_Domain = inputs[0].get();
var net_Port = inputs[1].get(); var net_Port = inputs[1].get();
net_Url = "wss://" + net_Domain + ":" + Std.string(net_Port); net_Url = "wss://" + net_Domain + ":" + Std.string(net_Port);

View File

@ -11,7 +11,7 @@ class NetworkHostCloseClientNode extends LogicNode {
} }
override function run(from:Int) { override function run(from:Int) {
#if sys #if (sys || kha_krom)
if(property0 == false){ if(property0 == false){
var connection = cast(inputs[1].get(), leenkx.network.WebSocketServer<HostHandler>); var connection = cast(inputs[1].get(), leenkx.network.WebSocketServer<HostHandler>);
if (connection == null) return; if (connection == null) return;

View File

@ -12,7 +12,7 @@ class NetworkHostGetIpNode extends LogicNode {
} }
override function run(from:Int) { override function run(from:Int) {
#if sys #if (sys || kha_krom)
if(property0 == false){ if(property0 == false){
var connection = cast(inputs[1].get(), leenkx.network.WebSocketServer<HostHandler>); var connection = cast(inputs[1].get(), leenkx.network.WebSocketServer<HostHandler>);
if (connection == null) return; if (connection == null) return;

View File

@ -12,7 +12,7 @@ class NetworkHostNode extends LogicNode {
} }
override function run(from:Int) { override function run(from:Int) {
#if sys #if (sys || kha_krom)
if(property0 == false) { if(property0 == false) {
final net_Object: Object = tree.object; final net_Object: Object = tree.object;
var net_Domain: String = inputs[1].get(); var net_Domain: String = inputs[1].get();
@ -49,7 +49,7 @@ class NetworkHostNode extends LogicNode {
#end #end
} }
#if sys #if (sys || kha_krom)
override function get(from: Int): Dynamic { override function get(from: Int): Dynamic {
if(property0 == false) { if(property0 == false) {
return switch (from) { return switch (from) {

Some files were not shown because too many files have changed in this diff Show More