1
0
forked from Onek8/LNXSDK

Compare commits

...

27 Commits

Author SHA1 Message Date
cbbcd053fb MacOS arm64 Kmake 2026-07-20 20:15:25 -07:00
160f6bdadf Viewport build 2026-07-18 21:33:47 -07:00
50ad462318 Live patch refactor 2026-07-18 19:49:25 -07:00
1239145da4 LivePatch Global 2026-07-18 16:32:51 -07:00
102941b5d2 Update 2026-07-18 16:31:21 -07:00
defff993a0 Merge pull request 'main' (#126) from Onek8/LNXSDK:main into main
Reviewed-on: LeenkxTeam/LNXSDK#126
2026-07-18 16:24:34 +00:00
e408151295 merge upstream 2026-07-18 16:22:37 +00:00
22159c9f37 Delete leenkx/Sources/leenkx/logicnode/PlayActionFromNode.hx.backup 2026-07-18 16:22:17 +00:00
9a2779982d Update leenkx/Sources/iron/data/Geometry.hx 2026-07-18 16:21:39 +00:00
2fc7e9880a Upload files to "Krom" 2026-07-18 04:30:47 +00:00
7e1ac5fa5c Merge pull request 'main' (#125) from Onek8/LNXSDK:main into main
Reviewed-on: LeenkxTeam/LNXSDK#125
2026-07-18 03:27:17 +00:00
b2395f30fc VOX_PATCH_2 + VOX_5 2026-07-16 23:49:44 -07:00
f4b0bf1e93 Merge pull request 'Webview for RunT/Krom' (#9) from Dante/LNXSDK:main into main
Reviewed-on: Onek8/LNXSDK#9
2026-07-14 00:02:33 +00:00
31a6a9d7ec Webview for RunT/Krom 2026-07-13 15:44:52 -07:00
7b2f21c499 Missing particle node 2026-07-10 19:04:13 -07:00
17c31b4a4b FSR Probe fix 2026-07-10 15:56:10 -07:00
013c8653ff SSGI rewrite 2026-07-10 12:43:24 -07:00
bd8b49a416 Multithreaded windows 2026-07-09 23:59:31 -07:00
9d83c318b6 Jolt Patch 2026-07-09 17:25:48 -07:00
cb19c9b5b4 Update lib/aura/Backends/hl/aura/math/FFT.h 2026-07-09 04:26:08 +00:00
f4fe822ef6 Update leenkx/Sources/leenkx/renderpath/Sky.hx 2026-07-09 02:06:06 +00:00
6689fbc734 Merge pull request 'Incremental recompute' (#8) from Dante/LNXSDK:main into main
Reviewed-on: Onek8/LNXSDK#8
2026-06-26 04:40:09 +00:00
91b6c203e9 Incremental recompute 2026-06-25 21:33:51 -07:00
2c30554504 Moises Jpelaez - CPU particle visual fixes 2026-06-24 20:33:46 -07:00
37c8779d12 Patch_5 2026-06-24 20:23:12 -07:00
6b704ff469 Merge pull request 'main' (#7) from Dante/LNXSDK:main into main
Reviewed-on: Onek8/LNXSDK#7
2026-06-24 07:15:29 +00:00
2a3bff5a18 Nishita to Sky 2026-06-24 00:12:30 -07:00
143 changed files with 5962 additions and 2249 deletions

View File

@ -142,6 +142,8 @@ class Image implements Canvas implements Resource {
return 5;
case A16:
return 7;
case R32UI:
return 8;
default:
return 1; // Grey8
}

View File

@ -234,6 +234,8 @@ class Image implements Canvas implements Resource {
return 5;
case A16:
return 7;
case R32UI:
return 8;
default:
return 1; // Grey8
}

View File

@ -23,6 +23,7 @@ extern class Krom {
static function setRenderTarget(stage: kha.graphics4.TextureUnit, renderTarget: Dynamic): Void;
static function setTextureDepth(unit: kha.graphics4.TextureUnit, texture: Dynamic): Void;
static function setImageTexture(stage: kha.graphics4.TextureUnit, texture: Dynamic): Void;
static function setImageRenderTarget(stage: kha.graphics4.TextureUnit, renderTarget: Dynamic): Void;
static function setTextureParameters(texunit: kha.graphics4.TextureUnit, uAddressing: Int, vAddressing: Int, minificationFilter: Int,
magnificationFilter: Int, mipmapFilter: Int): Void;
static function setTexture3DParameters(texunit: kha.graphics4.TextureUnit, uAddressing: Int, vAddressing: Int, wAddressing: Int, minificationFilter: Int,
@ -114,6 +115,7 @@ extern class Krom {
static function windowWidth(id: Int): Int;
static function windowHeight(id: Int): Int;
static function setWindowTitle(id: Int, title: String): Void;
static function windowSetForeground(id: Int): Void;
static function screenDpi(): Int;
static function systemId(): String;
static function requestShutdown(): Void;
@ -158,4 +160,45 @@ extern class Krom {
static function getConstantLocationCompute(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 webviewCreate(options: Dynamic): Int;
static function webviewLoadHTML(id: Int, html: String): Void;
static function webviewLoadURL(id: Int, url: String): Void;
static function webviewEvalJS(id: Int, js: String): Void;
static function webviewEvalJSAsync(id: Int, js: String, callback: String->Void): Void;
static function webviewShow(id: Int): Void;
static function webviewHide(id: Int): Void;
static function webviewDestroy(id: Int): Void;
static function webviewResize(id: Int, width: Int, height: Int): Void;
static function webviewMove(id: Int, x: Int, y: Int): Void;
static function webviewSetBounds(id: Int, x: Int, y: Int, width: Int, height: Int): Void;
static function webviewGetX(id: Int): Int;
static function webviewGetY(id: Int): Int;
static function webviewGetWidth(id: Int): Int;
static function webviewGetHeight(id: Int): Int;
static function webviewSetTransparent(id: Int, transparent: Bool): Void;
static function webviewSetClickThrough(id: Int, enabled: Bool): Void;
static function webviewSetTitle(id: Int, title: String): Void;
static function webviewSend(id: Int, message: String): Void;
static function webviewSetOnMessage(id: Int, callback: String->Void): Void;
static function webviewSetOnLoad(id: Int, callback: Void->Void): Void;
static function webviewSetOnError(id: Int, callback: String->Void): Void;
static function webviewSetOnClose(id: Int, callback: Void->Void): Void;
static function webviewCount(): Int;
static function webviewIsValid(id: Int): Bool;
static function webviewSetActiveDOM(id: Int): Void;
static function webviewGetActiveDOM(): Int;
static function webviewGoBack(id: Int): Void;
static function webviewGoForward(id: Int): Void;
static function webviewReload(id: Int): Void;
static function webviewCanGoBack(id: Int): Bool;
static function webviewCanGoForward(id: Int): Bool;
static function webviewGetURL(id: Int): String;
static function webviewGetPageTitle(id: Int): String;
static function webviewMinimize(id: Int): Void;
static function webviewMaximize(id: Int): Void;
static function webviewRestore(id: Int): Void;
static function webviewSetFullscreen(id: Int, fullscreen: Bool): Void;
static function webviewIsFullscreen(id: Int): Bool;
static function webviewEnableDevTools(id: Int, enabled: Bool): Void;
static function webviewSetContextMenu(id: Int, enabled: Bool): Void;
}

View File

@ -75,6 +75,8 @@ class Image implements Canvas implements Resource {
return 5;
case A16:
return 7;
case R32UI:
return 8;
default:
return 1; // Grey8
}
@ -200,6 +202,7 @@ class Image implements Canvas implements Resource {
case RGBA64: 8;
case A32: 4;
case A16: 2;
case R32UI: 4;
default: 4;
}
}

View File

@ -106,7 +106,7 @@ class Compute {
public static function setSampledDepthTexture(unit: TextureUnit, texture: Image) {
if (texture == null)
return;
Krom.setSampledDepthTextureCompute(unit, texture);
Krom.setSampledDepthTextureCompute(unit, texture.renderTarget_);
}
public static function setSampledCubeMap(unit: TextureUnit, cubeMap: CubeMap) {
@ -118,7 +118,7 @@ class Compute {
public static function setSampledDepthCubeMap(unit: TextureUnit, cubeMap: CubeMap) {
if (cubeMap == null)
return;
Krom.setSampledDepthTextureCompute(unit, cubeMap);
Krom.setSampledDepthTextureCompute(unit, cubeMap.renderTarget_);
}
public static function setTextureParameters(unit: TextureUnit, uAddressing: TextureAddressing, vAddressing: TextureAddressing,

View File

@ -9,14 +9,7 @@ class ShaderStorageBuffer {
public function new(indexCount: Int, type: VertexData) {
myCount = indexCount;
data = new Array<Int>();
data[myCount - 1] = 0;
init(indexCount, type);
}
function init(indexCount: Int, type: VertexData) {
myCount = indexCount;
data = new Array<Int>();
data[myCount - 1] = 0;
if (myCount > 0) data[myCount - 1] = 0;
}
public function delete(): Void {}

View File

@ -122,7 +122,12 @@ class Graphics implements kha.graphics4.Graphics {
public function setImageTexture(unit: kha.graphics4.TextureUnit, texture: kha.Image): Void {
if (texture == null)
return;
Krom.setImageTexture(unit, texture.texture_);
if (texture.texture_ != null) {
Krom.setImageTexture(unit, texture.texture_);
}
else if (texture.renderTarget_ != null) {
Krom.setImageRenderTarget(unit, texture.renderTarget_);
}
}
public function setTextureParameters(texunit: kha.graphics4.TextureUnit, uAddressing: TextureAddressing, vAddressing: TextureAddressing,

View File

@ -821,8 +821,19 @@ int kinc_g4_max_bound_textures(void) {
return units;
}
static int getUnitStage(kinc_g4_texture_unit_t unit) {
for (int i = 0; i < KINC_G4_SHADER_TYPE_COUNT; ++i) {
if (unit.stages[i] >= 0) {
return unit.stages[i];
}
}
return -1;
}
static void setTextureAddressingInternal(GLenum target, kinc_g4_texture_unit_t unit, kinc_g4_texture_direction_t dir, kinc_g4_texture_addressing_t addressing) {
glActiveTexture(GL_TEXTURE0 + unit.stages[KINC_G4_SHADER_TYPE_FRAGMENT]);
int stage = getUnitStage(unit);
if (stage < 0) return;
glActiveTexture(GL_TEXTURE0 + stage);
GLenum texDir;
switch (dir) {
case KINC_G4_TEXTURE_DIRECTION_U:
@ -841,39 +852,39 @@ static void setTextureAddressingInternal(GLenum target, kinc_g4_texture_unit_t u
case KINC_G4_TEXTURE_ADDRESSING_CLAMP:
glTexParameteri(target, texDir, GL_CLAMP_TO_EDGE);
if (dir == KINC_G4_TEXTURE_DIRECTION_U) {
texModesU[unit.stages[KINC_G4_SHADER_TYPE_FRAGMENT]] = GL_CLAMP_TO_EDGE;
texModesU[stage] = GL_CLAMP_TO_EDGE;
}
else {
texModesV[unit.stages[KINC_G4_SHADER_TYPE_FRAGMENT]] = GL_CLAMP_TO_EDGE;
texModesV[stage] = GL_CLAMP_TO_EDGE;
}
break;
case KINC_G4_TEXTURE_ADDRESSING_REPEAT:
glTexParameteri(target, texDir, GL_REPEAT);
if (dir == KINC_G4_TEXTURE_DIRECTION_U) {
texModesU[unit.stages[KINC_G4_SHADER_TYPE_FRAGMENT]] = GL_REPEAT;
texModesU[stage] = GL_REPEAT;
}
else {
texModesV[unit.stages[KINC_G4_SHADER_TYPE_FRAGMENT]] = GL_REPEAT;
texModesV[stage] = GL_REPEAT;
}
break;
case KINC_G4_TEXTURE_ADDRESSING_BORDER:
// unsupported
glTexParameteri(target, texDir, GL_CLAMP_TO_EDGE);
if (dir == KINC_G4_TEXTURE_DIRECTION_U) {
texModesU[unit.stages[KINC_G4_SHADER_TYPE_FRAGMENT]] = GL_CLAMP_TO_EDGE;
texModesU[stage] = GL_CLAMP_TO_EDGE;
}
else {
texModesV[unit.stages[KINC_G4_SHADER_TYPE_FRAGMENT]] = GL_CLAMP_TO_EDGE;
texModesV[stage] = GL_CLAMP_TO_EDGE;
}
break;
case KINC_G4_TEXTURE_ADDRESSING_MIRROR:
// unsupported
glTexParameteri(target, texDir, GL_REPEAT);
if (dir == KINC_G4_TEXTURE_DIRECTION_U) {
texModesU[unit.stages[KINC_G4_SHADER_TYPE_FRAGMENT]] = GL_REPEAT;
texModesU[stage] = GL_REPEAT;
}
else {
texModesV[unit.stages[KINC_G4_SHADER_TYPE_FRAGMENT]] = GL_REPEAT;
texModesV[stage] = GL_REPEAT;
}
break;
}
@ -881,11 +892,15 @@ static void setTextureAddressingInternal(GLenum target, kinc_g4_texture_unit_t u
}
int Kinc_G4_Internal_TextureAddressingU(kinc_g4_texture_unit_t unit) {
return texModesU[unit.stages[KINC_G4_SHADER_TYPE_FRAGMENT]];
int stage = getUnitStage(unit);
if (stage < 0) return GL_CLAMP_TO_EDGE;
return texModesU[stage];
}
int Kinc_G4_Internal_TextureAddressingV(kinc_g4_texture_unit_t unit) {
return texModesV[unit.stages[KINC_G4_SHADER_TYPE_FRAGMENT]];
int stage = getUnitStage(unit);
if (stage < 0) return GL_CLAMP_TO_EDGE;
return texModesV[stage];
}
void kinc_g4_set_texture_addressing(kinc_g4_texture_unit_t unit, kinc_g4_texture_direction_t dir, kinc_g4_texture_addressing_t addressing) {
@ -899,7 +914,9 @@ void kinc_g4_set_texture3d_addressing(kinc_g4_texture_unit_t unit, kinc_g4_textu
}
static void setTextureMagnificationFilterInternal(GLenum target, kinc_g4_texture_unit_t texunit, kinc_g4_texture_filter_t filter) {
glActiveTexture(GL_TEXTURE0 + texunit.stages[KINC_G4_SHADER_TYPE_FRAGMENT]);
int stage = getUnitStage(texunit);
if (stage < 0) return;
glActiveTexture(GL_TEXTURE0 + stage);
glCheckErrors();
switch (filter) {
case KINC_G4_TEXTURE_FILTER_POINT:
@ -964,26 +981,34 @@ static void setMinMipFilters(GLenum target, int unit) {
}
void kinc_g4_set_texture_minification_filter(kinc_g4_texture_unit_t texunit, kinc_g4_texture_filter_t filter) {
minFilters[texunit.stages[KINC_G4_SHADER_TYPE_FRAGMENT]] = filter;
setMinMipFilters(GL_TEXTURE_2D, texunit.stages[KINC_G4_SHADER_TYPE_FRAGMENT]);
int stage = getUnitStage(texunit);
if (stage < 0) return;
minFilters[stage] = filter;
setMinMipFilters(GL_TEXTURE_2D, stage);
}
void kinc_g4_set_texture3d_minification_filter(kinc_g4_texture_unit_t texunit, kinc_g4_texture_filter_t filter) {
minFilters[texunit.stages[KINC_G4_SHADER_TYPE_FRAGMENT]] = filter;
int stage = getUnitStage(texunit);
if (stage < 0) return;
minFilters[stage] = filter;
#ifndef KINC_OPENGL_ES
setMinMipFilters(GL_TEXTURE_3D, texunit.stages[KINC_G4_SHADER_TYPE_FRAGMENT]);
setMinMipFilters(GL_TEXTURE_3D, stage);
#endif
}
void kinc_g4_set_texture_mipmap_filter(kinc_g4_texture_unit_t texunit, kinc_g4_mipmap_filter_t filter) {
mipFilters[texunit.stages[KINC_G4_SHADER_TYPE_FRAGMENT]] = filter;
setMinMipFilters(GL_TEXTURE_2D, texunit.stages[KINC_G4_SHADER_TYPE_FRAGMENT]);
int stage = getUnitStage(texunit);
if (stage < 0) return;
mipFilters[stage] = filter;
setMinMipFilters(GL_TEXTURE_2D, stage);
}
void kinc_g4_set_texture3d_mipmap_filter(kinc_g4_texture_unit_t texunit, kinc_g4_mipmap_filter_t filter) {
mipFilters[texunit.stages[KINC_G4_SHADER_TYPE_FRAGMENT]] = filter;
int stage = getUnitStage(texunit);
if (stage < 0) return;
mipFilters[stage] = filter;
#ifndef KINC_OPENGL_ES
setMinMipFilters(GL_TEXTURE_3D, texunit.stages[KINC_G4_SHADER_TYPE_FRAGMENT]);
setMinMipFilters(GL_TEXTURE_3D, stage);
#endif
}

View File

@ -190,6 +190,17 @@ void kinc_g4_set_compute_shader(kinc_g4_compute_shader *shader) {
#endif
}
void kinc_g4_set_image_render_target(kinc_g4_texture_unit_t unit, kinc_g4_render_target_t *render_target) {
#if defined(KINC_WINDOWS) || (defined(KINC_LINUX) && defined(GL_VERSION_4_4))
for (int i = 0; i < KINC_G4_SHADER_TYPE_COUNT; ++i) {
if (unit.stages[i] >= 0) {
glBindImageTexture(unit.stages[i], render_target->impl._texture, 0, GL_FALSE, 0, GL_READ_WRITE, convertInternalRTFormat((kinc_g4_render_target_format_t)render_target->impl.format));
}
}
glCheckErrors();
#endif
}
void kinc_g4_compute(int x, int y, int z) {
#ifdef HAS_COMPUTE
glDispatchCompute(x, y, z);

View File

@ -360,17 +360,23 @@ void kinc_g4_render_target_destroy(kinc_g4_render_target_t *renderTarget) {
}
void kinc_g4_render_target_use_color_as_texture(kinc_g4_render_target_t *renderTarget, kinc_g4_texture_unit_t unit) {
glActiveTexture(GL_TEXTURE0 + unit.stages[KINC_G4_SHADER_TYPE_FRAGMENT]);
glCheckErrors();
glBindTexture(renderTarget->isCubeMap ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D, renderTarget->impl._texture);
glCheckErrors();
for (int i = 0; i < KINC_G4_SHADER_TYPE_COUNT; ++i) {
if (unit.stages[i] >= 0) {
glActiveTexture(GL_TEXTURE0 + unit.stages[i]);
glBindTexture(renderTarget->isCubeMap ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D, renderTarget->impl._texture);
glCheckErrors();
}
}
}
void kinc_g4_render_target_use_depth_as_texture(kinc_g4_render_target_t *renderTarget, kinc_g4_texture_unit_t unit) {
glActiveTexture(GL_TEXTURE0 + unit.stages[KINC_G4_SHADER_TYPE_FRAGMENT]);
glCheckErrors();
glBindTexture(renderTarget->isCubeMap ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D, renderTarget->impl._depthTexture);
glCheckErrors();
for (int i = 0; i < KINC_G4_SHADER_TYPE_COUNT; ++i) {
if (unit.stages[i] >= 0) {
glActiveTexture(GL_TEXTURE0 + unit.stages[i]);
glBindTexture(renderTarget->isCubeMap ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D, renderTarget->impl._depthTexture);
glCheckErrors();
}
}
}
void kinc_g4_render_target_set_depth_stencil_from(kinc_g4_render_target_t *renderTarget, kinc_g4_render_target_t *source) {

View File

@ -99,6 +99,8 @@ static int convertFormat(kinc_image_format_t format) {
case KINC_IMAGE_FORMAT_A16:
case KINC_IMAGE_FORMAT_GREY8:
return GL_RED;
case KINC_IMAGE_FORMAT_R32UI:
return GL_RED_INTEGER;
}
}
@ -132,6 +134,8 @@ static int convertInternalFormat(kinc_image_format_t format) {
#else
return GL_R8;
#endif
case KINC_IMAGE_FORMAT_R32UI:
return GL_R32UI;
}
}
@ -145,6 +149,8 @@ static int convertType(kinc_image_format_t format) {
case KINC_IMAGE_FORMAT_RGBA32:
default:
return GL_UNSIGNED_BYTE;
case KINC_IMAGE_FORMAT_R32UI:
return GL_UNSIGNED_INT;
}
}
@ -483,7 +489,7 @@ void kinc_g4_texture_init3d(kinc_g4_texture_t *texture, int width, int height, i
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glCheckErrors();
glTexImage3D(GL_TEXTURE_3D, 0, convertInternalFormat(format), width, height, depth, 0, convertFormat(format), GL_UNSIGNED_BYTE, NULL);
glTexImage3D(GL_TEXTURE_3D, 0, convertInternalFormat(format), width, height, depth, 0, convertFormat(format), convertType(format), NULL);
glCheckErrors();
#endif
}
@ -523,8 +529,8 @@ void Kinc_G4_Internal_TextureSet(kinc_g4_texture_t *texture, kinc_g4_texture_uni
#else
glBindTexture(target, texture->impl.texture);
glCheckErrors();
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, Kinc_G4_Internal_TextureAddressingU(unit));
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, Kinc_G4_Internal_TextureAddressingV(unit));
glTexParameteri(target, GL_TEXTURE_WRAP_S, Kinc_G4_Internal_TextureAddressingU(unit));
glTexParameteri(target, GL_TEXTURE_WRAP_T, Kinc_G4_Internal_TextureAddressingV(unit));
#endif
}
@ -532,7 +538,7 @@ void Kinc_G4_Internal_TextureImageSet(kinc_g4_texture_t *texture, kinc_g4_textur
#if defined(KINC_WINDOWS) || (defined(KINC_LINUX) && defined(GL_VERSION_4_4))
for (int i = 0; i < KINC_G4_SHADER_TYPE_COUNT; ++i) {
if (unit.stages[i] >= 0) {
glBindImageTexture(unit.stages[i], texture->impl.texture, 0, GL_FALSE, 0, GL_WRITE_ONLY, convertInternalFormat(texture->format));
glBindImageTexture(unit.stages[i], texture->impl.texture, 0, GL_FALSE, 0, GL_READ_WRITE, convertInternalFormat(texture->format));
}
}
glCheckErrors();

View File

@ -392,6 +392,11 @@ void kinc_window_hide(int window_index) {
UpdateWindow(windows[window_index].handle);
}
void kinc_window_set_foreground(int window_index) {
SetForegroundWindow(windows[window_index].handle);
SetFocus(windows[window_index].handle);
}
void kinc_window_set_title(int window_index, const char *title) {
wchar_t buffer[1024];
MultiByteToWideChar(CP_UTF8, 0, title, -1, buffer, 1024);

View File

@ -376,6 +376,8 @@ KINC_FUNC void kinc_g4_set_texture(kinc_g4_texture_unit_t unit, struct kinc_g4_t
/// <param name="texture">The texture to assign to the unit</param>
KINC_FUNC void kinc_g4_set_image_texture(kinc_g4_texture_unit_t unit, struct kinc_g4_texture *texture);
KINC_FUNC void kinc_g4_set_image_render_target(kinc_g4_texture_unit_t unit, struct kinc_g4_render_target *render_target);
KINC_FUNC bool kinc_g4_init_occlusion_query(unsigned *occlusionQuery);
KINC_FUNC void kinc_g4_delete_occlusion_query(unsigned occlusionQuery);

View File

@ -30,7 +30,8 @@ typedef enum kinc_image_format {
KINC_IMAGE_FORMAT_RGBA64,
KINC_IMAGE_FORMAT_A32,
KINC_IMAGE_FORMAT_BGRA32,
KINC_IMAGE_FORMAT_A16
KINC_IMAGE_FORMAT_A16,
KINC_IMAGE_FORMAT_R32UI
} kinc_image_format_t;
typedef struct kinc_image {
@ -608,6 +609,8 @@ int kinc_image_format_sizeof(kinc_image_format_t format) {
return 1;
case KINC_IMAGE_FORMAT_RGB24:
return 3;
case KINC_IMAGE_FORMAT_R32UI:
return 4;
}
return -1;
}

View File

@ -139,6 +139,11 @@ KINC_FUNC void kinc_window_show(int window);
/// </summary>
KINC_FUNC void kinc_window_hide(int window);
/// <summary>
/// Brings a window to the foreground and sets focus to it.
/// </summary>
KINC_FUNC void kinc_window_set_foreground(int window);
/// <summary>
/// Sets the title of a window.
/// </summary>

Binary file not shown.

View File

@ -0,0 +1,7 @@
package kha.compute;
enum abstract Access(Int) to Int {
var Read = 0;
var Write = 1;
var ReadWrite = 2;
}

View File

@ -0,0 +1,41 @@
package kha.compute;
import kha.arrays.Float32Array;
import kha.Image;
import kha.FastFloat;
import kha.math.FastMatrix3;
import kha.math.FastMatrix4;
import kha.math.FastVector2;
import kha.math.FastVector3;
import kha.math.FastVector4;
import kha.graphics4.CubeMap;
import kha.graphics4.TextureAddressing;
import kha.graphics4.TextureFilter;
import kha.graphics4.MipMapFilter;
extern class Compute {
public static function setBool(location: ConstantLocation, value: Bool): Void;
public static function setInt(location: ConstantLocation, value: Int): Void;
public static function setFloat(location: ConstantLocation, value: FastFloat): Void;
public static function setFloat2(location: ConstantLocation, value1: FastFloat, value2: FastFloat): Void;
public static function setFloat3(location: ConstantLocation, value1: FastFloat, value2: FastFloat, value3: FastFloat): Void;
public static function setFloat4(location: ConstantLocation, value1: FastFloat, value2: FastFloat, value3: FastFloat, value4: FastFloat): Void;
public static function setFloats(location: ConstantLocation, values: Float32Array): Void;
public static function setVector2(location: ConstantLocation, value: FastVector2): Void;
public static function setVector3(location: ConstantLocation, value: FastVector3): Void;
public static function setVector4(location: ConstantLocation, value: FastVector4): Void;
public static function setMatrix(location: ConstantLocation, value: FastMatrix4): Void;
public static function setMatrix3(location: ConstantLocation, value: FastMatrix3): Void;
public static function setBuffer(buffer: ShaderStorageBuffer, index: Int): Void;
public static function setTexture(unit: TextureUnit, texture: Image, access: Access): Void;
public static function setSampledTexture(unit: TextureUnit, texture: Image): Void;
public static function setSampledDepthTexture(unit: TextureUnit, texture: Image): Void;
public static function setSampledCubeMap(unit: TextureUnit, cubeMap: CubeMap): Void;
public static function setSampledDepthCubeMap(unit: TextureUnit, cubeMap: CubeMap): Void;
public static function setTextureParameters(unit: TextureUnit, uAddressing: TextureAddressing, vAddressing: TextureAddressing,
minificationFilter: TextureFilter, magnificationFilter: TextureFilter, mipmapFilter: MipMapFilter): Void;
public static function setTexture3DParameters(unit: TextureUnit, uAddressing: TextureAddressing, vAddressing: TextureAddressing,
wAddressing: TextureAddressing, minificationFilter: TextureFilter, magnificationFilter: TextureFilter, mipmapFilter: MipMapFilter): Void;
public static function setShader(shader: Shader): Void;
public static function compute(x: Int, y: Int, z: Int): Void;
}

View File

@ -0,0 +1,3 @@
package kha.compute;
interface ConstantLocation {}

View File

@ -0,0 +1,10 @@
package kha.compute;
import kha.Blob;
extern class Shader {
public function new(sources: Array<Blob>, files: Array<String>);
public function delete(): Void;
public function getConstantLocation(name: String): ConstantLocation;
public function getTextureUnit(name: String): TextureUnit;
}

View File

@ -0,0 +1,12 @@
package kha.compute;
import kha.graphics4.VertexData;
extern class ShaderStorageBuffer {
public function new(indexCount: Int, type: VertexData);
public function delete(): Void;
public function lock(): Array<Int>;
public function unlock(): Void;
public function set(): Void;
public function count(): Int;
}

View File

@ -0,0 +1,3 @@
package kha.compute;
interface TextureUnit {}

View File

@ -8,4 +8,5 @@ enum abstract TextureFormat(Int) to Int {
var RGBA64 = 4; // Half floats
var A32 = 5; // Float
var A16 = 6; // Half float
var R32UI = 7; // Unsigned 32-bit integer
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -379,7 +379,7 @@ void main() {
#ifdef _SSGI
vec3 ssgiColor = textureLod(ssgitex, texCoord, 0.0).rgb;
fragColor.rgb += ssgiColor * albedo;
fragColor.rgb += ssgiColor * basecolor;
#endif
#ifdef _EmissionShadeless

View File

@ -28,10 +28,12 @@ in vec2 texCoord;
out vec4 fragColor;
const float GOLDEN_ANGLE = 2.39996323;
const int RAY_STEPS = 12;
const int RAY_STEPS = 6;
const int BINARY_STEPS = 3;
vec2 getProjectedCoord(const vec3 viewPos) {
vec4 projectedCoord = P * vec4(viewPos, 1.0);
if (projectedCoord.w <= 0.0) return vec2(-1e5);
projectedCoord.xy /= projectedCoord.w;
projectedCoord.xy = projectedCoord.xy * 0.5 + 0.5;
#ifdef _InvY
@ -40,16 +42,12 @@ vec2 getProjectedCoord(const vec3 viewPos) {
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;
float linearZ(const float depth) {
return -P[3].z / (depth + P[2].z);
}
void buildTBN(vec3 n, out vec3 tangent, out vec3 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) {
@ -58,46 +56,70 @@ vec3 cosineSampleHemisphere(vec3 n, vec2 rand) {
tangent = normalize(cross(n, vec3(0.0, 0.0, 1.0)));
}
bitangent = cross(n, tangent);
}
vec3 sampleHemisphere(vec3 n, vec3 tangent, vec3 bitangent, float phi, float cosTheta) {
float sinTheta = sqrt(1.0 - cosTheta * cosTheta);
vec3 h = vec3(cos(phi) * sinTheta, sin(phi) * sinTheta, cosTheta);
return normalize(tangent * h.x + bitangent * h.y + n * h.z);
}
vec3 traceRay(vec3 origin, vec3 dir, float maxDist, float minDist) {
vec3 traceRay(vec3 origin, vec3 dir, float maxDist, float minDist, float jitter) {
float stepSize = maxDist / float(RAY_STEPS);
vec3 pos = origin + dir * minDist;
float rayDist = minDist + stepSize * (jitter - 1.0);
vec3 pos = origin + dir * rayDist;
float prevDepthDiff = 0.0;
float hadValidPrev = 0.0;
bool hasPrev = false;
for (int i = 1; i <= RAY_STEPS; i++) {
for (int i = 0; i < RAY_STEPS; i++) {
pos += dir * stepSize;
rayDist += stepSize;
vec2 uv = getProjectedCoord(pos);
if (uv.x < -100.0) return vec3(-1.0);
uv = clamp(uv, vec2(0.001), vec2(0.999));
vec2 sampleUV = clamp(uv, vec2(0.001), vec2(0.999));
float sampleDepth = textureLod(gbufferD, sampleUV, 0.0).r * 2.0 - 1.0;
float sampleDepth = textureLod(gbufferD, uv, 0.0).r * 2.0 - 1.0;
if (sampleDepth == 1.0) {
hadValidPrev = 0.0;
hasPrev = false;
continue;
}
float depthDiff = pos.z - linearZ(sampleDepth);
float thickness = maxDist * 0.075 + rayDist * 0.125;
vec3 sampleViewPos = getPosView2(invP, sampleDepth, sampleUV);
float depthDiff = pos.z - sampleViewPos.z;
float rayDist = length(pos - origin);
float thickness = 0.15 + rayDist * 0.25;
bool crossed = hasPrev && (prevDepthDiff > 0.0) && (depthDiff <= 0.0);
bool withinThickness = (depthDiff <= 0.0) && (-depthDiff < thickness);
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);
if (crossed || withinThickness) {
vec3 bPos = pos;
vec3 bDir = dir * stepSize;
for (int j = 0; j < BINARY_STEPS; j++) {
bDir *= 0.5;
bPos -= bDir;
vec2 bUV = getProjectedCoord(bPos);
bUV = clamp(bUV, vec2(0.001), vec2(0.999));
float bDepth = textureLod(gbufferD, bUV, 0.0).r * 2.0 - 1.0;
if (bDepth == 1.0) {
bPos += bDir;
continue;
}
if (bPos.z - linearZ(bDepth) > 0.0) bPos += bDir;
}
return vec3(sampleUV, distWeight);
vec2 bestUV = getProjectedCoord(bPos);
if (bestUV.x < -100.0) return vec3(-1.0);
bestUV = clamp(bestUV, vec2(0.001), vec2(0.999));
float hitDist = length(bPos - origin);
float distWeight = max(0.0, 1.0 - (hitDist / maxDist));
distWeight *= distWeight;
return vec3(bestUV, distWeight);
}
prevDepthDiff = depthDiff;
hadValidPrev = 1.0;
hasPrev = true;
}
return vec3(-1.0);
@ -111,77 +133,77 @@ void main() {
}
vec4 g0 = textureLod(gbuffer0, texCoord, 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);
n = normalize(n);
vec3 n = getNor(g0.rg);
vec3 basecolor = textureLod(gbuffer1, texCoord, 0.0).rgb;
vec3 viewNormal = V3 * n;
vec3 viewPos = getPosView2(invP, depth, texCoord);
#ifdef _CPostprocess
float radius = PPComp12.y;
float strength = PPComp12.x;
float radius = PPComp12.y * 2.0;
float strength = PPComp12.x * 0.5;
#else
float radius = ssgiRadius;
float strength = ssgiStrength;
float radius = ssgiRadius * 2.0;
float strength = ssgiStrength * 0.5;
#endif
float noise = fract(52.9829189 * fract(0.06711056 * texCoord.x * 1000.0 + 0.00583715 * texCoord.y * 1000.0));
radius = min(radius, max(-viewPos.z / P[1].y, 0.05));
float noise = fract(52.9829189 * fract(0.06711056 * gl_FragCoord.x + 0.00583715 * gl_FragCoord.y));
vec3 gi = vec3(0.0);
int validSamples = 0;
float missWeight = 0.0;
// min distance to avoid self shadowing artiffacts
float minDist = radius * 0.05;
vec3 tangent, bitangent;
buildTBN(viewNormal, tangent, bitangent);
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)
);
float phi = float(i) * GOLDEN_ANGLE + noise * PI2;
float cosTheta = sqrt(max(0.0, 1.0 - (float(i) + 0.5) / float(ssgiSamples)));
float jitter = fract(noise + float(i) * 0.618034);
vec3 rayDir = cosineSampleHemisphere(viewNormal, rand);
vec3 hitResult = traceRay(viewPos, rayDir, radius, minDist);
vec3 rayDir = sampleHemisphere(viewNormal, tangent, bitangent, phi, cosTheta);
vec3 hitResult = traceRay(viewPos, rayDir, radius, minDist, jitter);
if (hitResult.x < 0.0) continue;
if (hitResult.x < 0.0) {
missWeight += 1.0;
continue;
}
vec2 hitUV = hitResult.xy;
float distWeight = hitResult.z;
vec3 hitAlbedo = textureLod(gbuffer1, hitUV, 1.0).rgb;
vec3 hitN = getNor(textureLod(gbuffer0, hitUV, 0.0).rg);
float emitterCos = max(0.0, dot(V3 * hitN, -rayDir));
if (emitterCos <= 0.0) {
missWeight += 1.0;
continue;
}
vec3 hitAlbedo = textureLod(gbuffer1, hitUV, 0.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;
vec3 hitRadiance = hitAlbedo * (sunCol * hitNdotL + vec3(0.4));
#else
vec3 hitRadiance = hitAlbedo * 0.5;
vec3 hitRadiance = hitAlbedo * 0.4;
#endif
#ifdef _EmissionShaded
hitRadiance += textureLod(gbufferEmission, hitUV, 0.0).rgb;
#endif
gi += hitRadiance * distWeight;
validSamples++;
gi += hitRadiance * (distWeight * emitterCos);
}
if (validSamples > 0) {
gi /= float(validSamples);
}
gi *= strength;
gi += basecolor * 0.1 * missWeight;
gi *= 2.0 * strength / float(ssgiSamples);
#ifdef _EmissionShaded
gi += textureLod(gbufferEmission, texCoord, 0.0).rgb * 0.3;
gi += textureLod(gbufferEmission, texCoord, 0.0).rgb * 0.3;
#endif
fragColor = vec4(min(gi, vec3(2.0)), 1.0);

View File

@ -110,7 +110,7 @@ vec4 traceCone(const sampler3D voxels, const sampler3D voxelsSDF, const vec3 ori
float diam = max(voxelSize0, dist * coneCoefficient);
float lod = clamp(log2(diam / voxelSize0), clipmap_index0, voxelgiClipmapCount - 1);
float clipmap_index = floor(lod);
float clipmap_blend = fract(lod);
float clipmap_blend = smoothstep(0.0, 1.0, fract(lod));
vec3 p0 = start_pos + dir * dist;
samplePos = (p0 - vec3(clipmaps[int(clipmap_index * 10 + 4)], clipmaps[int(clipmap_index * 10 + 5)], clipmaps[int(clipmap_index * 10 + 6)])) / (float(clipmaps[int(clipmap_index * 10)]) * voxelgiResolution);
@ -121,11 +121,17 @@ vec4 traceCone(const sampler3D voxels, const sampler3D voxelsSDF, const vec3 ori
continue;
}
// Edge fade: blend toward coarser clipmap near boundaries
vec3 edgeDist = min(samplePos, 1.0 - samplePos);
float minEdgeDist = min(min(edgeDist.x, edgeDist.y), edgeDist.z);
float edgeBlend = 1.0 - smoothstep(0.0, 0.1, minEdgeDist);
float totalBlend = max(clipmap_blend, edgeBlend);
mipSample = sampleVoxel(voxels, p0, clipmaps, clipmap_index, step_dist, precomputed_direction, face_offset, direction_weight);
if(clipmap_blend > 0.0 && clipmap_index < voxelgiClipmapCount - 1) {
if(totalBlend > 0.0 && clipmap_index < voxelgiClipmapCount - 1) {
vec4 mipSampleNext = sampleVoxel(voxels, p0, clipmaps, clipmap_index + 1.0, step_dist, precomputed_direction, face_offset, direction_weight);
mipSample = mix(mipSample, mipSampleNext, clipmap_blend);
mipSample = mix(mipSample, mipSampleNext, totalBlend);
}
sampleCol += (1.0 - sampleCol.a) * mipSample;
@ -148,9 +154,8 @@ vec4 traceCone(const sampler3D voxels, const sampler3D voxelsSDF, const vec3 ori
vec4 traceDiffuse(const vec3 origin, const vec3 normal, const sampler3D voxels, const float clipmaps[voxelgiClipmapCount * 10]) {
float sum = 0.0;
vec4 amount = vec4(0.0);
mat3 TBN = makeTangentBasis(normal);
for (int i = 0; i < DIFFUSE_CONE_COUNT; ++i) {
vec3 coneDir = TBN * DIFFUSE_CONE_DIRECTIONS[i];
vec3 coneDir = DIFFUSE_CONE_DIRECTIONS[i];
const float cosTheta = dot(normal, coneDir);
if (cosTheta <= 0)
continue;
@ -159,7 +164,7 @@ vec4 traceDiffuse(const vec3 origin, const vec3 normal, const sampler3D voxels,
sum += cosTheta;
}
amount /= sum;
amount /= max(sum, 0.0001);
amount.rgb = max(amount.rgb, vec3(0.0));
amount.a = clamp(amount.a, 0.0, 1.0);
@ -215,10 +220,10 @@ float traceConeAO(const sampler3D voxels, const vec3 origin, const vec3 n, const
float diam = max(voxelSize0, dist * coneCoefficient);
float lod = clamp(log2(diam / voxelSize0), clipmap_index0, voxelgiClipmapCount - 1);
float clipmap_index = floor(lod);
float clipmap_blend = fract(lod);
float clipmap_blend = smoothstep(0.0, 1.0, fract(lod));
vec3 p0 = start_pos + dir * dist;
samplePos = (p0 - vec3(clipmaps[int(clipmap_index * 10 + 4)], clipmaps[int(clipmap_index * 10 + 5)], clipmaps[int(clipmap_index * 10 + 6)])) / (float(clipmaps[int(clipmap_index * 10)]) * voxelgiResolution.x);
samplePos = (p0 - vec3(clipmaps[int(clipmap_index * 10 + 4)], clipmaps[int(clipmap_index * 10 + 5)], clipmaps[int(clipmap_index * 10 + 6)])) / (float(clipmaps[int(clipmap_index * 10)]) * voxelgiResolution);
samplePos = samplePos * 0.5 + 0.5;
if ((any(notEqual(clamp(samplePos, 0.0, 1.0), samplePos)))) {
@ -226,11 +231,17 @@ float traceConeAO(const sampler3D voxels, const vec3 origin, const vec3 n, const
continue;
}
// Edge fade: blend toward coarser clipmap near boundaries
vec3 edgeDist = min(samplePos, 1.0 - samplePos);
float minEdgeDist = min(min(edgeDist.x, edgeDist.y), edgeDist.z);
float edgeBlend = 1.0 - smoothstep(0.0, 0.1, minEdgeDist);
float totalBlend = max(clipmap_blend, edgeBlend);
mipSample = sampleVoxel(voxels, p0, clipmaps, clipmap_index, step_dist, precomputed_direction, face_offset, direction_weight);
if(clipmap_blend > 0.0 && clipmap_index < voxelgiClipmapCount - 1) {
if(totalBlend > 0.0 && clipmap_index < voxelgiClipmapCount - 1) {
float mipSampleNext = sampleVoxel(voxels, p0, clipmaps, clipmap_index + 1.0, step_dist, precomputed_direction, face_offset, direction_weight);
mipSample = mix(mipSample, mipSampleNext, clipmap_blend);
mipSample = mix(mipSample, mipSampleNext, totalBlend);
}
sampleCol += (1.0 - sampleCol) * mipSample;
@ -254,7 +265,7 @@ float traceAO(const vec3 origin, const vec3 normal, const sampler3D voxels, cons
amount += traceConeAO(voxels, origin, normal, coneDir, precomputed_direction, DIFFUSE_CONE_APERTURE, 1.0, clipmaps) * cosTheta;
sum += cosTheta;
}
amount /= sum;
amount /= max(sum, 0.0001);
amount = clamp(amount, 0.0, 1.0);
return amount * voxelgiOcc;
}
@ -284,7 +295,7 @@ float traceConeShadow(const sampler3D voxels, const sampler3D voxelsSDF, const v
float diam = max(voxelSize0, dist * coneCoefficient);
float lod = clamp(log2(diam / voxelSize0), clipmap_index0, voxelgiClipmapCount - 1);
float clipmap_index = floor(lod);
float clipmap_blend = fract(lod);
float clipmap_blend = smoothstep(0.0, 1.0, fract(lod));
vec3 p0 = start_pos + dir * dist;
samplePos = (p0 - vec3(clipmaps[int(clipmap_index * 10 + 4)], clipmaps[int(clipmap_index * 10 + 5)], clipmaps[int(clipmap_index * 10 + 6)])) / (float(clipmaps[int(clipmap_index * 10)]) * voxelgiResolution);
@ -295,19 +306,25 @@ float traceConeShadow(const sampler3D voxels, const sampler3D voxelsSDF, const v
continue;
}
// Edge fade: blend toward coarser clipmap near boundaries
vec3 edgeDist = min(samplePos, 1.0 - samplePos);
float minEdgeDist = min(min(edgeDist.x, edgeDist.y), edgeDist.z);
float edgeBlend = 1.0 - smoothstep(0.0, 0.1, minEdgeDist);
float totalBlend = max(clipmap_blend, edgeBlend);
#ifdef _VoxelAOvar
mipSample = sampleVoxel(voxels, p0, clipmaps, clipmap_index, step_dist, 0, face_offset, direction_weight);
#else
mipSample = sampleVoxel(voxels, p0, clipmaps, clipmap_index, step_dist, 0, face_offset, direction_weight).a;
#endif
if(clipmap_blend > 0.0 && clipmap_index < voxelgiClipmapCount - 1) {
if(totalBlend > 0.0 && clipmap_index < voxelgiClipmapCount - 1) {
#ifdef _VoxelAOvar
float mipSampleNext = sampleVoxel(voxels, p0, clipmaps, clipmap_index + 1.0, step_dist, 0, face_offset, direction_weight);
#else
float mipSampleNext = sampleVoxel(voxels, p0, clipmaps, clipmap_index + 1.0, step_dist, 0, face_offset, direction_weight).a;
#endif
mipSample = mix(mipSample, mipSampleNext, clipmap_blend);
mipSample = mix(mipSample, mipSampleNext, totalBlend);
}
sampleCol += (1.0 - sampleCol) * mipSample;

View File

@ -24,42 +24,25 @@ const int DIFFUSE_CONE_COUNT = 16;
const float SHADOW_CONE_APERTURE = radians(15.0);
const float DIFFUSE_CONE_APERTURE = 0.872665;
const float DIFFUSE_CONE_APERTURE = 1.0;
mat3 makeTangentBasis(const vec3 normal) {
// Create a tangent basis from normal vector
vec3 tangent;
vec3 bitangent;
// Compute tangent (Frisvad's method)
if (abs(normal.z) < 0.999) {
tangent = normalize(cross(vec3(0, 1, 0), normal));
} else {
tangent = normalize(cross(normal, vec3(1, 0, 0)));
}
bitangent = cross(normal, tangent);
return mat3(tangent, bitangent, normal);
}
// 16 optimized cone directions for hemisphere sampling (Z-up, normalized)
const vec3 DIFFUSE_CONE_DIRECTIONS[16] = vec3[](
vec3(0.707107, 0.000000, 0.707107), // Front
vec3(-0.707107, 0.000000, 0.707107), // Back
vec3(0.000000, 0.707107, 0.707107), // Right
vec3(0.000000, -0.707107, 0.707107), // Left
vec3(0.500000, 0.500000, 0.707107), // Front-right
vec3(-0.500000, 0.500000, 0.707107), // Back-right
vec3(0.500000, -0.500000, 0.707107), // Front-left
vec3(-0.500000, -0.500000, 0.707107),// Back-left
vec3(0.353553, 0.000000, 0.935414), // Narrow front
vec3(-0.353553, 0.000000, 0.935414), // Narrow back
vec3(0.000000, 0.353553, 0.935414), // Narrow right
vec3(0.000000, -0.353553, 0.935414), // Narrow left
vec3(0.270598, 0.270598, 0.923880), // Narrow front-right
vec3(-0.270598, 0.270598, 0.923880), // Narrow back-right
vec3(0.270598, -0.270598, 0.923880), // Narrow front-left
vec3(-0.270598, -0.270598, 0.923880) // Narrow back-left
vec3( 0.3480, 0.0000, 0.9375),
vec3(-0.4299, 0.3938, 0.8125),
vec3( 0.0635, -0.7234, 0.6875),
vec3( 0.5031, 0.6561, 0.5625),
vec3(-0.8855, -0.1566, 0.4375),
vec3( 0.8015, -0.5098, 0.3125),
vec3(-0.2550, 0.9486, 0.1875),
vec3(-0.4600, -0.8857, 0.0625),
vec3( 0.9375, 0.3424, -0.0625),
vec3(-0.9080, 0.3748, -0.1875),
vec3( 0.4026, -0.8604, -0.3125),
vec3( 0.2691, 0.8580, -0.4375),
vec3(-0.7154, -0.4146, -0.5625),
vec3( 0.7092, -0.1559, -0.6875),
vec3(-0.3353, 0.4769, -0.8125),
vec3(-0.0447, -0.3451, -0.9375)
);
// TO DO - Disabled momentarily instead of changing formulas

View File

@ -185,19 +185,26 @@ vec3 multi_scatter_sample_lut(vec3 dir, float sun_rotation) {
}
vec3 multi_scatter_sun_disc(vec3 dir, vec3 sun_dir, float angular_diameter, float intensity) {
float dist = distance(dir, sun_dir) / (angular_diameter * 0.5);
if (dist > 1.0) return vec3(0.0);
float half_diameter = max(angular_diameter * 0.5, 0.0005);
float dist = distance(dir, sun_dir) / half_diameter;
float edge = smoothstep(1.0, 0.95, dist);
if (edge <= 0.0) return vec3(0.0);
float horizon = multiScatterSunBottom.w;
float dir_elev = asin(clamp(dir.z, -1.0, 1.0));
float horizon_fade = smoothstep(horizon - half_diameter * 0.1, horizon + half_diameter * 0.1, dir_elev);
edge *= horizon_fade;
if (edge <= 0.0) return vec3(0.0);
float invDist = 1.0 - dist;
float mu = sqrt(invDist * invDist);
vec3 limb_darkening = 1.0 - (1.0 - pow(vec3(mu), sun_limb_darkening_col));
float sun_elev = multiScatterParams.x;
float dir_elev = asin(clamp(dir.z, -1.0, 1.0));
float t = clamp((dir_elev - (sun_elev - angular_diameter * 0.5)) / angular_diameter, 0.0, 1.0);
float t = clamp((dir_elev - (sun_elev - half_diameter)) / (2.0 * half_diameter), 0.0, 1.0);
vec3 sun_color = mix(multiScatterSunBottom.rgb, multiScatterSunTop, t) * intensity * limb_darkening;
return xyz_to_rgb(sun_color);
return xyz_to_rgb(sun_color) * edge;
}
vec3 multi_scatter_atmosphere(vec3 dir) {

View File

@ -1,136 +0,0 @@
#version 450
layout (local_size_x = 8, local_size_y = 8, local_size_z = 8) in;
#include "compiled.inc"
#include "std/math.glsl"
#include "std/gbuffer.glsl"
#include "std/imageatomic.glsl"
#ifdef _VoxelShadow
#include "std/conetrace.glsl"
#endif
uniform vec3 lightPos;
uniform vec3 lightColor;
uniform int lightType;
uniform vec3 lightDir;
uniform vec2 spotData;
#ifdef _ShadowMap
uniform int lightShadow;
uniform vec2 lightProj;
uniform float shadowsBias;
uniform mat4 LVP;
#ifdef _ShadowMapAtlas
uniform int index;
uniform vec4 pointLightDataArray[maxLightsCluster * 6];
#endif
#endif
uniform float clipmaps[voxelgiClipmapCount * 10];
uniform int clipmapLevel;
uniform layout(r32ui) uimage3D voxelsLight;
#ifdef _ShadowMap
uniform sampler2DShadow shadowMap;
uniform sampler2D shadowMapTransparent;
uniform sampler2DShadow shadowMapSpot;
#ifdef _ShadowMapAtlas
uniform sampler2DShadow shadowMapPoint;
#else
uniform samplerCubeShadow shadowMapPoint;
#endif
#endif
#ifdef _ShadowMapAtlas
// 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/
vec2 sampleCube(vec3 dir, out int faceIndex) {
vec3 dirAbs = abs(dir);
float ma;
vec2 uv;
if(dirAbs.z >= dirAbs.x && dirAbs.z >= dirAbs.y) {
faceIndex = dir.z < 0.0 ? 5 : 4;
ma = 0.5 / dirAbs.z;
uv = vec2(dir.z < 0.0 ? -dir.x : dir.x, -dir.y);
}
else if(dirAbs.y >= dirAbs.x) {
faceIndex = dir.y < 0.0 ? 3 : 2;
ma = 0.5 / dirAbs.y;
uv = vec2(dir.x, dir.y < 0.0 ? -dir.z : dir.z);
}
else {
faceIndex = dir.x < 0.0 ? 1 : 0;
ma = 0.5 / dirAbs.x;
uv = vec2(dir.x < 0.0 ? dir.z : -dir.z, -dir.y);
}
// downscale uv a little to hide seams
// transform coordinates from clip space to texture space
#ifndef _FlipY
return uv * 0.9976 * ma + 0.5;
#else
#ifdef HLSL
return uv * 0.9976 * ma + 0.5;
#else
return vec2(uv.x * ma, uv.y * -ma) * 0.9976 + 0.5;
#endif
#endif
}
#endif
float lpToDepth(vec3 lp, const vec2 lightProj) {
lp = abs(lp);
float zcomp = max(lp.x, max(lp.y, lp.z));
zcomp = lightProj.x - lightProj.y / zcomp;
return zcomp * 0.5 + 0.5;
}
void main() {
int res = voxelgiResolution.x;
ivec3 dst = ivec3(gl_GlobalInvocationID.xyz);
vec3 wposition = (gl_GlobalInvocationID.xyz + 0.5) / voxelgiResolution.x;
wposition = wposition * 2.0 - 1.0;
wposition *= float(clipmaps[int(clipmapLevel * 10)]);
wposition *= voxelgiResolution.x;
wposition += vec3(clipmaps[clipmapLevel * 10 + 4], clipmaps[clipmapLevel * 10 + 5], clipmaps[clipmapLevel * 10 + 6]);
float visibility;
vec3 lp = lightPos - wposition;
vec3 l;
if (lightType == 0) { l = lightDir; visibility = 1.0; }
else { l = normalize(lp); visibility = attenuate(distance(wposition, lightPos)); }
#ifdef _ShadowMap
if (lightShadow == 1) {
vec4 lightPosition = LVP * vec4(wposition, 1.0);
vec3 lPos = lightPosition.xyz / lightPosition.w;
visibility *= texture(shadowMap, vec3(lPos.xy, lPos.z - shadowsBias)).r;
}
else if (lightShadow == 2) {
vec4 lightPosition = LVP * vec4(wposition, 1.0);
vec3 lPos = lightPosition.xyz / lightPosition.w;
visibility *= texture(shadowMapSpot, vec3(lPos.xy, lPos.z - shadowsBias)).r;
}
else if (lightShadow == 3) {
#ifdef _ShadowMapAtlas
int faceIndex = 0;
const int lightIndex = index * 6;
const vec2 uv = sampleCube(-l, faceIndex);
vec4 pointLightTile = pointLightDataArray[lightIndex + faceIndex]; // x: tile X offset, y: tile Y offset, z: tile size relative to atlas
vec2 uvtiled = pointLightTile.z * uv + pointLightTile.xy;
#ifdef _FlipY
uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system
#endif
visibility *= texture(shadowMapPoint, vec3(uvtiled, lpToDepth(lp, lightProj) - shadowsBias)).r;
#else
visibility *= texture(shadowMapPoint, vec4(-l, lpToDepth(lp, lightProj) - shadowsBias)).r;
#endif
}
#endif
vec3 light = visibility * lightColor;
imageAtomicAdd(voxelsLight, dst, uint(light.r * 255));
imageAtomicAdd(voxelsLight, dst + ivec3(0, 0, voxelgiResolution.x), uint(light.g * 255));
imageAtomicAdd(voxelsLight, dst + ivec3(0, 0, voxelgiResolution.x * 2), uint(light.b * 255));
}

View File

@ -43,7 +43,6 @@ uniform mat4 LVP;
#endif
uniform sampler3D voxelsSampler;
uniform layout(r32ui) uimage3D voxels;
uniform layout(r32ui) uimage3D voxelsLight;
uniform layout(rgba8) image3D voxelsB;
uniform layout(rgba8) image3D voxelsOut;
uniform layout(r8) image3D SDF;
@ -75,21 +74,13 @@ void main() {
#endif
#ifdef _VoxelGI
vec3 light = vec3(0.0);
light.r = float(imageLoad(voxelsLight, ivec3(gl_GlobalInvocationID.xyz)).r) / 255;
light.g = float(imageLoad(voxelsLight, ivec3(gl_GlobalInvocationID.xyz) + ivec3(0, 0, voxelgiResolution.x)).r) / 255;
light.b = float(imageLoad(voxelsLight, ivec3(gl_GlobalInvocationID.xyz) + ivec3(0, 0, voxelgiResolution.x * 2)).r) / 255;
light /= 3;
vec4 aniso_colors[6];
#else
float aniso_colors[6];
#endif
for (int i = 0; i < 6 + DIFFUSE_CONE_COUNT; i++)
{
#ifdef _VoxelGI
vec4 aniso_colors[6];
#else
float aniso_colors[6];
#endif
ivec3 src = ivec3(gl_GlobalInvocationID.xyz);
src.x += i * res;
ivec3 dst = src;
@ -103,30 +94,37 @@ void main() {
if (i < 6) {
#ifdef _VoxelGI
uint count = imageLoad(voxels, src + ivec3(0, 0, voxelgiResolution.x * 15)).r;
if (count > 0) {
vec4 basecol = vec4(0.0);
basecol.r = float(imageLoad(voxels, src)) / 255;
basecol.g = float(imageLoad(voxels, src + ivec3(0, 0, voxelgiResolution.x))) / 255;
basecol.b = float(imageLoad(voxels, src + ivec3(0, 0, voxelgiResolution.x * 2))) / 255;
basecol.a = float(imageLoad(voxels, src + ivec3(0, 0, voxelgiResolution.x * 3))) / 255;
basecol /= 4;
basecol /= count;
vec3 emission = vec3(0.0);
emission.r = float(imageLoad(voxels, src + ivec3(0, 0, voxelgiResolution.x * 4))) / 255;
emission.g = float(imageLoad(voxels, src + ivec3(0, 0, voxelgiResolution.x * 5))) / 255;
emission.b = float(imageLoad(voxels, src + ivec3(0, 0, voxelgiResolution.x * 6))) / 255;
emission /= 3;
emission /= count;
vec3 N = vec3(0.0);
N.r = float(imageLoad(voxels, src + ivec3(0, 0, voxelgiResolution.x * 7))) / 255;
N.g = float(imageLoad(voxels, src + ivec3(0, 0, voxelgiResolution.x * 8))) / 255;
N /= 2;
N /= count;
vec3 wnormal = decode_oct(N.rg * 2 - 1);
vec3 envl = vec3(0.0);
envl.r = float(imageLoad(voxels, src + ivec3(0, 0, voxelgiResolution.x * 9))) / 255;
envl.g = float(imageLoad(voxels, src + ivec3(0, 0, voxelgiResolution.x * 10))) / 255;
envl.b = float(imageLoad(voxels, src + ivec3(0, 0, voxelgiResolution.x * 11))) / 255;
envl /= 3;
envl /= count;
#ifdef _HOSEK
envl *= 100;
#endif
vec3 light = vec3(0.0);
light.r = float(imageLoad(voxels, src + ivec3(0, 0, voxelgiResolution.x * 12))) / 255;
light.g = float(imageLoad(voxels, src + ivec3(0, 0, voxelgiResolution.x * 13))) / 255;
light.b = float(imageLoad(voxels, src + ivec3(0, 0, voxelgiResolution.x * 14))) / 255;
light /= count;
//clipmap to world
vec3 wposition = (gl_GlobalInvocationID.xyz + 0.5) / voxelgiResolution.x;
@ -140,6 +138,7 @@ void main() {
vec3 indirect = trace.rgb + envl.rgb * (1.0 - trace.a);
radiance.rgb *= light.rgb + indirect.rgb;
radiance.rgb += emission.rgb;
}
#else
opac = float(imageLoad(voxels, src)) / 255;

View File

@ -55,6 +55,8 @@ class Geometry {
// Skinned
#if lnx_skin
public var skeletonTransformsI: Array<Mat4> = null;
public var actions: Map<String, Array<iron.data.SceneFormat.TObj>> = new Map();
public var mats: Map<String, Array<Mat4>> = new Map();
#end
public function new(data: MeshData, indices: Array<Uint32Array>, materialIndices: Array<Int>, usage: Usage = null) {

View File

@ -318,9 +318,9 @@ class MeshObject extends Object {
if (scontext.pipeState != lastPipeline) {
g.setPipeline(scontext.pipeState);
lastPipeline = scontext.pipeState;
// Uniforms.setContextConstants(g, scontext, bindParams);
Uniforms.setContextConstants(g, scontext, bindParams);
}
Uniforms.setContextConstants(g, scontext, bindParams); //
//Uniforms.setContextConstants(g, scontext, bindParams); //
Uniforms.setObjectConstants(g, scontext, this);
if (materialContexts.length > mi) {
Uniforms.setMaterialConstants(g, scontext, materialContexts[mi]);

View File

@ -394,7 +394,7 @@ class ParticleSystemCPU {
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);
var finalScale: FastFloat = 1 + (scaleMultiplier - 1) * physics.scaleRampSizeFactor;
particle.transform.scale.setFrom(physics.baseScale.clone().mult(finalScale));
}

View File

@ -8,7 +8,7 @@ class AlertNode extends LogicNode {
override function run(from: Int) {
#if kha_html5
#if js
js.Browser.window.alert(inputs[1].get());
#end

View File

@ -2,19 +2,31 @@ package leenkx.logicnode;
class ConfirmNode extends LogicNode {
var result: Dynamic;
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
#if kha_html5
var answer: Bool = js.Browser.window.confirm(inputs[1].get());
if(answer)
return runOutput(0);
else
return runOutput(1);
#if js
result = js.Browser.window.confirm(inputs[1].get());
if (Reflect.field(result, "loaded") != null) {
tree.notifyOnUpdate(poll);
} else {
if (result) runOutput(0);
else runOutput(1);
}
#end
}
function poll() {
#if js
if (result.loaded) {
tree.removeUpdate(poll);
if (result.data) runOutput(0);
else runOutput(1);
}
#end
}
}

View File

@ -11,12 +11,11 @@ class GetElementPropertyNode extends LogicNode {
override function get(from: Int): Dynamic {
return switch (from) {
case 0:
var object: Dynamic = inputs[0].get();
var property = inputs[1].get();
value = Reflect.field(object, property);
//value = object.getAttribute(property.toString());
#if js
var element: Dynamic = inputs[0].get();
var property = inputs[1].get();
value = Reflect.field(element, property);
#end
default: throw "Unreachable";
}
}

View File

@ -0,0 +1,16 @@
package leenkx.logicnode;
class KromCopyToClipboardNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
#if kha_krom
var text: String = inputs[1].get();
js.Syntax.code("Krom.copyToClipboard({0})", text);
#end
runOutput(0);
}
}

View File

@ -0,0 +1,15 @@
package leenkx.logicnode;
class KromDelayIdleSleepNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
#if kha_krom
js.Syntax.code("Krom.delayIdleSleep()");
#end
runOutput(0);
}
}

View File

@ -0,0 +1,16 @@
package leenkx.logicnode;
class KromDeleteFileNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
#if kha_krom
var path: String = inputs[1].get();
js.Syntax.code("Krom.deleteFile({0})", path);
#end
runOutput(0);
}
}

View File

@ -0,0 +1,17 @@
package leenkx.logicnode;
class KromFileExistsNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function get(from: Int): Dynamic {
#if kha_krom
var path: String = inputs[0].get();
return js.Syntax.code("Krom.fileExists({0})", path);
#else
return false;
#end
}
}

View File

@ -0,0 +1,16 @@
package leenkx.logicnode;
class KromGetArgCountNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function get(from: Int): Dynamic {
#if kha_krom
return Krom.getArgCount();
#else
return 0;
#end
}
}

View File

@ -0,0 +1,17 @@
package leenkx.logicnode;
class KromGetArgNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function get(from: Int): Dynamic {
#if kha_krom
var index: Int = inputs[0].get();
return Krom.getArg(index);
#else
return "";
#end
}
}

View File

@ -0,0 +1,16 @@
package leenkx.logicnode;
class KromGetFilesLocationNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function get(from: Int): Dynamic {
#if kha_krom
return Krom.getFilesLocation();
#else
return "";
#end
}
}

View File

@ -0,0 +1,16 @@
package leenkx.logicnode;
class KromSavePathNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function get(from: Int): Dynamic {
#if kha_krom
return Krom.savePath();
#else
return "";
#end
}
}

View File

@ -0,0 +1,16 @@
package leenkx.logicnode;
class KromSetApplicationNameNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
#if kha_krom
var name: String = inputs[1].get();
js.Syntax.code("Krom.setApplicationName({0})", name);
#end
runOutput(0);
}
}

View File

@ -0,0 +1,16 @@
package leenkx.logicnode;
class KromShowKeyboardNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
#if kha_krom
var show: Bool = inputs[1].get();
js.Syntax.code("Krom.showKeyboard({0})", show);
#end
runOutput(0);
}
}

View File

@ -0,0 +1,28 @@
package leenkx.logicnode;
class KromSysCommandNode extends LogicNode {
var exitCode: Int = 0;
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
#if kha_krom
var cmd: String = inputs[1].get();
var args: Dynamic = inputs[2].get();
if (args != null) {
exitCode = Krom.sysCommand(cmd, args);
} else {
exitCode = Krom.sysCommand(cmd);
}
#end
runOutput(0);
}
override function get(from: Int): Dynamic {
if (from == 1) return exitCode;
return null;
}
}

View File

@ -13,7 +13,7 @@ class LoadUrlNode extends LogicNode {
override function run(from: Int) {
//System.loadUrl(inputs[1].get());
#if kha_html5
#if js
if (inputs[2].get()){
var window = inputs[3].get() ? js.Browser.window.open(inputs[1].get(), "_blank", "width="+inputs[4].get()+",height="+inputs[5].get()+",left="+inputs[6].get()+",top="+inputs[7].get())
: js.Browser.window.open(inputs[1].get(), "_blank");

View File

@ -1,193 +0,0 @@
package leenkx.logicnode;
import iron.object.Animation;
import iron.object.Object;
import iron.Scene;
import kha.arrays.Float32Array;
import iron.object.ObjectAnimation;
class PlayActionFromNode extends LogicNode {
var animation: Animation;
var startFrame: Int;
var endFrame: Int = -1;
var loop: Bool;
var reverse: Bool;
var action: String;
var actionR: String;
public function new(tree: LogicTree) {
super(tree);
tree.notifyOnUpdate(update);
}
function update() {
if (animation != null && action == animation.action) {
if (animation.currentFrame() == endFrame-1) {
if (loop) animation.setFrame(startFrame);
else {
if (!animation.paused) {
animation.pause();
runOutput(1);
}
}
}
}
}
override function run(from: Int) {
var object: Object = inputs[1].get();
action = inputs[2].get();
startFrame = inputs[3].get();
endFrame = inputs[4].get();
var blendTime: Float = inputs[5].get();
var speed: Float = inputs[6].get();
loop = inputs[7].get();
reverse = inputs[8].get();
if (object == null) return;
animation = object.animation;
if (animation == null) animation = object.getParentArmature(object.name);
if (reverse){
var isnew = true;
actionR = action+'Reverse';
if (animation.isSkinned){
for(a in animation.armature.actions)
if (a.name == actionR) isnew = false;
if (isnew){
for(a in animation.armature.actions)
if(a.name == action){
var bones = [];
var cn = [];
for(bone in a.bones){
var v = bone.anim.tracks[0];
var len: Int = v.values.length;
var val = new Float32Array(len);
var l = Std.int(len/16);
for(i in 0...l)
for(j in 0...16){
val[i*16+j] = v.values[(l-i)*16+j-16];
}
if (bone.children != null){
var cdn = [];
for (child in bone.children)
cdn.push(child.name);
cn.push(cdn);
} else cn.push(null);
var a: iron.data.SceneFormat.TObj = {
type : bone.type,
name : bone.name,
transform : {
//target : null,
values : bone.transform.values
},
anim : {
tracks : [{
target : v.target,
frames : v.frames,
values : val,
ref_values : null
}]//,
//begin : null,
//end : null,
//has_delta : null,
//marker_frames : null,
//marker_names : null
},
children : bone.children,
data_ref : null
}
if (bone.parent != null){
a.parent = bone.parent;
}
bones.push(a);
}
for (i in 0...bones.length){
var cd = [];
if (cn[i] != null)
for (name in cn[i])
for (bone in bones)
if (bone.name == name)
cd.push(bone);
bones[i].children = cd;
}
for (i in 0...bones.length){
if (bones[i].parent != null)
for (bone in bones)
if (bone.name == bones[i].parent.name)
bones[i].parent = bone;
}
animation.armature.actions.push({
name: actionR,
bones: bones,
mats: null});
var mats: Array<iron.math.Mat4> = [];
for (bone in a.bones) mats.push(iron.math.Mat4.fromFloat32Array(bone.transform.values));
var castBoneAnim = cast(animation, iron.object.BoneAnimation);
castBoneAnim.data.geom.actions.set(actionR, bones);
castBoneAnim.data.geom.mats.set(actionR, mats);
for(o in iron.Scene.active.raw.objects)
if (o.name == object.name) o.bone_actions.push('action_'+o.bone_actions[0].split('_')[1]+'_'+actionR);
}
}
}
else {
var oaction = null;
var tracks: Array<iron.data.SceneFormat.TTrack> = [];
var oactions = cast(animation, ObjectAnimation).oactions;
for (a in oactions)
if (a != null && a.objects[0].name == actionR) isnew = false;
if (isnew){
for (a in oactions){
if (a != null && a.objects[0].name == action){
oaction = a.objects[0];
for(b in a.objects[0].anim.tracks){
var val: Array<Float> = [];
for(c in b.values) val.unshift(c);
var vali = new Float32Array(val.length);
for(i in 0...val.length) vali[i] = val[i];
tracks.push({target: b.target, frames: b.frames, values: vali});
}
oactions.push({
objects: [{name: actionR,
anim: {begin: oaction.anim.begin, end: oaction.anim.end, tracks: tracks},
type: 'object',
data_ref: '',
transform: null}]});
for(o in iron.Scene.active.raw.objects)
if (o.name == object.name) o.object_actions.push('action_'+actionR);
}
}
}
}
}
animation.play(reverse ? actionR : action, function() {
runOutput(1);
}, blendTime, speed, loop);
animation.update(startFrame * Scene.active.raw.frame_time);
runOutput(0);
}
}

View File

@ -3,18 +3,32 @@ package leenkx.logicnode;
class PromptNode extends LogicNode {
var input: String = null;
var result: Dynamic;
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
#if kha_html5
input = js.Browser.window.prompt(inputs[1].get(), inputs[2].get());
runOutput(0);
#if js
result = js.Browser.window.prompt(inputs[1].get(), inputs[2].get());
if (Reflect.field(result, "loaded") != null) {
tree.notifyOnUpdate(poll);
} else {
input = result;
runOutput(0);
}
#end
}
function poll() {
#if js
if (result.loaded) {
tree.removeUpdate(poll);
input = result.data;
runOutput(0);
}
#end
}
override function get(from: Int): Dynamic {

View File

@ -40,12 +40,14 @@ class RenderElementNode extends LogicNode {
tarElem.prepend(element);
runOutput(0);
case "innerHTML":
var tarElem = js.Browser.document.querySelector(selector);
tarElem.innerHTML = element.innerHTML;
var html: String = inputs[2].get();
if (html == null) { return; }
element.innerHTML = html;
runOutput(0);
case "innerText":
var tarElem = js.Browser.document.querySelector(selector);
tarElem.innerText = element.innerText;
var html: String = inputs[2].get();
if (html == null){ return; }
element.innerText = html;
runOutput(0);
case "insertAdjacentHTML":
var tarElem = js.Browser.document.querySelector(selector);

View File

@ -12,28 +12,7 @@ class RunJavascriptNode extends LogicNode {
#if js
var script = inputs[1].get();
js.Syntax.code('(1, eval)({0})', script.toString());
var promise:Dynamic = null;//js.Syntax.code('(1, eval)({0})', script.toString());
if (promise != null) {
promise.then(
function(_) {
//if(leenkx.network.Leenkx.data.get(element) != 'undefined'){return}
haxe.Timer.delay(function () {
//promiseResult(element,html);
return;
}, 100);
return;
}).then(null, function(error) {
trace("JS SYNTAX ERROR:" + error.toString());
haxe.Timer.delay(function () {
//promiseResult(element,html);
return;
}, 100);
}
);
}
runOutput(0);
runOutput(0);
#end
}

View File

@ -10,6 +10,7 @@ class SetFirstPersonControllerNode extends LogicNode {
}
override function run(from: Int): Void {
#if lnx_physics
// Control de las var de FirstPersonController...
// Control FirstPersonController var
@ -60,6 +61,7 @@ class SetFirstPersonControllerNode extends LogicNode {
// Alert the user if they do not have the trait assigning to the object.
trace("ERROR: The object '" + object.name + "' does not have the FirstPersonController script assigning(assign it from (Object->add trait->bundle)).");
}
#end
runOutput(0);
}

View File

@ -10,6 +10,7 @@ class SetOverheadPersonControllerNode extends LogicNode {
}
override function run(from: Int): Void {
#if lnx_physics
// Control de las var de OverheadPersonController...
// Control OverheadPersonController var
@ -58,6 +59,7 @@ class SetOverheadPersonControllerNode extends LogicNode {
// Alert the user if they do not have the trait assigning to the object..
trace("ERROR: The object '" + objectTrait.name + "' does not have the OverheadPersonController script assigning(assign it from (Object->add trait->bundle)).");
}
#end
runOutput(0);
}

View File

@ -0,0 +1,16 @@
package leenkx.logicnode;
class WebviewCountNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function get(from: Int): Dynamic {
#if kha_krom
return Krom.webviewCount();
#else
return 0;
#end
}
}

View File

@ -0,0 +1,50 @@
package leenkx.logicnode;
class WebviewCreateNode extends LogicNode {
public var property0: String;
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
var parentWindow: Int = (property0 == "embedded") ? 0 : -1;
var x: Int = inputs[1].get();
var y: Int = inputs[2].get();
var width: Int = inputs[3].get();
var height: Int = inputs[4].get();
var transparent: Bool = inputs[5].get();
var title: String = inputs[6].get();
var html: String = inputs[7].get();
var url: String = inputs[8].get();
var show: Bool = inputs[9].get();
#if kha_krom
var options: Dynamic = {
parentWindow: parentWindow,
x: x,
y: y,
width: width,
height: height,
transparent: transparent,
title: title
};
var id: Int = Krom.webviewCreate(options);
if (html != null && html != "") Krom.webviewLoadHTML(id, html);
if (url != null && url != "") Krom.webviewLoadURL(id, url);
if (show) Krom.webviewShow(id);
#else
var id: Int = -1;
#end
this.id = id;
runOutput(0);
}
var id: Int = -1;
override function get(from: Int): Dynamic {
return id;
}
}

View File

@ -0,0 +1,31 @@
package leenkx.logicnode;
class WebviewDOMNode extends LogicNode {
public var property0: String;
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
#if kha_krom
if (property0 == "set") {
var id: Int = inputs[1].get();
Krom.webviewSetActiveDOM(id);
}
#end
runOutput(0);
}
override function get(from: Int): Dynamic {
#if kha_krom
if (property0 == "get") {
return Krom.webviewGetActiveDOM();
}
#else
return -1;
#end
return -1;
}
}

View File

@ -0,0 +1,16 @@
package leenkx.logicnode;
class WebviewDestroyNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
#if kha_krom
var id: Int = inputs[1].get();
Krom.webviewDestroy(id);
#end
runOutput(0);
}
}

View File

@ -0,0 +1,24 @@
package leenkx.logicnode;
class WebviewDevSettingsNode extends LogicNode {
public var property0: String;
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
#if kha_krom
var id: Int = inputs[1].get();
var enabled: Bool = inputs[2].get();
if (property0 == "devtools") {
Krom.webviewEnableDevTools(id, enabled);
}
else {
Krom.webviewSetContextMenu(id, enabled);
}
#end
runOutput(0);
}
}

View File

@ -0,0 +1,17 @@
package leenkx.logicnode;
class WebviewEvalJSNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
#if kha_krom
var id: Int = inputs[1].get();
var jsCode: String = inputs[2].get();
Krom.webviewEvalJS(id, jsCode);
#end
runOutput(0);
}
}

View File

@ -0,0 +1,31 @@
package leenkx.logicnode;
class WebviewFullscreenNode extends LogicNode {
public var property0: String;
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
#if kha_krom
if (property0 == "set") {
var id: Int = inputs[1].get();
var fullscreen: Bool = inputs[2].get();
Krom.webviewSetFullscreen(id, fullscreen);
}
#end
runOutput(0);
}
override function get(from: Int): Dynamic {
#if kha_krom
if (property0 == "get") {
var id: Int = inputs[0].get();
return Krom.webviewIsFullscreen(id);
}
#end
return false;
}
}

View File

@ -0,0 +1,26 @@
package leenkx.logicnode;
class WebviewGetDimensionsNode extends LogicNode {
public var property0: String;
public function new(tree: LogicTree) {
super(tree);
}
override function get(from: Int): Dynamic {
#if kha_krom
var id: Int = inputs[0].get();
if (property0 == "position") {
if (from == 0) return Krom.webviewGetX(id);
else return Krom.webviewGetY(id);
}
else {
if (from == 0) return Krom.webviewGetWidth(id);
else return Krom.webviewGetHeight(id);
}
#else
return 0;
#end
}
}

View File

@ -0,0 +1,17 @@
package leenkx.logicnode;
class WebviewIsValidNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function get(from: Int): Dynamic {
#if kha_krom
var id: Int = inputs[0].get();
return Krom.webviewIsValid(id);
#else
return false;
#end
}
}

View File

@ -0,0 +1,25 @@
package leenkx.logicnode;
class WebviewLoadNode extends LogicNode {
public var property0: String;
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
#if kha_krom
var id: Int = inputs[1].get();
if (property0 == "html") {
var html: String = inputs[2].get();
Krom.webviewLoadHTML(id, html);
}
else {
var url: String = inputs[2].get();
Krom.webviewLoadURL(id, url);
}
#end
runOutput(0);
}
}

View File

@ -0,0 +1,22 @@
package leenkx.logicnode;
class WebviewNavigationNode extends LogicNode {
public var property0: String;
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
#if kha_krom
var id: Int = inputs[1].get();
switch (property0) {
case "go_back": Krom.webviewGoBack(id);
case "go_forward": Krom.webviewGoForward(id);
case "reload": Krom.webviewReload(id);
}
#end
runOutput(0);
}
}

View File

@ -0,0 +1,20 @@
package leenkx.logicnode;
class WebviewNavigationStateNode extends LogicNode {
public var property0: String;
public function new(tree: LogicTree) {
super(tree);
}
override function get(from: Int): Dynamic {
#if kha_krom
var id: Int = inputs[0].get();
if (property0 == "can_go_back") return Krom.webviewCanGoBack(id);
else return Krom.webviewCanGoForward(id);
#else
return false;
#end
}
}

View File

@ -0,0 +1,46 @@
package leenkx.logicnode;
class WebviewOnEventNode extends LogicNode {
public var property0: String;
var message: String = "";
var error: String = "";
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
#if kha_krom
var id: Int = inputs[0].get();
var self = this;
switch (property0) {
case "message":
Krom.webviewSetOnMessage(id, function(msg) { self.onEvent(msg); });
case "load":
Krom.webviewSetOnLoad(id, function() { self.onEvent(null); });
case "error":
Krom.webviewSetOnError(id, function(err) { self.onEvent(err); });
case "close":
Krom.webviewSetOnClose(id, function() { self.onEvent(null); });
}
#end
}
function onEvent(data: String) {
if (data != null) {
if (property0 == "message") this.message = data;
else if (property0 == "error") this.error = data;
}
runOutput(0);
}
override function get(from: Int): Dynamic {
if (from == 1) {
if (property0 == "message") return message;
if (property0 == "error") return error;
}
return null;
}
}

View File

@ -0,0 +1,20 @@
package leenkx.logicnode;
class WebviewPageInfoNode extends LogicNode {
public var property0: String;
public function new(tree: LogicTree) {
super(tree);
}
override function get(from: Int): Dynamic {
#if kha_krom
var id: Int = inputs[0].get();
if (property0 == "url") return Krom.webviewGetURL(id);
else return Krom.webviewGetPageTitle(id);
#else
return "";
#end
}
}

View File

@ -0,0 +1,17 @@
package leenkx.logicnode;
class WebviewSendNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
#if kha_krom
var id: Int = inputs[1].get();
var message: String = inputs[2].get();
Krom.webviewSend(id, message);
#end
runOutput(0);
}
}

View File

@ -0,0 +1,33 @@
package leenkx.logicnode;
class WebviewSetDimensionsNode extends LogicNode {
public var property0: String;
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
#if kha_krom
var id: Int = inputs[1].get();
switch (property0) {
case "resize":
var width: Int = inputs[2].get();
var height: Int = inputs[3].get();
Krom.webviewResize(id, width, height);
case "move":
var x: Int = inputs[2].get();
var y: Int = inputs[3].get();
Krom.webviewMove(id, x, y);
case "set_bounds":
var x: Int = inputs[2].get();
var y: Int = inputs[3].get();
var width: Int = inputs[4].get();
var height: Int = inputs[5].get();
Krom.webviewSetBounds(id, x, y, width, height);
}
#end
runOutput(0);
}
}

View File

@ -0,0 +1,28 @@
package leenkx.logicnode;
class WebviewSetPropertyNode extends LogicNode {
public var property0: String;
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
#if kha_krom
var id: Int = inputs[1].get();
switch (property0) {
case "transparent":
var value: Bool = inputs[2].get();
Krom.webviewSetTransparent(id, value);
case "click_through":
var value: Bool = inputs[2].get();
Krom.webviewSetClickThrough(id, value);
case "title":
var value: String = inputs[2].get();
Krom.webviewSetTitle(id, value);
}
#end
runOutput(0);
}
}

View File

@ -0,0 +1,23 @@
package leenkx.logicnode;
class WebviewVisibilityNode extends LogicNode {
public var property0: String;
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
#if kha_krom
var id: Int = inputs[1].get();
if (property0 == "show") {
Krom.webviewShow(id);
}
else {
Krom.webviewHide(id);
}
#end
runOutput(0);
}
}

View File

@ -0,0 +1,22 @@
package leenkx.logicnode;
class WebviewWindowActionsNode extends LogicNode {
public var property0: String;
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
#if kha_krom
var id: Int = inputs[1].get();
switch (property0) {
case "minimize": Krom.webviewMinimize(id);
case "maximize": Krom.webviewMaximize(id);
case "restore": Krom.webviewRestore(id);
}
#end
runOutput(0);
}
}

View File

@ -62,22 +62,25 @@ abstract class Downsampler {
private class DownsamplerFragment extends Downsampler {
var rtName: String;
public function new(path: RenderPath, shaderPassHandle: String, rtName: String, maxNumMips: Int) {
super(path, shaderPassHandle, maxNumMips);
this.rtName = rtName;
path.loadShader(shaderPassHandle);
}
var prevScale = 1.0;
for (i in 0...maxNumMips) {
function ensureMipTarget(i: Int): RenderTarget {
if (mipmaps[i] == null) {
var t = new RenderTargetRaw();
t.name = rtName + "_mip_" + i;
t.width = 0;
t.height = 0;
t.scale = (prevScale *= 0.5);
t.scale = Math.pow(0.5, i + 1);
t.format = Inc.getHdrFormat();
mipmaps[i] = path.createRenderTarget(t);
}
path.loadShader(shaderPassHandle);
return mipmaps[i];
}
public function dispatch(srcImageName: String, numMips: Int = 0) {
@ -95,9 +98,10 @@ private class DownsamplerFragment extends Downsampler {
Downsampler.numMipLevels = numMips;
for (i in 0...numMips) {
Downsampler.currentMipLevel = i;
path.setTarget(mipmaps[i].raw.name);
var mip = ensureMipTarget(i);
path.setTarget(mip.raw.name);
path.clearTarget();
path.bindTarget(i == 0 ? srcImageName : mipmaps[i - 1].raw.name, "tex");
path.bindTarget(i == 0 ? srcImageName : ensureMipTarget(i - 1).raw.name, "tex");
path.drawShader(shaderPassHandle);
}
}

View File

@ -2,6 +2,7 @@ package leenkx.renderpath;
import iron.RenderPath;
import iron.object.LightObject;
import iron.object.Clipmap;
import leenkx.math.Helper;
import kha.arrays.Float32Array;
@ -22,6 +23,7 @@ class Inc {
#end
#if (rp_voxels != "Off")
static var voxelClipmapsArray:Float32Array = null;
static var voxel_sh0:kha.graphics4.ComputeShader = null;
static var voxel_sh1:kha.graphics4.ComputeShader = null;
static var voxel_ta0:kha.graphics4.TextureUnit;
@ -37,7 +39,6 @@ class Inc {
#if (rp_voxels == "Voxel GI")
static var voxel_td1:kha.graphics4.TextureUnit;
static var voxel_te1:kha.graphics4.TextureUnit;
static var voxel_tf1:kha.graphics4.TextureUnit;
static var voxel_cc1:kha.graphics4.ConstantLocation;
#else
#if lnx_voxelgi_shadows
@ -92,31 +93,6 @@ class Inc {
static var voxel_cc4:kha.graphics4.ConstantLocation;
static var voxel_cd4:kha.graphics4.ConstantLocation;
static var voxel_sh5:kha.graphics4.ComputeShader = null;
static var voxel_ta5:kha.graphics4.TextureUnit;
static var voxel_te5:kha.graphics4.TextureUnit;
static var voxel_tf5:kha.graphics4.TextureUnit;
static var voxel_tg5:kha.graphics4.TextureUnit;
static var voxel_ca5:kha.graphics4.ConstantLocation;
static var voxel_cb5:kha.graphics4.ConstantLocation;
static var voxel_cc5:kha.graphics4.ConstantLocation;
static var voxel_cd5:kha.graphics4.ConstantLocation;
static var voxel_ce5:kha.graphics4.ConstantLocation;
static var voxel_cf5:kha.graphics4.ConstantLocation;
static var voxel_cg5:kha.graphics4.ConstantLocation;
#if rp_shadowmap
static var voxel_tb5:kha.graphics4.TextureUnit;
static var voxel_tc5:kha.graphics4.TextureUnit;
static var voxel_td5:kha.graphics4.TextureUnit;
static var voxel_ch5:kha.graphics4.ConstantLocation;
static var voxel_ci5:kha.graphics4.ConstantLocation;
static var voxel_cj5:kha.graphics4.ConstantLocation;
static var voxel_ck5:kha.graphics4.ConstantLocation;
#if lnx_shadowmap_atlas
static var voxel_cl5:kha.graphics4.ConstantLocation;
static var voxel_cm5:kha.graphics4.ConstantLocation;
#end
#end
#end //rp_voxels == "Voxel GI"
#end //rp_voxels != "Off"
@ -617,9 +593,6 @@ class Inc {
// Init voxels
#if (rp_voxels != 'Off')
initGI();
#if (rp_voxels == "Voxel GI")
initGI("voxelsLight");
#end
#end
#end // lnx_config
}
@ -740,12 +713,6 @@ class Inc {
t.height = res * Main.voxelgiClipmapCount;
t.depth = res;
}
else if (t.name == "voxelsLight") {
t.format = "R32UI";
t.width = res;
t.height = res * Main.voxelgiClipmapCount;
t.depth = res * 3; // Store R, G, B in separate z-slices
}
else if (t.name == "voxelsOut" || t.name == "voxelsOutB") {
#if (rp_voxels == "Voxel AO")
t.format = "R8";
@ -877,8 +844,7 @@ class Inc {
#if (rp_voxels == "Voxel GI")
voxel_td1 = voxel_sh1.getTextureUnit("voxelsSampler");
voxel_te1 = voxel_sh1.getTextureUnit("voxelsLight");
voxel_tf1 = voxel_sh1.getTextureUnit("SDF");
voxel_te1 = voxel_sh1.getTextureUnit("SDF");
voxel_cc1 = voxel_sh1.getConstantLocation("envmapStrength");
#else
#if lnx_voxelgi_shadows
@ -951,38 +917,27 @@ class Inc {
voxel_cc4 = voxel_sh4.getConstantLocation("eye");
voxel_cd4 = voxel_sh4.getConstantLocation("postprocess_resolution");
}
if (voxel_sh5 == null)
{
voxel_sh5 = path.getComputeShader("voxel_light");
voxel_ta5 = voxel_sh5.getTextureUnit("voxelsLight");
voxel_te5 = voxel_sh5.getTextureUnit("voxels");
voxel_tf5 = voxel_sh5.getTextureUnit("voxelsSampler");
voxel_tg5 = voxel_sh5.getTextureUnit("voxelsSDFSampler");
#end
}
voxel_ca5 = voxel_sh5.getConstantLocation("clipmaps");
voxel_cb5 = voxel_sh5.getConstantLocation("clipmapLevel");
voxel_cc5 = voxel_sh5.getConstantLocation("lightPos");
voxel_cd5 = voxel_sh5.getConstantLocation("lightColor");
voxel_ce5 = voxel_sh5.getConstantLocation("lightType");
voxel_cf5 = voxel_sh5.getConstantLocation("lightDir");
voxel_cg5 = voxel_sh5.getConstantLocation("spotData");
#if rp_shadowmap
voxel_tb5 = voxel_sh5.getTextureUnit("shadowMap");
voxel_tc5 = voxel_sh5.getTextureUnit("shadowMapSpot");
voxel_td5 = voxel_sh5.getTextureUnit("shadowMapPoint");
voxel_ch5 = voxel_sh5.getConstantLocation("lightShadow");
voxel_ci5 = voxel_sh5.getConstantLocation("lightProj");
voxel_cj5 = voxel_sh5.getConstantLocation("LVP");
voxel_ck5 = voxel_sh5.getConstantLocation("shadowsBias");
#if lnx_shadowmap_atlas
voxel_cl5 = voxel_sh5.getConstantLocation("index");
voxel_cm5 = voxel_sh5.getConstantLocation("pointLightDataArray");
#end
#end
public static function fillVoxelClipmapsArray(clipmaps: Array<Clipmap>): Float32Array {
if (voxelClipmapsArray == null || voxelClipmapsArray.length != Main.voxelgiClipmapCount * 10) {
voxelClipmapsArray = new Float32Array(Main.voxelgiClipmapCount * 10);
}
#end
var fa = voxelClipmapsArray;
for (i in 0...Main.voxelgiClipmapCount) {
fa[i * 10] = clipmaps[i].voxelSize;
fa[i * 10 + 1] = clipmaps[i].extents.x;
fa[i * 10 + 2] = clipmaps[i].extents.y;
fa[i * 10 + 3] = clipmaps[i].extents.z;
fa[i * 10 + 4] = clipmaps[i].center.x;
fa[i * 10 + 5] = clipmaps[i].center.y;
fa[i * 10 + 6] = clipmaps[i].center.z;
fa[i * 10 + 7] = clipmaps[i].offset_prev.x;
fa[i * 10 + 8] = clipmaps[i].offset_prev.y;
fa[i * 10 + 9] = clipmaps[i].offset_prev.z;
}
return fa;
}
public static function computeVoxelsOffsetPrev(g: kha.graphics4.Graphics) {
@ -996,19 +951,7 @@ class Inc {
g.setImageTexture(voxel_ta0, rts.get("voxelsOut").image);
g.setImageTexture(voxel_tb0, rts.get("voxelsOutB").image);
var fa:Float32Array = new Float32Array(Main.voxelgiClipmapCount * 10);
for (i in 0...Main.voxelgiClipmapCount) {
fa[i * 10] = clipmaps[i].voxelSize;
fa[i * 10 + 1] = clipmaps[i].extents.x;
fa[i * 10 + 2] = clipmaps[i].extents.y;
fa[i * 10 + 3] = clipmaps[i].extents.z;
fa[i * 10 + 4] = clipmaps[i].center.x;
fa[i * 10 + 5] = clipmaps[i].center.y;
fa[i * 10 + 6] = clipmaps[i].center.z;
fa[i * 10 + 7] = clipmaps[i].offset_prev.x;
fa[i * 10 + 8] = clipmaps[i].offset_prev.y;
fa[i * 10 + 9] = clipmaps[i].offset_prev.z;
}
var fa = fillVoxelClipmapsArray(clipmaps);
g.setFloats(voxel_ca0, fa);
@ -1029,7 +972,6 @@ class Inc {
if (rts.get("voxelsOut") == null) initGI("voxelsOut");
if (rts.get("voxelsOutB") == null) initGI("voxelsOutB");
#if (rp_voxels == "Voxel GI")
if (rts.get("voxelsLight") == null) initGI("voxelsLight");
if (rts.get("voxelsSDF") == null) initGI("voxelsSDF");
#elseif lnx_voxelgi_shadows
if (rts.get("voxelsSDF") == null) initGI("voxelsSDF");
@ -1045,8 +987,7 @@ class Inc {
g.setImageTexture(voxel_tc1, rts.get("voxelsOut").image);
#if (rp_voxels == "Voxel GI")
g.setTexture(voxel_td1, rts.get("voxelsOutB").image);
g.setImageTexture(voxel_te1, rts.get("voxelsLight").image);
g.setImageTexture(voxel_tf1, rts.get("voxelsSDF").image);
g.setImageTexture(voxel_te1, rts.get("voxelsSDF").image);
g.setFloat(voxel_cc1, iron.Scene.active.world == null ? 0.0 : iron.Scene.active.world.probe.raw.strength);
#else
#if lnx_voxelgi_shadows
@ -1054,19 +995,7 @@ class Inc {
#end
#end
var fa:Float32Array = new Float32Array(Main.voxelgiClipmapCount * 10);
for (i in 0...Main.voxelgiClipmapCount) {
fa[i * 10] = clipmaps[i].voxelSize;
fa[i * 10 + 1] = clipmaps[i].extents.x;
fa[i * 10 + 2] = clipmaps[i].extents.y;
fa[i * 10 + 3] = clipmaps[i].extents.z;
fa[i * 10 + 4] = clipmaps[i].center.x;
fa[i * 10 + 5] = clipmaps[i].center.y;
fa[i * 10 + 6] = clipmaps[i].center.z;
fa[i * 10 + 7] = clipmaps[i].offset_prev.x;
fa[i * 10 + 8] = clipmaps[i].offset_prev.y;
fa[i * 10 + 9] = clipmaps[i].offset_prev.z;
}
var fa = fillVoxelClipmapsArray(clipmaps);
g.setFloats(voxel_ca1, fa);
@ -1084,6 +1013,8 @@ class Inc {
var clipmaps = iron.RenderPath.clipmaps;
var clipmap = clipmaps[iron.RenderPath.clipmapLevel];
if (rts.get("voxelsSDF") == null || rts.get("voxelsSDFtmp") == null) return;
var read_sdf = "voxelsSDF";
var write_sdf = "voxelsSDFtmp";
@ -1095,19 +1026,7 @@ class Inc {
g.setImageTexture(voxel_ta2, rts.get(read_sdf).image);
g.setImageTexture(voxel_tb2, rts.get(write_sdf).image);
var fa:Float32Array = new Float32Array(Main.voxelgiClipmapCount * 10);
for (i in 0...Main.voxelgiClipmapCount) {
fa[i * 10] = clipmaps[i].voxelSize;
fa[i * 10 + 1] = clipmaps[i].extents.x;
fa[i * 10 + 2] = clipmaps[i].extents.y;
fa[i * 10 + 3] = clipmaps[i].extents.z;
fa[i * 10 + 4] = clipmaps[i].center.x;
fa[i * 10 + 5] = clipmaps[i].center.y;
fa[i * 10 + 6] = clipmaps[i].center.z;
fa[i * 10 + 7] = clipmaps[i].offset_prev.x;
fa[i * 10 + 8] = clipmaps[i].offset_prev.y;
fa[i * 10 + 9] = clipmaps[i].offset_prev.z;
}
var fa = fillVoxelClipmapsArray(clipmaps);
g.setFloats(voxel_ca2, fa);
@ -1124,6 +1043,18 @@ class Inc {
write_sdf = write_sdf == "voxelsSDF" ? "voxelsSDFtmp" : "voxelsSDF";
}
}
// If passcount is odd, result is in voxelsSDFtmp but consumers read voxelsSDF
if (passcount % 2 != 0) {
g.setComputeShader(voxel_sh2);
g.setImageTexture(voxel_ta2, rts.get("voxelsSDFtmp").image);
g.setImageTexture(voxel_tb2, rts.get("voxelsSDF").image);
fillVoxelClipmapsArray(clipmaps);
g.setFloats(voxel_ca2, voxelClipmapsArray);
g.setInt(voxel_cb2, iron.RenderPath.clipmapLevel);
g.setFloat(voxel_cc2, 1.0);
g.compute(Std.int(res / 8), Std.int(res / 8), Std.int(res / 8));
}
}
#end
@ -1153,19 +1084,7 @@ class Inc {
g.setTexture(voxel_th3, iron.Scene.active.world.probe.radiance);
#end
var fa:Float32Array = new Float32Array(Main.voxelgiClipmapCount * 10);
for (i in 0...Main.voxelgiClipmapCount) {
fa[i * 10] = clipmaps[i].voxelSize;
fa[i * 10 + 1] = clipmaps[i].extents.x;
fa[i * 10 + 2] = clipmaps[i].extents.y;
fa[i * 10 + 3] = clipmaps[i].extents.z;
fa[i * 10 + 4] = clipmaps[i].center.x;
fa[i * 10 + 5] = clipmaps[i].center.y;
fa[i * 10 + 6] = clipmaps[i].center.z;
fa[i * 10 + 7] = clipmaps[i].offset_prev.x;
fa[i * 10 + 8] = clipmaps[i].offset_prev.y;
fa[i * 10 + 9] = clipmaps[i].offset_prev.z;
}
var fa = fillVoxelClipmapsArray(clipmaps);
g.setFloats(voxel_ca3, fa);
@ -1248,19 +1167,7 @@ class Inc {
g.setTexture(voxel_th3, iron.Scene.active.world.probe.radiance);
#end
var fa:Float32Array = new Float32Array(Main.voxelgiClipmapCount * 10);
for (i in 0...Main.voxelgiClipmapCount) {
fa[i * 10] = clipmaps[i].voxelSize;
fa[i * 10 + 1] = clipmaps[i].extents.x;
fa[i * 10 + 2] = clipmaps[i].extents.y;
fa[i * 10 + 3] = clipmaps[i].extents.z;
fa[i * 10 + 4] = clipmaps[i].center.x;
fa[i * 10 + 5] = clipmaps[i].center.y;
fa[i * 10 + 6] = clipmaps[i].center.z;
fa[i * 10 + 7] = clipmaps[i].offset_prev.x;
fa[i * 10 + 8] = clipmaps[i].offset_prev.y;
fa[i * 10 + 9] = clipmaps[i].offset_prev.z;
}
var fa = fillVoxelClipmapsArray(clipmaps);
g.setFloats(voxel_ca3, fa);
@ -1338,19 +1245,7 @@ class Inc {
g.setTexture(voxel_tf4, rts.get("gbuffer2").image);
#end
var fa:Float32Array = new Float32Array(Main.voxelgiClipmapCount * 10);
for (i in 0...Main.voxelgiClipmapCount) {
fa[i * 10] = clipmaps[i].voxelSize;
fa[i * 10 + 1] = clipmaps[i].extents.x;
fa[i * 10 + 2] = clipmaps[i].extents.y;
fa[i * 10 + 3] = clipmaps[i].extents.z;
fa[i * 10 + 4] = clipmaps[i].center.x;
fa[i * 10 + 5] = clipmaps[i].center.y;
fa[i * 10 + 6] = clipmaps[i].center.z;
fa[i * 10 + 7] = clipmaps[i].offset_prev.x;
fa[i * 10 + 8] = clipmaps[i].offset_prev.y;
fa[i * 10 + 9] = clipmaps[i].offset_prev.z;
}
var fa = fillVoxelClipmapsArray(clipmaps);
g.setFloats(voxel_ca4, fa);
@ -1385,147 +1280,6 @@ class Inc {
}
#if (rp_voxels == "Voxel GI")
public static function computeVoxelsLight(g: kha.graphics4.Graphics) {
var rts = path.renderTargets;
var res = iron.RenderPath.getVoxelRes();
var camera = iron.Scene.active.camera;
var clipmaps = iron.RenderPath.clipmaps;
var clipmap = clipmaps[iron.RenderPath.clipmapLevel];
var lights = iron.Scene.active.lights;
var lightIndex = 0;
for (i in 0...lights.length) {
var l = lights[i];
if (!l.visible) continue;
path.light = l;
g.setComputeShader(voxel_sh5);
g.setImageTexture(voxel_ta5, rts.get("voxelsLight").image);
g.setImageTexture(voxel_te5, rts.get("voxels").image);
g.setTexture(voxel_tf5, rts.get("voxelsOut").image);
g.setTexture(voxel_tg5, rts.get("voxelsSDF").image);
var fa:Float32Array = new Float32Array(Main.voxelgiClipmapCount * 10);
for (i in 0...Main.voxelgiClipmapCount) {
fa[i * 10] = clipmaps[i].voxelSize;
fa[i * 10 + 1] = clipmaps[i].extents.x;
fa[i * 10 + 2] = clipmaps[i].extents.y;
fa[i * 10 + 3] = clipmaps[i].extents.z;
fa[i * 10 + 4] = clipmaps[i].center.x;
fa[i * 10 + 5] = clipmaps[i].center.y;
fa[i * 10 + 6] = clipmaps[i].center.z;
fa[i * 10 + 7] = clipmaps[i].offset_prev.x;
fa[i * 10 + 8] = clipmaps[i].offset_prev.y;
fa[i * 10 + 9] = clipmaps[i].offset_prev.z;
}
g.setFloats(voxel_ca5, fa);
g.setInt(voxel_cb5, iron.RenderPath.clipmapLevel);
#if rp_shadowmap
if (l.data.raw.type == "sun") {
#if lnx_shadowmap_atlas
#if lnx_shadowmap_atlas_single_map
g.setTexture(voxel_tb5, rts.get("shadowMapAtlas").image);
#else
g.setTexture(voxel_tb5, rts.get("shadowMapAtlasSun").image);
#end
#else
g.setTexture(voxel_tb5, rts.get("shadowMap").image);
#end
g.setInt(voxel_ch5, 1); // lightShadow
}
else if (l.data.raw.type == "spot" || l.data.raw.type == "area") {
#if lnx_shadowmap_atlas
#if lnx_shadowmap_atlas_single_map
g.setTexture(voxel_tc5, rts.get("shadowMapAtlas").image);
#else
g.setTexture(voxel_tc5, rts.get("shadowMapAtlasSpot").image);
#end
#else
g.setTexture(voxel_tc5, rts.get("shadowMapSpot[" + lightIndex + "]").image);
#end
g.setInt(voxel_ch5, 2);
}
else {
#if lnx_shadowmap_atlas
#if lnx_shadowmap_atlas_single_map
g.setTexture(voxel_td5, rts.get("shadowMapAtlas").image);
#else
g.setTexture(voxel_td5, rts.get("shadowMapAtlasPoint").image);
g.setInt(voxel_cl5, i);
g.setFloats(voxel_cm5, iron.object.LightObject.pointLightsData);
#end
#else
g.setCubeMap(voxel_td5, rts.get("shadowMapPoint[" + lightIndex + "]").cubeMap);
#end
g.setInt(voxel_ch5, 3);
}
// lightProj
var near = l.data.raw.near_plane;
var far = l.data.raw.far_plane;
var a:kha.FastFloat = far + near;
var b:kha.FastFloat = far - near;
var f2:kha.FastFloat = 2.0;
var c:kha.FastFloat = f2 * far * near;
var vx:kha.FastFloat = a / b;
var vy:kha.FastFloat = c / b;
g.setFloat2(voxel_ci5, vx, vy);
// LVP
m.setFrom(l.VP);
m.multmat(iron.object.Uniforms.biasMat);
#if lnx_shadowmap_atlas
if (l.data.raw.type == "sun")
{
// tile matrix
m.setIdentity();
// scale [0-1] coords to [0-tilescale]
m._00 = l.tileScale[0];
m._11 = l.tileScale[0];
// offset coordinate start from [0, 0] to [tile-start-x, tile-start-y]
m._30 = l.tileOffsetX[0];
m._31 = l.tileOffsetY[0];
m.multmat(m);
#if (!kha_opengl)
m.setIdentity();
m._11 = -1.0;
m._31 = 1.0;
m.multmat(m);
#end
}
#end
g.setMatrix(voxel_cj5, m.self);
// shadowsBias
g.setFloat(voxel_ck5, l.data.raw.shadows_bias);
#end // rp_shadowmap
// lightPos
g.setFloat3(voxel_cc5, l.transform.worldx(), l.transform.worldy(), l.transform.worldz());
// lightCol
var f = l.data.raw.strength;
g.setFloat3(voxel_cd5, l.data.raw.color[0] * f, l.data.raw.color[1] * f, l.data.raw.color[2] * f);
// lightType
g.setInt(voxel_ce5, iron.data.LightData.typeToInt(l.data.raw.type));
// lightDir
var v = l.look();
g.setFloat3(voxel_cf5, v.x, v.y, v.z);
// spotData
if (l.data.raw.type == "spot") {
var vx = l.data.raw.spot_size;
var vy = vx - l.data.raw.spot_blend;
g.setFloat2(voxel_cg5, vx, vy);
}
g.compute(Std.int(res / 8), Std.int(res / 8), Std.int(res / 8));
if (!iron.object.LightObject.discardLightCulled(l)) {
lightIndex++;
}
}
}
#end // GI
#end // Voxels
}
@ -1881,7 +1635,8 @@ class ShadowMapTile {
if (size < oldTile.size) {
oldTile.forEachTileLinked(function(lTile) {
var childTile = findFreeChildTile(lTile, size);
tilesFound.push(childTile);
if (childTile != null)
tilesFound.push(childTile);
});
}
// reuse parent tiles
@ -1910,6 +1665,7 @@ class ShadowMapTile {
while (size < childrenTile.size) {
childrenTile = childrenTile.tiles[0];
}
if (childrenTile.light != null) return null;
return childrenTile;
}

View File

@ -1,674 +0,0 @@
package leenkx.renderpath;
import kha.FastFloat;
import kha.arrays.Float32Array;
import kha.graphics4.TextureFormat;
import kha.graphics4.Usage;
import iron.data.WorldData;
import iron.math.Vec2;
import iron.math.Vec3;
import iron.math.Vec4;
import leenkx.math.Helper;
/**
Utility class to control the sky models (single and multiple scattering).
**/
class Sky {
public static var singleScatterData: SingleScatteringData = null;
public static var multiScatterData: MultipleScatteringData = null;
/**
Recomputes the single scattering lookup table after the density settings changed.
Do not call this method on every frame (it's slow)!
**/
public static function recomputeSingleScatter(world: WorldData) {
if (world == null || world.raw.sky_density == null) return;
if (singleScatterData == null) singleScatterData = new SingleScatteringData();
var density = world.raw.sky_density;
singleScatterData.computeLUT(new Vec3(density[0], density[1], density[2]));
}
/** Sets the sky's density parameters and calls `recomputeSingleScatter()` afterwards. **/
public static function setDensity(world: WorldData, densityAir: FastFloat, densityDust: FastFloat, densityOzone: FastFloat) {
if (world == null) return;
if (world.raw.sky_density == null) world.raw.sky_density = new Float32Array(3);
var density = world.raw.sky_density;
density[0] = Helper.clamp(densityAir, 0, 10);
density[1] = Helper.clamp(densityDust, 0, 10);
density[2] = Helper.clamp(densityOzone, 0, 10);
recomputeSingleScatter(world);
}
public static function recomputeMultiScatter(world: WorldData) {
if (world == null) return;
var raw = world.raw;
if (raw.sky_density == null) return;
if (multiScatterData == null) multiScatterData = new MultipleScatteringData();
var density = raw.sky_density;
var sunElevation = raw.sky_sun_elevation != null ? raw.sky_sun_elevation : 0.0;
var sunRotation = raw.sky_sun_rotation != null ? raw.sky_sun_rotation : 0.0;
var sunSize = raw.sky_sun_size != null ? raw.sky_sun_size : 0.545;
var sunIntensity = raw.sky_sun_intensity != null ? raw.sky_sun_intensity : 1.0;
var altitude = raw.sky_altitude != null ? raw.sky_altitude : 0.0;
var sunDisc = raw.sky_sun_disc != null ? raw.sky_sun_disc : 1;
multiScatterData.compute(
sunElevation,
sunRotation,
sunSize,
sunIntensity,
altitude,
sunDisc != 0,
new Vec3(density[0], density[1], density[2])
);
}
public static function setMultiScatterParams(world: WorldData, sunElevation: FastFloat, sunRotation: FastFloat,
sunSize: FastFloat, sunIntensity: FastFloat, altitude: FastFloat, sunDisc: Bool,
densityAir: FastFloat, densityDust: FastFloat, densityOzone: FastFloat) {
if (world == null) return;
if (world.raw.sky_density == null) world.raw.sky_density = new Float32Array(3);
var density = world.raw.sky_density;
density[0] = Helper.clamp(densityAir, 0, 10);
density[1] = Helper.clamp(densityDust, 0, 10);
density[2] = Helper.clamp(densityOzone, 0, 10);
world.raw.sky_sun_elevation = sunElevation;
world.raw.sky_sun_rotation = sunRotation;
world.raw.sky_sun_size = sunSize;
world.raw.sky_sun_intensity = sunIntensity;
world.raw.sky_altitude = altitude;
world.raw.sky_sun_disc = sunDisc ? 1 : 0;
recomputeMultiScatter(world);
}
}
/**
This class holds the precalculated result of the inner scattering integral
of the single scattering sky model. The outer integral is calculated in
[`leenkx/Shaders/std/sky.glsl`](https://github.com/leenkx3d/leenkx/blob/master/Shaders/std/sky.glsl).
@see `leenkx.renderpath.Sky`
**/
class SingleScatteringData {
public var lut: kha.Image;
/**
The amount of individual sample heights stored in the LUT (and the width
of the LUT image).
**/
public static var lutHeightSteps = 128;
/**
The amount of individual sun angle steps stored in the LUT (and the
height of the LUT image).
**/
public static var lutAngleSteps = 128;
/**
Amount of steps for calculating the inner scattering integral. Heigher
values are more precise but take longer to compute.
**/
public static var jSteps = 8;
/** Radius of the atmosphere in kilometers. **/
public static var radiusAtmo = 6420.0;
/**
Radius of the planet in kilometers. The default value is the earth radius as
defined in Cycles.
**/
public static var radiusPlanet = 6360.0;
/** Rayleigh scattering coefficient. **/
public static var rayleighCoeff = new Vec3(5.5e-6, 13.0e-6, 22.4e-6);
/** Rayleigh scattering scale parameter. **/
public static var rayleighScale = 8e3;
/** Mie scattering coefficient. **/
public static var mieCoeff = 2e-5;
/** Mie scattering scale parameter. **/
public static var mieScale = 1.2e3;
/** Ozone scattering coefficient. **/
// The ozone absorption coefficients are taken from Cycles code.
// Because Cycles calculates 21 wavelengths, we use the coefficients
// which are closest to the RGB wavelengths (645nm, 510nm, 440nm).
// Precalculating values by simulating Blender's spec_to_xyz() function
// to include all 21 wavelengths gave unrealistic results.
public static var ozoneCoeff = new Vec3(1.59051840791988e-6, 0.00000096707041180970, 0.00000007309568762914);
public function new() {}
/** Approximates the density of ozone for a given sample height. **/
function getOzoneDensity(height: FastFloat): FastFloat {
// Values are taken from Cycles code
if (height < 10000.0 || height >= 40000.0) {
return 0.0;
}
if (height < 25000.0) {
return (height - 10000.0) / 15000.0;
}
return -((height - 40000.0) / 15000.0);
}
/**
Ray-sphere intersection test that assumes the sphere is centered at the
origin. There is no intersection when result.x > result.y. Otherwise
this function returns the distances to the two intersection points,
which might be equal.
**/
function raySphereIntersection(rayOrigin: Vec3, rayDirection: Vec3, sphereRadius: Float): Vec2 {
// Algorithm is described here: https://en.wikipedia.org/wiki/Line%E2%80%93sphere_intersection
var a = rayDirection.dot(rayDirection);
var b = 2.0 * rayDirection.dot(rayOrigin);
var c = rayOrigin.dot(rayOrigin) - (sphereRadius * sphereRadius);
var d = (b * b) - 4.0 * a * c;
// Ray does not intersect the sphere
if (d < 0.0) return new Vec2(1e5, -1e5);
return new Vec2(
(-b - Math.sqrt(d)) / (2.0 * a),
(-b + Math.sqrt(d)) / (2.0 * a)
);
}
/**
Computes the LUT texture for the given density values.
@param density 3D vector of air density, dust density, ozone density
**/
public function computeLUT(density: Vec3) {
var imageData = new haxe.io.Float32Array(lutHeightSteps * lutAngleSteps * 4);
for (x in 0...lutHeightSteps) {
var height = (x / (lutHeightSteps - 1));
// Use quadratic height for better horizon precision
height *= height;
height *= radiusAtmo * 1000; // Denormalize height
for (y in 0...lutAngleSteps) {
var sunTheta = y / (lutAngleSteps - 1) * 2 - 1;
// Improve horizon precision
// See https://sebh.github.io/publications/egsr2020.pdf (5.3)
sunTheta = Helper.sign(sunTheta) * sunTheta * sunTheta;
sunTheta = sunTheta * Math.PI / 2 + Math.PI / 2; // Denormalize
var jODepth = sampleSecondaryRay(height, sunTheta, density);
var pixelIndex = (x + y * lutHeightSteps) * 4;
imageData[pixelIndex + 0] = jODepth.x;
imageData[pixelIndex + 1] = jODepth.y;
imageData[pixelIndex + 2] = jODepth.z;
imageData[pixelIndex + 3] = 1.0; // Unused
}
}
lut = kha.Image.fromBytes(imageData.view.buffer, lutHeightSteps, lutAngleSteps, TextureFormat.RGBA128, Usage.StaticUsage);
}
/**
Calculates the integral for the secondary ray.
**/
public function sampleSecondaryRay(height: FastFloat, sunTheta: FastFloat, density: Vec3): Vec3 {
var radiusPlanetMeters = radiusPlanet * 1000;
// Reconstruct values from the shader
var iPos = new Vec3(0, 0, height + radiusPlanetMeters);
var pSun = new Vec3(0.0, Math.sin(sunTheta), Math.cos(sunTheta)).normalize();
var jTime: FastFloat = 0.0;
// We compute the ray-sphere intersection in km to allow larger
// atmosphere radii (radius is squared inside raySphereIntersection())
var jStepSize: FastFloat = raySphereIntersection(iPos.clone().mult(0.001), pSun, radiusAtmo).y / jSteps;
jStepSize *= 1000; // convert back to m
// Optical depth accumulators for the secondary ray (Rayleigh, Mie, ozone)
var jODepth = new Vec3();
for (i in 0...jSteps) {
// Calculate the secondary ray sample position and height
var jPos = iPos.clone().add(pSun.clone().mult(jTime + jStepSize * 0.5));
var jHeight = jPos.length() - radiusPlanetMeters;
// Accumulate optical depth
var optDepthRayleigh = Math.exp(-jHeight / rayleighScale) * density.x;
var optDepthMie = Math.exp(-jHeight / mieScale) * density.y;
var optDepthOzone = getOzoneDensity(jHeight) * density.z;
jODepth.addf(optDepthRayleigh, optDepthMie, optDepthOzone);
jTime += jStepSize;
}
jODepth.mult(jStepSize);
// Precalculate a part of the secondary attenuation.
// For one variable (e.g. x) in the vector, the formula is as follows:
//
// attn.x = exp(-(coeffX * (firstOpticalDepth.x + secondOpticalDepth.x)))
//
// We can split that up via:
//
// attn.x = exp(-(coeffX * firstOpticalDepth.x + coeffX * secondOpticalDepth.x))
// = exp(-(coeffX * firstOpticalDepth.x)) * exp(-(coeffX * secondOpticalDepth.x))
//
// The first factor of the resulting multiplication is calculated in the
// shader, but we can already precalculate the second one. As a side
// effect this keeps the range of the LUT values small because we don't
// store the optical depth but the attenuation.
var jAttenuation = new Vec3();
var mie = mieCoeff * jODepth.y;
jAttenuation.addf(mie, mie, mie);
jAttenuation.add(rayleighCoeff.clone().mult(jODepth.x));
jAttenuation.add(ozoneCoeff.clone().mult(jODepth.z));
jAttenuation.exp(jAttenuation.mult(-1));
return jAttenuation;
}
}
/**
This class precomputes the full sky radiance LUT
Unlike the single scattering model matching Blenders implementation.
**/
class MultipleScatteringData {
public var lut: kha.Image;
public var sunBottom: Vec3;
public var sunTop: Vec3;
public var earthIntersectionAngle: FastFloat;
public static var lutWidth = 256;
public static var lutHeight = 128;
static var transmittanceResX = 256;
static var transmittanceResY = 64;
var transmittanceLUT: haxe.io.Float32Array;
static var transmittanceSteps = 64;
static var inScatteringSteps = 64;
// Atmosphere constants sky_multiple_scattering.cpp
static var EARTH_RADIUS = 6371.0;
static var ATMOSPHERE_THICKNESS = 100.0;
static var ATMOSPHERE_RADIUS = 6471.0; // EARTH_RADIUS + ATMOSPHERE_THICKNESS
static var PHASE_ISOTROPIC = 1.0 / (4.0 * Math.PI);
static var RAYLEIGH_PHASE_SCALE = (3.0 / 16.0) * (1.0 / Math.PI);
static var G = 0.8;
static var SQR_G = 0.64;
static var GROUND_ALBEDO: Array<Float> = [0.3, 0.3, 0.3, 0.3];
// Spectral data sampled at 630, 560, 490, 430 nm
static var SUN_SPECTRAL_IRRADIANCE: Array<Float> = [1.679, 1.828, 1.986, 1.307];
static var MOLECULAR_SCATTERING_COEFFICIENT_BASE: Array<Float> = [6.605e-3, 1.067e-2, 1.842e-2, 3.156e-2];
static var OZONE_ABSORPTION_CROSS_SECTION: Array<Float> = [3.472e-25, 3.914e-25, 1.349e-25, 11.03e-27];
static var OZONE_MEAN_DOBSON = 334.5;
static var AEROSOL_ABSORPTION_CROSS_SECTION: Array<Float> = [2.8722e-24, 4.6168e-24, 7.9706e-24, 1.3578e-23];
static var AEROSOL_SCATTERING_CROSS_SECTION: Array<Float> = [1.5908e-22, 1.7711e-22, 2.0942e-22, 2.4033e-22];
static var AEROSOL_BASE_DENSITY = 1.3681e20;
static var AEROSOL_BACKGROUND_DENSITY = 2e6;
static var AEROSOL_HEIGHT_SCALE = 0.73;
static var SPECTRAL_XYZ: Array<Array<Float>> = [
[53.386917738564668023, 22.981337506691024754, 0.0],
[43.904844466369358263, 71.347795700053393866, 0.102506867965741307],
[1.6137278251608962005, 18.422960591455485011, 31.742921188390805758],
[20.762668673810577145, 2.3614213523314368527, 110.48009643252140334]
];
var airDensity: Float = 1.0;
var aerosolDensity: Float = 1.0;
var ozoneDensity: Float = 1.0;
public function new() {
sunBottom = new Vec3();
sunTop = new Vec3();
earthIntersectionAngle = 0.0;
transmittanceLUT = new haxe.io.Float32Array(transmittanceResY * transmittanceResX * 4);
}
static inline function exp4(a: Array<Float>): Array<Float> {
return [Math.exp(a[0]), Math.exp(a[1]), Math.exp(a[2]), Math.exp(a[3])];
}
static inline function scale4(a: Array<Float>, s: Float): Array<Float> {
return [a[0] * s, a[1] * s, a[2] * s, a[3] * s];
}
static inline function add4(a: Array<Float>, b: Array<Float>): Array<Float> {
return [a[0] + b[0], a[1] + b[1], a[2] + b[2], a[3] + b[3]];
}
static inline function mult4(a: Array<Float>, b: Array<Float>): Array<Float> {
return [a[0] * b[0], a[1] * b[1], a[2] * b[2], a[3] * b[3]];
}
static inline function div4(a: Array<Float>, b: Array<Float>): Array<Float> {
return [a[0] / b[0], a[1] / b[1], a[2] / b[2], a[3] / b[3]];
}
static inline function max4(a: Array<Float>, b: Float): Array<Float> {
return [Math.max(a[0], b), Math.max(a[1], b), Math.max(a[2], b), Math.max(a[3], b)];
}
static inline function mix4(a: Array<Float>, b: Array<Float>, t: Float): Array<Float> {
return [a[0] + t * (b[0] - a[0]), a[1] + t * (b[1] - a[1]), a[2] + t * (b[2] - a[2]), a[3] + t * (b[3] - a[3])];
}
// Atmospheric functions
function getMolecularScatteringCoefficient(h: Float): Array<Float> {
// MOLECULAR_SCATTERING_COEFFICIENT_BASE * exp(-0.07771971 * h^1.16364243)
var f = Math.exp(-0.07771971 * Math.pow(h, 1.16364243));
return scale4(MOLECULAR_SCATTERING_COEFFICIENT_BASE, f);
}
function getMolecularAbsorptionCoefficient(h: Float): Array<Float> {
var logH = Math.log(Math.max(h, 1e-4));
var density = 3.78547397e20 * Math.exp(-(logH - 3.22261) * (logH - 3.22261) * 5.55555555 - logH);
var result: Array<Float> = [];
for (i in 0...4) {
result[i] = OZONE_ABSORPTION_CROSS_SECTION[i] * OZONE_MEAN_DOBSON * density;
}
return result;
}
function getAerosolDensity(h: Float): Float {
var division = AEROSOL_BACKGROUND_DENSITY / AEROSOL_BASE_DENSITY;
return AEROSOL_BASE_DENSITY * (Math.exp(-h / AEROSOL_HEIGHT_SCALE) + division);
}
function getAtmosphereCollisionCoefficients(h: Float): {
aerosolAbs: Array<Float>, aerosolSca: Array<Float>, molAbs: Array<Float>, molSca: Array<Float>
} {
var localAerosolDensity = getAerosolDensity(h) * aerosolDensity;
var aerosolAbs = scale4(AEROSOL_ABSORPTION_CROSS_SECTION, localAerosolDensity);
var aerosolSca = scale4(AEROSOL_SCATTERING_CROSS_SECTION, localAerosolDensity);
var molAbs = scale4(getMolecularAbsorptionCoefficient(h), ozoneDensity);
var molSca = scale4(getMolecularScatteringCoefficient(h), airDensity);
return { aerosolAbs: aerosolAbs, aerosolSca: aerosolSca, molAbs: molAbs, molSca: molSca };
}
function molecularPhaseFunction(cosTheta: Float): Float {
return RAYLEIGH_PHASE_SCALE * (1.0 + cosTheta * cosTheta);
}
function aerosolPhaseFunction(cosTheta: Float): Float {
var den = 1.0 + SQR_G + 2.0 * G * cosTheta;
return PHASE_ISOTROPIC * (1.0 - SQR_G) / (den * Math.sqrt(den));
}
// nearest positive intersection distance, or -1 if no intersection
function raySphereIntersection(pos: Vec3, dir: Vec3, radius: Float): Float {
var b = pos.dot(dir);
var c = pos.dot(pos) - radius * radius;
if (c > 0.0 && b > 0.0) return -1.0;
var d = b * b - c;
if (d < 0.0) return -1.0;
if (d >= b * b) return -b + Math.sqrt(d);
return -b - Math.sqrt(d);
}
function sunDirection(cosTheta: Float): Vec3 {
// Place sun at azimuth=0 in the atan(dir.x, dir.y) convention
// Blender uses (-sqrt(1-cos²θ), 0, cosθ) but our azimuth convention
// requires (0, sqrt(1-cos²θ), cosθ) for the sun to be at azimuth=0
return new Vec3(0.0, Math.sqrt(1.0 - cosTheta * cosTheta), cosTheta);
}
function spectralToXYZ(L: Array<Float>): Vec3 {
var x = 0.0, y = 0.0, z = 0.0;
for (i in 0...4) {
x += SPECTRAL_XYZ[i][0] * L[i];
y += SPECTRAL_XYZ[i][1] * L[i];
z += SPECTRAL_XYZ[i][2] * L[i];
}
return new Vec3(x, y, z);
}
function getTransmittance(cosTheta: Float, normalizedAltitude: Float): Array<Float> {
var sunDir = sunDirection(cosTheta);
var distToCenter = EARTH_RADIUS + (ATMOSPHERE_RADIUS - EARTH_RADIUS) * normalizedAltitude;
var rayOrigin = new Vec3(0.0, 0.0, distToCenter);
var tD = raySphereIntersection(rayOrigin, sunDir, ATMOSPHERE_RADIUS);
var tStep = tD / transmittanceSteps;
var result: Array<Float> = [0.0, 0.0, 0.0, 0.0];
for (step in 0...transmittanceSteps) {
var t = (step + 0.5) * tStep;
var xT = rayOrigin.clone().add(sunDir.clone().mult(t));
var altitude = Math.max(xT.length() - EARTH_RADIUS, 0.0);
var coeffs = getAtmosphereCollisionCoefficients(altitude);
var extinction = add4(add4(coeffs.aerosolAbs, coeffs.aerosolSca), add4(coeffs.molAbs, coeffs.molSca));
result = add4(result, scale4(extinction, tStep));
}
return exp4(scale4(result, -1.0));
}
function precomputeTransmittanceLUT() {
for (y in 0...transmittanceResY) {
for (x in 0...transmittanceResX) {
var u = x / (transmittanceResX - 1);
var v = y / (transmittanceResY - 1);
var t = getTransmittance(u * 2.0 - 1.0, v);
var idx = (y * transmittanceResX + x) * 4;
transmittanceLUT[idx] = t[0];
transmittanceLUT[idx + 1] = t[1];
transmittanceLUT[idx + 2] = t[2];
transmittanceLUT[idx + 3] = t[3];
}
}
}
function lookupTransmittance(cosTheta: Float, normalizedAltitude: Float): Array<Float> {
var u = Math.max(0.0, Math.min(1.0, cosTheta * 0.5 + 0.5));
var v = Math.max(0.0, Math.min(1.0, normalizedAltitude));
var fx = u * (transmittanceResX - 1);
var fy = v * (transmittanceResY - 1);
var x1 = Std.int(fx);
var y1 = Std.int(fy);
var x2 = Std.int(Math.min(x1 + 1, transmittanceResX - 1));
var y2 = Std.int(Math.min(y1 + 1, transmittanceResY - 1));
var dxF = fx - x1;
var dyF = fy - y1;
function getPixel(px: Int, py: Int): Array<Float> {
var idx = (py * transmittanceResX + px) * 4;
return [transmittanceLUT[idx], transmittanceLUT[idx + 1], transmittanceLUT[idx + 2], transmittanceLUT[idx + 3]];
}
var bottom = mix4(getPixel(x1, y1), getPixel(x2, y1), dxF);
var top = mix4(getPixel(x1, y2), getPixel(x2, y2), dxF);
return mix4(bottom, top, dyF);
}
function lookupTransmittanceAtGround(cosTheta: Float): Array<Float> {
var u = Math.max(0.0, Math.min(1.0, cosTheta * 0.5 + 0.5));
var fx = u * (transmittanceResX - 1);
var x1 = Std.int(fx);
var x2 = Std.int(Math.min(x1 + 1, transmittanceResX - 1));
var dxF = fx - x1;
var y = 0;
function getPixel(px: Int): Array<Float> {
var idx = (y * transmittanceResX + px) * 4;
return [transmittanceLUT[idx], transmittanceLUT[idx + 1], transmittanceLUT[idx + 2], transmittanceLUT[idx + 3]];
}
return mix4(getPixel(x1), getPixel(x2), dxF);
}
function lookupTransmittanceToSun(normalizedAltitude: Float): Array<Float> {
var v = Math.max(0.0, Math.min(1.0, normalizedAltitude));
var fy = v * (transmittanceResY - 1);
var y1 = Std.int(fy);
var y2 = Std.int(Math.min(y1 + 1, transmittanceResY - 1));
var dyF = fy - y1;
var x = transmittanceResX - 1;
function getPixel(py: Int): Array<Float> {
var idx = (py * transmittanceResX + x) * 4;
return [transmittanceLUT[idx], transmittanceLUT[idx + 1], transmittanceLUT[idx + 2], transmittanceLUT[idx + 3]];
}
return mix4(getPixel(y1), getPixel(y2), dyF);
}
// approximation
function lookupMultiscattering(cosTheta: Float, normalizedHeight: Float, d: Float): Array<Float> {
var omega = 2.0 * Math.PI * (1.0 - Math.sqrt(Math.max(0.0, 1.0 - (EARTH_RADIUS / d) * (EARTH_RADIUS / d))));
var tToGround = lookupTransmittanceAtGround(cosTheta);
var tGroundToSample = div4(lookupTransmittanceToSun(0.0), lookupTransmittanceToSun(normalizedHeight));
// 2nd order scattering from the ground
var lGround = scale4(mult4(mult4(mult4(scale4(GROUND_ALBEDO, 1.0 / Math.PI), tToGround), tGroundToSample), [cosTheta, cosTheta, cosTheta, cosTheta]), PHASE_ISOTROPIC * omega);
// Fit of Earth's multiple scattering from other points in the atmosphere
var msFactor = 1.0 / (1.0 + 5.0 * Math.exp(-17.92 * cosTheta));
var lMs = scale4([0.217, 0.347, 0.594, 1.0], 0.02 * msFactor);
return add4(lMs, lGround);
}
function getInscattering(sunDir: Vec3, rayOrigin: Vec3, rayDir: Vec3, tD: Float): Array<Float> {
var cosTheta = rayDir.clone().mult(-1.0).dot(sunDir);
var molPhase = molecularPhaseFunction(cosTheta);
var aerPhase = aerosolPhaseFunction(cosTheta);
var dt = tD / inScatteringSteps;
var lInscattering: Array<Float> = [0.0, 0.0, 0.0, 0.0];
var transmittance: Array<Float> = [1.0, 1.0, 1.0, 1.0];
for (i in 0...inScatteringSteps) {
var t = (i + 0.5) * dt;
var xT = rayOrigin.clone().add(rayDir.clone().mult(t));
var distToCenter = xT.length();
var zenithDir = xT.clone().mult(1.0 / distToCenter);
var altitude = Math.max(distToCenter - EARTH_RADIUS, 0.0);
var normalizedAltitude = altitude / ATMOSPHERE_THICKNESS;
var sampleCosTheta = zenithDir.dot(sunDir);
var coeffs = getAtmosphereCollisionCoefficients(altitude);
var extinction = add4(add4(coeffs.aerosolAbs, coeffs.aerosolSca), add4(coeffs.molAbs, coeffs.molSca));
var tToSun = lookupTransmittance(sampleCosTheta, normalizedAltitude);
var ms = lookupMultiscattering(sampleCosTheta, normalizedAltitude, distToCenter);
// S = SUN_SPECTRAL_IRRADIANCE * (molSca * (molPhase * tToSun + ms) + aerSca * (aerPhase * tToSun + ms))
var s: Array<Float> = [0.0, 0.0, 0.0, 0.0];
for (w in 0...4) {
var molTerm = coeffs.molSca[w] * (molPhase * tToSun[w] + ms[w]);
var aerTerm = coeffs.aerosolSca[w] * (aerPhase * tToSun[w] + ms[w]);
s[w] = SUN_SPECTRAL_IRRADIANCE[w] * (molTerm + aerTerm);
}
var stepTransmittance = exp4(scale4(extinction, -dt));
var cutExt = max4(extinction, 1e-7);
var sInt: Array<Float> = [];
for (w in 0...4) {
sInt[w] = (s[w] - s[w] * stepTransmittance[w]) / cutExt[w];
}
lInscattering = add4(lInscattering, mult4(transmittance, sInt));
transmittance = mult4(transmittance, stepTransmittance);
}
return lInscattering;
}
function computeEarthAngle(altitude: Float): Float {
return Math.PI / 2.0 - Math.asin(EARTH_RADIUS / (EARTH_RADIUS + altitude / 1000.0));
}
function precomputeSun(sunElevation: Float, angularDiameter: Float, altitude: Float): { bottom: Vec3, top: Vec3 } {
var halfAngular = angularDiameter / 2.0;
var solidAngle = 2.0 * Math.PI * (1.0 - Math.cos(halfAngular));
var normalizedAltitude = altitude / ATMOSPHERE_THICKNESS;
function getSunXYZ(elevation: Float): Vec3 {
var sunZenithCosAngle = Math.cos(Math.PI / 2.0 - elevation);
var tToSun = getTransmittance(sunZenithCosAngle, normalizedAltitude);
var spectrum: Array<Float> = [];
for (w in 0...4) {
spectrum[w] = SUN_SPECTRAL_IRRADIANCE[w] * tToSun[w] / solidAngle;
}
return spectralToXYZ(spectrum);
}
var bottom = getSunXYZ(sunElevation - halfAngular);
var top = getSunXYZ(sunElevation + halfAngular);
return { bottom: bottom, top: top };
}
public function compute(sunElevation: Float, sunRotation: Float, sunSize: Float,
sunIntensity: Float, altitude: Float, sunDisc: Bool, density: Vec3) {
airDensity = density.x;
aerosolDensity = density.y;
ozoneDensity = density.z;
precomputeTransmittanceLUT();
var altKm = Math.max(1.0, Math.min(99999.0, altitude)) / 1000.0;
var sunZenithCosAngle = Math.cos(Math.PI / 2.0 - sunElevation);
var sunDir = sunDirection(sunZenithCosAngle);
var rayOrigin = new Vec3(0.0, 0.0, EARTH_RADIUS + altKm);
earthIntersectionAngle = computeEarthAngle(altitude);
var imageData = new haxe.io.Float32Array(lutWidth * lutHeight * 4);
for (y in 0...lutHeight) {
var uvY = (y + 0.5) / lutHeight;
var l = uvY * 2.0 - 1.0;
var elevation = (l < 0 ? -1.0 : 1.0) * l * l * Math.PI / 2.0;
var cosEl = Math.cos(elevation);
var sinEl = Math.sin(elevation);
for (x in 0...lutWidth) {
var uvX = (x + 0.5) / lutWidth;
var azimuth = 2.0 * Math.PI * uvX;
// atan(dir.x, dir.y) convention
var rayDir = new Vec3(
Math.sin(azimuth) * cosEl,
Math.cos(azimuth) * cosEl,
sinEl
).normalize();
var atmosDist = raySphereIntersection(rayOrigin, rayDir, ATMOSPHERE_RADIUS);
var groundDist = raySphereIntersection(rayOrigin, rayDir, EARTH_RADIUS);
var tD = (groundDist < 0.0) ? atmosDist : groundDist;
var radiance = getInscattering(sunDir, rayOrigin, rayDir, tD);
var xyz = spectralToXYZ(radiance);
var pixelIndex = (x + y * lutWidth) * 4;
imageData[pixelIndex + 0] = xyz.x;
imageData[pixelIndex + 1] = xyz.y;
imageData[pixelIndex + 2] = xyz.z;
imageData[pixelIndex + 3] = 1.0;
}
}
lut = kha.Image.fromBytes(imageData.view.buffer, lutWidth, lutHeight, TextureFormat.RGBA128, Usage.StaticUsage);
if (sunDisc) {
var sunData = precomputeSun(sunElevation, sunSize, altKm);
sunBottom = sunData.bottom;
sunTop = sunData.top;
} else {
sunBottom.set(0, 0, 0);
sunTop.set(0, 0, 0);
}
}
}

View File

@ -415,9 +415,8 @@ class RenderPathDeferred {
t.width = 0;
t.height = 0;
t.displayp = Inc.getDisplayp();
t.format = Inc.getHdrFormat();
t.format = "RGBA64";
t.scale = Inc.getSuperSampling();
t.depth_buffer = "main";
path.createRenderTarget(t);
}
#end
@ -676,7 +675,10 @@ class RenderPathDeferred {
if (leenkx.data.Config.raw.rp_gi != false)
{
var path = RenderPath.active;
// TODO: investigate currentTarget
#if rp_probes
if (!path.isProbe) {
#end
// TODO: investigate currentTarget
var g = path.frameG;
Inc.computeVoxelsBegin(g);
@ -717,11 +719,11 @@ class RenderPathDeferred {
Inc.computeVoxelsTemporal(g);
#if (rp_voxels == "Voxel GI")
Inc.computeVoxelsLight(g);
#if (lnx_voxelgi_shadows || (rp_voxels == "Voxel GI"))
Inc.computeVoxelsSDF(g);
#end
if (iron.RenderPath.res_pre_clear == true) {
iron.RenderPath.res_pre_clear = false;
#if (rp_voxels == "Voxel GI")
@ -732,6 +734,9 @@ class RenderPathDeferred {
#end
}
}
#if rp_probes
}
#end
#end
// ---
// Deferred light
@ -779,16 +784,25 @@ class RenderPathDeferred {
#if (rp_voxels != "Off")
if (leenkx.data.Config.raw.rp_gi != false)
{
#if rp_probes
if (!path.isProbe) {
#end
var g = path.frameG;
#if (lnx_config && (rp_voxels == "Voxel AO"))
voxelao_pass = true;
#end
#if (rp_voxels == "Voxel AO")
Inc.resolveAO(g);
path.bindTarget("voxels_ao", "voxels_ao");
#else
Inc.resolveDiffuse(g);
Inc.resolveSpecular(g);
#end
#if rp_probes
}
#end
#if (rp_voxels == "Voxel AO")
path.bindTarget("voxels_ao", "voxels_ao");
#else
path.bindTarget("voxels_diffuse", "voxels_diffuse");
path.bindTarget("voxels_specular", "voxels_specular");
#end
@ -869,6 +883,7 @@ class RenderPathDeferred {
#if rp_water
{
path.setDepthFrom("tex", "gbuffer1");
path.setTarget("buf");
path.bindTarget("tex", "tex");
path.drawShader("shader_datas/copy_pass/copy_pass");
@ -876,6 +891,7 @@ class RenderPathDeferred {
path.bindTarget("_main", "gbufferD");
path.bindTarget("buf", "tex");
path.drawShader("shader_datas/water_pass/water_pass");
path.setDepthFrom("tex", "gbuffer0");
}
#end
@ -951,20 +967,34 @@ class RenderPathDeferred {
{
if (leenkx.data.Config.raw.rp_ssrefr != false)
{
//#if (!kha_opengl)
//path.setDepthFrom("gbuffer0", "gbuffer1"); // Unbind depth so we can read it
//path.depthToRenderTarget.set("main", path.renderTargets.get("tex"));
//#end
//save depth
path.setTarget("gbufferD1");
path.bindTarget("_main", "tex");
path.drawShader("shader_datas/copy_pass/copy_pass");
//#if (!kha_opengl)
//path.setDepthFrom("gbuffer0", "tex"); // Re-bind depth
//path.depthToRenderTarget.set("main", path.renderTargets.get("gbuffer0"));
//#end
//save background color
path.setTarget("refr");
path.bindTarget("tex", "tex");
path.drawShader("shader_datas/copy_pass/copy_pass");
path.setTarget("gbuffer0", ["tex", "gbuffer_refraction",
#if rp_gbuffer2 "gbuffer1", #end
#if rp_gbuffer_emission "buf", #end
]);
path.setTarget("gbuffer0", ["gbuffer1", "gbuffer_refraction"]);
#if (rp_voxels != "Off")
path.bindTarget("voxelsOut", "voxels");
#if (rp_voxels == "Voxel GI" || lnx_voxelgi_shadows)
path.bindTarget("voxelsSDF", "voxelsSDF");
#end
#end
#if rp_shadowmap
{
@ -976,13 +1006,6 @@ class RenderPathDeferred {
}
#end
#if (rp_voxels != "Off")
path.bindTarget("voxelsOut", "voxels");
#if (rp_voxels == "Voxel GI" || lnx_voxelgi_shadows)
path.bindTarget("voxelsSDF", "voxelsSDF");
#end
#end
#if rp_ssrs
path.bindTarget("_main", "gbufferD");
#end
@ -990,18 +1013,18 @@ class RenderPathDeferred {
path.drawMeshes("refraction");
path.setTarget("buf");
path.bindTarget("tex", "tex"); // scene with refractive objects
path.bindTarget("refr", "tex1"); // background without refractive objects
path.bindTarget("_main", "gbufferD");
path.bindTarget("tex", "tex");
path.bindTarget("gbufferD1", "gbufferD1");
path.bindTarget("gbuffer0", "gbuffer0");
path.bindTarget("refr", "tex1");
path.bindTarget("_main", "gbufferD");
path.bindTarget("gbuffer_refraction", "gbuffer_refraction");
path.drawShader("shader_datas/ssrefr_pass/ssrefr_pass");
path.setTarget("tex");
path.bindTarget("buf", "tex");
path.drawShader("shader_datas/copy_pass/copy_pass");
path.setTarget("tex");
path.bindTarget("buf", "tex");
path.drawShader("shader_datas/copy_pass/copy_pass");
}
}
#end
@ -1164,12 +1187,14 @@ class RenderPathDeferred {
#if (rp_antialiasing == "TAA")
#if rp_fsr1
path.isProbe ? path.setTarget("buf") : path.setTarget("bufa");
path.setTarget("bufa");
#else
path.isProbe ? path.setTarget(framebuffer) : path.setTarget("bufa");
#end
#elseif (rp_supersampling == 4)
path.setTarget("bufa");
#elseif rp_fsr1
path.setTarget("buf");
path.setTarget("bufa");
#else
path.setTarget(framebuffer);
#end
@ -1182,21 +1207,36 @@ class RenderPathDeferred {
#end
path.drawShader("shader_datas/smaa_neighborhood_blend/smaa_neighborhood_blend");
#if ((rp_supersampling == 4) || rp_fsr1)
#if (rp_antialiasing == "SMAA")
path.setTarget("buf");
path.bindTarget("bufa", "tex");
path.drawShader("shader_datas/copy_pass/copy_pass");
#end
#end
#if (rp_antialiasing == "TAA")
{
if (!path.isProbe) { // No last frame for probe
path.setTarget("taa");
// Write TAA blend to bufb to avoid read-write hazard on taa
path.setTarget("bufb");
path.bindTarget("bufa", "tex");
path.bindTarget("taa", "tex2");
path.bindTarget("gbuffer2", "sveloc");
path.drawShader("shader_datas/taa_pass/taa_pass");
// Save blended result as history for next frame
path.setTarget("taa");
path.bindTarget("bufb", "tex");
path.drawShader("shader_datas/copy_pass/copy_pass");
// Output to framebuffer
#if rp_fsr1
path.setTarget("buf");
#else
path.setTarget(framebuffer);
#end
path.bindTarget("taa", "tex");
path.bindTarget("bufb", "tex");
path.drawShader("shader_datas/copy_pass/copy_pass");
}
}
@ -1220,7 +1260,7 @@ class RenderPathDeferred {
var finalTarget = "";
path.setTarget(finalTarget);
path.bindTarget(framebuffer, "tex");
path.drawShader("shader_datas/copy_pass/copy_pass");
path.drawShader("shader_datas/supersample_resolve/supersample_resolve");
}
#end

View File

@ -147,7 +147,7 @@ class RenderPathForward {
t.width = 0;
t.height = 0;
t.displayp = Inc.getDisplayp();
t.format = "DEPTH24";
t.format = "R32";
t.scale = Inc.getSuperSampling();
path.createRenderTarget(t);
@ -420,8 +420,13 @@ class RenderPathForward {
if (leenkx.data.Config.raw.rp_gi != false)
{
var path = RenderPath.active;
#if rp_probes
if (!path.isProbe) {
#end
Inc.computeVoxelsBegin();
var g = path.frameG;
Inc.computeVoxelsBegin(g);
if (iron.RenderPath.pre_clear == true)
{
@ -437,7 +442,7 @@ class RenderPathForward {
else
{
path.clearImage("voxels", 0x00000000);
Inc.computeVoxelsOffsetPrev();
Inc.computeVoxelsOffsetPrev(g);
}
path.setTarget("");
@ -446,16 +451,27 @@ class RenderPathForward {
path.bindTarget("voxels", "voxels");
Inc.computeVoxelsTemporal();
#if (rp_voxels == "Voxel GI")
Inc.computeVoxelsLight();
#if rp_shadowmap
{
#if lnx_shadowmap_atlas
Inc.bindShadowMapAtlas();
#else
Inc.bindShadowMap();
#end
}
#end
path.drawMeshes("voxel");
Inc.computeVoxelsTemporal(g);
#if (lnx_voxelgi_shadows || (rp_voxels == "Voxel GI"))
Inc.computeVoxelsSDF();
Inc.computeVoxelsSDF(g);
#end
}
#if rp_probes
}
#end
#end
RenderPathCreator.setTargetMeshes();
@ -473,7 +489,7 @@ class RenderPathForward {
#if (rp_ssrefr || lnx_voxelgi_refract)
{
path.setTarget("gbuffer_refraction");
path.clearTarget(0xffff00ff);
path.clearTarget(0xffffff00);
}
#end
@ -544,11 +560,21 @@ class RenderPathForward {
{
if (leenkx.data.Config.raw.rp_ssrefr != false)
{
#if (!kha_opengl)
path.setDepthFrom("lbuffer0", "bufa"); // Unbind depth so we can read it
path.depthToRenderTarget.set("main", path.renderTargets.get("buf"));
#end
//save depth
path.setTarget("gbufferD1");
path.bindTarget("_main", "tex");
path.drawShader("shader_datas/copy_pass/copy_pass");
#if (!kha_opengl)
path.setDepthFrom("lbuffer0", "buf"); // Re-bind depth
path.depthToRenderTarget.set("main", path.renderTargets.get("lbuffer0"));
#end
//save background color
path.setTarget("refr");
path.bindTarget("lbuffer0", "tex");
@ -579,18 +605,17 @@ class RenderPathForward {
path.drawMeshes("refraction");
path.setTarget("bufa");
path.setTarget("lbuffer0");
path.bindTarget("lbuffer0", "tex");
path.bindTarget("refr", "tex1");
path.bindTarget("_main", "gbufferD");
path.bindTarget("gbufferD1", "gbufferD1");
path.bindTarget("lbuffer1", "gbuffer0");
path.bindTarget("lbuffer0", "gbuffer1");
path.bindTarget("gbuffer_refraction", "gbuffer_refraction");
path.drawShader("shader_datas/ssrefr_pass/ssrefr_pass");
path.setTarget("lbuffer0");
path.bindTarget("bufa", "tex");
path.drawShader("shader_datas/copy_pass/copy_pass");
}
}
#end
@ -630,53 +655,6 @@ class RenderPathForward {
}
#end
#if rp_ssrefr
{
if (leenkx.data.Config.raw.rp_ssrefr != false)
{
path.setTarget("gbufferD1");
path.bindTarget("_main", "tex");
path.drawShader("shader_datas/copy_pass/copy_pass");
path.setTarget("refr");
path.bindTarget("lbuffer0", "tex");
path.drawShader("shader_datas/copy_pass/copy_pass");
path.setTarget("lbuffer0", ["lbuffer1", "gbuffer_refraction"]);
#if rp_shadowmap
{
#if lnx_shadowmap_atlas
Inc.bindShadowMapAtlas();
#else
Inc.bindShadowMap();
#end
}
#end
#if (rp_voxels != "Off")
path.bindTarget("voxelsOut", "voxels");
path.bindTarget("voxelsSDF", "voxelsSDF");
#end
path.drawMeshes("refraction");
path.setTarget("bufa");
path.bindTarget("lbuffer0", "tex");
path.bindTarget("refr", "tex1");
path.bindTarget("_main", "gbufferD");
path.bindTarget("gbufferD1", "gbufferD1");
path.bindTarget("lbuffer1", "gbuffer0");
path.bindTarget("gbuffer_refraction", "gbuffer_refraction");
path.drawShader("shader_datas/ssrefr_pass/ssrefr_pass");
path.setTarget("lbuffer0");
path.bindTarget("bufa", "tex");
path.drawShader("shader_datas/copy_pass/copy_pass");
}
}
#end
#if rp_bloom
{
inline Inc.drawBloom("lbuffer0", bloomDownsampler, bloomUpsampler);
@ -718,8 +696,10 @@ class RenderPathForward {
#if rp_water
{
#if (!kha_opengl)
path.setDepthFrom("lbuffer0", "bufa"); // Unbind depth so we can read it
path.depthToRenderTarget.set("main", path.renderTargets.get("buf"));
#end
path.setTarget("bufa");
path.bindTarget("lbuffer0", "tex");
@ -730,8 +710,10 @@ class RenderPathForward {
path.bindTarget("bufa", "tex");
path.drawShader("shader_datas/water_pass/water_pass");
#if (!kha_opengl)
path.setDepthFrom("lbuffer0", "buf"); // Re-bind depth
path.depthToRenderTarget.set("main", path.renderTargets.get("lbuffer0"));
#end
}
#end
@ -794,10 +776,20 @@ class RenderPathForward {
path.bindTarget("bufa", "edgesTex");
path.drawShader("shader_datas/smaa_blend_weight/smaa_blend_weight");
#if (rp_supersampling == 4)
path.setTarget("bufa");
#else
path.setTarget(framebuffer);
#end
path.bindTarget("buf", "colorTex");
path.bindTarget("bufb", "blendTex");
path.drawShader("shader_datas/smaa_neighborhood_blend/smaa_neighborhood_blend");
#if (rp_supersampling == 4)
path.setTarget("buf");
path.bindTarget("bufa", "tex");
path.drawShader("shader_datas/copy_pass/copy_pass");
#end
}
#end
@ -805,17 +797,16 @@ class RenderPathForward {
{
// FSR1 RCAS sharpening pass applied after AA, expects sRGB [0-1] input
#if ((rp_antialiasing == "SMAA") || (rp_antialiasing == "TAA"))
#if (rp_supersampling == 4)
var fsrSource = "buf";
var fsrDest = "buf";
#else
// SMAA outputs to framebuffer which needs an intermediate buffer
path.setTarget("bufb");
path.bindTarget(framebuffer != "" ? framebuffer : "buf", "tex");
path.drawShader("shader_datas/copy_pass/copy_pass");
var fsrSource = "bufb";
var fsrDest = "";
#end
// SMAA outputs to framebuffer which needs an intermediate buffer
path.setTarget("bufb");
path.bindTarget(framebuffer != "" ? framebuffer : "buf", "tex");
path.drawShader("shader_datas/copy_pass/copy_pass");
var fsrSource = "bufb";
#if (rp_supersampling == 4)
var fsrDest = "buf";
#else
var fsrDest = "";
#end
#else
#if (rp_supersampling == 4)
var fsrSource = "buf";

View File

@ -0,0 +1,938 @@
package leenkx.renderpath;
import kha.FastFloat;
import kha.arrays.Float32Array;
import kha.graphics4.TextureFormat;
import kha.graphics4.Usage;
import iron.data.WorldData;
import iron.math.Vec3;
import leenkx.math.Helper;
/**
Utility class to control the sky models (single and multiple scattering).
**/
class Sky {
public static var singleScatterData: SingleScatteringData = null;
public static var multiScatterData: MultipleScatteringData = null;
// Recomputes the single scattering lookup table after the density settings changed.
static var registered = false;
static inline function register() {
registered = true;
iron.App.notifyOnUpdate(update);
iron.App.notifyOnReset(function() {
iron.App.notifyOnUpdate(update);
});
}
static var multiPrevData: haxe.io.Float32Array = null;
static var multiNewData: haxe.io.Float32Array = null;
static var multiBlendData: haxe.io.Float32Array = null;
static var multiBlend: Float = 1.0; // 0 = old data, 1 = new data
static var multiBlendStartTime: Float = 0.0;
static var recomputeStartTime: Float = 0.0;
static var singlePending: { density: Vec3, data: haxe.io.Float32Array, row: Int } = null;
static var multiPending: {
sunElevation: Float, sunSize: Float, altitude: Float, sunDisc: Bool, density: Vec3,
data: haxe.io.Float32Array, row: Int, transRow: Int, phase: Int,
sdx: Float, sdy: Float, sdz: Float, roz: Float
} = null;
// incremental sky recompute where larger values do less work per frame
public static var recomputTime: Float = 0.3;
public static function recomputeSingleScatter(world: WorldData) {
if (!registered) register();
if (world == null || world.raw.sky_density == null) return;
if (singleScatterData == null) singleScatterData = new SingleScatteringData();
var density = world.raw.sky_density;
var densityVec = new Vec3(density[0], density[1], density[2]);
if (!singleScatterData.needsRecompute(densityVec)) return;
if (singlePending != null) return;
if (singleScatterData.lut == null) {
singleScatterData.computeLUT(densityVec);
} else {
recomputeStartTime = haxe.Timer.stamp();
singlePending = {
density: densityVec,
data: new haxe.io.Float32Array(SingleScatteringData.lutHeightSteps * SingleScatteringData.lutAngleSteps * 4),
row: 0
};
}
}
// Sets the sky's density parameters and calls `recomputeSingleScatter()` afterwards.
public static function setDensity(world: WorldData, densityAir: FastFloat, densityDust: FastFloat, densityOzone: FastFloat) {
if (world == null) return;
if (world.raw.sky_density == null) world.raw.sky_density = new Float32Array(3);
var density = world.raw.sky_density;
density[0] = Helper.clamp(densityAir, 0, 10);
density[1] = Helper.clamp(densityDust, 0, 10);
density[2] = Helper.clamp(densityOzone, 0, 10);
recomputeSingleScatter(world);
}
public static function recomputeMultiScatter(world: WorldData) {
if (!registered) register();
if (world == null) return;
var raw = world.raw;
if (raw.sky_density == null) return;
if (multiScatterData == null) multiScatterData = new MultipleScatteringData();
var density = raw.sky_density;
var densityVec = new Vec3(density[0], density[1], density[2]);
var sunElevation:FastFloat = raw.sky_sun_elevation != null ? raw.sky_sun_elevation : cast 0.0;
var sunRotation:FastFloat = raw.sky_sun_rotation != null ? raw.sky_sun_rotation : cast 0.0;
var sunSize:FastFloat = raw.sky_sun_size != null ? raw.sky_sun_size : cast 0.545;
var sunIntensity:FastFloat = raw.sky_sun_intensity != null ? raw.sky_sun_intensity : cast 1.0;
var altitude:FastFloat = raw.sky_altitude != null ? raw.sky_altitude : cast 0.0;
var sunDisc = raw.sky_sun_disc != null ? raw.sky_sun_disc : 1;
if (!multiScatterData.needsRecompute(densityVec, sunElevation, altitude, sunSize, sunDisc != 0)) return;
if (multiPending != null) return;
if (multiScatterData.lut == null) {
multiScatterData.compute(sunElevation, sunRotation, sunSize, sunIntensity,
altitude, sunDisc != 0, densityVec);
} else {
recomputeStartTime = haxe.Timer.stamp();
multiScatterData.setDensities(densityVec);
var needTrans = multiScatterData.needsTransmittance(densityVec);
var altKm = Math.max(1.0, Math.min(99999.0, altitude)) / 1000.0;
var sunZenithCosAngle = Math.cos(Math.PI / 2.0 - sunElevation);
var sdy = Math.sqrt(1.0 - sunZenithCosAngle * sunZenithCosAngle);
var sdz = sunZenithCosAngle;
var sdx = 0.0;
var roz = MultipleScatteringData.EARTH_RADIUS + altKm;
multiPending = {
sunElevation: sunElevation, sunSize: sunSize, altitude: altitude,
sunDisc: sunDisc != 0, density: densityVec,
data: new haxe.io.Float32Array(MultipleScatteringData.lutWidth * MultipleScatteringData.lutHeight * 4),
row: 0, transRow: 0, phase: needTrans ? 0 : 1,
sdx: sdx, sdy: sdy, sdz: sdz, roz: roz
};
}
}
public static function setMultiScatterParams(world: WorldData, sunElevation: FastFloat, sunRotation: FastFloat,
sunSize: FastFloat, sunIntensity: FastFloat, altitude: FastFloat, sunDisc: Bool,
densityAir: FastFloat, densityDust: FastFloat, densityOzone: FastFloat) {
if (world == null) return;
if (world.raw.sky_density == null) world.raw.sky_density = new Float32Array(3);
var density = world.raw.sky_density;
density[0] = Helper.clamp(densityAir, 0, 10);
density[1] = Helper.clamp(densityDust, 0, 10);
density[2] = Helper.clamp(densityOzone, 0, 10);
world.raw.sky_sun_elevation = sunElevation;
world.raw.sky_sun_rotation = sunRotation;
world.raw.sky_sun_size = sunSize;
world.raw.sky_sun_intensity = sunIntensity;
world.raw.sky_altitude = altitude;
world.raw.sky_sun_disc = sunDisc ? 1 : 0;
recomputeMultiScatter(world);
}
// CPU LUT blending for smooth transition during expensive recomputation
public static function update() {
if (multiBlend < 1.0 && multiPrevData != null && multiNewData != null) {
multiBlend = Math.min(1.0, (haxe.Timer.stamp() - multiBlendStartTime) / recomputTime);
var lutSize = MultipleScatteringData.lutWidth * MultipleScatteringData.lutHeight * 4;
if (multiBlendData == null || multiBlendData.length != lutSize) {
multiBlendData = new haxe.io.Float32Array(lutSize);
}
for (i in 0...lutSize) {
multiBlendData[i] = multiPrevData[i] * (1.0 - multiBlend) + multiNewData[i] * multiBlend;
}
multiScatterData.applyLUTData(multiBlendData);
if (multiBlend >= 1.0) {
multiPrevData = null;
multiNewData = null;
multiBlendData = null;
}
}
if (singlePending == null && multiPending == null) return;
var elapsed = haxe.Timer.stamp() - recomputeStartTime;
var fraction = Math.min(1.0, elapsed / recomputTime);
if (singlePending != null) {
var targetRow = Math.ceil(SingleScatteringData.lutHeightSteps * fraction);
while (singlePending.row < targetRow) {
singleScatterData.computeLUTRow(singlePending.density, singlePending.row, singlePending.data);
singlePending.row++;
}
if (singlePending.row >= SingleScatteringData.lutHeightSteps) {
singleScatterData.applyLUTData(singlePending.data);
singleScatterData.setLastDensity(singlePending.density);
singlePending = null;
}
}
if (multiPending != null) {
if (multiPending.phase == 0) {
var targetTrans = Math.ceil(MultipleScatteringData.transmittanceResY * fraction);
while (multiPending.transRow < targetTrans) {
multiScatterData.precomputeTransmittanceRow(multiPending.transRow);
multiPending.transRow++;
}
if (multiPending.transRow >= MultipleScatteringData.transmittanceResY) {
multiScatterData.finishTransmittance(multiPending.density);
multiPending.phase = 1;
}
}
if (multiPending.phase == 1) {
var targetRow = Math.ceil(MultipleScatteringData.lutHeight * fraction);
while (multiPending.row < targetRow) {
multiScatterData.computeLUTRow(
multiPending.sdx, multiPending.sdy, multiPending.sdz, multiPending.roz,
multiPending.row, multiPending.data
);
multiPending.row++;
}
if (multiPending.row >= MultipleScatteringData.lutHeight) {
// old LUT data for blending and use current blended data if a blend is in progress
if (multiBlend < 1.0 && multiBlendData != null) {
multiPrevData = multiBlendData;
} else {
multiPrevData = multiScatterData.lastLUTData;
}
multiNewData = multiPending.data;
multiScatterData.lastLUTData = multiPending.data;
multiScatterData.lastElevation = multiPending.sunElevation;
multiScatterData.lastAltitude = multiPending.altitude;
multiScatterData.lastSunSize = multiPending.sunSize;
multiScatterData.lastSunDisc = multiPending.sunDisc;
multiScatterData.setSunData(
multiPending.sunElevation, multiPending.sunSize,
multiPending.altitude, multiPending.sunDisc
);
// blend if we have old data, otherwise apply directly
if (multiPrevData != null) {
multiBlend = 0.0;
multiBlendStartTime = haxe.Timer.stamp();
} else {
multiScatterData.applyLUTData(multiPending.data);
}
multiPending = null;
}
}
}
}
}
/**
This class holds the precalculated result of the inner scattering integral
of the single scattering sky model. The outer integral is calculated in
[`leenkx/Shaders/std/sky.glsl`](https://github.com/leenkx3d/leenkx/blob/master/Shaders/std/sky.glsl).
@see `leenkx.renderpath.Sky`
**/
class SingleScatteringData {
public var lut: kha.Image;
var lastDensity: Vec3 = null;
/**
The amount of individual sample heights stored in the LUT (and the width
of the LUT image).
**/
public static var lutHeightSteps = 128;
/**
The amount of individual sun angle steps stored in the LUT (and the
height of the LUT image).
**/
public static var lutAngleSteps = 128;
/**
Amount of steps for calculating the inner scattering integral. Heigher
values are more precise but take longer to compute.
**/
public static var jSteps = 8;
/** Radius of the atmosphere in kilometers. **/
public static var radiusAtmo = 6420.0;
/**
Radius of the planet in kilometers. The default value is the earth radius as
defined in Cycles.
**/
public static var radiusPlanet = 6360.0;
/** Rayleigh scattering coefficient. **/
public static var rayleighCoeff = new Vec3(5.5e-6, 13.0e-6, 22.4e-6);
/** Rayleigh scattering scale parameter. **/
public static var rayleighScale = 8e3;
/** Mie scattering coefficient. **/
public static var mieCoeff = 2e-5;
/** Mie scattering scale parameter. **/
public static var mieScale = 1.2e3;
/** Ozone scattering coefficient. **/
// The ozone absorption coefficients are taken from Cycles code.
// Because Cycles calculates 21 wavelengths, we use the coefficients
// which are closest to the RGB wavelengths (645nm, 510nm, 440nm).
// Precalculating values by simulating Blender's spec_to_xyz() function
// to include all 21 wavelengths gave unrealistic results.
public static var ozoneCoeff = new Vec3(1.59051840791988e-6, 0.00000096707041180970, 0.00000007309568762914);
// salar versions of coefficients
static inline var rx = 5.5e-6;
static inline var ry = 13.0e-6;
static inline var rz = 22.4e-6;
static inline var ox = 1.59051840791988e-6;
static inline var oy = 0.00000096707041180970;
static inline var oz = 0.00000007309568762914;
public function new() {}
/** Approximates the density of ozone for a given sample height. **/
static inline function getOzoneDensity(height: FastFloat): FastFloat {
// Values are taken from Cycles code
if (height < 10000.0 || height >= 40000.0) return 0.0;
if (height < 25000.0) return (height - 10000.0) / 15000.0;
return -((height - 40000.0) / 15000.0);
}
/**
Computes the LUT data for the given density values (thread-safe).
Uses scalar math — zero allocations in the inner loop.
@param density 3D vector of air density, dust density, ozone density
@return Float32Array of RGBA128 pixel data
**/
public function computeLUTData(density: Vec3): haxe.io.Float32Array {
var imageData = new haxe.io.Float32Array(lutHeightSteps * lutAngleSteps * 4);
for (x in 0...lutHeightSteps) {
computeLUTRow(density, x, imageData);
}
return imageData;
}
// computes a single height row of the LUT for incremental recompute
public function computeLUTRow(density: Vec3, x: Int, imageData: haxe.io.Float32Array) {
var radiusPlanetMeters = radiusPlanet * 1000;
var dAir = density.x;
var dDust = density.y;
var dOzone = density.z;
var invRayleigh = 1.0 / rayleighScale;
var invMie = 1.0 / mieScale;
var invJ = 1.0 / jSteps;
// Use quadratic height for better horizon precision
var height = (x / (lutHeightSteps - 1));
height *= height;
height *= radiusAtmo * 1000; // Denormalize height
var iz = height + radiusPlanetMeters;
for (y in 0...lutAngleSteps) {
var sunTheta = y / (lutAngleSteps - 1) * 2 - 1;
// Improve horizon precision
// See https://sebh.github.io/publications/egsr2020.pdf (5.3)
sunTheta = Helper.sign(sunTheta) * sunTheta * sunTheta;
sunTheta = sunTheta * Math.PI / 2 + Math.PI / 2; // Denormalize
var sy = Math.sin(sunTheta);
var sz = Math.cos(sunTheta);
var ozKm = iz * 0.001;
var b = 2.0 * sz * ozKm;
var c = ozKm * ozKm - radiusAtmo * radiusAtmo;
var disc = b * b - 4.0 * c;
var jStepSize: FastFloat;
if (disc < 0.0) {
jStepSize = 1e5 * invJ * 1000;
} else {
var sqd = Math.sqrt(disc);
jStepSize = ((-b + sqd) * 0.5) / jSteps * 1000;
}
var odRay = 0.0;
var odMie = 0.0;
var odOzone = 0.0;
var jTime = 0.0;
for (i in 0...jSteps) {
var t = jTime + jStepSize * 0.5;
var jy = sy * t;
var jz = iz + sz * t;
var jHeight = Math.sqrt(jy * jy + jz * jz) - radiusPlanetMeters;
odRay += Math.exp(-jHeight * invRayleigh) * dAir;
odMie += Math.exp(-jHeight * invMie) * dDust;
odOzone += getOzoneDensity(jHeight) * dOzone;
jTime += jStepSize;
}
odRay *= jStepSize;
odMie *= jStepSize;
odOzone *= jStepSize;
var mie = mieCoeff * odMie;
var pixelIndex = (x + y * lutHeightSteps) * 4;
imageData[pixelIndex + 0] = Math.exp(-(rx * odRay + mie + ox * odOzone));
imageData[pixelIndex + 1] = Math.exp(-(ry * odRay + mie + oy * odOzone));
imageData[pixelIndex + 2] = Math.exp(-(rz * odRay + mie + oz * odOzone));
imageData[pixelIndex + 3] = 1.0; // Unused
}
}
// creates the LUT image from precomputed data called on main
public function applyLUTData(imageData: haxe.io.Float32Array) {
lut = kha.Image.fromBytes(imageData.view.buffer, lutHeightSteps, lutAngleSteps, TextureFormat.RGBA128, Usage.StaticUsage);
}
public function needsRecompute(density: Vec3): Bool {
return lut == null || lastDensity == null ||
lastDensity.x != density.x || lastDensity.y != density.y || lastDensity.z != density.z;
}
public function setLastDensity(density: Vec3) {
lastDensity = new Vec3(density.x, density.y, density.z);
}
public function computeLUT(density: Vec3) {
applyLUTData(computeLUTData(density));
lastDensity = new Vec3(density.x, density.y, density.z);
}
}
/**
This class precomputes the full sky radiance LUT
Unlike the single scattering model matching Blenders implementation.
**/
class MultipleScatteringData {
public var lut: kha.Image;
public var sunBottom: Vec3;
public var sunTop: Vec3;
public var earthIntersectionAngle: FastFloat;
public static var lutWidth = 128;
public static var lutHeight = 64;
static var transmittanceResX = 128;
public static var transmittanceResY = 32;
var transmittanceLUT: haxe.io.Float32Array;
var transmittanceValid = false;
var transmittanceDensity: Vec3 = null;
public var lastElevation: Float = 0;
public var lastAltitude: Float = 0;
public var lastSunSize: Float = 0;
public var lastSunDisc: Bool = false;
public var lastLUTData: haxe.io.Float32Array = null;
static var transmittanceSteps = 32;
static var inScatteringSteps = 32;
// Atmosphere constants sky_multiple_scattering.cpp
public static var EARTH_RADIUS = 6371.0;
static var ATMOSPHERE_THICKNESS = 100.0;
static var ATMOSPHERE_RADIUS = 6471.0; // EARTH_RADIUS + ATMOSPHERE_THICKNESS
static var PHASE_ISOTROPIC = 1.0 / (4.0 * Math.PI);
static var RAYLEIGH_PHASE_SCALE = (3.0 / 16.0) * (1.0 / Math.PI);
static var G = 0.8;
static var SQR_G = 0.64;
static var GROUND_ALBEDO: Array<Float> = [0.3, 0.3, 0.3, 0.3];
// Spectral data sampled at 630, 560, 490, 430 nm
static var SUN_SPECTRAL_IRRADIANCE: Array<Float> = [1.679, 1.828, 1.986, 1.307];
static var MOLECULAR_SCATTERING_COEFFICIENT_BASE: Array<Float> = [6.605e-3, 1.067e-2, 1.842e-2, 3.156e-2];
static var OZONE_ABSORPTION_CROSS_SECTION: Array<Float> = [3.472e-25, 3.914e-25, 1.349e-25, 11.03e-27];
static var OZONE_MEAN_DOBSON = 334.5;
static var AEROSOL_ABSORPTION_CROSS_SECTION: Array<Float> = [2.8722e-24, 4.6168e-24, 7.9706e-24, 1.3578e-23];
static var AEROSOL_SCATTERING_CROSS_SECTION: Array<Float> = [1.5908e-22, 1.7711e-22, 2.0942e-22, 2.4033e-22];
static var AEROSOL_BASE_DENSITY = 1.3681e20;
static var AEROSOL_BACKGROUND_DENSITY = 2e6;
static var AEROSOL_HEIGHT_SCALE = 0.73;
static var SPECTRAL_XYZ: Array<Array<Float>> = [
[53.386917738564668023, 22.981337506691024754, 0.0],
[43.904844466369358263, 71.347795700053393866, 0.102506867965741307],
[1.6137278251608962005, 18.422960591455485011, 31.742921188390805758],
[20.762668673810577145, 2.3614213523314368527, 110.48009643252140334]
];
var airDensity: Float = 1.0;
var aerosolDensity: Float = 1.0;
var ozoneDensity: Float = 1.0;
// avoid allocations in hot loops
static var scratch0: Array<Float> = [0, 0, 0, 0];
static var scratch1: Array<Float> = [0, 0, 0, 0];
static var scratch2: Array<Float> = [0, 0, 0, 0];
static var scratch3: Array<Float> = [0, 0, 0, 0];
static var scratch4: Array<Float> = [0, 0, 0, 0];
static var scratch5: Array<Float> = [0, 0, 0, 0];
public function new() {
sunBottom = new Vec3();
sunTop = new Vec3();
earthIntersectionAngle = 0.0;
transmittanceLUT = new haxe.io.Float32Array(transmittanceResY * transmittanceResX * 4);
}
static inline function add4(a: Array<Float>, b: Array<Float>, out: Array<Float>) {
out[0] = a[0] + b[0]; out[1] = a[1] + b[1]; out[2] = a[2] + b[2]; out[3] = a[3] + b[3];
}
static inline function scale4(a: Array<Float>, s: Float, out: Array<Float>) {
out[0] = a[0] * s; out[1] = a[1] * s; out[2] = a[2] * s; out[3] = a[3] * s;
}
// Atmospheric functions
function getMolecularScatteringCoefficient(h: Float, out: Array<Float>) {
var f = Math.exp(-0.07771971 * Math.pow(h, 1.16364243));
out[0] = MOLECULAR_SCATTERING_COEFFICIENT_BASE[0] * f;
out[1] = MOLECULAR_SCATTERING_COEFFICIENT_BASE[1] * f;
out[2] = MOLECULAR_SCATTERING_COEFFICIENT_BASE[2] * f;
out[3] = MOLECULAR_SCATTERING_COEFFICIENT_BASE[3] * f;
}
function getMolecularAbsorptionCoefficient(h: Float, out: Array<Float>) {
var logH = Math.log(Math.max(h, 1e-4));
var density = 3.78547397e20 * Math.exp(-(logH - 3.22261) * (logH - 3.22261) * 5.55555555 - logH);
out[0] = OZONE_ABSORPTION_CROSS_SECTION[0] * OZONE_MEAN_DOBSON * density;
out[1] = OZONE_ABSORPTION_CROSS_SECTION[1] * OZONE_MEAN_DOBSON * density;
out[2] = OZONE_ABSORPTION_CROSS_SECTION[2] * OZONE_MEAN_DOBSON * density;
out[3] = OZONE_ABSORPTION_CROSS_SECTION[3] * OZONE_MEAN_DOBSON * density;
}
static inline function getAerosolDensity(h: Float): Float {
return AEROSOL_BASE_DENSITY * (Math.exp(-h / AEROSOL_HEIGHT_SCALE) + AEROSOL_BACKGROUND_DENSITY / AEROSOL_BASE_DENSITY);
}
function getAtmosphereCollisionCoefficients(h: Float) {
var localAerosolDensity = getAerosolDensity(h) * aerosolDensity;
scale4(AEROSOL_ABSORPTION_CROSS_SECTION, localAerosolDensity, scratch0);
scale4(AEROSOL_SCATTERING_CROSS_SECTION, localAerosolDensity, scratch1);
getMolecularAbsorptionCoefficient(h, scratch2);
scale4(scratch2, ozoneDensity, scratch2);
getMolecularScatteringCoefficient(h, scratch3);
scale4(scratch3, airDensity, scratch3);
}
static inline function molecularPhaseFunction(cosTheta: Float): Float {
return RAYLEIGH_PHASE_SCALE * (1.0 + cosTheta * cosTheta);
}
static inline function aerosolPhaseFunction(cosTheta: Float): Float {
var den = 1.0 + SQR_G + 2.0 * G * cosTheta;
return PHASE_ISOTROPIC * (1.0 - SQR_G) / (den * Math.sqrt(den));
}
// nearest positive intersection distance, or -1 if no intersection
static inline function raySphereIntersectScalar(px: Float, py: Float, pz: Float,
dx: Float, dy: Float, dz: Float, radius: Float): Float {
var b = px * dx + py * dy + pz * dz;
var c = px * px + py * py + pz * pz - radius * radius;
if (c > 0.0 && b > 0.0) return -1.0;
var d = b * b - c;
if (d < 0.0) return -1.0;
if (d >= b * b) return -b + Math.sqrt(d);
return -b - Math.sqrt(d);
}
static inline function spectralToXYZScalars(l0: Float, l1: Float, l2: Float, l3: Float): Vec3 {
return new Vec3(
SPECTRAL_XYZ[0][0] * l0 + SPECTRAL_XYZ[1][0] * l1 + SPECTRAL_XYZ[2][0] * l2 + SPECTRAL_XYZ[3][0] * l3,
SPECTRAL_XYZ[0][1] * l0 + SPECTRAL_XYZ[1][1] * l1 + SPECTRAL_XYZ[2][1] * l2 + SPECTRAL_XYZ[3][1] * l3,
SPECTRAL_XYZ[0][2] * l0 + SPECTRAL_XYZ[1][2] * l1 + SPECTRAL_XYZ[2][2] * l2 + SPECTRAL_XYZ[3][2] * l3
);
}
// XYZ directly into imageData avoiding Vec3 allocation in loops
static inline function spectralToXYZ(l0: Float, l1: Float, l2: Float, l3: Float,
imageData: haxe.io.Float32Array, pixelIndex: Int) {
imageData[pixelIndex + 0] = SPECTRAL_XYZ[0][0] * l0 + SPECTRAL_XYZ[1][0] * l1 + SPECTRAL_XYZ[2][0] * l2 + SPECTRAL_XYZ[3][0] * l3;
imageData[pixelIndex + 1] = SPECTRAL_XYZ[0][1] * l0 + SPECTRAL_XYZ[1][1] * l1 + SPECTRAL_XYZ[2][1] * l2 + SPECTRAL_XYZ[3][1] * l3;
imageData[pixelIndex + 2] = SPECTRAL_XYZ[0][2] * l0 + SPECTRAL_XYZ[1][2] * l1 + SPECTRAL_XYZ[2][2] * l2 + SPECTRAL_XYZ[3][2] * l3;
imageData[pixelIndex + 3] = 1.0;
}
function getTransmittance(cosTheta: Float, normalizedAltitude: Float, out: Array<Float>) {
var sy = Math.sqrt(1.0 - cosTheta * cosTheta);
var sz = cosTheta;
var distToCenter = EARTH_RADIUS + (ATMOSPHERE_RADIUS - EARTH_RADIUS) * normalizedAltitude;
var tD = raySphereIntersectScalar(0, 0, distToCenter, 0, sy, sz, ATMOSPHERE_RADIUS);
var tStep = tD / transmittanceSteps;
// Accumulate in local scalars (out may alias scratch0 which gets clobbered)
var acc0 = 0.0, acc1 = 0.0, acc2 = 0.0, acc3 = 0.0;
for (step in 0...transmittanceSteps) {
var t = (step + 0.5) * tStep;
var xtY = sy * t;
var xtZ = distToCenter + sz * t;
var altitude = Math.max(Math.sqrt(xtY * xtY + xtZ * xtZ) - EARTH_RADIUS, 0.0);
getAtmosphereCollisionCoefficients(altitude);
// extinction = aerosolAbs + aerosolSca + molAbs + molSca
add4(scratch0, scratch1, scratch4);
add4(scratch2, scratch3, scratch5);
add4(scratch4, scratch5, scratch4);
// result += extinction * tStep
acc0 += scratch4[0] * tStep;
acc1 += scratch4[1] * tStep;
acc2 += scratch4[2] * tStep;
acc3 += scratch4[3] * tStep;
}
out[0] = Math.exp(-acc0);
out[1] = Math.exp(-acc1);
out[2] = Math.exp(-acc2);
out[3] = Math.exp(-acc3);
}
function precomputeTransmittanceLUT() {
var d = new Vec3(airDensity, aerosolDensity, ozoneDensity);
if (!needsTransmittance(d)) return;
for (y in 0...transmittanceResY) {
precomputeTransmittanceRow(y);
}
finishTransmittance(d);
}
public function needsTransmittance(density: Vec3): Bool {
return !(transmittanceValid && transmittanceDensity != null &&
transmittanceDensity.x == density.x &&
transmittanceDensity.y == density.y &&
transmittanceDensity.z == density.z);
}
public function setDensities(density: Vec3) {
airDensity = density.x;
aerosolDensity = density.y;
ozoneDensity = density.z;
}
public function precomputeTransmittanceRow(y: Int) {
for (x in 0...transmittanceResX) {
var u = x / (transmittanceResX - 1);
var v = y / (transmittanceResY - 1);
getTransmittance(u * 2.0 - 1.0, v, scratch0);
var idx = (y * transmittanceResX + x) * 4;
transmittanceLUT[idx] = scratch0[0];
transmittanceLUT[idx + 1] = scratch0[1];
transmittanceLUT[idx + 2] = scratch0[2];
transmittanceLUT[idx + 3] = scratch0[3];
}
}
public function finishTransmittance(density: Vec3) {
transmittanceValid = true;
if (transmittanceDensity == null) transmittanceDensity = new Vec3();
transmittanceDensity.x = density.x;
transmittanceDensity.y = density.y;
transmittanceDensity.z = density.z;
}
function lookupTransmittance(cosTheta: Float, normalizedAltitude: Float, out: Array<Float>) {
var u = Math.max(0.0, Math.min(1.0, cosTheta * 0.5 + 0.5));
var v = Math.max(0.0, Math.min(1.0, normalizedAltitude));
var fx = u * (transmittanceResX - 1);
var fy = v * (transmittanceResY - 1);
var x1 = Std.int(fx);
var y1 = Std.int(fy);
var x2 = Std.int(Math.min(x1 + 1, transmittanceResX - 1));
var y2 = Std.int(Math.min(y1 + 1, transmittanceResY - 1));
var dxF = fx - x1;
var dyF = fy - y1;
var idx00 = (y1 * transmittanceResX + x1) * 4;
var idx01 = (y1 * transmittanceResX + x2) * 4;
var idx10 = (y2 * transmittanceResX + x1) * 4;
var idx11 = (y2 * transmittanceResX + x2) * 4;
// Bilinear interpolation directly into out — no scratch arrays needed
for (w in 0...4) {
var b = transmittanceLUT[idx00 + w] + dxF * (transmittanceLUT[idx01 + w] - transmittanceLUT[idx00 + w]);
var t = transmittanceLUT[idx10 + w] + dxF * (transmittanceLUT[idx11 + w] - transmittanceLUT[idx10 + w]);
out[w] = b + dyF * (t - b);
}
}
function lookupTransmittanceAtGround(cosTheta: Float, out: Array<Float>) {
var u = Math.max(0.0, Math.min(1.0, cosTheta * 0.5 + 0.5));
var fx = u * (transmittanceResX - 1);
var x1 = Std.int(fx);
var x2 = Std.int(Math.min(x1 + 1, transmittanceResX - 1));
var dxF = fx - x1;
var idx0 = (0 * transmittanceResX + x1) * 4;
var idx1 = (0 * transmittanceResX + x2) * 4;
for (w in 0...4) {
out[w] = transmittanceLUT[idx0 + w] + dxF * (transmittanceLUT[idx1 + w] - transmittanceLUT[idx0 + w]);
}
}
function lookupTransmittanceToSun(normalizedAltitude: Float, out: Array<Float>) {
var v = Math.max(0.0, Math.min(1.0, normalizedAltitude));
var fy = v * (transmittanceResY - 1);
var y1 = Std.int(fy);
var y2 = Std.int(Math.min(y1 + 1, transmittanceResY - 1));
var dyF = fy - y1;
var x = transmittanceResX - 1;
var idx0 = (y1 * transmittanceResX + x) * 4;
var idx1 = (y2 * transmittanceResX + x) * 4;
for (w in 0...4) {
out[w] = transmittanceLUT[idx0 + w] + dyF * (transmittanceLUT[idx1 + w] - transmittanceLUT[idx0 + w]);
}
}
function lookupMultiscattering(cosTheta: Float, normalizedHeight: Float, d: Float, out: Array<Float>) {
var omega = 2.0 * Math.PI * (1.0 - Math.sqrt(Math.max(0.0, 1.0 - (EARTH_RADIUS / d) * (EARTH_RADIUS / d))));
lookupTransmittanceAtGround(cosTheta, scratch0); // tToGround
lookupTransmittanceToSun(0.0, scratch1); // tSun0
lookupTransmittanceToSun(normalizedHeight, scratch2); // tSunH
// tGroundToSample = tSun0 / tSunH
scratch3[0] = scratch1[0] / scratch2[0]; scratch3[1] = scratch1[1] / scratch2[1];
scratch3[2] = scratch1[2] / scratch2[2]; scratch3[3] = scratch1[3] / scratch2[3];
// lGround = GROUND_ALBEDO/PI * tToGround * tGroundToSample * cosTheta * PHASE_ISOTROPIC * omega
var groundScale = (1.0 / Math.PI) * cosTheta * PHASE_ISOTROPIC * omega;
for (w in 0...4) {
out[w] = GROUND_ALBEDO[w] * scratch0[w] * scratch3[w] * groundScale;
}
// lMs = [0.217, 0.347, 0.594, 1.0] * 0.02 * msFactor
var msFactor = 0.02 / (1.0 + 5.0 * Math.exp(-17.92 * cosTheta));
out[0] += 0.217 * msFactor;
out[1] += 0.347 * msFactor;
out[2] += 0.594 * msFactor;
out[3] += 1.0 * msFactor;
}
// Scalar inscattering — sunDir and rayDir as scalars, uses scratch arrays
function getInscatteringScalars(
sdx: Float, sdy: Float, sdz: Float, // sun dir
ox: Float, oy: Float, oz: Float, // ray origin
dx: Float, dy: Float, dz: Float, // ray dir (normalized)
tD: Float, out: Array<Float>) {
// cosTheta = -rayDir . sunDir
var cosTheta = -(dx * sdx + dy * sdy + dz * sdz);
var molPhase = molecularPhaseFunction(cosTheta);
var aerPhase = aerosolPhaseFunction(cosTheta);
var dt = tD / inScatteringSteps;
out[0] = 0; out[1] = 0; out[2] = 0; out[3] = 0;
// transmittance = [1,1,1,1]
var tr0 = 1.0, tr1 = 1.0, tr2 = 1.0, tr3 = 1.0;
// Accumulate inscattering in local scalars (out may alias scratch arrays)
var li0 = 0.0, li1 = 0.0, li2 = 0.0, li3 = 0.0;
for (i in 0...inScatteringSteps) {
var t = (i + 0.5) * dt;
// xT = rayOrigin + rayDir * t
var xtx = ox + dx * t;
var xty = oy + dy * t;
var xtz = oz + dz * t;
var distToCenter = Math.sqrt(xtx * xtx + xty * xty + xtz * xtz);
var altitude = Math.max(distToCenter - EARTH_RADIUS, 0.0);
var normalizedAltitude = altitude / ATMOSPHERE_THICKNESS;
// zenithDir = xT / distToCenter
var invDist = 1.0 / distToCenter;
var zdx = xtx * invDist, zdy = xty * invDist, zdz = xtz * invDist;
var sampleCosTheta = zdx * sdx + zdy * sdy + zdz * sdz;
getAtmosphereCollisionCoefficients(altitude);
// extinction = aerosolAbs + aerosolSca + molAbs + molSca
add4(scratch0, scratch1, scratch4);
add4(scratch2, scratch3, scratch5);
add4(scratch4, scratch5, scratch4); // extinction in scratch4
// Save molSca (scratch3) and aerSca (scratch1) before lookups clobber scratch arrays
var molSca0 = scratch3[0], molSca1 = scratch3[1], molSca2 = scratch3[2], molSca3 = scratch3[3];
var aerSca0 = scratch1[0], aerSca1 = scratch1[1], aerSca2 = scratch1[2], aerSca3 = scratch1[3];
lookupTransmittance(sampleCosTheta, normalizedAltitude, scratch5); // tToSun
lookupMultiscattering(sampleCosTheta, normalizedAltitude, distToCenter, scratch1); // ms
// stepTransmittance = exp(-extinction * dt)
var st0 = Math.exp(-scratch4[0] * dt);
var st1 = Math.exp(-scratch4[1] * dt);
var st2 = Math.exp(-scratch4[2] * dt);
var st3 = Math.exp(-scratch4[3] * dt);
// S = SUN_SPECTRAL_IRRADIANCE * (molSca * (molPhase * tToSun + ms) + aerSca * (aerPhase * tToSun + ms))
var s0 = SUN_SPECTRAL_IRRADIANCE[0] * (molSca0 * (molPhase * scratch5[0] + scratch1[0]) + aerSca0 * (aerPhase * scratch5[0] + scratch1[0]));
var s1 = SUN_SPECTRAL_IRRADIANCE[1] * (molSca1 * (molPhase * scratch5[1] + scratch1[1]) + aerSca1 * (aerPhase * scratch5[1] + scratch1[1]));
var s2 = SUN_SPECTRAL_IRRADIANCE[2] * (molSca2 * (molPhase * scratch5[2] + scratch1[2]) + aerSca2 * (aerPhase * scratch5[2] + scratch1[2]));
var s3 = SUN_SPECTRAL_IRRADIANCE[3] * (molSca3 * (molPhase * scratch5[3] + scratch1[3]) + aerSca3 * (aerPhase * scratch5[3] + scratch1[3]));
// sInt[w] = (s[w] - s[w]*st[w]) / max(ext[w], 1e-7)
var ce0 = scratch4[0] < 1e-7 ? 1e-7 : scratch4[0];
var ce1 = scratch4[1] < 1e-7 ? 1e-7 : scratch4[1];
var ce2 = scratch4[2] < 1e-7 ? 1e-7 : scratch4[2];
var ce3 = scratch4[3] < 1e-7 ? 1e-7 : scratch4[3];
var si0 = (s0 - s0 * st0) / ce0;
var si1 = (s1 - s1 * st1) / ce1;
var si2 = (s2 - s2 * st2) / ce2;
var si3 = (s3 - s3 * st3) / ce3;
// lInscattering += transmittance * sInt
li0 += tr0 * si0;
li1 += tr1 * si1;
li2 += tr2 * si2;
li3 += tr3 * si3;
// transmittance *= stepTransmittance
tr0 *= st0; tr1 *= st1; tr2 *= st2; tr3 *= st3;
}
out[0] = li0; out[1] = li1; out[2] = li2; out[3] = li3;
}
function computeEarthAngle(altitude: Float): Float {
return Math.PI / 2.0 - Math.asin(EARTH_RADIUS / (EARTH_RADIUS + altitude / 1000.0));
}
function precomputeSun(sunElevation: Float, angularDiameter: Float, altitude: Float): { bottom: Vec3, top: Vec3 } {
var halfAngular = Math.max(angularDiameter, 0.001) / 2.0;
var solidAngle = 2.0 * Math.PI * (1.0 - Math.cos(halfAngular));
var normalizedAltitude = altitude / ATMOSPHERE_THICKNESS;
function getSunXYZ(elevation: Float): Vec3 {
var sunZenithCosAngle = Math.cos(Math.PI / 2.0 - elevation);
getTransmittance(sunZenithCosAngle, normalizedAltitude, scratch0);
return spectralToXYZScalars(
SUN_SPECTRAL_IRRADIANCE[0] * scratch0[0] / solidAngle,
SUN_SPECTRAL_IRRADIANCE[1] * scratch0[1] / solidAngle,
SUN_SPECTRAL_IRRADIANCE[2] * scratch0[2] / solidAngle,
SUN_SPECTRAL_IRRADIANCE[3] * scratch0[3] / solidAngle
);
}
var bottom = getSunXYZ(sunElevation - halfAngular);
var top = getSunXYZ(sunElevation + halfAngular);
return { bottom: bottom, top: top };
}
// single row of the main LUT for incremental recompute
public function computeLUTRow(sdx: Float, sdy: Float, sdz: Float, roz: Float,
y: Int, imageData: haxe.io.Float32Array) {
var uvY = (y + 0.5) / lutHeight;
var l = uvY * 2.0 - 1.0;
var elevation = (l < 0 ? -1.0 : 1.0) * l * l * Math.PI / 2.0;
var cosEl = Math.cos(elevation);
var sinEl = Math.sin(elevation);
for (x in 0...lutWidth) {
var uvX = (x + 0.5) / lutWidth;
var azimuth = 2.0 * Math.PI * uvX;
var dx = Math.sin(azimuth) * cosEl;
var dy = Math.cos(azimuth) * cosEl;
var dz = sinEl;
var atmosDist = raySphereIntersectScalar(0, 0, roz, dx, dy, dz, ATMOSPHERE_RADIUS);
var groundDist = -1.0;
if (dz < 0.0) {
groundDist = raySphereIntersectScalar(0, 0, roz, dx, dy, dz, EARTH_RADIUS);
}
var tD = (groundDist < 0.0) ? atmosDist : groundDist;
getInscatteringScalars(sdx, sdy, sdz, 0, 0, roz, dx, dy, dz, tD, scratch0);
var pixelIndex = (x + y * lutWidth) * 4;
spectralToXYZ(scratch0[0], scratch0[1], scratch0[2], scratch0[3], imageData, pixelIndex);
}
}
// Sets sun disc data without creating a GPU texture.
public function setSunData(sunElevation: Float, sunSize: Float, altitude: Float, sunDisc: Bool) {
var altKm = Math.max(1.0, Math.min(99999.0, altitude)) / 1000.0;
earthIntersectionAngle = computeEarthAngle(altitude);
if (sunDisc) {
var sunData = precomputeSun(sunElevation, sunSize, altKm);
sunBottom = sunData.bottom;
sunTop = sunData.top;
} else {
sunBottom = new Vec3();
sunTop = new Vec3();
}
}
public function computeData(sunElevation: Float, sunRotation: Float, sunSize: Float,
sunIntensity: Float, altitude: Float, sunDisc: Bool, density: Vec3): { data: haxe.io.Float32Array, sunBottom: Vec3, sunTop: Vec3, earthAngle: FastFloat } {
airDensity = density.x;
aerosolDensity = density.y;
ozoneDensity = density.z;
precomputeTransmittanceLUT();
var altKm = Math.max(1.0, Math.min(99999.0, altitude)) / 1000.0;
var sunZenithCosAngle = Math.cos(Math.PI / 2.0 - sunElevation);
var sdy = Math.sqrt(1.0 - sunZenithCosAngle * sunZenithCosAngle);
var sdz = sunZenithCosAngle;
var sdx = 0.0;
var roz = EARTH_RADIUS + altKm;
var earthAngle = computeEarthAngle(altitude);
var imageData = new haxe.io.Float32Array(lutWidth * lutHeight * 4);
for (y in 0...lutHeight) {
computeLUTRow(sdx, sdy, sdz, roz, y, imageData);
}
var bottom = new Vec3();
var top = new Vec3();
if (sunDisc) {
var sunData = precomputeSun(sunElevation, sunSize, altKm);
bottom = sunData.bottom;
top = sunData.top;
}
return { data: imageData, sunBottom: bottom, sunTop: top, earthAngle: earthAngle };
}
public function applyData(result: { data: haxe.io.Float32Array, sunBottom: Vec3, sunTop: Vec3, earthAngle: FastFloat }) {
lut = kha.Image.fromBytes(result.data.view.buffer, lutWidth, lutHeight, TextureFormat.RGBA128, Usage.StaticUsage);
sunBottom = result.sunBottom;
sunTop = result.sunTop;
earthIntersectionAngle = result.earthAngle;
}
// LUT data for blending
public function applyLUTData(imageData: haxe.io.Float32Array) {
lut = kha.Image.fromBytes(imageData.view.buffer, lutWidth, lutHeight, TextureFormat.RGBA128, Usage.DynamicUsage);
}
public function needsRecompute(density: Vec3, sunElevation: Float, altitude: Float, sunSize: Float, sunDisc: Bool): Bool {
return lut == null || needsTransmittance(density) ||
sunElevation != lastElevation || altitude != lastAltitude ||
sunSize != lastSunSize || sunDisc != lastSunDisc;
}
public function compute(sunElevation: Float, sunRotation: Float, sunSize: Float,
sunIntensity: Float, altitude: Float, sunDisc: Bool, density: Vec3) {
var result = computeData(sunElevation, sunRotation, sunSize, sunIntensity, altitude, sunDisc, density);
applyData(result);
lastLUTData = result.data;
lastElevation = sunElevation;
lastAltitude = altitude;
lastSunSize = sunSize;
lastSunDisc = sunDisc;
}
}

View File

@ -48,6 +48,21 @@ class Starter {
mode: windowMode, windowFeatures: windowFeatures}, framebuffer: {samplesPerPixel: c.window_msaa, verticalSync: c.window_vsync}}, function(window: kha.Window) {
iron.App.init(function() {
// TODO: Currently breaks after a second
// TODO: Figure out why full width and height need a math operation like /2 or -1
//#if (lnx_webview && kha_krom)
//var wvOptions: Dynamic = {
// parentWindow: 0,
// x: 0,
// y: 0,
// width: c.window_w - 1,
// height: c.window_h - 1,
// transparent: true,
// title: Main.projectName
//};
//var wvId: Int = Krom.webviewCreate(wvOptions);
//Krom.webviewShow(wvId);
//#end
#if lnx_loadscreen
function load(g: kha.graphics2.Graphics) {
if (iron.Scene.active != null && iron.Scene.active.ready) iron.App.removeRender2D(load);
@ -101,6 +116,15 @@ class Starter {
kha.Assets.loadBlobFromPath(name, function(b: kha.Blob) {
js.Syntax.code("(1,eval)({0})", b.toString());
#if kha_krom
#if lnx_jolt_mt
js.Syntax.code("Jolt({print:function(s){iron.log(s);},instantiateWasm:function(imports,successCallback) {
var wasmbin = Krom.loadBlob('jolt.mt.wasm.wasm');
var module = new WebAssembly.Module(wasmbin);
var inst = new WebAssembly.Instance(module,imports);
successCallback(inst,module);
return inst.exports;
}}).then(function(m){ Jolt=m; tasks--; start();})");
#else
js.Syntax.code("Jolt({print:function(s){iron.log(s);},instantiateWasm:function(imports,successCallback) {
var wasmbin = Krom.loadBlob('jolt.wasm.wasm');
var module = new WebAssembly.Module(wasmbin);
@ -108,6 +132,7 @@ class Starter {
successCallback(inst);
return inst.exports;
}}).then(function(m){ Jolt=m; tasks--; start();})");
#end
#else
js.Syntax.code("Jolt({print:function(s){iron.log(s);},locateFile:function(f){return 'jolt.wasm.wasm';}}).then(function(m){ Jolt=m; tasks--; start();})");
#end
@ -147,8 +172,12 @@ class Starter {
#if (js && lnx_jolt)
tasks++;
#if lnx_jolt_mt
loadLibJolt("jolt.mt.wasm.js");
#else
loadLibJolt("jolt.wasm.js");
#end
#end
#if (js && lnx_navigation)
tasks++;

View File

@ -6,14 +6,14 @@ import iron.Trait;
import iron.object.MeshObject;
import iron.data.MeshData;
import iron.data.SceneFormat;
#if (lnx_bullet || lnx_oimo)
#if lnx_physics
import leenkx.trait.physics.RigidBody;
import leenkx.trait.physics.PhysicsWorld;
#end
class PhysicsBreak extends Trait {
#if (!lnx_bullet && !lnx_oimo)
#if (!lnx_physics)
public function new() { super(); }
#else
@ -74,14 +74,16 @@ class PhysicsBreak extends Trait {
impactNormal = p.normOnB;
#elseif lnx_oimo
impactNormal = p.nor;
#else
impactNormal = p.normOnB;
#end
}
}
#if lnx_bullet
var fractureImpulse = 4.0;
#elseif lnx_oimo
#if lnx_oimo
var fractureImpulse = 1.0;
#else
var fractureImpulse = 4.0;
#end
if (maxImpulse > fractureImpulse && impactPoint != null && impactNormal != null) {
var radialIter = 1;

View File

@ -11,25 +11,38 @@ import leenkx.trait.physics.PhysicsWorld;
class PhysicsDrag extends Trait {
#if (!lnx_bullet)
#if (!lnx_physics)
public function new() { super(); }
#else
#if lnx_bullet
@prop public var linearLowerLimit = new Vec3(0,0,0);
@prop public var linearUpperLimit = new Vec3(0,0,0);
@prop public var angularLowerLimit = new Vec3(-10,-10,-10);
@prop public var angularUpperLimit = new Vec3(10,10,10);
var pickConstraint: bullet.Bt.Generic6DofConstraint = null;
var pickDist: Float;
var pickedBody: RigidBody = null;
var rayFrom: bullet.Bt.Vector3;
var rayTo: bullet.Bt.Vector3;
#end
#if lnx_jolt
var pickConstraint:jolt.Jt.TwoBodyConstraint = null;
var anchorBody:jolt.Jt.Body = null;
var anchorBodyId:jolt.Jt.BodyID = null;
var localPivot:Vec4 = null;
#end
var pickDist:Float;
var pickedBody:RigidBody = null;
static var v = new Vec4();
static var m = Mat4.identity();
static var first = true;
static var start = new Vec4();
static var end = new Vec4();
#if lnx_jolt
static var anchorPos:jolt.Jt.RVec3 = null;
static var anchorRot:jolt.Jt.Quat = null;
#end
public function new() {
super();
@ -45,81 +58,135 @@ class PhysicsDrag extends Trait {
var mouse = Input.getMouse();
if (mouse.started()) {
var b = physics.pickClosest(mouse.x, mouse.y);
if (b != null && b.mass > 0 && !b.body.isKinematicObject() && b.object.getTrait(PhysicsDrag) != null) {
setRays();
if (b != null && b.mass > 0 && isDraggable(b) && b.object.getTrait(PhysicsDrag) != null) {
pickedBody = b;
m.getInverse(b.object.transform.world);
var hit = physics.hitPointWorld;
var camera = iron.Scene.active.camera;
var camLoc = camera.transform.world.getLoc();
pickDist = v.set(hit.x - camLoc.x, hit.y - camLoc.y, hit.z - camLoc.z).length();
#if lnx_bullet
setRays();
m.getInverse(b.object.transform.world);
v.setFrom(hit);
v.applymat4(m);
var localPivot = new bullet.Bt.Vector3(v.x, v.y, v.z);
var tr = new bullet.Bt.Transform();
tr.setIdentity();
tr.setOrigin(localPivot);
pickConstraint = new bullet.Bt.Generic6DofConstraint(b.body, tr, false);
pickConstraint.setLinearLowerLimit(new bullet.Bt.Vector3(linearLowerLimit.x, linearLowerLimit.y, linearLowerLimit.z));
pickConstraint.setLinearUpperLimit(new bullet.Bt.Vector3(linearUpperLimit.x, linearUpperLimit.y, linearUpperLimit.z));
pickConstraint.setAngularLowerLimit(new bullet.Bt.Vector3(angularLowerLimit.x, angularLowerLimit.y, angularLowerLimit.z));
pickConstraint.setAngularUpperLimit(new bullet.Bt.Vector3(angularUpperLimit.x, angularUpperLimit.y, angularUpperLimit.z));
physics.world.addConstraint(pickConstraint, false);
#elseif lnx_jolt
// Compute local pivot on the body (world hit -> body local)
m.getInverse(b.object.transform.world);
localPivot = new Vec4();
localPivot.setFrom(hit);
localPivot.applymat4(m);
/*pickConstraint.setParam(4, 0.8, 0);
pickConstraint.setParam(4, 0.8, 1);
pickConstraint.setParam(4, 0.8, 2);
pickConstraint.setParam(4, 0.8, 3);
pickConstraint.setParam(4, 0.8, 4);
pickConstraint.setParam(4, 0.8, 5);
// Create a small kinematic anchor body at the hit point
var aPos = new jolt.Jt.RVec3(hit.x, hit.y, hit.z);
var aRot = new jolt.Jt.Quat(0, 0, 0, 1);
var anchorShape = new jolt.Jt.SphereShape(0.01);
var anchorSettings = new jolt.Jt.BodyCreationSettings(anchorShape, aPos, aRot, jolt.Jt.EMotionType.Kinematic, 0);
anchorSettings.mGravityFactor = 0.0;
anchorBody = physics.bodyInterface.CreateBody(anchorSettings);
anchorBodyId = anchorBody.GetID();
physics.bodyInterface.AddBody(anchorBodyId, jolt.Jt.EActivation.Activate);
#if hl anchorSettings.delete(); aPos.delete(); aRot.delete(); #end
pickConstraint.setParam(1, 0.1, 0);
pickConstraint.setParam(1, 0.1, 1);
pickConstraint.setParam(1, 0.1, 2);
pickConstraint.setParam(1, 0.1, 3);
pickConstraint.setParam(1, 0.1, 4);
pickConstraint.setParam(1, 0.1, 5);*/
pickDist = v.set(hit.x - rayFrom.x(), hit.y - rayFrom.y(), hit.z - rayFrom.z()).length();
// Create SixDOFConstraint: translation locked, rotation free
var sixDof = new jolt.Jt.SixDOFConstraintSettings();
sixDof.mSpace = 0; // LocalToBodyCOM
var p1 = new jolt.Jt.RVec3(localPivot.x, localPivot.y, localPivot.z);
var p2 = new jolt.Jt.RVec3(0, 0, 0);
var ax1 = new jolt.Jt.Vec3(1, 0, 0);
var ay1 = new jolt.Jt.Vec3(0, 1, 0);
var ax2 = new jolt.Jt.Vec3(1, 0, 0);
var ay2 = new jolt.Jt.Vec3(0, 1, 0);
sixDof.mPosition1 = p1;
sixDof.mPosition2 = p2;
sixDof.mAxisX1 = ax1;
sixDof.mAxisY1 = ay1;
sixDof.mAxisX2 = ax2;
sixDof.mAxisY2 = ay2;
sixDof.MakeFixedAxis(0);
sixDof.MakeFixedAxis(1);
sixDof.MakeFixedAxis(2);
sixDof.MakeFreeAxis(3);
sixDof.MakeFreeAxis(4);
sixDof.MakeFreeAxis(5);
pickConstraint = sixDof.Create(b.body, anchorBody);
physics.physicsSystem.AddConstraint(pickConstraint);
#if hl sixDof.delete(); p1.delete(); p2.delete(); ax1.delete(); ay1.delete(); ax2.delete(); ay2.delete(); #end
#end
Input.occupied = true;
}
}
else if (mouse.released()) {
#if lnx_bullet
if (pickConstraint != null) {
physics.world.removeConstraint(pickConstraint);
pickConstraint = null;
pickedBody = null;
}
#elseif lnx_jolt
if (pickConstraint != null) {
physics.physicsSystem.RemoveConstraint(pickConstraint);
pickConstraint = null;
}
if (anchorBodyId != null) {
physics.bodyInterface.RemoveBody(anchorBodyId);
physics.bodyInterface.DestroyBody(anchorBodyId);
anchorBodyId = null;
anchorBody = null;
}
localPivot = null;
#end
pickedBody = null;
Input.occupied = false;
}
else if (mouse.down()) {
if (pickConstraint != null) {
setRays();
// Keep it at the same picking distance
var dir = new bullet.Bt.Vector3(rayTo.x() - rayFrom.x(), rayTo.y() - rayFrom.y(), rayTo.z() - rayFrom.z());
dir.normalize();
dir.setX(dir.x() * pickDist);
dir.setY(dir.y() * pickDist);
dir.setZ(dir.z() * pickDist);
var newPivotB = new bullet.Bt.Vector3(rayFrom.x() + dir.x(), rayFrom.y() + dir.y(), rayFrom.z() + dir.z());
#if (js || hl)
pickConstraint.getFrameOffsetA().setOrigin(newPivotB);
#elseif cpp
pickConstraint.setFrameOffsetAOrigin(newPivotB);
if (pickedBody != null) {
#if lnx_bullet
if (pickConstraint != null) {
setRays();
var dir = new bullet.Bt.Vector3(rayTo.x() - rayFrom.x(), rayTo.y() - rayFrom.y(), rayTo.z() - rayFrom.z());
dir.normalize();
dir.setX(dir.x() * pickDist);
dir.setY(dir.y() * pickDist);
dir.setZ(dir.z() * pickDist);
var newPivotB = new bullet.Bt.Vector3(rayFrom.x() + dir.x(), rayFrom.y() + dir.y(), rayFrom.z() + dir.z());
#if (js || hl)
pickConstraint.getFrameOffsetA().setOrigin(newPivotB);
#elseif cpp
pickConstraint.setFrameOffsetAOrigin(newPivotB);
#end
}
#elseif lnx_jolt
if (pickConstraint != null) {
setAnchorTarget(physics);
}
#end
}
}
}
static var start = new Vec4();
static var end = new Vec4();
inline function isDraggable(b:RigidBody):Bool {
#if lnx_bullet
return !b.body.isKinematicObject();
#elseif lnx_jolt
return !b.staticObj && !b.animated;
#else
return true;
#end
}
#if lnx_bullet
inline function setRays() {
var mouse = Input.getMouse();
var camera = iron.Scene.active.camera;
@ -128,5 +195,37 @@ class PhysicsDrag extends Trait {
RayCaster.getDirection(start, end, mouse.x, mouse.y, camera);
rayTo = new bullet.Bt.Vector3(end.x, end.y, end.z);
}
#end
#if lnx_jolt
inline function setAnchorTarget(physics:PhysicsWorld) {
var mouse = Input.getMouse();
var camera = iron.Scene.active.camera;
var camLoc = camera.transform.world.getLoc();
RayCaster.getDirection(start, end, mouse.x, mouse.y, camera);
var dirX = end.x - camLoc.x;
var dirY = end.y - camLoc.y;
var dirZ = end.z - camLoc.z;
var len = Math.sqrt(dirX * dirX + dirY * dirY + dirZ * dirZ);
if (len > 0) { dirX /= len; dirY /= len; dirZ /= len; }
var targetX = camLoc.x + dirX * pickDist;
var targetY = camLoc.y + dirY * pickDist;
var targetZ = camLoc.z + dirZ * pickDist;
var t = iron.system.Time.fixedStep;
#if hl
RigidBody.hlPos.Set(targetX, targetY, targetZ);
physics.bodyInterface.MoveKinematic(anchorBodyId, RigidBody.hlPos, RigidBody.hlRot, t);
#else
if (anchorPos == null) {
anchorPos = new jolt.Jt.RVec3(0, 0, 0);
anchorRot = new jolt.Jt.Quat(0, 0, 0, 1);
}
anchorPos.Set(targetX, targetY, targetZ);
physics.bodyInterface.MoveKinematic(anchorBodyId, anchorPos, anchorRot, t);
#end
}
#end
#end
}

View File

@ -7,12 +7,17 @@ import iron.object.Transform;
import iron.system.Time;
import leenkx.trait.physics.PhysicsWorld;
class VehicleBody extends Trait {
#if (!lnx_physics)
class VehicleBody extends Trait { public function new() { super(); } }
class VehicleWheel { public function new() {} }
#if (!lnx_bullet)
public function new() { super(); }
#else
#if lnx_bullet
class VehicleBody extends Trait {
@prop var wheel0Name: String = "Wheel0";
@prop var wheel1Name: String = "Wheel1";
@prop var wheel2Name: String = "Wheel2";
@ -232,34 +237,44 @@ class VehicleBody extends Trait {
static inline var keyStrafeUp = "e";
static inline var keyStrafeDown = "q";
#end
#end
}
class VehicleWheel {
#if (!lnx_bullet)
public function new() {}
#else
public var isFrontWheel: Bool;
public var wheelRadius: Float;
public var wheelWidth: Float;
public var isFrontWheel: Bool;
public var wheelRadius: Float;
public var wheelWidth: Float;
var locX: Float;
var locY: Float;
var locZ: Float;
var locX: Float;
var locY: Float;
var locZ: Float;
public function new(id: Int, transform: Transform, vehicleTransform: Transform) {
wheelRadius = transform.dim.z / 2;
wheelWidth = transform.dim.x > transform.dim.y ? transform.dim.y : transform.dim.x;
public function new(id: Int, transform: Transform, vehicleTransform: Transform) {
wheelRadius = transform.dim.z / 2;
wheelWidth = transform.dim.x > transform.dim.y ? transform.dim.y : transform.dim.x;
locX = transform.loc.x;
locY = transform.loc.y;
locZ = vehicleTransform.dim.z / 2 + transform.loc.z;
}
locX = transform.loc.x;
locY = transform.loc.y;
locZ = vehicleTransform.dim.z / 2 + transform.loc.z;
public function getConnectionPoint(): bullet.Bt.Vector3 {
return new bullet.Bt.Vector3(locX, locY, locZ);
}
}
public function getConnectionPoint(): bullet.Bt.Vector3 {
return new bullet.Bt.Vector3(locX, locY, locZ);
}
#end
#elseif lnx_jolt
typedef VehicleBody = leenkx.trait.physics.jolt.VehicleBody;
typedef VehicleWheel = leenkx.trait.physics.jolt.VehicleWheel;
#else
class VehicleBody extends Trait { public function new() { super(); } }
class VehicleWheel { public function new() {} }
#end
#end
}

View File

@ -27,7 +27,7 @@ class LivePatch extends iron.Trait {
var id = Std.parseInt(lines[0]);
if (id > patchId) {
patchId = id;
js.Lib.eval(lines[1]);
js.Syntax.code("var iron = {0}, LivePatch = {1}; eval({2})", Bridge, LivePatch, lines[1]);
}
});
}

View File

@ -8,7 +8,7 @@ import iron.math.Quat;
import iron.math.Mat4;
import iron.object.Object;
@:enum abstract ConstraintType(Int) from Int to Int {
enum abstract ConstraintType(Int) from Int to Int {
var Fixed = 0;
var Point = 1;
var Hinge = 2;
@ -396,7 +396,7 @@ class PhysicsConstraint extends Trait {
}
}
@:enum abstract ConstraintAxis(Int) from Int to Int {
enum abstract ConstraintAxis(Int) from Int to Int {
var X = 0;
var Y = 1;
var Z = 2;

View File

@ -59,6 +59,7 @@ class PhysicsWorld extends Trait {
var contacts:Array<ContactPair>;
var preUpdates:Array<Void->Void> = null;
public var rbMap:Map<Int, RigidBody>;
public var bodyIdMap:Map<Int, RigidBody>;
public var conMap:Map<Int, PhysicsConstraint>;
public var constraints:Array<PhysicsConstraint> = [];
public var timeScale = 1.0;
@ -77,6 +78,21 @@ class PhysicsWorld extends Trait {
static var vec2:jolt.Jt.Vec3 = null;
static var quat1:jolt.Jt.Quat = null;
// Cached raycast objects
static var rayOrigin:jolt.Jt.RVec3 = null;
static var rayDir:jolt.Jt.Vec3 = null;
static var cachedRay:jolt.Jt.RRayCast = null;
static var rayResult:jolt.Jt.RayCastResult = null;
static var raySettings:jolt.Jt.RayCastSettings = null;
#if js
static var rayCollector:jolt.Jt.CastRayClosestHitCollisionCollector = null;
#end
static var bpFilter:jolt.Jt.BroadPhaseLayerFilter = null;
static var objFilter:jolt.Jt.ObjectLayerFilter = null;
static var bodyFilter:jolt.Jt.BodyFilter = null;
static var shapeFilter:jolt.Jt.ShapeFilter = null;
static var narrowPhaseQuery:jolt.Jt.NarrowPhaseQuery = null;
#if js
var joltInterface:jolt.Jt.JoltInterface;
static var joltReady = false;
@ -110,6 +126,7 @@ class PhysicsWorld extends Trait {
contacts = [];
rbMap = new Map();
bodyIdMap = new Map();
conMap = new Map();
active = this;
@ -157,6 +174,7 @@ class PhysicsWorld extends Trait {
#if hl
// Must initialize Jolt allocator before ANY Jolt object creation
hlJoltInit();
RigidBody.hlInitVars();
#end
if (nullvec) {
@ -164,6 +182,21 @@ class PhysicsWorld extends Trait {
vec1 = new jolt.Jt.Vec3(0, 0, 0);
vec2 = new jolt.Jt.Vec3(0, 0, 0);
quat1 = new jolt.Jt.Quat(0, 0, 0, 1);
rayOrigin = new jolt.Jt.RVec3(0, 0, 0);
rayDir = new jolt.Jt.Vec3(0, 0, 0);
cachedRay = new jolt.Jt.RRayCast(rayOrigin, rayDir);
rayResult = new jolt.Jt.RayCastResult();
raySettings = new jolt.Jt.RayCastSettings();
#if js
rayCollector = new jolt.Jt.CastRayClosestHitCollisionCollector();
#end
bpFilter = new jolt.Jt.BroadPhaseLayerFilter();
#if hl
objFilter = new jolt.Jt.ObjectLayerFilter();
#end
bodyFilter = new jolt.Jt.BodyFilter();
shapeFilter = new jolt.Jt.ShapeFilter();
narrowPhaseQuery = null;
}
if (!physicsReady) {
@ -220,6 +253,10 @@ class PhysicsWorld extends Trait {
joltInterface = new jolt.Jt.JoltInterface(settings);
physicsSystem = joltInterface.GetPhysicsSystem();
bodyInterface = physicsSystem.GetBodyInterface();
if (objFilter == null) {
objFilter = new jolt.Jt.DefaultObjectLayerFilter(objectLayerPair, 0);
}
#end
physicsReady = true;
@ -243,8 +280,9 @@ class PhysicsWorld extends Trait {
}
public function addRigidBody(body:RigidBody, activate:Bool = true) {
bodyInterface.AddBody(body.bodyId, activate ? 1 : 0);
bodyInterface.AddBody(body.bodyId, activate ? 0 : 1);
rbMap.set(body.id, body);
bodyIdMap.set(body.bodyId.GetIndex(), body);
}
public function addPhysicsConstraint(constraint:PhysicsConstraint) {
@ -262,6 +300,7 @@ class PhysicsWorld extends Trait {
bodyInterface.RemoveBody(body.bodyId);
bodyInterface.DestroyBody(body.bodyId);
rbMap.remove(body.id);
bodyIdMap.remove(body.bodyId.GetIndex());
}
public function removePhysicsConstraint(constraint:PhysicsConstraint) {
@ -354,41 +393,39 @@ class PhysicsWorld extends Trait {
var dirY = to.y - from.y;
var dirZ = to.z - from.z;
var origin = new jolt.Jt.RVec3(from.x, from.y, from.z);
var direction = new jolt.Jt.Vec3(dirX, dirY, dirZ);
var ray = new jolt.Jt.RRayCast(origin, direction);
var result = new jolt.Jt.RayCastResult();
var narrowPhase = physicsSystem.GetNarrowPhaseQuery();
var didHit = narrowPhase.CastRay(ray, result);
// reuse cached objects
rayOrigin.Set(from.x, from.y, from.z);
rayDir.Set(dirX, dirY, dirZ);
cachedRay.mOrigin = rayOrigin;
cachedRay.mDirection = rayDir;
if (narrowPhaseQuery == null){
narrowPhaseQuery = physicsSystem.GetNarrowPhaseQuery();
}
rayResult.mFraction = 1.0;
#if hl
var didHit = narrowPhaseQuery.CastRay(cachedRay, rayResult, raySettings, bpFilter, objFilter, bodyFilter, shapeFilter);
#else
rayCollector.Reset();
narrowPhaseQuery.CastRay(cachedRay, raySettings, rayCollector, bpFilter, objFilter, bodyFilter, shapeFilter);
var didHit = rayCollector.HadHit();
if (didHit) {
var bodyId = result.mBodyID;
var fraction = result.mFraction;
var hit = rayCollector.mHit;
rayResult.mBodyID = hit.mBodyID;
rayResult.mFraction = hit.mFraction;
}
#end
var fraction = rayResult.mFraction;
if (didHit && fraction < 1.0) {
var bodyId = rayResult.mBodyID;
hitPointWorld.set(from.x + dirX * fraction, from.y + dirY * fraction, from.z + dirZ * fraction);
// Find rigid body by ID
for (rb in rbMap) {
if (rb.bodyId.GetIndex() == bodyId.GetIndex()) {
#if hl
origin.delete();
direction.delete();
ray.delete();
result.delete();
#end
return new Hit(rb, hitPointWorld.clone(), hitNormalWorld.clone());
}
var rb = bodyIdMap.get(bodyId.GetIndex());
if (rb != null) {
return new Hit(rb, hitPointWorld.clone(), hitNormalWorld.clone());
}
}
#if hl
origin.delete();
direction.delete();
ray.delete();
result.delete();
#end
return null;
}

View File

@ -9,7 +9,7 @@ import iron.data.SceneFormat;
import iron.object.Transform;
import iron.object.MeshObject;
@:enum abstract Shape(Int) from Int to Int {
enum abstract Shape(Int) from Int to Int {
var Box = 0;
var Sphere = 1;
var ConvexHull = 2;
@ -410,14 +410,14 @@ class RigidBody extends Trait {
var scalePos = mo.data.scalePos;
var numVerts = Std.int(positions.length / 4);
var settings:Dynamic = untyped __js__("new Jolt.ConvexHullShapeSettings()");
var settings:Dynamic = new jolt.Jt.ConvexHullShapeSettings();
var points:Dynamic = untyped settings.mPoints;
points.clear();
for (i in 0...numVerts) {
var x:Float = (positions[i * 4] / 32767) * scalePos * scale.x;
var y:Float = (positions[i * 4 + 1] / 32767) * scalePos * scale.y;
var z:Float = (positions[i * 4 + 2] / 32767) * scalePos * scale.z;
var pt:Dynamic = untyped __js__("new Jolt.Vec3({0}, {1}, {2})", x, y, z);
var pt:Dynamic = new jolt.Jt.Vec3(x, y, z);
untyped points.push_back(pt);
}
@ -439,7 +439,7 @@ class RigidBody extends Trait {
var scalePos = mo.data.scalePos;
var numVerts = Std.int(positions.length / 4);
var settings:Dynamic = untyped __js__("new Jolt.MeshShapeSettings()");
var settings:Dynamic = new jolt.Jt.MeshShapeSettings();
var verts:Dynamic = untyped settings.mTriangleVertices;
var tris:Dynamic = untyped settings.mIndexedTriangles;
verts.clear();
@ -449,14 +449,14 @@ class RigidBody extends Trait {
var x:Float = (positions[i * 4] / 32767) * scalePos * scale.x;
var y:Float = (positions[i * 4 + 1] / 32767) * scalePos * scale.y;
var z:Float = (positions[i * 4 + 2] / 32767) * scalePos * scale.z;
var v:Dynamic = untyped __js__("new Jolt.Float3({0}, {1}, {2})", x, y, z);
var v:Dynamic = new jolt.Jt.Float3(x, y, z);
untyped verts.push_back(v);
}
for (indexArray in indices) {
var numTris = Std.int(indexArray.length / 3);
for (i in 0...numTris) {
var tri:Dynamic = untyped __js__("new Jolt.IndexedTriangle()");
var tri:Dynamic = new jolt.Jt.IndexedTriangle();
untyped tri.set_mIdx(0, indexArray[i * 3]);
untyped tri.set_mIdx(1, indexArray[i * 3 + 1]);
untyped tri.set_mIdx(2, indexArray[i * 3 + 2]);
@ -482,6 +482,25 @@ class RigidBody extends Trait {
var currentRotZ:Float = 0;
var currentRotW:Float = 1;
#if hl
public static var hlPos:jolt.Jt.RVec3 = null;
public static var hlRot:jolt.Jt.Quat = null;
public static var hlVel:jolt.Jt.Vec3 = null;
public static var hlForce:jolt.Jt.Vec3 = null;
public static var hlPosR:jolt.Jt.RVec3 = null;
public static var hlQuat:jolt.Jt.Quat = null;
public static function hlInitVars() {
if (hlPos != null) return;
hlPos = new jolt.Jt.RVec3(0, 0, 0);
hlRot = new jolt.Jt.Quat(0, 0, 0, 1);
hlVel = new jolt.Jt.Vec3(0, 0, 0);
hlForce = new jolt.Jt.Vec3(0, 0, 0);
hlPosR = new jolt.Jt.RVec3(0, 0, 0);
hlQuat = new jolt.Jt.Quat(0, 0, 0, 1);
}
#end
public function physicsUpdate() {
if (!ready)
return;
@ -494,18 +513,26 @@ class RigidBody extends Trait {
return;
}
var active = physics.bodyInterface.IsActive(bodyId);
if (!active) {
// Activate body if sleeping
physics.bodyInterface.ActivateBody(bodyId);
#if hl
if (!body.IsActive())
return;
}
// Read position and rotation from Jolt into cached state
body.GetPositionInto(hlPos);
body.GetRotationInto(hlRot);
currentPosX = hlPos.GetX();
currentPosY = hlPos.GetY();
currentPosZ = hlPos.GetZ();
currentRotX = hlRot.GetX();
currentRotY = hlRot.GetY();
currentRotZ = hlRot.GetZ();
currentRotW = hlRot.GetW();
#else
// JS/WASM: re-activate sleeping bodies (Jolt deactivates even with mAllowSleeping=false in WASM)
if (!physics.bodyInterface.IsActive(bodyId))
physics.bodyInterface.ActivateBody(bodyId);
var p = physics.bodyInterface.GetPosition(bodyId);
var q = physics.bodyInterface.GetRotation(bodyId);
#if js
currentPosX = cast p.GetX();
currentPosY = cast p.GetY();
currentPosZ = cast p.GetZ();
@ -513,17 +540,6 @@ class RigidBody extends Trait {
currentRotY = cast q.GetY();
currentRotZ = cast q.GetZ();
currentRotW = cast q.GetW();
// JS: getter return values use internal WASM wrappers - do NOT destroy
#else
currentPosX = p.GetX();
currentPosY = p.GetY();
currentPosZ = p.GetZ();
currentRotX = q.GetX();
currentRotY = q.GetY();
currentRotZ = q.GetZ();
currentRotW = q.GetW();
p.delete();
q.delete();
#end
}
@ -554,50 +570,78 @@ class RigidBody extends Trait {
// Physics methods
public function applyForce(force:Vec4, ?loc:Vec4) {
activate();
#if hl
hlForce.Set(force.x, force.y, force.z);
if (loc == null) {
physics.bodyInterface.AddForce(bodyId, hlForce);
} else {
hlPosR.Set(loc.x, loc.y, loc.z);
physics.bodyInterface.AddForceAtPosition(bodyId, hlForce, hlPosR);
}
#else
if (loc == null) {
var f = new jolt.Jt.Vec3(force.x, force.y, force.z);
physics.bodyInterface.AddForce(bodyId, f);
#if hl f.delete(); #end
} else {
var f = new jolt.Jt.Vec3(force.x, force.y, force.z);
var l = new jolt.Jt.RVec3(loc.x, loc.y, loc.z);
physics.bodyInterface.AddForceAtPosition(bodyId, f, l);
#if hl f.delete(); l.delete(); #end
}
#end
}
public function applyImpulse(impulse:Vec4, ?loc:Vec4) {
activate();
#if hl
hlForce.Set(impulse.x, impulse.y, impulse.z);
if (loc == null) {
physics.bodyInterface.AddImpulse(bodyId, hlForce);
} else {
hlPosR.Set(loc.x, loc.y, loc.z);
physics.bodyInterface.AddImpulseAtPosition(bodyId, hlForce, hlPosR);
}
#else
if (loc == null) {
var i = new jolt.Jt.Vec3(impulse.x, impulse.y, impulse.z);
physics.bodyInterface.AddImpulse(bodyId, i);
#if hl i.delete(); #end
} else {
var i = new jolt.Jt.Vec3(impulse.x, impulse.y, impulse.z);
var l = new jolt.Jt.RVec3(loc.x, loc.y, loc.z);
physics.bodyInterface.AddImpulseAtPosition(bodyId, i, l);
#if hl i.delete(); l.delete(); #end
}
#end
}
public function applyTorque(torque:Vec4) {
activate();
#if hl
hlForce.Set(torque.x, torque.y, torque.z);
physics.bodyInterface.AddTorque(bodyId, hlForce);
#else
var t = new jolt.Jt.Vec3(torque.x, torque.y, torque.z);
physics.bodyInterface.AddTorque(bodyId, t);
#if hl t.delete(); #end
#end
}
public function applyTorqueImpulse(impulse:Vec4) {
activate();
#if hl
hlForce.Set(impulse.x, impulse.y, impulse.z);
physics.bodyInterface.AddAngularImpulse(bodyId, hlForce);
#else
var i = new jolt.Jt.Vec3(impulse.x, impulse.y, impulse.z);
physics.bodyInterface.AddAngularImpulse(bodyId, i);
#if hl i.delete(); #end
#end
}
public function setLinearVelocity(v:Vec4) {
#if hl
hlVel.Set(v.x, v.y, v.z);
physics.bodyInterface.SetLinearVelocity(bodyId, hlVel);
#else
var vel = new jolt.Jt.Vec3(v.x, v.y, v.z);
physics.bodyInterface.SetLinearVelocity(bodyId, vel);
#if hl vel.delete(); #end
#end
}
public function getLinearVelocity():Vec4 {
@ -608,9 +652,13 @@ class RigidBody extends Trait {
}
public function setAngularVelocity(v:Vec4) {
#if hl
hlVel.Set(v.x, v.y, v.z);
physics.bodyInterface.SetAngularVelocity(bodyId, hlVel);
#else
var vel = new jolt.Jt.Vec3(v.x, v.y, v.z);
physics.bodyInterface.SetAngularVelocity(bodyId, vel);
#if hl vel.delete(); #end
#end
}
public function getAngularVelocity():Vec4 {
@ -643,15 +691,23 @@ class RigidBody extends Trait {
}
public function setPosition(pos:Vec4) {
#if hl
hlPos.Set(pos.x, pos.y, pos.z);
physics.bodyInterface.SetPosition(bodyId, hlPos, 0);
#else
var p = new jolt.Jt.RVec3(pos.x, pos.y, pos.z);
physics.bodyInterface.SetPosition(bodyId, p, 0);
#if hl p.delete(); #end
#end
}
public function setRotation(rot:Quat) {
#if hl
hlQuat.Set(rot.x, rot.y, rot.z, rot.w);
physics.bodyInterface.SetRotation(bodyId, hlQuat, 0);
#else
var q = new jolt.Jt.Quat(rot.x, rot.y, rot.z, rot.w);
physics.bodyInterface.SetRotation(bodyId, q, 0);
#if hl q.delete(); #end
#end
}
public function syncTransform() {

View File

@ -2198,25 +2198,28 @@ class LeenkxExporter:
light_object = light_objects[0] if len(light_objects) > 0 else None
objtype = light_ref.type
color = [light_ref.color[0], light_ref.color[1], light_ref.color[2]]
if light_ref.use_temperature:
temperature_color = light_ref.temperature_color
color[0] *= temperature_color[0]
color[1] *= temperature_color[1]
color[2] *= temperature_color[2]
if bpy.app.version >= (4, 5, 0):
if light_ref.use_temperature:
temperature_color = light_ref.temperature_color
color[0] *= temperature_color[0]
color[1] *= temperature_color[1]
color[2] *= temperature_color[2]
strength = light_ref.energy * math.pow(2.0, light_ref.exposure)
if not light_ref.normalize:
area = 0.0
try:
if light_object is not None:
area = light_ref.area(matrix_world=light_object.matrix_world)
else:
strength = light_ref.energy * math.pow(2.0, light_ref.exposure)
if not light_ref.normalize:
area = 0.0
try:
if light_object is not None:
area = light_ref.area(matrix_world=light_object.matrix_world)
else:
area = light_ref.area()
except TypeError:
area = light_ref.area()
except TypeError:
area = light_ref.area()
if area > 0.0:
strength *= area
if area > 0.0:
strength *= area
else:
strength = light_ref.energy
out_light = {
'name': object_ref[1]["structName"],

View File

@ -3,7 +3,6 @@ import os
import queue
import sys
import threading
import time
import types
from typing import Dict, Tuple, Callable, Set
@ -13,6 +12,7 @@ from bpy.app.handlers import persistent
import lnx
import lnx.api
import lnx.nodes_logic
import lnx.render_engine
import lnx.make_state as state
import lnx.utils
import lnx.utils_vs
@ -25,6 +25,7 @@ if lnx.is_reload(__name__):
log = lnx.reload_module(log)
lnx_nodes = lnx.reload_module(lnx_nodes)
lnx.nodes_logic = lnx.reload_module(lnx.nodes_logic)
lnx.render_engine = lnx.reload_module(lnx.render_engine)
make = lnx.reload_module(make)
state = lnx.reload_module(state)
props = lnx.reload_module(props)
@ -33,10 +34,8 @@ if lnx.is_reload(__name__):
else:
lnx.enable_reload(__name__)
# Module-level storage for active threads (eliminates re-queuing overhead)
# Module-level storage for active threads
_active_threads: Dict[threading.Thread, Callable] = {}
_last_poll_time = 0.0
_consecutive_empty_polls = 0
_last_render_engine = None
@persistent
@ -68,10 +67,11 @@ def on_depsgraph_update_post(self):
# Send last operator to Krom
wrd = bpy.data.worlds['Lnx']
if state.proc_play is not None and state.target == 'krom' and wrd.lnx_live_patch:
ops = bpy.context.window_manager.operators
if len(ops) > 0 and ops[-1] is not None:
live_patch.on_operator(ops[-1].bl_idname)
if (state.proc_play is not None or make._viewport_processes) and state.target == 'krom' and wrd.lnx_live_patch:
if not live_patch.on_depsgraph_update(depsgraph.updates):
ops = bpy.context.window_manager.operators
if len(ops) > 0 and ops[-1] is not None:
live_patch.on_operator(ops[-1].bl_idname)
# Hacky solution to update leenkx props after operator executions.
# bpy.context.active_operator doesn't always exist, in some cases
@ -169,6 +169,14 @@ def check_render_engine() -> float:
elif _last_render_engine == 'KROM_VIEWPORT':
try:
for vid, engine in list(lnx.render_engine._active_krom_engines.items()):
try:
engine._restore_overlay()
except:
pass
lnx.render_engine._active_krom_engines.clear()
lnx.render_engine._active_krom_engine = None
lnx.render_engine._active_viewport_id = None
make.stop_viewport()
except Exception as e:
log.warn(f'Failed to stop viewport: {e}')
@ -183,113 +191,31 @@ def check_render_engine() -> float:
def poll_threads() -> float:
"""
Improved thread polling with:
- No re-queuing overhead
- Batch processing of completed threads
- Adaptive timing based on activity
- Better memory management
- Simplified logic flow
"""
global _last_poll_time, _consecutive_empty_polls
current_time = time.time()
# Process all new threads from queue at once (batch processing)
new_threads_added = 0
"""Polls the thread callback queue and processes completed threads."""
# Drain queue into active threads
try:
while True:
thread, callback = make.thread_callback_queue.get(block=False)
_active_threads[thread] = callback
new_threads_added += 1
except queue.Empty:
pass
# Early return if no active threads
if not _active_threads:
_consecutive_empty_polls += 1
# Adaptive timing: longer intervals when consistently empty
if _consecutive_empty_polls > 10:
return 0.5 # Back off when no activity
return 0.25
# Reset empty poll counter when we have active threads
_consecutive_empty_polls = 0
# Find completed threads (single pass, no re-queuing)
completed_threads = []
# Join and callback all completed threads
for thread in list(_active_threads.keys()):
if not thread.is_alive():
completed_threads.append(thread)
# Batch process all completed threads
if completed_threads:
_process_completed_threads(completed_threads)
# Adaptive timing based on activity level
active_count = len(_active_threads)
if active_count == 0:
return 0.25
elif active_count <= 3:
return 0.05 # Medium frequency for low activity
else:
return 0.01 # High frequency for high activity
callback = _active_threads.pop(thread)
try:
thread.join()
callback()
except Exception as e:
bpy.app.timers.unregister(poll_threads)
bpy.app.timers.register(poll_threads, first_interval=0.01, persistent=True)
raise e
def _process_completed_threads(completed_threads: list) -> None:
"""Process a batch of completed threads with robust error handling."""
for thread in completed_threads:
callback = _active_threads.pop(thread) # Remove from tracking
try:
thread.join() # Should be instant since thread is dead
callback()
except Exception as e:
# Robust error recovery
_handle_callback_error(e)
continue # Continue processing other threads
# Explicit cleanup for better memory management
del thread, callback
def _handle_callback_error(exception: Exception) -> None:
"""Centralized error handling with better recovery."""
try:
# Try to unregister existing timer
bpy.app.timers.unregister(poll_threads)
except ValueError:
pass # Timer wasn't registered, that's fine
# Re-register timer with slightly longer interval for stability
bpy.app.timers.register(poll_threads, first_interval=0.1, persistent=True)
# Re-raise the original exception after ensuring timer continuity
raise exception
def cleanup_polling_system() -> None:
"""Optional cleanup function for proper shutdown."""
global _active_threads, _consecutive_empty_polls
# Wait for remaining threads to complete (with timeout)
for thread in list(_active_threads.keys()):
if thread.is_alive():
thread.join(timeout=1.0) # 1 second timeout
# Clear tracking structures
_active_threads.clear()
_consecutive_empty_polls = 0
# Unregister timer
try:
bpy.app.timers.unregister(poll_threads)
except ValueError:
pass
def get_polling_stats() -> dict:
"""Get statistics about the polling system for monitoring."""
return {
'active_threads': len(_active_threads),
'consecutive_empty_polls': _consecutive_empty_polls,
'thread_ids': [t.ident for t in _active_threads.keys()]
}
return 0.01
loaded_py_libraries: Dict[str, types.ModuleType] = {}

View File

@ -32,6 +32,9 @@ else:
__running = False
"""Whether live patch is currently active"""
scene_cache = {}
"""Keys: (event_id, object_name) -> cached value tuple"""
# Any object can act as a message bus owner
msgbus_owner = object()
@ -65,6 +68,62 @@ def stop():
bpy.msgbus.clear_by_owner(msgbus_owner)
def get_obj_transforms(obj):
loc = (round(obj.location.x, 6), round(obj.location.y, 6), round(obj.location.z, 6))
rot = obj.rotation_euler.to_quaternion()
rot = (round(rot.x, 6), round(rot.y, 6), round(rot.z, 6), round(rot.w, 6))
scale = (round(obj.scale.x, 6), round(obj.scale.y, 6), round(obj.scale.z, 6))
return loc, rot, scale
def get_light_props(obj):
light = obj.data
color = (round(light.color[0], 6), round(light.color[1], 6), round(light.color[2], 6))
energy = round(light.energy, 6)
return color, energy
DEPSGRAPH_HANDLERS = {
'is_updated_transform': [
('obj_location', lambda obj: get_obj_transforms(obj)[0]),
('obj_rotation', lambda obj: get_obj_transforms(obj)[1]),
('obj_scale', lambda obj: get_obj_transforms(obj)[2]),
],
}
def on_depsgraph_update(updates):
if not __running:
return False
changed = False
for update in updates:
uid = update.id
if isinstance(uid, bpy.types.Object):
name = uid.name
if update.is_updated_transform:
for event_id, extractor in DEPSGRAPH_HANDLERS['is_updated_transform']:
current = extractor(uid)
cache_key = (event_id, name)
if current != scene_cache.get(cache_key):
scene_cache[cache_key] = current
send_event(event_id)
changed = True
if update.is_updated_shading and isinstance(uid.data, bpy.types.Light):
color, energy = get_light_props(uid)
for event_id, current in (('light_color', color), ('light_energy', energy)):
cache_key = (event_id, uid.data.name)
if current != scene_cache.get(cache_key):
scene_cache[cache_key] = current
send_event(event_id)
changed = True
return changed
def patch_export():
"""Re-export the current scene and update the game accordingly."""
if not __running or state.proc_build is not None:

View File

@ -33,8 +33,10 @@ def init_categories():
lnx_nodes.add_category('Input', icon='GREASEPENCIL', section="basic")
lnx_nodes.add_category('Native', icon='MEMORY', section="basic",
description="The Native category contains nodes which interact with the system (Input/Output functionality, etc.) or Haxe.")
lnx_nodes.add_category('Browser', icon='URL', section="basic")
lnx_nodes.add_category('Krom Runtime', icon='WINDOW', section="basic",
description="Krom specific nodes for WebView, Networking, Window, Display, File and System operations etc.")
lnx_nodes.add_category('Camera', icon='OUTLINER_OB_CAMERA', section="data")
lnx_nodes.add_category('Material', icon='MATERIAL', section="data")
lnx_nodes.add_category('Light', icon='LIGHT', section="data")

View File

@ -0,0 +1,16 @@
from lnx.logicnode.lnx_nodes import *
class KromCopyToClipboardNode(LnxLogicTreeNode):
"""Copies the given text to the system clipboard"""
bl_idname = 'LNKromCopyToClipboardNode'
bl_label = 'Copy To Clipboard'
lnx_category = 'Krom Runtime'
lnx_section = 'system'
lnx_version = 1
def lnx_init(self, context):
self.add_input('LnxNodeSocketAction', 'In')
self.add_input('LnxStringSocket', 'Text')
self.add_output('LnxNodeSocketAction', 'Out')

View File

@ -0,0 +1,15 @@
from lnx.logicnode.lnx_nodes import *
class KromDelayIdleSleepNode(LnxLogicTreeNode):
"""Delays idle sleep on mobile platforms to keep the runtime active"""
bl_idname = 'LNKromDelayIdleSleepNode'
bl_label = 'Delay Idle Sleep'
lnx_category = 'Krom Runtime'
lnx_section = 'system'
lnx_version = 1
def lnx_init(self, context):
self.add_input('LnxNodeSocketAction', 'In')
self.add_output('LnxNodeSocketAction', 'Out')

View File

@ -0,0 +1,16 @@
from lnx.logicnode.lnx_nodes import *
class KromDeleteFileNode(LnxLogicTreeNode):
"""Deletes the file at the given path."""
bl_idname = 'LNKromDeleteFileNode'
bl_label = 'Delete File'
lnx_category = 'Krom Runtime'
lnx_section = 'file'
lnx_version = 1
def lnx_init(self, context):
self.add_input('LnxNodeSocketAction', 'In')
self.add_input('LnxStringSocket', 'Path')
self.add_output('LnxNodeSocketAction', 'Out')

View File

@ -0,0 +1,15 @@
from lnx.logicnode.lnx_nodes import *
class KromFileExistsNode(LnxLogicTreeNode):
"""Checks whether a file exists at the given path"""
bl_idname = 'LNKromFileExistsNode'
bl_label = 'File Exists'
lnx_category = 'Krom Runtime'
lnx_section = 'file'
lnx_version = 1
def lnx_init(self, context):
self.add_input('LnxStringSocket', 'Path')
self.add_output('LnxBoolSocket', 'Exists')

View File

@ -0,0 +1,15 @@
from lnx.logicnode.lnx_nodes import *
class KromGetArgNode(LnxLogicTreeNode):
"""Returns the command-line argument at the given index"""
bl_idname = 'LNKromGetArgNode'
bl_label = 'Get Arg'
lnx_category = 'Krom Runtime'
lnx_section = 'system'
lnx_version = 1
def lnx_init(self, context):
self.add_input('LnxIntSocket', 'Index')
self.add_output('LnxStringSocket', 'Arg')

View File

@ -0,0 +1,13 @@
from lnx.logicnode.lnx_nodes import *
class KromGetArgCountNode(LnxLogicTreeNode):
"""Returns the number of command line arguments passed to the application"""
bl_idname = 'LNKromGetArgCountNode'
bl_label = 'Get Arg Count'
lnx_category = 'Krom Runtime'
lnx_section = 'system'
lnx_version = 1
def lnx_init(self, context):
self.add_output('LnxIntSocket', 'Count')

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