forked from LeenkxTeam/LNXSDK
Compare commits
263 Commits
00493bed9c
...
e2002e_0
| Author | SHA1 | Date | |
|---|---|---|---|
| d5f3f05ab6 | |||
| 3bee97a560 | |||
| 4f4f28d62f | |||
| 7076fb6b7e | |||
| b72a22b5e9 | |||
| b265ab863c | |||
| f5fa754e17 | |||
| 48f5575e4e | |||
| f2c4be6336 | |||
| 2ddc938db8 | |||
| 5eb735ada2 | |||
| 9894cc20f2 | |||
| dbe6d0829a | |||
| 6f383e2ab2 | |||
| 5c2d29d7ce | |||
| 28579e14d7 | |||
| 2ec6f43cc5 | |||
| 027021815a | |||
| b9b387803f | |||
| e05d9d0237 | |||
| c908e6cad2 | |||
| 506a0a0245 | |||
| 5cf33724e4 | |||
| ac5aa3d19c | |||
| 0c534ee632 | |||
| 69a2bb1e7e | |||
| e3e7855d26 | |||
| f7917974f8 | |||
| fa2d8f05d5 | |||
| 5b86f32b51 | |||
| 73fcb55acc | |||
| c24baa3364 | |||
| 4517c4863f | |||
| 1299306e09 | |||
| f97d8fd846 | |||
| 8f8d4b1376 | |||
| a926fa8dbb | |||
| 6c3efa6c83 | |||
| 21afad6d09 | |||
| 04c6983a09 | |||
| 45966ef0bb | |||
| a72edc6203 | |||
| 6af1ef2df1 | |||
| 46e3047877 | |||
| de74af215a | |||
| b6e96553c2 | |||
| 58e009f709 | |||
| e88f101ca6 | |||
| d28d59b9e6 | |||
| a4398c7279 | |||
| abedfd799e | |||
| 4520422f6b | |||
| 88418c06c3 | |||
| aedc2783ab | |||
| 1505414c4c | |||
| fa818602c4 | |||
| 79dc458671 | |||
| 8e635fb1e9 | |||
| 4c2e6ab26a | |||
| 2371e3777e | |||
| b458b77e5c | |||
| 9b76f8cca9 | |||
| 5f2acb209e | |||
| 6fc446e7a9 | |||
| 71e57026e1 | |||
| 5288a98440 | |||
| 35e346be39 | |||
| 843ef0b058 | |||
| 177890bf39 | |||
| 9ac37e6dc7 | |||
| e697437778 | |||
| c94fc0fd97 | |||
| cd0a6f6788 | |||
| 4400e0e9c8 | |||
| 20cf07cfc3 | |||
| 1939f19c05 | |||
| 0d2b152ccb | |||
| 7f58e0fc85 | |||
| 0e4a6575c7 | |||
| 024676f43a | |||
| 8fe758862c | |||
| 1f3d1b47ae | |||
| f659a3c2be | |||
| 6eeb9017d4 | |||
| afe89c3834 | |||
| 8b695f72bb | |||
| 3d99fa60c0 | |||
| 43be7729ba | |||
| de0b1075c2 | |||
| c7aba23fa4 | |||
| 881f3267cc | |||
| 19b79d61c7 | |||
| fcbab54a0c | |||
| 8fd05d5514 | |||
| ad4013ed75 | |||
| 590e6219d5 | |||
| 8ac567b57b | |||
| 43b7ae7060 | |||
| 662981fa03 | |||
| a3866fb604 | |||
| 29e9e71a6a | |||
| bfb85b0a3b | |||
| ef99b800e0 | |||
| 7cca955fc5 | |||
| 7e7bbd5eae | |||
| c31b2a18ad | |||
| fb47bf2564 | |||
| 7ae6750620 | |||
| 5b87010f76 | |||
| 97e952fc15 | |||
| b440539d65 | |||
| fbf63e4f17 | |||
| a318e08072 | |||
| 7ae458a9dd | |||
| 60a9db6459 | |||
| 3b5a93c92a | |||
| 4af990796e | |||
| 9fb4916c3c | |||
| f61d5833bb | |||
| 40b52be713 | |||
| 07d8422f22 | |||
| 7179d42b27 | |||
| 9984615f8c | |||
| 99a5d7d445 | |||
| 5e0acd3d5d | |||
| 0430e06acd | |||
| f4077e461b | |||
| 28943f1522 | |||
| df4feac132 | |||
| 82412dbf81 | |||
| 6afc209db7 | |||
| e9aae53be9 | |||
| a65675ef75 | |||
| 8f073c5ae1 | |||
| 08d08e42d9 | |||
| a1ee335c68 | |||
| de6bf8a08a | |||
| 9622f35b73 | |||
| da666e6d23 | |||
| 6ff7804ec1 | |||
| 0265ef5b64 | |||
| 53c5000975 | |||
| 7647231696 | |||
| 1e583a795d | |||
| da25d8c313 | |||
| 70695b3b31 | |||
| 858537d54c | |||
| 2998a5585e | |||
| c35c59e6a9 | |||
| 15ac833f2c | |||
| 8077f00ada | |||
| b9848cd2dc | |||
| 58140ad583 | |||
| e922cc38e6 | |||
| 9e2b601445 | |||
| 0439dde4a8 | |||
| 57f0e937d0 | |||
| e234c8615c | |||
| 2e7ccb5151 | |||
| e594518e57 | |||
| a41be0f436 | |||
| 1306033b36 | |||
| eee0011cdd | |||
| 315ac0bd16 | |||
| f289e6f89c | |||
| b89ebfd9c6 | |||
| a142b248ef | |||
| 700d236bf1 | |||
| f228eab8d3 | |||
| 863d884b76 | |||
| 34e0f5a282 | |||
| 45e2e52008 | |||
| 444a215e63 | |||
| fb2d2a1a7c | |||
| f88c04abea | |||
| 6fdd4b3f70 | |||
| a389c27d75 | |||
| 1909c3da9f | |||
| 1c648b0433 | |||
| 5824bd91aa | |||
| 43fe559eef | |||
| 12c09545ce | |||
| 0e60951ec9 | |||
| ccb8b358d3 | |||
| 3103a976a6 | |||
| 1a8586777b | |||
| 3721c774a1 | |||
| a58fba408d | |||
| 82fa7bcfe3 | |||
| 268fba6cd5 | |||
| 4ab14ce6c8 | |||
| 9023e8d1da | |||
| b58c7a9632 | |||
| 99b70622f5 | |||
| 647b73b746 | |||
| 935c30ec08 | |||
| 0b0d597f89 | |||
| d5878afb30 | |||
| 96b55a1a56 | |||
| 91b3072305 | |||
| 1d0b338d92 | |||
| 8e83c0d0d0 | |||
| 927baec4df | |||
| f5c9e70d1a | |||
| 709e0ed9cb | |||
| 0423a735fc | |||
| 7bcf985023 | |||
| bd413917fc | |||
| 852377f60d | |||
| e17e9a8e35 | |||
| 4055c979a1 | |||
| 06b003ecdb | |||
| fd7f215bb2 | |||
| 6a1df9ec46 | |||
| deccac3c46 | |||
| 432b0210b2 | |||
| 14cf5cebed | |||
| d23232205b | |||
| 2307e1504f | |||
| 1ebfecb644 | |||
| 175b575b23 | |||
| 63943a9cbf | |||
| 224d9be76f | |||
| 62d3c8757b | |||
| 3785f93573 | |||
| ffb276745f | |||
| d1c9258da5 | |||
| 3e0cd2be35 | |||
| 1fd1973470 | |||
| a01c72ef76 | |||
| 4b01a562c9 | |||
| 88c7c5b99e | |||
| 6972d9abc4 | |||
| 63c6b9d98b | |||
| 313d24bbc8 | |||
| 6d2812306d | |||
| e84d6ada84 | |||
| 5057f2b946 | |||
| 2715fe3398 | |||
| 7cb8b8a2d2 | |||
| cd606009e0 | |||
| 965162b101 | |||
| c61a57bfb3 | |||
| 2434ad07f2 | |||
| cdc425fbcb | |||
| 846bb28c86 | |||
| 7fabd77ef8 | |||
| fb1a5c88bf | |||
| e05c83a8bb | |||
| ee4f62e881 | |||
| 59f8dff22f | |||
| 5572226ac5 | |||
| d04874e0b3 | |||
| ef8b3a99ab | |||
| 1d3254a237 | |||
| 188af4a50f | |||
| c45baaf396 | |||
| 4b1da08819 | |||
| aeb353fb20 | |||
| 65961b1593 | |||
| 1c472155e2 | |||
| 4238f0b2a0 | |||
| b40aadf76c |
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
*.hdr binary
|
||||||
|
blender/lnx/props.py ident
|
||||||
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
*.DS_Store
|
||||||
@ -2,10 +2,12 @@
|
|||||||
-cp ../Kha/Backends/Krom
|
-cp ../Kha/Backends/Krom
|
||||||
-cp ../leenkx/Sources
|
-cp ../leenkx/Sources
|
||||||
-cp ../iron/Sources
|
-cp ../iron/Sources
|
||||||
|
-cp ../lib/aura/Sources
|
||||||
-cp ../lib/haxebullet/Sources
|
-cp ../lib/haxebullet/Sources
|
||||||
-cp ../lib/haxerecast/Sources
|
-cp ../lib/haxerecast/Sources
|
||||||
-cp ../lib/zui/Sources
|
-cp ../lib/zui/Sources
|
||||||
--macro include('iron', true, null, ['../iron/Sources'])
|
--macro include('iron', true, null, ['../iron/Sources'])
|
||||||
|
--macro include('aura', true, null, ['../lib/aura/Sources'])
|
||||||
--macro include('haxebullet', true, null, ['../lib/haxebullet/Sources'])
|
--macro include('haxebullet', true, null, ['../lib/haxebullet/Sources'])
|
||||||
--macro include('haxerecast', true, null, ['../lib/haxerecast/Sources'])
|
--macro include('haxerecast', true, null, ['../lib/haxerecast/Sources'])
|
||||||
--macro include('leenkx', true, ['leenkx.network'], ['../leenkx/Sources','../iron/Sources'])
|
--macro include('leenkx', true, ['leenkx.network'], ['../leenkx/Sources','../iron/Sources'])
|
||||||
|
|||||||
26
leenkx.py
26
leenkx.py
@ -24,7 +24,7 @@ import textwrap
|
|||||||
import threading
|
import threading
|
||||||
import traceback
|
import traceback
|
||||||
import typing
|
import typing
|
||||||
from typing import Callable, Optional
|
from typing import Callable, Optional, List
|
||||||
import webbrowser
|
import webbrowser
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
@ -33,6 +33,12 @@ from bpy.props import *
|
|||||||
from bpy.types import Operator, AddonPreferences
|
from bpy.types import Operator, AddonPreferences
|
||||||
|
|
||||||
|
|
||||||
|
if bpy.app.version < (2, 90, 0):
|
||||||
|
ListType = List
|
||||||
|
else:
|
||||||
|
ListType = list
|
||||||
|
|
||||||
|
|
||||||
class SDKSource(IntEnum):
|
class SDKSource(IntEnum):
|
||||||
PREFS = 0
|
PREFS = 0
|
||||||
LOCAL = 1
|
LOCAL = 1
|
||||||
@ -73,9 +79,10 @@ def detect_sdk_path():
|
|||||||
area = win.screen.areas[0]
|
area = win.screen.areas[0]
|
||||||
area_type = area.type
|
area_type = area.type
|
||||||
area.type = "INFO"
|
area.type = "INFO"
|
||||||
with bpy.context.temp_override(window=win, screen=win.screen, area=area):
|
if bpy.app.version >= (2, 92, 0):
|
||||||
bpy.ops.info.select_all(action='SELECT')
|
with bpy.context.temp_override(window=win, screen=win.screen, area=area):
|
||||||
bpy.ops.info.report_copy()
|
bpy.ops.info.select_all(action='SELECT')
|
||||||
|
bpy.ops.info.report_copy()
|
||||||
area.type = area_type
|
area.type = area_type
|
||||||
clipboard = bpy.context.window_manager.clipboard
|
clipboard = bpy.context.window_manager.clipboard
|
||||||
|
|
||||||
@ -85,6 +92,7 @@ def detect_sdk_path():
|
|||||||
if match:
|
if match:
|
||||||
addon_prefs.sdk_path = os.path.dirname(match[-1])
|
addon_prefs.sdk_path = os.path.dirname(match[-1])
|
||||||
|
|
||||||
|
|
||||||
def get_link_web_server(self):
|
def get_link_web_server(self):
|
||||||
return self.get('link_web_server', 'http://localhost/')
|
return self.get('link_web_server', 'http://localhost/')
|
||||||
|
|
||||||
@ -558,7 +566,7 @@ def remove_readonly(func, path, excinfo):
|
|||||||
func(path)
|
func(path)
|
||||||
|
|
||||||
|
|
||||||
def run_proc(cmd: list[str], done: Optional[Callable[[bool], None]] = None):
|
def run_proc(cmd: ListType[str], done: Optional[Callable[[bool], None]] = None):
|
||||||
def fn(p, done):
|
def fn(p, done):
|
||||||
p.wait()
|
p.wait()
|
||||||
if done is not None:
|
if done is not None:
|
||||||
@ -840,7 +848,13 @@ def update_leenkx_py(sdk_path: str, force_relink=False):
|
|||||||
else:
|
else:
|
||||||
raise err
|
raise err
|
||||||
else:
|
else:
|
||||||
lnx_module_file.unlink(missing_ok=True)
|
if bpy.app.version < (2, 92, 0):
|
||||||
|
try:
|
||||||
|
lnx_module_file.unlink()
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
lnx_module_file.unlink(missing_ok=True)
|
||||||
shutil.copy(Path(sdk_path) / 'leenkx.py', lnx_module_file)
|
shutil.copy(Path(sdk_path) / 'leenkx.py', lnx_module_file)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -319,7 +319,7 @@ void main() {
|
|||||||
#ifdef _VoxelGI
|
#ifdef _VoxelGI
|
||||||
fragColor.rgb = textureLod(voxels_diffuse, texCoord, 0.0).rgb * voxelgiDiff;
|
fragColor.rgb = textureLod(voxels_diffuse, texCoord, 0.0).rgb * voxelgiDiff;
|
||||||
if(roughness < 1.0 && occspec.y > 0.0)
|
if(roughness < 1.0 && occspec.y > 0.0)
|
||||||
fragColor.rgb += textureLod(voxels_specular, texCoord, 0.0).rgb * F * voxelgiRefl;
|
fragColor.rgb += textureLod(voxels_specular, texCoord, 0.0).rgb * occspec.y * voxelgiRefl;
|
||||||
#else
|
#else
|
||||||
#ifdef _VoxelAOvar
|
#ifdef _VoxelAOvar
|
||||||
fragColor.rgb = textureLod(voxels_ao, texCoord, 0.0).rgb * voxelgiOcc;
|
fragColor.rgb = textureLod(voxels_ao, texCoord, 0.0).rgb * voxelgiOcc;
|
||||||
|
|||||||
@ -64,7 +64,7 @@ vec4 rayCast(vec3 dir) {
|
|||||||
ddepth = getDeltaDepth(hitCoord);
|
ddepth = getDeltaDepth(hitCoord);
|
||||||
if (ddepth > 0.0) return binarySearch(dir);
|
if (ddepth > 0.0) return binarySearch(dir);
|
||||||
}
|
}
|
||||||
return vec4(getProjectedCoord(hitCoord), 0.0, 1.0);
|
return vec4(texCoord, 0.0, 1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
@ -72,10 +72,11 @@ void main() {
|
|||||||
float roughness = g0.z;
|
float roughness = g0.z;
|
||||||
vec4 gr = textureLod(gbuffer_refraction, texCoord, 0.0);
|
vec4 gr = textureLod(gbuffer_refraction, texCoord, 0.0);
|
||||||
float ior = gr.x;
|
float ior = gr.x;
|
||||||
float opac = gr.y;
|
float opac = 1.0 - gr.y;
|
||||||
float d = textureLod(gbufferD, texCoord, 0.0).r * 2.0 - 1.0;
|
float d = textureLod(gbufferD, texCoord, 0.0).r * 2.0 - 1.0;
|
||||||
if (d == 0.0 || d == 1.0 || opac == 1.0 || ior == 1.0) {
|
if (d == 0.0 || d == 1.0 || opac == 1.0 || ior == 1.0) {
|
||||||
fragColor.rgb = textureLod(tex1, texCoord, 0.0).rgb;
|
fragColor.rgb = textureLod(tex1, texCoord, 0.0).rgb;
|
||||||
|
fragColor.a = opac;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
vec2 enc = g0.rg;
|
vec2 enc = g0.rg;
|
||||||
@ -86,7 +87,7 @@ void main() {
|
|||||||
|
|
||||||
vec3 viewNormal = V3 * n;
|
vec3 viewNormal = V3 * n;
|
||||||
vec3 viewPos = getPosView(viewRay, d, cameraProj);
|
vec3 viewPos = getPosView(viewRay, d, cameraProj);
|
||||||
vec3 refracted = refract(viewPos, viewNormal, 1.0 / ior);
|
vec3 refracted = refract(normalize(viewPos), viewNormal, 1.0 / ior);
|
||||||
hitCoord = viewPos;
|
hitCoord = viewPos;
|
||||||
|
|
||||||
vec3 dir = refracted * (1.0 - rand(texCoord) * ss_refractionJitter * roughness) * 2.0;
|
vec3 dir = refracted * (1.0 - rand(texCoord) * ss_refractionJitter * roughness) * 2.0;
|
||||||
@ -98,9 +99,12 @@ void main() {
|
|||||||
clamp(-refracted.z, 0.0, 1.0) * clamp((length(viewPos - hitCoord)), 0.0, 1.0) * coords.w;
|
clamp(-refracted.z, 0.0, 1.0) * clamp((length(viewPos - hitCoord)), 0.0, 1.0) * coords.w;
|
||||||
intensity = clamp(intensity, 0.0, 1.0);
|
intensity = clamp(intensity, 0.0, 1.0);
|
||||||
|
|
||||||
vec3 refractionCol = textureLod(tex1, coords.xy, 0.0).rgb;
|
vec4 refractionCol = textureLod(tex1, coords.xy, 0.0).rgba;
|
||||||
refractionCol *= intensity;
|
refractionCol.a = opac;
|
||||||
vec3 color = textureLod(tex, texCoord.xy, 0.0).rgb;
|
//refractionCol *= intensity;
|
||||||
|
vec4 color = textureLod(tex, texCoord.xy, 0.0).rgba;
|
||||||
|
color.a = opac;
|
||||||
|
|
||||||
fragColor.rgb = mix(refractionCol, color, opac);
|
fragColor.rgba = mix(refractionCol, color, opac);
|
||||||
|
fragColor.a = opac;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,12 @@
|
|||||||
"depth_write": false,
|
"depth_write": false,
|
||||||
"compare_mode": "always",
|
"compare_mode": "always",
|
||||||
"cull_mode": "none",
|
"cull_mode": "none",
|
||||||
|
"blend_source": "source_alpha",
|
||||||
|
"blend_destination": "inverse_source_alpha",
|
||||||
|
"blend_operation": "add",
|
||||||
|
"alpha_blend_source": "blend_one",
|
||||||
|
"alpha_blend_destination": "blend_one",
|
||||||
|
"alpha_blend_operation": "add",
|
||||||
"links": [
|
"links": [
|
||||||
{
|
{
|
||||||
"name": "P",
|
"name": "P",
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
//
|
//
|
||||||
// Copyright (C) 2012 Jorge Jimenez (jorge@iryoku.com)
|
// Copyright (C) 2012 Jorge Jimenez (jorge@iryoku.com)
|
||||||
// Copyright (C) 2012 Diego Gutierrez (diegog@unizar.es)
|
// Copyright (C) 2012 Diego Gutierrez (diegog@unizar.es)
|
||||||
|
// Copyright (C) 2025 Onek8 (info@leenkx.com)
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
//
|
//
|
||||||
// Redistribution and use in source and binary forms, with or without
|
// Redistribution and use in source and binary forms, with or without
|
||||||
@ -33,6 +34,14 @@
|
|||||||
// policies, either expressed or implied, of the copyright holders.
|
// policies, either expressed or implied, of the copyright holders.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// Add real sss radius
|
||||||
|
// Add real sss scale
|
||||||
|
// Move temp hash, reorganize shader utility functions
|
||||||
|
// Add compiler flag for quality presets or with samples parameter
|
||||||
|
// Clean up + Document comment
|
||||||
|
|
||||||
|
|
||||||
#version 450
|
#version 450
|
||||||
|
|
||||||
#include "compiled.inc"
|
#include "compiled.inc"
|
||||||
@ -49,67 +58,93 @@ out vec4 fragColor;
|
|||||||
|
|
||||||
const float SSSS_FOVY = 108.0;
|
const float SSSS_FOVY = 108.0;
|
||||||
|
|
||||||
// Separable SSS Reflectance
|
// Temp hash func -
|
||||||
// const float sssWidth = 0.005;
|
float hash13(vec3 p3) {
|
||||||
|
p3 = fract(p3 * vec3(0.1031, 0.1030, 0.0973));
|
||||||
|
p3 += dot(p3, p3.yzx + 33.33);
|
||||||
|
return fract((p3.x + p3.y) * p3.z);
|
||||||
|
}
|
||||||
|
|
||||||
vec4 SSSSBlur() {
|
vec4 SSSSBlur() {
|
||||||
// Quality = 0
|
const int SSSS_N_SAMPLES = 15;
|
||||||
const int SSSS_N_SAMPLES = 11;
|
vec4 kernel[SSSS_N_SAMPLES];
|
||||||
vec4 kernel[SSSS_N_SAMPLES];
|
|
||||||
kernel[0] = vec4(0.560479, 0.669086, 0.784728, 0);
|
// color neutral kernel weights to prevent color shifting
|
||||||
kernel[1] = vec4(0.00471691, 0.000184771, 5.07566e-005, -2);
|
kernel[0] = vec4(0.2, 0.2, 0.2, 0.0);
|
||||||
kernel[2] = vec4(0.0192831, 0.00282018, 0.00084214, -1.28);
|
kernel[1] = vec4(0.12, 0.12, 0.12, 0.2);
|
||||||
kernel[3] = vec4(0.03639, 0.0130999, 0.00643685, -0.72);
|
kernel[2] = vec4(0.09, 0.09, 0.09, 0.4);
|
||||||
kernel[4] = vec4(0.0821904, 0.0358608, 0.0209261, -0.32);
|
kernel[3] = vec4(0.06, 0.06, 0.06, 0.8);
|
||||||
kernel[5] = vec4(0.0771802, 0.113491, 0.0793803, -0.08);
|
kernel[4] = vec4(0.04, 0.04, 0.04, 1.2);
|
||||||
kernel[6] = vec4(0.0771802, 0.113491, 0.0793803, 0.08);
|
kernel[5] = vec4(0.025, 0.025, 0.025, 1.6);
|
||||||
kernel[7] = vec4(0.0821904, 0.0358608, 0.0209261, 0.32);
|
kernel[6] = vec4(0.015, 0.015, 0.015, 2.0);
|
||||||
kernel[8] = vec4(0.03639, 0.0130999, 0.00643685, 0.72);
|
kernel[7] = vec4(0.005, 0.005, 0.005, 2.5);
|
||||||
kernel[9] = vec4(0.0192831, 0.00282018, 0.00084214, 1.28);
|
kernel[8] = vec4(0.12, 0.12, 0.12, -0.2);
|
||||||
kernel[10] = vec4(0.00471691, 0.000184771, 5.07565e-005, 2);
|
kernel[9] = vec4(0.09, 0.09, 0.09, -0.4);
|
||||||
|
kernel[10] = vec4(0.06, 0.06, 0.06, -0.8);
|
||||||
|
kernel[11] = vec4(0.04, 0.04, 0.04, -1.2);
|
||||||
|
kernel[12] = vec4(0.025, 0.025, 0.025, -1.6);
|
||||||
|
kernel[13] = vec4(0.015, 0.015, 0.015, -2.0);
|
||||||
|
kernel[14] = vec4(0.005, 0.005, 0.005, -2.5);
|
||||||
|
|
||||||
vec4 colorM = textureLod(tex, texCoord, 0.0);
|
vec4 colorM = textureLod(tex, texCoord, 0.0);
|
||||||
|
|
||||||
// Fetch linear depth of current pixel
|
|
||||||
float depth = textureLod(gbufferD, texCoord, 0.0).r;
|
float depth = textureLod(gbufferD, texCoord, 0.0).r;
|
||||||
float depthM = cameraProj.y / (depth - cameraProj.x);
|
float depthM = cameraProj.y / (depth - cameraProj.x);
|
||||||
|
|
||||||
// Calculate the sssWidth scale (1.0 for a unit plane sitting on the projection window)
|
|
||||||
float distanceToProjectionWindow = 1.0 / tan(0.5 * radians(SSSS_FOVY));
|
float distanceToProjectionWindow = 1.0 / tan(0.5 * radians(SSSS_FOVY));
|
||||||
float scale = distanceToProjectionWindow / depthM;
|
float scale = distanceToProjectionWindow / depthM;
|
||||||
|
|
||||||
// Calculate the final step to fetch the surrounding pixels
|
|
||||||
vec2 finalStep = sssWidth * scale * dir;
|
vec2 finalStep = sssWidth * scale * dir;
|
||||||
finalStep *= 1.0;//SSSS_STREGTH_SOURCE; // Modulate it using the alpha channel.
|
|
||||||
finalStep *= 1.0 / 3.0; // Divide by 3 as the kernels range from -3 to 3.
|
|
||||||
finalStep *= 0.05; //
|
|
||||||
|
|
||||||
// Accumulate the center sample:
|
vec3 jitterSeed = vec3(texCoord.xy * 1000.0, fract(cameraProj.x * 0.0001));
|
||||||
vec4 colorBlurred = colorM;
|
float jitterOffset = (hash13(jitterSeed) * 2.0 - 1.0) * 0.15; // 15% jitteR
|
||||||
colorBlurred.rgb *= kernel[0].rgb;
|
|
||||||
|
finalStep *= (1.0 + jitterOffset);
|
||||||
// Accumulate the other samples
|
finalStep *= 0.05;
|
||||||
|
vec3 colorBlurred = vec3(0.0);
|
||||||
|
vec3 weightSum = vec3(0.0);
|
||||||
|
colorBlurred += colorM.rgb * kernel[0].rgb;
|
||||||
|
weightSum += kernel[0].rgb;
|
||||||
|
|
||||||
|
// Accumulate the other samples with per-pixel jittering to reduce banding
|
||||||
for (int i = 1; i < SSSS_N_SAMPLES; i++) {
|
for (int i = 1; i < SSSS_N_SAMPLES; i++) {
|
||||||
// Fetch color and depth for current sample
|
float sampleJitter = hash13(vec3(texCoord.xy * 720.0, float(i) * 37.45)) * 0.1 - 0.05;
|
||||||
vec2 offset = texCoord + kernel[i].a * finalStep;
|
|
||||||
|
vec2 offset = texCoord + (kernel[i].a + sampleJitter) * finalStep;
|
||||||
vec4 color = textureLod(tex, offset, 0.0);
|
vec4 color = textureLod(tex, offset, 0.0);
|
||||||
//#if SSSS_FOLLOW_SURFACE == 1
|
|
||||||
// If the difference in depth is huge, we lerp color back to "colorM":
|
// ADJUST FOR SURFACE FOLLOWING
|
||||||
//float depth = textureLod(tex, offset, 0.0).r;
|
// 0.0 = disabled (maximum SSS but with bleeding), 1.0 = fully enabled (prevents bleeding but might reduce SSS effect)
|
||||||
//float s = clamp(300.0f * distanceToProjectionWindow * sssWidth * abs(depthM - depth),0.0,1.0);
|
const float SURFACE_FOLLOWING_STRENGTH = 0.15; // Reduced to preserve more SSS effect
|
||||||
//color.rgb = mix(color.rgb, colorM.rgb, s);
|
|
||||||
//#endif
|
if (SURFACE_FOLLOWING_STRENGTH > 0.0) {
|
||||||
// Accumulate
|
float sampleDepth = textureLod(gbufferD, offset, 0.0).r;
|
||||||
colorBlurred.rgb += kernel[i].rgb * color.rgb;
|
float depthScale = 5.0;
|
||||||
|
float depthDiff = abs(depth - sampleDepth) * depthScale;
|
||||||
|
if (depthDiff > 0.3) {
|
||||||
|
float blendFactor = clamp(depthDiff - 0.3, 0.0, 1.0) * SURFACE_FOLLOWING_STRENGTH;
|
||||||
|
color.rgb = mix(color.rgb, colorM.rgb, blendFactor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
colorBlurred += color.rgb * kernel[i].rgb;
|
||||||
|
weightSum += kernel[i].rgb;
|
||||||
}
|
}
|
||||||
|
vec3 normalizedColor = colorBlurred / max(weightSum, vec3(0.00001));
|
||||||
return colorBlurred;
|
float dither = hash13(vec3(texCoord * 1333.0, 0.0)) * 0.003 - 0.0015;
|
||||||
|
return vec4(normalizedColor + vec3(dither), colorM.a);
|
||||||
}
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
|
||||||
if (textureLod(gbuffer0, texCoord, 0.0).a == 8192.0) {
|
if (textureLod(gbuffer0, texCoord, 0.0).a == 8192.0) {
|
||||||
fragColor = clamp(SSSSBlur(), 0.0, 1.0);
|
vec4 originalColor = textureLod(tex, texCoord, 0.0);
|
||||||
}
|
vec4 blurredColor = SSSSBlur();
|
||||||
else {
|
vec4 finalColor = mix(blurredColor, originalColor, 0.15);
|
||||||
|
|
||||||
|
fragColor = clamp(finalColor, 0.0, 1.0);
|
||||||
|
} else {
|
||||||
fragColor = textureLod(tex, texCoord, 0.0);
|
fragColor = textureLod(tex, texCoord, 0.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -97,9 +97,9 @@ vec4 traceCone(const sampler3D voxels, const sampler3D voxelsSDF, const vec3 ori
|
|||||||
|
|
||||||
vec3 aniso_direction = -dir;
|
vec3 aniso_direction = -dir;
|
||||||
vec3 face_offset = vec3(
|
vec3 face_offset = vec3(
|
||||||
aniso_direction.x > 0.0 ? 0 : 1,
|
aniso_direction.x > 0.0 ? 0.0 : 1.0,
|
||||||
aniso_direction.y > 0.0 ? 2 : 3,
|
aniso_direction.y > 0.0 ? 2.0 : 3.0,
|
||||||
aniso_direction.z > 0.0 ? 4 : 5
|
aniso_direction.z > 0.0 ? 4.0 : 5.0
|
||||||
) / (6 + DIFFUSE_CONE_COUNT);
|
) / (6 + DIFFUSE_CONE_COUNT);
|
||||||
vec3 direction_weight = abs(dir);
|
vec3 direction_weight = abs(dir);
|
||||||
|
|
||||||
@ -202,9 +202,9 @@ float traceConeAO(const sampler3D voxels, const vec3 origin, const vec3 n, const
|
|||||||
|
|
||||||
vec3 aniso_direction = -dir;
|
vec3 aniso_direction = -dir;
|
||||||
vec3 face_offset = vec3(
|
vec3 face_offset = vec3(
|
||||||
aniso_direction.x > 0.0 ? 0 : 1,
|
aniso_direction.x > 0.0 ? 0.0 : 1.0,
|
||||||
aniso_direction.y > 0.0 ? 2 : 3,
|
aniso_direction.y > 0.0 ? 2.0 : 3.0,
|
||||||
aniso_direction.z > 0.0 ? 4 : 5
|
aniso_direction.z > 0.0 ? 4.0 : 5.0
|
||||||
) / (6 + DIFFUSE_CONE_COUNT);
|
) / (6 + DIFFUSE_CONE_COUNT);
|
||||||
vec3 direction_weight = abs(dir);
|
vec3 direction_weight = abs(dir);
|
||||||
|
|
||||||
@ -272,9 +272,9 @@ float traceConeShadow(const sampler3D voxels, const sampler3D voxelsSDF, const v
|
|||||||
|
|
||||||
vec3 aniso_direction = -dir;
|
vec3 aniso_direction = -dir;
|
||||||
vec3 face_offset = vec3(
|
vec3 face_offset = vec3(
|
||||||
aniso_direction.x > 0.0 ? 0 : 1,
|
aniso_direction.x > 0.0 ? 0.0 : 1.0,
|
||||||
aniso_direction.y > 0.0 ? 2 : 3,
|
aniso_direction.y > 0.0 ? 2.0 : 3.0,
|
||||||
aniso_direction.z > 0.0 ? 4 : 5
|
aniso_direction.z > 0.0 ? 4.0 : 5.0
|
||||||
) / (6 + DIFFUSE_CONE_COUNT);
|
) / (6 + DIFFUSE_CONE_COUNT);
|
||||||
vec3 direction_weight = abs(dir);
|
vec3 direction_weight = abs(dir);
|
||||||
float coneCoefficient = 2.0 * tan(aperture * 0.5);
|
float coneCoefficient = 2.0 * tan(aperture * 0.5);
|
||||||
|
|||||||
@ -24,37 +24,44 @@ const int DIFFUSE_CONE_COUNT = 16;
|
|||||||
|
|
||||||
const float SHADOW_CONE_APERTURE = radians(15.0);
|
const float SHADOW_CONE_APERTURE = radians(15.0);
|
||||||
|
|
||||||
const float DIFFUSE_CONE_APERTURE = radians(50.0);
|
const float DIFFUSE_CONE_APERTURE = 0.872665; // 50 degrees in radians
|
||||||
|
|
||||||
const vec3 DIFFUSE_CONE_DIRECTIONS[DIFFUSE_CONE_COUNT] = vec3[](
|
mat3 makeTangentBasis(const vec3 normal) {
|
||||||
vec3(0.0, 0.0, 1.0), // center
|
// Create a tangent basis from normal vector
|
||||||
|
vec3 tangent;
|
||||||
|
vec3 bitangent;
|
||||||
|
|
||||||
vec3(0.0, 0.5, 0.866),
|
// Compute tangent (Frisvad's method)
|
||||||
vec3(0.5, 0.0, 0.866),
|
if (abs(normal.z) < 0.999) {
|
||||||
vec3(0.0, -0.5, 0.866),
|
tangent = normalize(cross(vec3(0, 1, 0), normal));
|
||||||
vec3(-0.5, 0.0, 0.866),
|
} else {
|
||||||
|
tangent = normalize(cross(normal, vec3(1, 0, 0)));
|
||||||
|
}
|
||||||
|
bitangent = cross(normal, tangent);
|
||||||
|
|
||||||
vec3(0.353, 0.353, 0.866),
|
|
||||||
vec3(0.353, -0.353, 0.866),
|
|
||||||
vec3(-0.353, -0.353, 0.866),
|
|
||||||
vec3(-0.353, 0.353, 0.866),
|
|
||||||
|
|
||||||
vec3(0.707, 0.0, 0.707),
|
|
||||||
vec3(0.0, 0.707, 0.707),
|
|
||||||
vec3(-0.707, 0.0, 0.707),
|
|
||||||
vec3(0.0, -0.707, 0.707),
|
|
||||||
|
|
||||||
vec3(0.5, 0.5, 0.707),
|
|
||||||
vec3(-0.5, 0.5, 0.707),
|
|
||||||
vec3(-0.5, -0.5, 0.707)
|
|
||||||
);
|
|
||||||
|
|
||||||
mat3 makeTangentBasis(vec3 normal) {
|
|
||||||
vec3 tangent = normalize(abs(normal.y) < 0.999 ? cross(normal, vec3(0, 1, 0)) : cross(normal, vec3(1, 0, 0)));
|
|
||||||
vec3 bitangent = cross(normal, tangent);
|
|
||||||
return mat3(tangent, bitangent, normal);
|
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
|
||||||
|
);
|
||||||
|
|
||||||
// TO DO - Disabled momentarily instead of changing formulas
|
// TO DO - Disabled momentarily instead of changing formulas
|
||||||
const float off_BayerMatrix8[8][8] =
|
const float off_BayerMatrix8[8][8] =
|
||||||
{
|
{
|
||||||
|
|||||||
@ -251,28 +251,69 @@ vec3 PCFFakeCube(sampler2DShadow shadowMap,
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (any(lessThan(uvtiled, vec2(0.0))) || any(greaterThan(uvtiled, vec2(1.0)))) {
|
if (any(lessThan(uvtiled, vec2(0.0))) || any(greaterThan(uvtiled, vec2(1.0)))) {
|
||||||
return vec3(1.0); // Or handle edge cases differently
|
return vec3(1.0); // Handle edge cases by returning full light
|
||||||
}
|
}
|
||||||
|
|
||||||
vec3 result = vec3(0.0);
|
vec3 result = vec3(0.0);
|
||||||
// In PCFFakeCube(), modify the sampling pattern to be more robust:
|
result.x += texture(shadowMap, vec3(uvtiled, compare));
|
||||||
const vec2 offsets[9] = vec2[](
|
// soft shadowing
|
||||||
vec2(0, 0),
|
int newFaceIndex = 0;
|
||||||
vec2(1, 0), vec2(-1, 0), vec2(0, 1), vec2(0, -1),
|
uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(-1.0, 0.0) / smSize)));
|
||||||
vec2(1, 1), vec2(-1, 1), vec2(1, -1), vec2(-1, -1)
|
pointLightTile = pointLightDataArray[lightIndex + newFaceIndex];
|
||||||
);
|
uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy;
|
||||||
|
#ifdef _FlipY
|
||||||
|
uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system
|
||||||
|
#endif
|
||||||
|
result.x += texture(shadowMap, vec3(uvtiled, compare));
|
||||||
|
|
||||||
|
uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(0.0, -1.0) / smSize)));
|
||||||
|
pointLightTile = pointLightDataArray[lightIndex + newFaceIndex];
|
||||||
|
uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy;
|
||||||
|
#ifdef _FlipY
|
||||||
|
uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system
|
||||||
|
#endif
|
||||||
|
result.x += texture(shadowMap, vec3(uvtiled, compare));
|
||||||
|
|
||||||
|
uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(-1.0, -1.0) / smSize)));
|
||||||
|
pointLightTile = pointLightDataArray[lightIndex + newFaceIndex];
|
||||||
|
uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy;
|
||||||
|
#ifdef _FlipY
|
||||||
|
uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system
|
||||||
|
#endif
|
||||||
|
result.x += texture(shadowMap, vec3(uvtiled, compare));
|
||||||
|
|
||||||
|
uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(0.0, 1.0) / smSize)));
|
||||||
|
pointLightTile = pointLightDataArray[lightIndex + newFaceIndex];
|
||||||
|
uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy;
|
||||||
|
#ifdef _FlipY
|
||||||
|
uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system
|
||||||
|
#endif
|
||||||
|
result.x += texture(shadowMap, vec3(uvtiled, compare));
|
||||||
|
|
||||||
|
uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(1.0, -1.0) / smSize)));
|
||||||
|
pointLightTile = pointLightDataArray[lightIndex + newFaceIndex];
|
||||||
|
uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy;
|
||||||
|
#ifdef _FlipY
|
||||||
|
uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system
|
||||||
|
#endif
|
||||||
|
result.x += texture(shadowMap, vec3(uvtiled, compare));
|
||||||
|
|
||||||
|
uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(1.0, 0.0) / smSize)));
|
||||||
|
pointLightTile = pointLightDataArray[lightIndex + newFaceIndex];
|
||||||
|
uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy;
|
||||||
|
#ifdef _FlipY
|
||||||
|
uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system
|
||||||
|
#endif
|
||||||
|
result.x += texture(shadowMap, vec3(uvtiled, compare));
|
||||||
|
|
||||||
|
uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(1.0, 1.0) / smSize)));
|
||||||
|
pointLightTile = pointLightDataArray[lightIndex + newFaceIndex];
|
||||||
|
uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy;
|
||||||
|
#ifdef _FlipY
|
||||||
|
uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system
|
||||||
|
#endif
|
||||||
|
result.x += texture(shadowMap, vec3(uvtiled, compare));
|
||||||
|
|
||||||
for (int i = 0; i < 9; i++) {
|
|
||||||
vec2 sampleUV = uv + offsets[i] / smSize;
|
|
||||||
int newFaceIndex;
|
|
||||||
vec2 transformedUV = transformOffsetedUV(faceIndex, newFaceIndex, sampleUV);
|
|
||||||
pointLightTile = pointLightDataArray[lightIndex + newFaceIndex];
|
|
||||||
uvtiled = pointLightTile.z * transformedUV + pointLightTile.xy;
|
|
||||||
#ifdef _FlipY
|
|
||||||
uvtiled.y = 1.0 - uvtiled.y;
|
|
||||||
#endif
|
|
||||||
result.x += texture(shadowMap, vec3(uvtiled, compare));
|
|
||||||
}
|
|
||||||
result = result.xxx / 9.0;
|
result = result.xxx / 9.0;
|
||||||
|
|
||||||
pointLightTile = pointLightDataArray[lightIndex + faceIndex]; // x: tile X offset, y: tile Y offset, z: tile size relative to atlas
|
pointLightTile = pointLightDataArray[lightIndex + faceIndex]; // x: tile X offset, y: tile Y offset, z: tile size relative to atlas
|
||||||
|
|||||||
@ -106,8 +106,11 @@ void main() {
|
|||||||
#ifdef _Brdf
|
#ifdef _Brdf
|
||||||
vec2 envBRDF = texelFetch(senvmapBrdf, ivec2(vec2(dotNV, 1.0 - roughness) * 256.0), 0).xy;
|
vec2 envBRDF = texelFetch(senvmapBrdf, ivec2(vec2(dotNV, 1.0 - roughness) * 256.0), 0).xy;
|
||||||
vec3 F = f0 * envBRDF.x + envBRDF.y;
|
vec3 F = f0 * envBRDF.x + envBRDF.y;
|
||||||
|
#else
|
||||||
|
vec3 F = f0;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
// Envmap
|
// Envmap
|
||||||
#ifdef _Irr
|
#ifdef _Irr
|
||||||
vec4 shPacked[7];
|
vec4 shPacked[7];
|
||||||
|
|||||||
@ -74,8 +74,9 @@ void main() {
|
|||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
mat3 TBN = mat3(1.0);
|
int nor_count = 0;
|
||||||
vec3 avgNormal = vec3(0.0);
|
vec3 avgNormal = vec3(0.0);
|
||||||
|
mat3 TBN = mat3(0.0);
|
||||||
|
|
||||||
for (int i = 0; i < 6 + DIFFUSE_CONE_COUNT; i++)
|
for (int i = 0; i < 6 + DIFFUSE_CONE_COUNT; i++)
|
||||||
{
|
{
|
||||||
@ -116,10 +117,18 @@ void main() {
|
|||||||
N.g = float(imageLoad(voxels, src + ivec3(0, 0, voxelgiResolution.x * 8))) / 255;
|
N.g = float(imageLoad(voxels, src + ivec3(0, 0, voxelgiResolution.x * 8))) / 255;
|
||||||
N /= count;
|
N /= count;
|
||||||
N = decode_oct(N.rg * 2.0 - 1.0);
|
N = decode_oct(N.rg * 2.0 - 1.0);
|
||||||
avgNormal += N;
|
|
||||||
|
|
||||||
|
if (abs(N.x) > 0)
|
||||||
|
avgNormal.x += N.x;
|
||||||
|
if (abs(N.y) > 0)
|
||||||
|
avgNormal.y += N.y;
|
||||||
|
if (abs(N.z) > 0)
|
||||||
|
avgNormal.z += N.z;
|
||||||
if (i == 5)
|
if (i == 5)
|
||||||
TBN = makeTangentBasis(normalize(avgNormal));
|
{
|
||||||
|
avgNormal = normalize(avgNormal);
|
||||||
|
TBN = makeTangentBasis(avgNormal);
|
||||||
|
}
|
||||||
|
|
||||||
vec3 envl = vec3(0.0);
|
vec3 envl = vec3(0.0);
|
||||||
envl.r = float(imageLoad(voxels, src + ivec3(0, 0, voxelgiResolution.x * 9))) / 255;
|
envl.r = float(imageLoad(voxels, src + ivec3(0, 0, voxelgiResolution.x * 9))) / 255;
|
||||||
@ -245,4 +254,4 @@ void main() {
|
|||||||
imageStore(SDF, dst_sdf, vec4(sdf));
|
imageStore(SDF, dst_sdf, vec4(sdf));
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@ -54,6 +54,22 @@ class App {
|
|||||||
if (Scene.active == null || !Scene.active.ready) return;
|
if (Scene.active == null || !Scene.active.ready) return;
|
||||||
|
|
||||||
iron.system.Time.update();
|
iron.system.Time.update();
|
||||||
|
|
||||||
|
if (lastw == -1) {
|
||||||
|
lastw = App.w();
|
||||||
|
lasth = App.h();
|
||||||
|
}
|
||||||
|
if (lastw != App.w() || lasth != App.h()) {
|
||||||
|
if (onResize != null) onResize();
|
||||||
|
else {
|
||||||
|
if (Scene.active != null && Scene.active.camera != null) {
|
||||||
|
Scene.active.camera.buildProjection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastw = App.w();
|
||||||
|
lasth = App.h();
|
||||||
|
|
||||||
if (pauseUpdates) return;
|
if (pauseUpdates) return;
|
||||||
|
|
||||||
#if lnx_debug
|
#if lnx_debug
|
||||||
@ -98,22 +114,6 @@ class App {
|
|||||||
for (cb in endFrameCallbacks) cb();
|
for (cb in endFrameCallbacks) cb();
|
||||||
updateTime = kha.Scheduler.realTime() - startTime;
|
updateTime = kha.Scheduler.realTime() - startTime;
|
||||||
#end
|
#end
|
||||||
|
|
||||||
// Rebuild projection on window resize
|
|
||||||
if (lastw == -1) {
|
|
||||||
lastw = App.w();
|
|
||||||
lasth = App.h();
|
|
||||||
}
|
|
||||||
if (lastw != App.w() || lasth != App.h()) {
|
|
||||||
if (onResize != null) onResize();
|
|
||||||
else {
|
|
||||||
if (Scene.active != null && Scene.active.camera != null) {
|
|
||||||
Scene.active.camera.buildProjection();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lastw = App.w();
|
|
||||||
lasth = App.h();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static function render(frames: Array<kha.Framebuffer>) {
|
static function render(frames: Array<kha.Framebuffer>) {
|
||||||
|
|||||||
@ -331,15 +331,18 @@ class RenderPath {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function sortMeshesShader(meshes: Array<MeshObject>) {
|
public static function sortMeshesIndex(meshes: Array<MeshObject>) {
|
||||||
meshes.sort(function(a, b): Int {
|
meshes.sort(function(a, b): Int {
|
||||||
#if rp_depth_texture
|
#if rp_depth_texture
|
||||||
var depthDiff = boolToInt(a.depthRead) - boolToInt(b.depthRead);
|
var depthDiff = boolToInt(a.depthRead) - boolToInt(b.depthRead);
|
||||||
if (depthDiff != 0) return depthDiff;
|
if (depthDiff != 0) return depthDiff;
|
||||||
#end
|
#end
|
||||||
|
|
||||||
return a.materials[0].name >= b.materials[0].name ? 1 : -1;
|
if (a.data.sortingIndex != b.data.sortingIndex) {
|
||||||
});
|
return a.data.sortingIndex > b.data.sortingIndex ? 1 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.data.name >= b.data.name ? 1 : -1; });
|
||||||
}
|
}
|
||||||
|
|
||||||
public function drawMeshes(context: String) {
|
public function drawMeshes(context: String) {
|
||||||
@ -399,7 +402,7 @@ class RenderPath {
|
|||||||
#if lnx_batch
|
#if lnx_batch
|
||||||
sortMeshesDistance(Scene.active.meshBatch.nonBatched);
|
sortMeshesDistance(Scene.active.meshBatch.nonBatched);
|
||||||
#else
|
#else
|
||||||
drawOrder == DrawOrder.Shader ? sortMeshesShader(meshes) : sortMeshesDistance(meshes);
|
drawOrder == DrawOrder.Index ? sortMeshesIndex(meshes) : sortMeshesDistance(meshes);
|
||||||
#end
|
#end
|
||||||
meshesSorted = true;
|
meshesSorted = true;
|
||||||
}
|
}
|
||||||
@ -518,12 +521,44 @@ class RenderPath {
|
|||||||
return Reflect.field(kha.Shaders, handle + "_comp");
|
return Reflect.field(kha.Shaders, handle + "_comp");
|
||||||
}
|
}
|
||||||
|
|
||||||
#if (kha_krom && lnx_vr)
|
#if lnx_vr
|
||||||
public function drawStereo(drawMeshes: Int->Void) {
|
public function drawStereo(drawMeshes: Void->Void) {
|
||||||
for (eye in 0...2) {
|
var vr = kha.vr.VrInterface.instance;
|
||||||
Krom.vrBeginRender(eye);
|
var appw = iron.App.w();
|
||||||
drawMeshes(eye);
|
var apph = iron.App.h();
|
||||||
Krom.vrEndRender(eye);
|
var halfw = Std.int(appw / 2);
|
||||||
|
var g = currentG;
|
||||||
|
|
||||||
|
if (vr != null && vr.IsPresenting()) {
|
||||||
|
// Left eye
|
||||||
|
Scene.active.camera.V.setFrom(Scene.active.camera.leftV);
|
||||||
|
Scene.active.camera.P.self = vr.GetProjectionMatrix(0);
|
||||||
|
g.viewport(0, 0, halfw, apph);
|
||||||
|
drawMeshes();
|
||||||
|
|
||||||
|
// Right eye
|
||||||
|
begin(g, additionalTargets);
|
||||||
|
Scene.active.camera.V.setFrom(Scene.active.camera.rightV);
|
||||||
|
Scene.active.camera.P.self = vr.GetProjectionMatrix(1);
|
||||||
|
g.viewport(halfw, 0, halfw, apph);
|
||||||
|
drawMeshes();
|
||||||
|
}
|
||||||
|
else { // Simulate
|
||||||
|
Scene.active.camera.buildProjection(halfw / apph);
|
||||||
|
|
||||||
|
// Left eye
|
||||||
|
g.viewport(0, 0, halfw, apph);
|
||||||
|
drawMeshes();
|
||||||
|
|
||||||
|
// Right eye
|
||||||
|
begin(g, additionalTargets);
|
||||||
|
Scene.active.camera.transform.move(Scene.active.camera.right(), 0.032);
|
||||||
|
Scene.active.camera.buildMatrix();
|
||||||
|
g.viewport(halfw, 0, halfw, apph);
|
||||||
|
drawMeshes();
|
||||||
|
|
||||||
|
Scene.active.camera.transform.move(Scene.active.camera.right(), -0.032);
|
||||||
|
Scene.active.camera.buildMatrix();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#end
|
#end
|
||||||
@ -882,6 +917,6 @@ class CachedShaderContext {
|
|||||||
|
|
||||||
@:enum abstract DrawOrder(Int) from Int {
|
@:enum abstract DrawOrder(Int) from Int {
|
||||||
var Distance = 0; // Early-z
|
var Distance = 0; // Early-z
|
||||||
var Shader = 1; // Less state changes
|
var Index = 1; // Less state changes
|
||||||
// var Mix = 2; // Distance buckets sorted by shader
|
// var Mix = 2; // Distance buckets sorted by shader
|
||||||
}
|
}
|
||||||
|
|||||||
@ -783,6 +783,11 @@ class Scene {
|
|||||||
if (o.tilesheet_ref != null) {
|
if (o.tilesheet_ref != null) {
|
||||||
cast(object, MeshObject).setupTilesheet(sceneName, o.tilesheet_ref, o.tilesheet_action_ref);
|
cast(object, MeshObject).setupTilesheet(sceneName, o.tilesheet_ref, o.tilesheet_action_ref);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (o.camera_list != null){
|
||||||
|
cast(object, MeshObject).cameraList = o.camera_list;
|
||||||
|
}
|
||||||
|
|
||||||
returnObject(object, o, done);
|
returnObject(object, o, done);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -882,8 +887,12 @@ class Scene {
|
|||||||
var ptype: String = t.props[i * 3 + 1];
|
var ptype: String = t.props[i * 3 + 1];
|
||||||
var pval: Dynamic = t.props[i * 3 + 2];
|
var pval: Dynamic = t.props[i * 3 + 2];
|
||||||
|
|
||||||
if (StringTools.endsWith(ptype, "Object") && pval != "") {
|
if (StringTools.endsWith(ptype, "Object") && pval != "" && pval != null) {
|
||||||
Reflect.setProperty(traitInst, pname, Scene.active.getChild(pval));
|
Reflect.setProperty(traitInst, pname, Scene.active.getChild(pval));
|
||||||
|
} else if (ptype == "TSceneFormat" && pval != "") {
|
||||||
|
Data.getSceneRaw(pval, function (r: TSceneFormat) {
|
||||||
|
Reflect.setProperty(traitInst, pname, r);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
switch (ptype) {
|
switch (ptype) {
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import iron.data.SceneFormat;
|
|||||||
class MeshData {
|
class MeshData {
|
||||||
|
|
||||||
public var name: String;
|
public var name: String;
|
||||||
|
public var sortingIndex: Int;
|
||||||
public var raw: TMeshData;
|
public var raw: TMeshData;
|
||||||
public var format: TSceneFormat;
|
public var format: TSceneFormat;
|
||||||
public var geom: Geometry;
|
public var geom: Geometry;
|
||||||
@ -23,7 +24,8 @@ class MeshData {
|
|||||||
public function new(raw: TMeshData, done: MeshData->Void) {
|
public function new(raw: TMeshData, done: MeshData->Void) {
|
||||||
this.raw = raw;
|
this.raw = raw;
|
||||||
this.name = raw.name;
|
this.name = raw.name;
|
||||||
|
this.sortingIndex = raw.sorting_index;
|
||||||
|
|
||||||
if (raw.scale_pos != null) scalePos = raw.scale_pos;
|
if (raw.scale_pos != null) scalePos = raw.scale_pos;
|
||||||
if (raw.scale_tex != null) scaleTex = raw.scale_tex;
|
if (raw.scale_tex != null) scaleTex = raw.scale_tex;
|
||||||
|
|
||||||
|
|||||||
@ -49,6 +49,7 @@ typedef TMeshData = {
|
|||||||
@:structInit class TMeshData {
|
@:structInit class TMeshData {
|
||||||
#end
|
#end
|
||||||
public var name: String;
|
public var name: String;
|
||||||
|
public var sorting_index: Int;
|
||||||
public var vertex_arrays: Array<TVertexArray>;
|
public var vertex_arrays: Array<TVertexArray>;
|
||||||
public var index_arrays: Array<TIndexArray>;
|
public var index_arrays: Array<TIndexArray>;
|
||||||
@:optional public var dynamic_usage: Null<Bool>;
|
@:optional public var dynamic_usage: Null<Bool>;
|
||||||
@ -222,6 +223,7 @@ typedef TShaderData = {
|
|||||||
@:structInit class TShaderData {
|
@:structInit class TShaderData {
|
||||||
#end
|
#end
|
||||||
public var name: String;
|
public var name: String;
|
||||||
|
public var next_pass: String;
|
||||||
public var contexts: Array<TShaderContext>;
|
public var contexts: Array<TShaderContext>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -393,6 +395,7 @@ typedef TParticleData = {
|
|||||||
public var name: String;
|
public var name: String;
|
||||||
public var type: Int; // 0 - Emitter, Hair
|
public var type: Int; // 0 - Emitter, Hair
|
||||||
public var auto_start: Bool;
|
public var auto_start: Bool;
|
||||||
|
public var dynamic_emitter: Bool;
|
||||||
public var is_unique: Bool;
|
public var is_unique: Bool;
|
||||||
public var loop: Bool;
|
public var loop: Bool;
|
||||||
public var count: Int;
|
public var count: Int;
|
||||||
@ -441,6 +444,7 @@ typedef TObj = {
|
|||||||
@:optional public var traits: Array<TTrait>;
|
@:optional public var traits: Array<TTrait>;
|
||||||
@:optional public var properties: Array<TProperty>;
|
@:optional public var properties: Array<TProperty>;
|
||||||
@:optional public var vertex_groups: Array<TVertex_groups>;
|
@:optional public var vertex_groups: Array<TVertex_groups>;
|
||||||
|
@:optional public var camera_list: Array<String>;
|
||||||
@:optional public var constraints: Array<TConstraint>;
|
@:optional public var constraints: Array<TConstraint>;
|
||||||
@:optional public var dimensions: Float32Array; // Geometry objects
|
@:optional public var dimensions: Float32Array; // Geometry objects
|
||||||
@:optional public var object_actions: Array<String>;
|
@:optional public var object_actions: Array<String>;
|
||||||
|
|||||||
@ -22,6 +22,7 @@ using StringTools;
|
|||||||
class ShaderData {
|
class ShaderData {
|
||||||
|
|
||||||
public var name: String;
|
public var name: String;
|
||||||
|
public var nextPass: String;
|
||||||
public var raw: TShaderData;
|
public var raw: TShaderData;
|
||||||
public var contexts: Array<ShaderContext> = [];
|
public var contexts: Array<ShaderContext> = [];
|
||||||
|
|
||||||
@ -33,6 +34,7 @@ class ShaderData {
|
|||||||
public function new(raw: TShaderData, done: ShaderData->Void, overrideContext: TShaderOverride = null) {
|
public function new(raw: TShaderData, done: ShaderData->Void, overrideContext: TShaderOverride = null) {
|
||||||
this.raw = raw;
|
this.raw = raw;
|
||||||
this.name = raw.name;
|
this.name = raw.name;
|
||||||
|
this.nextPass = raw.next_pass;
|
||||||
|
|
||||||
for (c in raw.contexts) contexts.push(null);
|
for (c in raw.contexts) contexts.push(null);
|
||||||
var contextsLoaded = 0;
|
var contextsLoaded = 0;
|
||||||
|
|||||||
@ -30,12 +30,22 @@ class CameraObject extends Object {
|
|||||||
static var sphereCenter = new Vec4();
|
static var sphereCenter = new Vec4();
|
||||||
static var vcenter = new Vec4();
|
static var vcenter = new Vec4();
|
||||||
static var vup = new Vec4();
|
static var vup = new Vec4();
|
||||||
|
|
||||||
|
#if lnx_vr
|
||||||
|
var helpMat = Mat4.identity();
|
||||||
|
public var leftV = Mat4.identity();
|
||||||
|
public var rightV = Mat4.identity();
|
||||||
|
#end
|
||||||
|
|
||||||
public function new(data: CameraData) {
|
public function new(data: CameraData) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.data = data;
|
this.data = data;
|
||||||
|
|
||||||
|
#if lnx_vr
|
||||||
|
iron.system.VR.initButton();
|
||||||
|
#end
|
||||||
|
|
||||||
buildProjection();
|
buildProjection();
|
||||||
|
|
||||||
V = Mat4.identity();
|
V = Mat4.identity();
|
||||||
@ -117,6 +127,26 @@ class CameraObject extends Object {
|
|||||||
V.getInverse(transform.world);
|
V.getInverse(transform.world);
|
||||||
VP.multmats(P, V);
|
VP.multmats(P, V);
|
||||||
|
|
||||||
|
|
||||||
|
#if lnx_vr
|
||||||
|
var vr = kha.vr.VrInterface.instance;
|
||||||
|
if (vr != null && vr.IsPresenting()) {
|
||||||
|
leftV.setFrom(V);
|
||||||
|
helpMat.self = vr.GetViewMatrix(0);
|
||||||
|
leftV.multmat(helpMat);
|
||||||
|
|
||||||
|
rightV.setFrom(V);
|
||||||
|
helpMat.self = vr.GetViewMatrix(1);
|
||||||
|
rightV.multmat(helpMat);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
leftV.setFrom(V);
|
||||||
|
}
|
||||||
|
VP.multmats(P, leftV);
|
||||||
|
#else
|
||||||
|
VP.multmats(P, V);
|
||||||
|
#end
|
||||||
|
|
||||||
if (data.raw.frustum_culling) {
|
if (data.raw.frustum_culling) {
|
||||||
buildViewFrustum(VP, frustumPlanes);
|
buildViewFrustum(VP, frustumPlanes);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -155,8 +155,13 @@ class LightObject extends Object {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function setCascade(camera: CameraObject, cascade: Int) {
|
public function setCascade(camera: CameraObject, cascade: Int) {
|
||||||
m.setFrom(camera.V);
|
|
||||||
|
|
||||||
|
#if lnx_vr
|
||||||
|
m.setFrom(camera.leftV);
|
||||||
|
#else
|
||||||
|
m.setFrom(camera.V);
|
||||||
|
#end
|
||||||
|
|
||||||
#if lnx_csm
|
#if lnx_csm
|
||||||
if (camSlicedP == null) {
|
if (camSlicedP == null) {
|
||||||
camSlicedP = [];
|
camSlicedP = [];
|
||||||
|
|||||||
@ -24,6 +24,7 @@ class MeshObject extends Object {
|
|||||||
public var render_emitter = true;
|
public var render_emitter = true;
|
||||||
#end
|
#end
|
||||||
public var cameraDistance: Float;
|
public var cameraDistance: Float;
|
||||||
|
public var cameraList: Array<String> = null;
|
||||||
public var screenSize = 0.0;
|
public var screenSize = 0.0;
|
||||||
public var frustumCulling = true;
|
public var frustumCulling = true;
|
||||||
public var activeTilesheet: Tilesheet = null;
|
public var activeTilesheet: Tilesheet = null;
|
||||||
@ -235,6 +236,8 @@ class MeshObject extends Object {
|
|||||||
if (cullMesh(context, Scene.active.camera, RenderPath.active.light)) return;
|
if (cullMesh(context, Scene.active.camera, RenderPath.active.light)) return;
|
||||||
var meshContext = raw != null ? context == "mesh" : false;
|
var meshContext = raw != null ? context == "mesh" : false;
|
||||||
|
|
||||||
|
if (cameraList != null && cameraList.indexOf(Scene.active.camera.name) < 0) return;
|
||||||
|
|
||||||
#if lnx_particles
|
#if lnx_particles
|
||||||
if (raw != null && raw.is_particle && particleOwner == null) return; // Instancing not yet set-up by particle system owner
|
if (raw != null && raw.is_particle && particleOwner == null) return; // Instancing not yet set-up by particle system owner
|
||||||
if (particleSystems != null && meshContext) {
|
if (particleSystems != null && meshContext) {
|
||||||
@ -245,6 +248,7 @@ class MeshObject extends Object {
|
|||||||
Scene.active.spawnObject(psys.data.raw.instance_object, null, function(o: Object) {
|
Scene.active.spawnObject(psys.data.raw.instance_object, null, function(o: Object) {
|
||||||
if (o != null) {
|
if (o != null) {
|
||||||
var c: MeshObject = cast o;
|
var c: MeshObject = cast o;
|
||||||
|
c.cameraList = this.cameraList;
|
||||||
particleChildren.push(c);
|
particleChildren.push(c);
|
||||||
c.particleOwner = this;
|
c.particleOwner = this;
|
||||||
c.particleIndex = particleChildren.length - 1;
|
c.particleIndex = particleChildren.length - 1;
|
||||||
@ -298,6 +302,10 @@ class MeshObject extends Object {
|
|||||||
|
|
||||||
// Render mesh
|
// Render mesh
|
||||||
var ldata = lod.data;
|
var ldata = lod.data;
|
||||||
|
|
||||||
|
// Next pass rendering first (inverse order)
|
||||||
|
renderNextPass(g, context, bindParams, lod);
|
||||||
|
|
||||||
for (i in 0...ldata.geom.indexBuffers.length) {
|
for (i in 0...ldata.geom.indexBuffers.length) {
|
||||||
|
|
||||||
var mi = ldata.geom.materialIndices[i];
|
var mi = ldata.geom.materialIndices[i];
|
||||||
@ -401,4 +409,85 @@ class MeshObject extends Object {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function renderNextPass(g: Graphics, context: String, bindParams: Array<String>, lod: MeshObject) {
|
||||||
|
var ldata = lod.data;
|
||||||
|
for (i in 0...ldata.geom.indexBuffers.length) {
|
||||||
|
var mi = ldata.geom.materialIndices[i];
|
||||||
|
if (mi >= materials.length) continue;
|
||||||
|
|
||||||
|
var currentMaterial: MaterialData = materials[mi];
|
||||||
|
if (currentMaterial == null || currentMaterial.shader == null) continue;
|
||||||
|
|
||||||
|
var nextPassName: String = currentMaterial.shader.nextPass;
|
||||||
|
if (nextPassName == null || nextPassName == "") continue;
|
||||||
|
|
||||||
|
var nextMaterial: MaterialData = null;
|
||||||
|
for (mat in materials) {
|
||||||
|
// First try exact match
|
||||||
|
if (mat.name == nextPassName) {
|
||||||
|
nextMaterial = mat;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// If no exact match, try to match base name for linked materials
|
||||||
|
if (mat.name.indexOf("_") > 0 && mat.name.substr(mat.name.length - 6) == ".blend") {
|
||||||
|
var baseName = mat.name.substring(0, mat.name.indexOf("_"));
|
||||||
|
if (baseName == nextPassName) {
|
||||||
|
nextMaterial = mat;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextMaterial == null) continue;
|
||||||
|
|
||||||
|
var nextMaterialContext: MaterialContext = null;
|
||||||
|
var nextShaderContext: ShaderContext = null;
|
||||||
|
|
||||||
|
for (j in 0...nextMaterial.raw.contexts.length) {
|
||||||
|
if (nextMaterial.raw.contexts[j].name.substr(0, context.length) == context) {
|
||||||
|
nextMaterialContext = nextMaterial.contexts[j];
|
||||||
|
nextShaderContext = nextMaterial.shader.getContext(context);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextShaderContext == null) continue;
|
||||||
|
if (skipContext(context, nextMaterial)) continue;
|
||||||
|
|
||||||
|
var elems = nextShaderContext.raw.vertex_elements;
|
||||||
|
|
||||||
|
// Uniforms
|
||||||
|
if (nextShaderContext.pipeState != lastPipeline) {
|
||||||
|
g.setPipeline(nextShaderContext.pipeState);
|
||||||
|
lastPipeline = nextShaderContext.pipeState;
|
||||||
|
}
|
||||||
|
Uniforms.setContextConstants(g, nextShaderContext, bindParams);
|
||||||
|
Uniforms.setObjectConstants(g, nextShaderContext, this);
|
||||||
|
Uniforms.setMaterialConstants(g, nextShaderContext, nextMaterialContext);
|
||||||
|
|
||||||
|
// VB / IB
|
||||||
|
#if lnx_deinterleaved
|
||||||
|
g.setVertexBuffers(ldata.geom.get(elems));
|
||||||
|
#else
|
||||||
|
if (ldata.geom.instancedVB != null) {
|
||||||
|
g.setVertexBuffers([ldata.geom.get(elems), ldata.geom.instancedVB]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
g.setVertexBuffer(ldata.geom.get(elems));
|
||||||
|
}
|
||||||
|
#end
|
||||||
|
|
||||||
|
g.setIndexBuffer(ldata.geom.indexBuffers[i]);
|
||||||
|
|
||||||
|
// Draw next pass for this specific geometry section
|
||||||
|
if (ldata.geom.instanced) {
|
||||||
|
g.drawIndexedVerticesInstanced(ldata.geom.instanceCount, ldata.geom.start, ldata.geom.count);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
g.drawIndexedVertices(ldata.geom.start, ldata.geom.count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,6 +24,9 @@ class ObjectAnimation extends Animation {
|
|||||||
|
|
||||||
public var transformMap: Map<String, FastFloat>;
|
public var transformMap: Map<String, FastFloat>;
|
||||||
|
|
||||||
|
var defaultSampler: ActionSampler = null;
|
||||||
|
static inline var DEFAULT_SAMPLER_ID = "__object_default_action__";
|
||||||
|
|
||||||
public static var trackNames: Array<String> = [ "xloc", "yloc", "zloc",
|
public static var trackNames: Array<String> = [ "xloc", "yloc", "zloc",
|
||||||
"xrot", "yrot", "zrot",
|
"xrot", "yrot", "zrot",
|
||||||
"qwrot", "qxrot", "qyrot", "qzrot",
|
"qwrot", "qxrot", "qyrot", "qzrot",
|
||||||
@ -39,7 +42,6 @@ class ObjectAnimation extends Animation {
|
|||||||
isSkinned = false;
|
isSkinned = false;
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAction(action: String): TObj {
|
function getAction(action: String): TObj {
|
||||||
for (a in oactions) if (a != null && a.objects[0].name == action) return a.objects[0];
|
for (a in oactions) if (a != null && a.objects[0].name == action) return a.objects[0];
|
||||||
return null;
|
return null;
|
||||||
@ -47,10 +49,29 @@ class ObjectAnimation extends Animation {
|
|||||||
|
|
||||||
override public function play(action = "", onComplete: Void->Void = null, blendTime = 0.0, speed = 1.0, loop = true) {
|
override public function play(action = "", onComplete: Void->Void = null, blendTime = 0.0, speed = 1.0, loop = true) {
|
||||||
super.play(action, onComplete, blendTime, speed, loop);
|
super.play(action, onComplete, blendTime, speed, loop);
|
||||||
if (this.action == "" && oactions[0] != null) this.action = oactions[0].objects[0].name;
|
if (this.action == "" && oactions != null && oactions[0] != null){
|
||||||
|
this.action = oactions[0].objects[0].name;
|
||||||
|
}
|
||||||
oaction = getAction(this.action);
|
oaction = getAction(this.action);
|
||||||
if (oaction != null) {
|
if (oaction != null) {
|
||||||
isSampled = oaction.sampled != null && oaction.sampled;
|
isSampled = oaction.sampled != null && oaction.sampled;
|
||||||
|
if (defaultSampler != null) {
|
||||||
|
deRegisterAction(DEFAULT_SAMPLER_ID);
|
||||||
|
}
|
||||||
|
var callbacks = onComplete != null ? [onComplete] : null;
|
||||||
|
defaultSampler = new ActionSampler(this.action, speed, loop, false, callbacks);
|
||||||
|
registerAction(DEFAULT_SAMPLER_ID, defaultSampler);
|
||||||
|
if (paused) defaultSampler.paused = true;
|
||||||
|
updateAnimation = function(map: Map<String, FastFloat>) {
|
||||||
|
sampleAction(defaultSampler, map);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (defaultSampler != null) {
|
||||||
|
deRegisterAction(DEFAULT_SAMPLER_ID);
|
||||||
|
defaultSampler = null;
|
||||||
|
}
|
||||||
|
updateAnimation = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,12 +82,13 @@ class ObjectAnimation extends Animation {
|
|||||||
Animation.beginProfile();
|
Animation.beginProfile();
|
||||||
#end
|
#end
|
||||||
|
|
||||||
if(transformMap == null) transformMap = new Map();
|
if (transformMap == null) transformMap = new Map();
|
||||||
transformMap = initTransformMap();
|
transformMap = initTransformMap();
|
||||||
|
|
||||||
super.update(delta);
|
super.update(delta);
|
||||||
|
if (defaultSampler != null) defaultSampler.paused = paused;
|
||||||
if (paused) return;
|
if (paused) return;
|
||||||
if(updateAnimation == null) return;
|
if (updateAnimation == null) return;
|
||||||
if (!isSkinned) updateObjectAnimation();
|
if (!isSkinned) updateObjectAnimation();
|
||||||
|
|
||||||
#if lnx_debug
|
#if lnx_debug
|
||||||
|
|||||||
@ -8,6 +8,8 @@ import kha.arrays.Float32Array;
|
|||||||
import iron.data.Data;
|
import iron.data.Data;
|
||||||
import iron.data.ParticleData;
|
import iron.data.ParticleData;
|
||||||
import iron.data.SceneFormat;
|
import iron.data.SceneFormat;
|
||||||
|
import iron.data.Geometry;
|
||||||
|
import iron.data.MeshData;
|
||||||
import iron.system.Time;
|
import iron.system.Time;
|
||||||
import iron.math.Mat4;
|
import iron.math.Mat4;
|
||||||
import iron.math.Quat;
|
import iron.math.Quat;
|
||||||
@ -17,6 +19,7 @@ import iron.math.Vec4;
|
|||||||
class ParticleSystem {
|
class ParticleSystem {
|
||||||
public var data: ParticleData;
|
public var data: ParticleData;
|
||||||
public var speed = 1.0;
|
public var speed = 1.0;
|
||||||
|
public var dynamicEmitter: Bool = true;
|
||||||
var currentSpeed = 0.0;
|
var currentSpeed = 0.0;
|
||||||
var particles: Array<Particle>;
|
var particles: Array<Particle>;
|
||||||
var ready: Bool;
|
var ready: Bool;
|
||||||
@ -52,6 +55,12 @@ class ParticleSystem {
|
|||||||
|
|
||||||
var random = 0.0;
|
var random = 0.0;
|
||||||
|
|
||||||
|
var tmpV4 = new Vec4();
|
||||||
|
|
||||||
|
var instancedData: Float32Array = null;
|
||||||
|
var lastSpawnedCount: Int = 0;
|
||||||
|
var hasUniqueGeom: Bool = false;
|
||||||
|
|
||||||
public function new(sceneName: String, pref: TParticleReference) {
|
public function new(sceneName: String, pref: TParticleReference) {
|
||||||
seed = pref.seed;
|
seed = pref.seed;
|
||||||
currentSpeed = speed;
|
currentSpeed = speed;
|
||||||
@ -62,6 +71,12 @@ class ParticleSystem {
|
|||||||
Data.getParticle(sceneName, pref.particle, function(b: ParticleData) {
|
Data.getParticle(sceneName, pref.particle, function(b: ParticleData) {
|
||||||
data = b;
|
data = b;
|
||||||
r = data.raw;
|
r = data.raw;
|
||||||
|
var dyn: Null<Bool> = r.dynamic_emitter;
|
||||||
|
var dynValue: Bool = true;
|
||||||
|
if (dyn != null) {
|
||||||
|
dynValue = dyn;
|
||||||
|
}
|
||||||
|
dynamicEmitter = dynValue;
|
||||||
if (Scene.active.raw.gravity != null) {
|
if (Scene.active.raw.gravity != null) {
|
||||||
gx = Scene.active.raw.gravity[0] * r.weight_gravity;
|
gx = Scene.active.raw.gravity[0] * r.weight_gravity;
|
||||||
gy = Scene.active.raw.gravity[1] * r.weight_gravity;
|
gy = Scene.active.raw.gravity[1] * r.weight_gravity;
|
||||||
@ -98,6 +113,8 @@ class ParticleSystem {
|
|||||||
lap = 0;
|
lap = 0;
|
||||||
lapTime = 0;
|
lapTime = 0;
|
||||||
speed = currentSpeed;
|
speed = currentSpeed;
|
||||||
|
lastSpawnedCount = 0;
|
||||||
|
instancedData = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function pause() {
|
public function pause() {
|
||||||
@ -130,8 +147,13 @@ class ParticleSystem {
|
|||||||
|
|
||||||
// Copy owner world transform but discard scale
|
// Copy owner world transform but discard scale
|
||||||
owner.transform.world.decompose(ownerLoc, ownerRot, ownerScl);
|
owner.transform.world.decompose(ownerLoc, ownerRot, ownerScl);
|
||||||
object.transform.loc = ownerLoc;
|
if (dynamicEmitter) {
|
||||||
object.transform.rot = ownerRot;
|
object.transform.loc.x = 0; object.transform.loc.y = 0; object.transform.loc.z = 0;
|
||||||
|
object.transform.rot = new Quat();
|
||||||
|
} else {
|
||||||
|
object.transform.loc = ownerLoc;
|
||||||
|
object.transform.rot = ownerRot;
|
||||||
|
}
|
||||||
|
|
||||||
// Set particle size per particle system
|
// Set particle size per particle system
|
||||||
object.transform.scale = new Vec4(r.particle_size, r.particle_size, r.particle_size, 1);
|
object.transform.scale = new Vec4(r.particle_size, r.particle_size, r.particle_size, 1);
|
||||||
@ -158,13 +180,18 @@ class ParticleSystem {
|
|||||||
if (lap > prevLap && !r.loop) {
|
if (lap > prevLap && !r.loop) {
|
||||||
end();
|
end();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (lap > prevLap && r.loop) {
|
||||||
|
lastSpawnedCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
updateGpu(object, owner);
|
updateGpu(object, owner);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getData(): Mat4 {
|
public function getData(): Mat4 {
|
||||||
var hair = r.type == 1;
|
var hair = r.type == 1;
|
||||||
m._00 = animtime;
|
// Store loop flag in the sign: positive -> loop, negative -> no loop
|
||||||
|
m._00 = r.loop ? animtime : -animtime;
|
||||||
m._01 = hair ? 1 / particles.length : spawnRate;
|
m._01 = hair ? 1 / particles.length : spawnRate;
|
||||||
m._02 = hair ? 1 : lifetime;
|
m._02 = hair ? 1 : lifetime;
|
||||||
m._03 = particles.length;
|
m._03 = particles.length;
|
||||||
@ -187,17 +214,26 @@ class ParticleSystem {
|
|||||||
return r.size_random;
|
return r.size_random;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getRandom(): FastFloat {
|
public inline function getRandom(): FastFloat {
|
||||||
return random;
|
return random;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSize(): FastFloat {
|
public inline function getSize(): FastFloat {
|
||||||
return r.particle_size;
|
return r.particle_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateGpu(object: MeshObject, owner: MeshObject) {
|
function updateGpu(object: MeshObject, owner: MeshObject) {
|
||||||
if (!object.data.geom.instanced) setupGeomGpu(object, owner);
|
if (dynamicEmitter) {
|
||||||
// GPU particles transform is attached to owner object
|
if (!hasUniqueGeom) ensureUniqueGeom(object);
|
||||||
|
var needSetup = instancedData == null || object.data.geom.instancedVB == null;
|
||||||
|
if (needSetup) setupGeomGpuDynamic(object, owner);
|
||||||
|
updateSpawnedInstances(object, owner);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (!hasUniqueGeom) ensureUniqueGeom(object);
|
||||||
|
if (!object.data.geom.instanced) setupGeomGpu(object, owner);
|
||||||
|
}
|
||||||
|
// GPU particles transform is attached to owner object in static mode
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupGeomGpu(object: MeshObject, owner: MeshObject) {
|
function setupGeomGpu(object: MeshObject, owner: MeshObject) {
|
||||||
@ -258,18 +294,134 @@ class ParticleSystem {
|
|||||||
object.data.geom.setupInstanced(instancedData, 1, Usage.StaticUsage);
|
object.data.geom.setupInstanced(instancedData, 1, Usage.StaticUsage);
|
||||||
}
|
}
|
||||||
|
|
||||||
function fhash(n: Int): Float {
|
// allocate instanced VB once for this object
|
||||||
var s = n + 1.0;
|
function setupGeomGpuDynamic(object: MeshObject, owner: MeshObject) {
|
||||||
s *= 9301.0 % s;
|
if (instancedData == null) instancedData = new Float32Array(particles.length * 3);
|
||||||
s = (s * 9301.0 + 49297.0) % 233280.0;
|
lastSpawnedCount = 0;
|
||||||
return s / 233280.0;
|
// Create instanced VB once if missing (seed with our instancedData)
|
||||||
|
if (object.data.geom.instancedVB == null) {
|
||||||
|
object.data.geom.setupInstanced(instancedData, 1, Usage.DynamicUsage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ensureUniqueGeom(object: MeshObject) {
|
||||||
|
if (hasUniqueGeom) return;
|
||||||
|
var newData: MeshData = null;
|
||||||
|
new MeshData(object.data.raw, function(dat: MeshData) {
|
||||||
|
dat.scalePos = object.data.scalePos;
|
||||||
|
dat.scaleTex = object.data.scaleTex;
|
||||||
|
dat.format = object.data.format;
|
||||||
|
newData = dat;
|
||||||
|
});
|
||||||
|
if (newData != null) object.setData(newData);
|
||||||
|
hasUniqueGeom = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSpawnedInstances(object: MeshObject, owner: MeshObject) {
|
||||||
|
if (instancedData == null) return;
|
||||||
|
var targetCount = count;
|
||||||
|
if (targetCount > particles.length) targetCount = particles.length;
|
||||||
|
if (targetCount <= lastSpawnedCount) return;
|
||||||
|
|
||||||
|
var normFactor = 1 / 32767;
|
||||||
|
var scalePosOwner = owner.data.scalePos;
|
||||||
|
var scalePosParticle = object.data.scalePos;
|
||||||
|
var particleSize = r.particle_size;
|
||||||
|
var base = 1.0 / (particleSize * scalePosParticle);
|
||||||
|
|
||||||
|
switch (r.emit_from) {
|
||||||
|
case 0: // Vert
|
||||||
|
var pa = owner.data.geom.positions;
|
||||||
|
var osx = owner.transform.scale.x;
|
||||||
|
var osy = owner.transform.scale.y;
|
||||||
|
var osz = owner.transform.scale.z;
|
||||||
|
var pCount = Std.int(pa.values.length / pa.size);
|
||||||
|
for (idx in lastSpawnedCount...targetCount) {
|
||||||
|
var j = Std.int(fhash(idx) * pCount);
|
||||||
|
var lx = pa.values[j * pa.size ] * normFactor * scalePosOwner * osx;
|
||||||
|
var ly = pa.values[j * pa.size + 1] * normFactor * scalePosOwner * osy;
|
||||||
|
var lz = pa.values[j * pa.size + 2] * normFactor * scalePosOwner * osz;
|
||||||
|
tmpV4.x = lx; tmpV4.y = ly; tmpV4.z = lz; tmpV4.w = 1;
|
||||||
|
tmpV4.applyQuat(ownerRot);
|
||||||
|
var o = idx * 3;
|
||||||
|
instancedData.set(o , (tmpV4.x + ownerLoc.x) * base);
|
||||||
|
instancedData.set(o + 1, (tmpV4.y + ownerLoc.y) * base);
|
||||||
|
instancedData.set(o + 2, (tmpV4.z + ownerLoc.z) * base);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 1: // Face
|
||||||
|
var positions = owner.data.geom.positions.values;
|
||||||
|
var osx1 = owner.transform.scale.x;
|
||||||
|
var osy1 = owner.transform.scale.y;
|
||||||
|
var osz1 = owner.transform.scale.z;
|
||||||
|
for (idx in lastSpawnedCount...targetCount) {
|
||||||
|
var ia = owner.data.geom.indices[Std.random(owner.data.geom.indices.length)];
|
||||||
|
var faceIndex = Std.random(Std.int(ia.length / 3));
|
||||||
|
var i0 = ia[faceIndex * 3 + 0];
|
||||||
|
var i1 = ia[faceIndex * 3 + 1];
|
||||||
|
var i2 = ia[faceIndex * 3 + 2];
|
||||||
|
var v0x = positions[i0 * 4 ], v0y = positions[i0 * 4 + 1], v0z = positions[i0 * 4 + 2];
|
||||||
|
var v1x = positions[i1 * 4 ], v1y = positions[i1 * 4 + 1], v1z = positions[i1 * 4 + 2];
|
||||||
|
var v2x = positions[i2 * 4 ], v2y = positions[i2 * 4 + 1], v2z = positions[i2 * 4 + 2];
|
||||||
|
var rx = Math.random(); var ry = Math.random(); if (rx + ry > 1) { rx = 1 - rx; ry = 1 - ry; }
|
||||||
|
var pxs = v0x + rx * (v1x - v0x) + ry * (v2x - v0x);
|
||||||
|
var pys = v0y + rx * (v1y - v0y) + ry * (v2y - v0y);
|
||||||
|
var pzs = v0z + rx * (v1z - v0z) + ry * (v2z - v0z);
|
||||||
|
var px = pxs * normFactor * scalePosOwner * osx1;
|
||||||
|
var py = pys * normFactor * scalePosOwner * osy1;
|
||||||
|
var pz = pzs * normFactor * scalePosOwner * osz1;
|
||||||
|
tmpV4.x = px; tmpV4.y = py; tmpV4.z = pz; tmpV4.w = 1;
|
||||||
|
tmpV4.applyQuat(ownerRot);
|
||||||
|
var o1 = idx * 3;
|
||||||
|
instancedData.set(o1 , (tmpV4.x + ownerLoc.x) * base);
|
||||||
|
instancedData.set(o1 + 1, (tmpV4.y + ownerLoc.y) * base);
|
||||||
|
instancedData.set(o1 + 2, (tmpV4.z + ownerLoc.z) * base);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 2: // Volume
|
||||||
|
var dim = object.transform.dim;
|
||||||
|
for (idx in lastSpawnedCount...targetCount) {
|
||||||
|
tmpV4.x = (Math.random() * 2.0 - 1.0) * (dim.x * 0.5);
|
||||||
|
tmpV4.y = (Math.random() * 2.0 - 1.0) * (dim.y * 0.5);
|
||||||
|
tmpV4.z = (Math.random() * 2.0 - 1.0) * (dim.z * 0.5);
|
||||||
|
tmpV4.w = 1;
|
||||||
|
tmpV4.applyQuat(ownerRot);
|
||||||
|
var o2 = idx * 3;
|
||||||
|
instancedData.set(o2 , (tmpV4.x + ownerLoc.x) * base);
|
||||||
|
instancedData.set(o2 + 1, (tmpV4.y + ownerLoc.y) * base);
|
||||||
|
instancedData.set(o2 + 2, (tmpV4.z + ownerLoc.z) * base);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload full active range [0..targetCount) to this object's instanced VB
|
||||||
|
var geom = object.data.geom;
|
||||||
|
if (geom.instancedVB == null) {
|
||||||
|
geom.setupInstanced(instancedData, 1, Usage.DynamicUsage);
|
||||||
|
}
|
||||||
|
var vb = geom.instancedVB.lock();
|
||||||
|
var totalFloats = targetCount * 3; // xyz per instance
|
||||||
|
var i = 0;
|
||||||
|
while (i < totalFloats) {
|
||||||
|
vb.setFloat32(i * 4, instancedData[i]);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
geom.instancedVB.unlock();
|
||||||
|
geom.instanceCount = targetCount;
|
||||||
|
lastSpawnedCount = targetCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline function fhash(n: Int): Float {
|
||||||
|
var s = n + 1.0;
|
||||||
|
s *= 9301.0 % s;
|
||||||
|
s = (s * 9301.0 + 49297.0) % 233280.0;
|
||||||
|
return s / 233280.0;
|
||||||
|
}
|
||||||
|
|
||||||
public function remove() {}
|
public function remove() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Generates a random point in the triangle with vertex positions abc.
|
Generates a random point in the triangle with vertex positions abc.
|
||||||
|
|
||||||
Please note that the given position vectors are changed in-place by this
|
Please note that the given position vectors are changed in-place by this
|
||||||
function and can be considered garbage afterwards, so make sure to clone
|
function and can be considered garbage afterwards, so make sure to clone
|
||||||
them first if needed.
|
them first if needed.
|
||||||
|
|||||||
@ -14,7 +14,7 @@ class Time {
|
|||||||
return 1 / frequency;
|
return 1 / frequency;
|
||||||
}
|
}
|
||||||
|
|
||||||
static var _fixedStep: Null<Float>;
|
static var _fixedStep: Null<Float> = 1/60;
|
||||||
public static var fixedStep(get, never): Float;
|
public static var fixedStep(get, never): Float;
|
||||||
static function get_fixedStep(): Float {
|
static function get_fixedStep(): Float {
|
||||||
return _fixedStep;
|
return _fixedStep;
|
||||||
@ -39,11 +39,11 @@ class Time {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static inline function time(): Float {
|
public static inline function time(): Float {
|
||||||
return kha.Scheduler.time();
|
return kha.Scheduler.time() * scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static inline function realTime(): Float {
|
public static inline function realTime(): Float {
|
||||||
return kha.Scheduler.realTime();
|
return kha.Scheduler.realTime() * scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function update() {
|
public static function update() {
|
||||||
|
|||||||
@ -94,34 +94,34 @@ class Tween {
|
|||||||
|
|
||||||
// Way too much Reflect trickery..
|
// Way too much Reflect trickery..
|
||||||
var ps = Reflect.fields(a.props);
|
var ps = Reflect.fields(a.props);
|
||||||
for (i in 0...ps.length) {
|
for (j in 0...ps.length) {
|
||||||
var p = ps[i];
|
var p = ps[j];
|
||||||
var k = a._time / a.duration;
|
var k = a._time / a.duration;
|
||||||
if (k > 1) k = 1;
|
if (k > 1) k = 1;
|
||||||
|
|
||||||
if (a._comps[i] == 1) {
|
if (a._comps[j] == 1) {
|
||||||
var fromVal: Float = a._x[i];
|
var fromVal: Float = a._x[j];
|
||||||
var toVal: Float = Reflect.getProperty(a.props, p);
|
var toVal: Float = Reflect.getProperty(a.props, p);
|
||||||
var val: Float = fromVal + (toVal - fromVal) * eases[a.ease](k);
|
var val: Float = fromVal + (toVal - fromVal) * eases[a.ease](k);
|
||||||
Reflect.setProperty(a.target, p, val);
|
Reflect.setProperty(a.target, p, val);
|
||||||
}
|
}
|
||||||
else { // _comps[i] == 4
|
else { // _comps[j] == 4
|
||||||
var obj = Reflect.getProperty(a.props, p);
|
var obj = Reflect.getProperty(a.props, p);
|
||||||
var toX: Float = Reflect.getProperty(obj, "x");
|
var toX: Float = Reflect.getProperty(obj, "x");
|
||||||
var toY: Float = Reflect.getProperty(obj, "y");
|
var toY: Float = Reflect.getProperty(obj, "y");
|
||||||
var toZ: Float = Reflect.getProperty(obj, "z");
|
var toZ: Float = Reflect.getProperty(obj, "z");
|
||||||
var toW: Float = Reflect.getProperty(obj, "w");
|
var toW: Float = Reflect.getProperty(obj, "w");
|
||||||
if (a._normalize[i]) {
|
if (a._normalize[j]) {
|
||||||
var qdot = (a._x[i] * toX) + (a._y[i] * toY) + (a._z[i] * toZ) + (a._w[i] * toW);
|
var qdot = (a._x[j] * toX) + (a._y[j] * toY) + (a._z[j] * toZ) + (a._w[j] * toW);
|
||||||
if (qdot < 0.0) {
|
if (qdot < 0.0) {
|
||||||
toX = -toX; toY = -toY; toZ = -toZ; toW = -toW;
|
toX = -toX; toY = -toY; toZ = -toZ; toW = -toW;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var x: Float = a._x[i] + (toX - a._x[i]) * eases[a.ease](k);
|
var x: Float = a._x[j] + (toX - a._x[j]) * eases[a.ease](k);
|
||||||
var y: Float = a._y[i] + (toY - a._y[i]) * eases[a.ease](k);
|
var y: Float = a._y[j] + (toY - a._y[j]) * eases[a.ease](k);
|
||||||
var z: Float = a._z[i] + (toZ - a._z[i]) * eases[a.ease](k);
|
var z: Float = a._z[j] + (toZ - a._z[j]) * eases[a.ease](k);
|
||||||
var w: Float = a._w[i] + (toW - a._w[i]) * eases[a.ease](k);
|
var w: Float = a._w[j] + (toW - a._w[j]) * eases[a.ease](k);
|
||||||
if (a._normalize[i]) {
|
if (a._normalize[j]) {
|
||||||
var l = Math.sqrt(x * x + y * y + z * z + w * w);
|
var l = Math.sqrt(x * x + y * y + z * z + w * w);
|
||||||
if (l > 0.0) {
|
if (l > 0.0) {
|
||||||
l = 1.0 / l;
|
l = 1.0 / l;
|
||||||
|
|||||||
52
leenkx/Sources/iron/system/VR.hx
Normal file
52
leenkx/Sources/iron/system/VR.hx
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
package iron.system;
|
||||||
|
|
||||||
|
import iron.math.Mat4;
|
||||||
|
|
||||||
|
#if lnx_vr
|
||||||
|
class VR {
|
||||||
|
|
||||||
|
static var undistortionMatrix: Mat4 = null;
|
||||||
|
|
||||||
|
public function new() {}
|
||||||
|
|
||||||
|
public static function getUndistortionMatrix(): Mat4 {
|
||||||
|
if (undistortionMatrix == null) {
|
||||||
|
undistortionMatrix = Mat4.identity();
|
||||||
|
}
|
||||||
|
|
||||||
|
return undistortionMatrix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getMaxRadiusSq(): Float {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function initButton() {
|
||||||
|
function vrDownListener(index: Int, x: Float, y: Float) {
|
||||||
|
var vr = kha.vr.VrInterface.instance;
|
||||||
|
if (vr == null || !vr.IsVrEnabled() || vr.IsPresenting()) return;
|
||||||
|
var w: Float = iron.App.w();
|
||||||
|
var h: Float = iron.App.h();
|
||||||
|
if (x < w - 150 || y < h - 150) return;
|
||||||
|
vr.onVRRequestPresent();
|
||||||
|
}
|
||||||
|
|
||||||
|
function vrRender2D(g: kha.graphics2.Graphics) {
|
||||||
|
var vr = kha.vr.VrInterface.instance;
|
||||||
|
if (vr == null || !vr.IsVrEnabled() || vr.IsPresenting()) return;
|
||||||
|
var w: Float = iron.App.w();
|
||||||
|
var h: Float = iron.App.h();
|
||||||
|
g.color = 0xffff0000;
|
||||||
|
g.fillRect(w - 150, h - 150, 140, 140);
|
||||||
|
}
|
||||||
|
|
||||||
|
kha.input.Mouse.get().notify(vrDownListener, null, null, null);
|
||||||
|
iron.App.notifyOnRender2D(vrRender2D);
|
||||||
|
|
||||||
|
var vr = kha.vr.VrInterface.instance; // Straight to VR (Oculus Carmel)
|
||||||
|
if (vr != null && vr.IsVrEnabled()) {
|
||||||
|
vr.onVRRequestPresent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#end
|
||||||
48
leenkx/Sources/leenkx/logicnode/AnyContactNode.hx
Normal file
48
leenkx/Sources/leenkx/logicnode/AnyContactNode.hx
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package leenkx.logicnode;
|
||||||
|
import iron.object.Object;
|
||||||
|
|
||||||
|
#if lnx_physics
|
||||||
|
import leenkx.trait.physics.PhysicsCache;
|
||||||
|
import leenkx.trait.physics.RigidBody;
|
||||||
|
#end
|
||||||
|
|
||||||
|
class AnyContactNode extends LogicNode {
|
||||||
|
|
||||||
|
public var property0: String;
|
||||||
|
var lastContact = false;
|
||||||
|
|
||||||
|
public function new(tree: LogicTree) {
|
||||||
|
super(tree);
|
||||||
|
tree.notifyOnUpdate(update);
|
||||||
|
}
|
||||||
|
|
||||||
|
function update() {
|
||||||
|
var object1: Object = inputs[0].get();
|
||||||
|
if (object1 == null) object1 = tree.object;
|
||||||
|
if (object1 == null) return;
|
||||||
|
|
||||||
|
var contact = false;
|
||||||
|
|
||||||
|
#if lnx_physics
|
||||||
|
var rb1 = PhysicsCache.getCachedRigidBody(object1);
|
||||||
|
if (rb1 != null) {
|
||||||
|
var rbs = PhysicsCache.getCachedContacts(rb1);
|
||||||
|
contact = (rbs != null && rbs.length > 0);
|
||||||
|
}
|
||||||
|
#end
|
||||||
|
|
||||||
|
var shouldTrigger = false;
|
||||||
|
switch (property0) {
|
||||||
|
case "begin":
|
||||||
|
shouldTrigger = contact && !lastContact;
|
||||||
|
case "overlap":
|
||||||
|
shouldTrigger = contact;
|
||||||
|
case "end":
|
||||||
|
shouldTrigger = !contact && lastContact;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastContact = contact;
|
||||||
|
|
||||||
|
if (shouldTrigger) runOutput(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -62,7 +62,7 @@ class DrawStringNode extends LogicNode {
|
|||||||
|
|
||||||
override function get(from: Int): Dynamic {
|
override function get(from: Int): Dynamic {
|
||||||
|
|
||||||
return from == 1 ? RenderToTexture.g.font.height(RenderToTexture.g.fontSize) : RenderToTexture.g.font.width(RenderToTexture.g.fontSize, string);
|
return from == 1 ? RenderToTexture.g.font.width(RenderToTexture.g.fontSize, string) : RenderToTexture.g.font.height(RenderToTexture.g.fontSize);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
17
leenkx/Sources/leenkx/logicnode/GetAudioPositionNode.hx
Normal file
17
leenkx/Sources/leenkx/logicnode/GetAudioPositionNode.hx
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package leenkx.logicnode;
|
||||||
|
|
||||||
|
import aura.Aura;
|
||||||
|
import aura.Types;
|
||||||
|
|
||||||
|
class GetAudioPositionNode extends LogicNode {
|
||||||
|
|
||||||
|
public function new(tree: LogicTree) {
|
||||||
|
super(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
override function get(from: Int): Dynamic {
|
||||||
|
var audio = inputs[0].get();
|
||||||
|
if (audio == null || audio.channel == null) return 0.0;
|
||||||
|
return audio.channel.floatPosition / audio.channel.sampleRate;
|
||||||
|
}
|
||||||
|
}
|
||||||
19
leenkx/Sources/leenkx/logicnode/GetCameraRenderFilterNode.hx
Normal file
19
leenkx/Sources/leenkx/logicnode/GetCameraRenderFilterNode.hx
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package leenkx.logicnode;
|
||||||
|
|
||||||
|
import iron.object.MeshObject;
|
||||||
|
import iron.object.CameraObject;
|
||||||
|
|
||||||
|
class GetCameraRenderFilterNode extends LogicNode {
|
||||||
|
|
||||||
|
public function new(tree: LogicTree) {
|
||||||
|
super(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
override function get(from: Int): Dynamic {
|
||||||
|
var mo: MeshObject = cast inputs[0].get();
|
||||||
|
|
||||||
|
if (mo == null) return null;
|
||||||
|
|
||||||
|
return mo.cameraList;
|
||||||
|
}
|
||||||
|
}
|
||||||
33
leenkx/Sources/leenkx/logicnode/GetPositionSpeakerNode.hx
Normal file
33
leenkx/Sources/leenkx/logicnode/GetPositionSpeakerNode.hx
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package leenkx.logicnode;
|
||||||
|
|
||||||
|
#if lnx_audio
|
||||||
|
import iron.object.SpeakerObject;
|
||||||
|
import kha.audio1.AudioChannel;
|
||||||
|
#end
|
||||||
|
|
||||||
|
class GetPositionSpeakerNode extends LogicNode {
|
||||||
|
|
||||||
|
public function new(tree: LogicTree) {
|
||||||
|
super(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
override function get(from: Int): Dynamic {
|
||||||
|
#if lnx_audio
|
||||||
|
var object: SpeakerObject = cast(inputs[0].get(), SpeakerObject);
|
||||||
|
if (object == null || object.sound == null) return 0.0;
|
||||||
|
|
||||||
|
if (object.channels.length == 0) return 0.0;
|
||||||
|
|
||||||
|
var channel = object.channels[0];
|
||||||
|
|
||||||
|
var position = 0.0;
|
||||||
|
if (channel != null) {
|
||||||
|
position = @:privateAccess channel.get_position();
|
||||||
|
}
|
||||||
|
|
||||||
|
return position;
|
||||||
|
#else
|
||||||
|
return 0.0;
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,26 +1,12 @@
|
|||||||
package leenkx.logicnode;
|
package leenkx.logicnode;
|
||||||
|
|
||||||
import iron.object.Object;
|
|
||||||
import iron.math.Vec4;
|
|
||||||
|
|
||||||
class GetWorldNode extends LogicNode {
|
class GetWorldNode extends LogicNode {
|
||||||
|
|
||||||
public var property0: String;
|
|
||||||
|
|
||||||
public function new(tree: LogicTree) {
|
public function new(tree: LogicTree) {
|
||||||
super(tree);
|
super(tree);
|
||||||
}
|
}
|
||||||
|
|
||||||
override function get(from: Int): Dynamic {
|
override function get(from: Int): Dynamic {
|
||||||
var object: Object = inputs[0].get();
|
return iron.Scene.active.raw.world_ref;
|
||||||
|
|
||||||
if (object == null) return null;
|
|
||||||
|
|
||||||
return switch (property0) {
|
|
||||||
case "Right": object.transform.world.right();
|
|
||||||
case "Look": object.transform.world.look();
|
|
||||||
case "Up": object.transform.world.up();
|
|
||||||
default: null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
26
leenkx/Sources/leenkx/logicnode/GetWorldOrientationNode.hx
Normal file
26
leenkx/Sources/leenkx/logicnode/GetWorldOrientationNode.hx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package leenkx.logicnode;
|
||||||
|
|
||||||
|
import iron.object.Object;
|
||||||
|
import iron.math.Vec4;
|
||||||
|
|
||||||
|
class GetWorldOrientationNode extends LogicNode {
|
||||||
|
|
||||||
|
public var property0: String;
|
||||||
|
|
||||||
|
public function new(tree: LogicTree) {
|
||||||
|
super(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
override function get(from: Int): Dynamic {
|
||||||
|
var object: Object = inputs[0].get();
|
||||||
|
|
||||||
|
if (object == null) return null;
|
||||||
|
|
||||||
|
return switch (property0) {
|
||||||
|
case "Right": object.transform.world.right();
|
||||||
|
case "Look": object.transform.world.look();
|
||||||
|
case "Up": object.transform.world.up();
|
||||||
|
default: null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,7 +1,10 @@
|
|||||||
package leenkx.logicnode;
|
package leenkx.logicnode;
|
||||||
|
|
||||||
import iron.object.Object;
|
import iron.object.Object;
|
||||||
|
|
||||||
|
#if lnx_physics
|
||||||
|
import leenkx.trait.physics.PhysicsCache;
|
||||||
import leenkx.trait.physics.RigidBody;
|
import leenkx.trait.physics.RigidBody;
|
||||||
|
#end
|
||||||
|
|
||||||
class HasContactNode extends LogicNode {
|
class HasContactNode extends LogicNode {
|
||||||
|
|
||||||
@ -15,12 +18,15 @@ class HasContactNode extends LogicNode {
|
|||||||
|
|
||||||
if (object1 == null || object2 == null) return false;
|
if (object1 == null || object2 == null) return false;
|
||||||
|
|
||||||
#if lnx_physics
|
#if lnx_physics
|
||||||
var physics = leenkx.trait.physics.PhysicsWorld.active;
|
var rb1 = PhysicsCache.getCachedRigidBody(object1);
|
||||||
var rb2 = object2.getTrait(RigidBody);
|
var rb2 = PhysicsCache.getCachedRigidBody(object2);
|
||||||
var rbs = physics.getContacts(object1.getTrait(RigidBody));
|
|
||||||
if (rbs != null) for (rb in rbs) if (rb == rb2) return true;
|
if (rb1 != null && rb2 != null) {
|
||||||
#end
|
var rbs = PhysicsCache.getCachedContacts(rb1);
|
||||||
|
return PhysicsCache.hasContactWith(rbs, rb2);
|
||||||
|
}
|
||||||
|
#end
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
233
leenkx/Sources/leenkx/logicnode/MouseLookNode.hx
Normal file
233
leenkx/Sources/leenkx/logicnode/MouseLookNode.hx
Normal file
@ -0,0 +1,233 @@
|
|||||||
|
package leenkx.logicnode;
|
||||||
|
|
||||||
|
import iron.math.Vec4;
|
||||||
|
import iron.system.Input;
|
||||||
|
import iron.object.Object;
|
||||||
|
import kha.System;
|
||||||
|
import kha.FastFloat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MouseLookNode - FPS-style mouse look camera controller
|
||||||
|
*
|
||||||
|
* This node provides smooth, resolution-independent mouse look functionality for
|
||||||
|
* first-person perspective controls. It supports separate body and head objects,
|
||||||
|
* allowing for realistic FPS camera movement where the body rotates horizontally
|
||||||
|
* and the head/camera rotates vertically.
|
||||||
|
*
|
||||||
|
* Key Features:
|
||||||
|
* - Resolution-adaptive scaling for consistent feel across different screen sizes
|
||||||
|
* - Configurable axis orientations (X, Y, Z as front)
|
||||||
|
* - Optional mouse cursor locking and hiding
|
||||||
|
* - Invertible X/Y axes
|
||||||
|
* - Rotation capping/limiting for both horizontal and vertical movement
|
||||||
|
* - Smoothing support for smoother camera movement
|
||||||
|
* - Physics integration with automatic rigid body synchronization
|
||||||
|
* - Support for both local and world space head rotation
|
||||||
|
*/
|
||||||
|
class MouseLookNode extends LogicNode {
|
||||||
|
// Configuration properties (set from Blender node interface)
|
||||||
|
public var property0: String; // Front axis: "X", "Y", or "Z"
|
||||||
|
public var property1: Bool; // Hide Locked: auto-lock mouse cursor
|
||||||
|
public var property2: Bool; // Invert X: invert horizontal mouse movement
|
||||||
|
public var property3: Bool; // Invert Y: invert vertical mouse movement
|
||||||
|
public var property4: Bool; // Cap Left/Right: limit horizontal rotation
|
||||||
|
public var property5: Bool; // Cap Up/Down: limit vertical rotation
|
||||||
|
public var property6: Bool; // Head Local Space: use local space for head rotation
|
||||||
|
|
||||||
|
// Smoothing state variables - maintain previous frame values for interpolation
|
||||||
|
var smoothX: Float = 0.0; // Smoothed horizontal mouse delta
|
||||||
|
var smoothY: Float = 0.0; // Smoothed vertical mouse delta
|
||||||
|
|
||||||
|
// Rotation limits (in radians)
|
||||||
|
var maxHorizontal: Float = Math.PI; // Maximum horizontal rotation (180 degrees)
|
||||||
|
var maxVertical: Float = Math.PI / 2; // Maximum vertical rotation (90 degrees)
|
||||||
|
|
||||||
|
// Current rotation tracking for capping calculations
|
||||||
|
var currentHorizontal: Float = 0.0; // Accumulated horizontal rotation
|
||||||
|
var currentVertical: Float = 0.0; // Accumulated vertical rotation
|
||||||
|
|
||||||
|
// Resolution scaling reference - base resolution for consistent sensitivity
|
||||||
|
var baseResolutionWidth: Float = 1920.0;
|
||||||
|
|
||||||
|
// Sensitivity scaling constants
|
||||||
|
static inline var BASE_SCALE: Float = 1500.0; // Base sensitivity scale factor
|
||||||
|
static var RADIAN_SCALING_FACTOR: Float = Math.PI * 50.0 / 180.0; // Degrees to radians conversion with sensitivity scaling
|
||||||
|
|
||||||
|
public function new(tree: LogicTree) {
|
||||||
|
super(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main execution function called every frame when the node is active
|
||||||
|
*
|
||||||
|
* Input connections:
|
||||||
|
* [0] - Action trigger (not used in current implementation)
|
||||||
|
* [1] - Body Object: the main object that rotates horizontally
|
||||||
|
* [2] - Head Object: optional object that rotates vertically (typically camera)
|
||||||
|
* [3] - Sensitivity: mouse sensitivity multiplier
|
||||||
|
* [4] - Smoothing: movement smoothing factor (0.0 = no smoothing, 0.99 = maximum smoothing)
|
||||||
|
*/
|
||||||
|
override function run(from: Int) {
|
||||||
|
// Get input values from connected nodes
|
||||||
|
var bodyObject: Object = inputs[1].get();
|
||||||
|
var headObject: Object = inputs[2].get();
|
||||||
|
var sensitivity: FastFloat = inputs[3].get();
|
||||||
|
var smoothing: FastFloat = inputs[4].get();
|
||||||
|
|
||||||
|
// Early exit if no body object is provided
|
||||||
|
if (bodyObject == null) {
|
||||||
|
runOutput(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get mouse input state
|
||||||
|
var mouse = Input.getMouse();
|
||||||
|
|
||||||
|
// Handle automatic mouse cursor locking for FPS controls
|
||||||
|
if (property1) {
|
||||||
|
if (mouse.started() && !mouse.locked) {
|
||||||
|
mouse.lock(); // Center and hide cursor, enable unlimited movement
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only process mouse look when cursor is locked or mouse button is held
|
||||||
|
// This prevents unwanted camera movement when UI elements are being used
|
||||||
|
if (!mouse.locked && !mouse.down()) {
|
||||||
|
runOutput(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get raw mouse movement delta (pixels moved since last frame)
|
||||||
|
var deltaX: Float = mouse.movementX;
|
||||||
|
var deltaY: Float = mouse.movementY;
|
||||||
|
|
||||||
|
// Apply axis inversion if configured
|
||||||
|
if (property2) deltaX = -deltaX; // Invert horizontal movement
|
||||||
|
if (property3) deltaY = -deltaY; // Invert vertical movement
|
||||||
|
|
||||||
|
// Calculate resolution-adaptive scaling to maintain consistent sensitivity
|
||||||
|
// across different screen resolutions. Higher resolutions will have proportionally
|
||||||
|
// higher scaling to compensate for increased pixel density.
|
||||||
|
var resolutionMultiplier: Float = System.windowWidth() / baseResolutionWidth;
|
||||||
|
|
||||||
|
// Apply movement smoothing if enabled
|
||||||
|
// This creates a weighted average between current and previous movement values
|
||||||
|
// to reduce jittery camera movement, especially useful for low framerates
|
||||||
|
if (smoothing > 0.0) {
|
||||||
|
var smoothingFactor: Float = Math.min(smoothing, 0.99); // Cap smoothing to prevent complete freeze
|
||||||
|
smoothX = smoothX * smoothingFactor + deltaX * (1.0 - smoothingFactor);
|
||||||
|
smoothY = smoothY * smoothingFactor + deltaY * (1.0 - smoothingFactor);
|
||||||
|
deltaX = smoothX;
|
||||||
|
deltaY = smoothY;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define rotation axes based on the configured front axis
|
||||||
|
// These determine which 3D axes are used for horizontal and vertical rotation
|
||||||
|
var horizontalAxis = new Vec4(); // Axis for left/right body rotation
|
||||||
|
var verticalAxis = new Vec4(); // Axis for up/down head rotation
|
||||||
|
|
||||||
|
switch (property0) {
|
||||||
|
case "X": // X-axis forward (e.g., for side-scrolling or specific orientations)
|
||||||
|
horizontalAxis.set(0, 0, 1); // Z-axis for horizontal rotation
|
||||||
|
verticalAxis.set(0, 1, 0); // Y-axis for vertical rotation
|
||||||
|
case "Y": // Y-axis forward (most common for 3D games)
|
||||||
|
#if lnx_yaxisup
|
||||||
|
// Y-up coordinate system (Blender default)
|
||||||
|
horizontalAxis.set(0, 0, 1); // Z-axis for horizontal rotation
|
||||||
|
verticalAxis.set(1, 0, 0); // X-axis for vertical rotation
|
||||||
|
#else
|
||||||
|
// Z-up coordinate system
|
||||||
|
horizontalAxis.set(0, 0, 1); // Z-axis for horizontal rotation
|
||||||
|
verticalAxis.set(1, 0, 0); // X-axis for vertical rotation
|
||||||
|
#end
|
||||||
|
case "Z": // Z-axis forward (top-down or specific orientations)
|
||||||
|
horizontalAxis.set(0, 1, 0); // Y-axis for horizontal rotation
|
||||||
|
verticalAxis.set(1, 0, 0); // X-axis for vertical rotation
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate final sensitivity scaling combining base scale and resolution adaptation
|
||||||
|
var finalScale: Float = BASE_SCALE * resolutionMultiplier;
|
||||||
|
|
||||||
|
// Apply user-defined sensitivity multiplier
|
||||||
|
deltaX *= sensitivity;
|
||||||
|
deltaY *= sensitivity;
|
||||||
|
|
||||||
|
// Convert pixel movement to rotation angles (radians)
|
||||||
|
// Negative values ensure natural movement direction (moving mouse right rotates right)
|
||||||
|
var horizontalRotation: Float = (-deltaX / finalScale) * RADIAN_SCALING_FACTOR;
|
||||||
|
var verticalRotation: Float = (-deltaY / finalScale) * RADIAN_SCALING_FACTOR;
|
||||||
|
|
||||||
|
// Apply horizontal rotation capping if enabled
|
||||||
|
// This prevents the character from rotating beyond specified limits
|
||||||
|
if (property4) {
|
||||||
|
currentHorizontal += horizontalRotation;
|
||||||
|
// Clamp rotation to maximum horizontal range and adjust current frame rotation
|
||||||
|
if (currentHorizontal > maxHorizontal) {
|
||||||
|
horizontalRotation -= (currentHorizontal - maxHorizontal);
|
||||||
|
currentHorizontal = maxHorizontal;
|
||||||
|
} else if (currentHorizontal < -maxHorizontal) {
|
||||||
|
horizontalRotation -= (currentHorizontal + maxHorizontal);
|
||||||
|
currentHorizontal = -maxHorizontal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply vertical rotation capping if enabled
|
||||||
|
// This prevents looking too far up or down (like human neck limitations)
|
||||||
|
if (property5) {
|
||||||
|
currentVertical += verticalRotation;
|
||||||
|
// Clamp rotation to maximum vertical range and adjust current frame rotation
|
||||||
|
if (currentVertical > maxVertical) {
|
||||||
|
verticalRotation -= (currentVertical - maxVertical);
|
||||||
|
currentVertical = maxVertical;
|
||||||
|
} else if (currentVertical < -maxVertical) {
|
||||||
|
verticalRotation -= (currentVertical + maxVertical);
|
||||||
|
currentVertical = -maxVertical;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply horizontal rotation to body object (character turning left/right)
|
||||||
|
if (horizontalRotation != 0.0) {
|
||||||
|
bodyObject.transform.rotate(horizontalAxis, horizontalRotation);
|
||||||
|
|
||||||
|
// Synchronize physics rigid body if present
|
||||||
|
// This ensures physics simulation stays in sync with visual transform
|
||||||
|
#if lnx_physics
|
||||||
|
var rigidBody = bodyObject.getTrait(leenkx.trait.physics.RigidBody);
|
||||||
|
if (rigidBody != null) rigidBody.syncTransform();
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply vertical rotation to head object (camera looking up/down)
|
||||||
|
if (headObject != null && verticalRotation != 0.0) {
|
||||||
|
if (property6) {
|
||||||
|
// Local space rotation - recommended when head is a child of body
|
||||||
|
// This prevents gimbal lock and rotation inheritance issues
|
||||||
|
headObject.transform.rotate(verticalAxis, verticalRotation);
|
||||||
|
} else {
|
||||||
|
// World space rotation - uses head object's current right vector
|
||||||
|
// More accurate for independent head objects but can cause issues with parenting
|
||||||
|
var headVerticalAxis = headObject.transform.world.right();
|
||||||
|
headObject.transform.rotate(headVerticalAxis, verticalRotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Synchronize head physics rigid body if present
|
||||||
|
#if lnx_physics
|
||||||
|
var headRigidBody = headObject.getTrait(leenkx.trait.physics.RigidBody);
|
||||||
|
if (headRigidBody != null) headRigidBody.syncTransform();
|
||||||
|
#end
|
||||||
|
} else if (headObject == null && verticalRotation != 0.0) {
|
||||||
|
// Fallback: if no separate head object, apply vertical rotation to body
|
||||||
|
// This creates a simpler single-object camera control
|
||||||
|
bodyObject.transform.rotate(verticalAxis, verticalRotation);
|
||||||
|
|
||||||
|
// Synchronize body physics rigid body
|
||||||
|
#if lnx_physics
|
||||||
|
var rigidBody = bodyObject.getTrait(leenkx.trait.physics.RigidBody);
|
||||||
|
if (rigidBody != null) rigidBody.syncTransform();
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continue to next connected node in the logic tree
|
||||||
|
runOutput(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,7 +1,11 @@
|
|||||||
package leenkx.logicnode;
|
package leenkx.logicnode;
|
||||||
|
|
||||||
import iron.object.Object;
|
import iron.object.Object;
|
||||||
|
|
||||||
|
#if lnx_physics
|
||||||
|
import leenkx.trait.physics.PhysicsCache;
|
||||||
import leenkx.trait.physics.RigidBody;
|
import leenkx.trait.physics.RigidBody;
|
||||||
|
#end
|
||||||
|
|
||||||
|
|
||||||
class OnContactNode extends LogicNode {
|
class OnContactNode extends LogicNode {
|
||||||
|
|
||||||
@ -23,22 +27,15 @@ class OnContactNode extends LogicNode {
|
|||||||
|
|
||||||
var contact = false;
|
var contact = false;
|
||||||
|
|
||||||
#if lnx_physics
|
#if lnx_physics
|
||||||
var physics = leenkx.trait.physics.PhysicsWorld.active;
|
var rb1 = PhysicsCache.getCachedRigidBody(object1);
|
||||||
var rb1 = object1.getTrait(RigidBody);
|
var rb2 = PhysicsCache.getCachedRigidBody(object2);
|
||||||
if (rb1 != null) {
|
|
||||||
var rbs = physics.getContacts(rb1);
|
if (rb1 != null && rb2 != null) {
|
||||||
if (rbs != null) {
|
var rbs = PhysicsCache.getCachedContacts(rb1);
|
||||||
var rb2 = object2.getTrait(RigidBody);
|
contact = PhysicsCache.hasContactWith(rbs, rb2);
|
||||||
for (rb in rbs) {
|
|
||||||
if (rb == rb2) {
|
|
||||||
contact = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
#end
|
||||||
#end
|
|
||||||
|
|
||||||
var b = false;
|
var b = false;
|
||||||
switch (property0) {
|
switch (property0) {
|
||||||
|
|||||||
23
leenkx/Sources/leenkx/logicnode/OnceNode.hx
Normal file
23
leenkx/Sources/leenkx/logicnode/OnceNode.hx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package leenkx.logicnode;
|
||||||
|
|
||||||
|
class OnceNode extends LogicNode {
|
||||||
|
|
||||||
|
var triggered:Bool = false;
|
||||||
|
|
||||||
|
public function new(tree: LogicTree) {
|
||||||
|
super(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
override function run(from: Int) {
|
||||||
|
if(from == 1){
|
||||||
|
triggered = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!triggered) {
|
||||||
|
triggered = true;
|
||||||
|
runOutput(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -9,19 +9,38 @@ import iron.Scene;
|
|||||||
|
|
||||||
class PlayAnimationTreeNode extends LogicNode {
|
class PlayAnimationTreeNode extends LogicNode {
|
||||||
|
|
||||||
|
var object: Object;
|
||||||
|
var action: Dynamic;
|
||||||
|
var init: Bool = false;
|
||||||
|
|
||||||
public function new(tree: LogicTree) {
|
public function new(tree: LogicTree) {
|
||||||
super(tree);
|
super(tree);
|
||||||
}
|
}
|
||||||
|
|
||||||
override function run(from: Int) {
|
override function run(from: Int) {
|
||||||
var object: Object = inputs[1].get();
|
object = inputs[1].get();
|
||||||
var action: Dynamic = inputs[2].get();
|
action = inputs[2].get();
|
||||||
|
|
||||||
assert(Error, object != null, "The object input not be null");
|
assert(Error, object != null, "The object input not be null");
|
||||||
|
init = true;
|
||||||
|
tree.notifyOnUpdate(playAnim);
|
||||||
|
// TO DO: Investigate AnimAction get and PlayAnimationTree notifiers
|
||||||
|
}
|
||||||
|
|
||||||
|
function playAnim() {
|
||||||
|
if (init = false) return;
|
||||||
|
|
||||||
|
init = false;
|
||||||
|
tree.removeUpdate(playAnim);
|
||||||
|
|
||||||
var animation = object.animation;
|
var animation = object.animation;
|
||||||
if(animation == null) {
|
if(animation == null) {
|
||||||
#if lnx_skin
|
#if lnx_skin
|
||||||
animation = object.getBoneAnimation(object.uid);
|
animation = object.getBoneAnimation(object.uid);
|
||||||
|
if (animation == null) {
|
||||||
|
tree.notifyOnUpdate(playAnim);
|
||||||
|
init = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
cast(animation, BoneAnimation).setAnimationLoop(function f(mats) {
|
cast(animation, BoneAnimation).setAnimationLoop(function f(mats) {
|
||||||
action(mats);
|
action(mats);
|
||||||
});
|
});
|
||||||
@ -32,7 +51,6 @@ class PlayAnimationTreeNode extends LogicNode {
|
|||||||
action(mats);
|
action(mats);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
runOutput(0);
|
runOutput(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
41
leenkx/Sources/leenkx/logicnode/ProbabilisticIndexNode.hx
Normal file
41
leenkx/Sources/leenkx/logicnode/ProbabilisticIndexNode.hx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package leenkx.logicnode;
|
||||||
|
|
||||||
|
class ProbabilisticIndexNode extends LogicNode {
|
||||||
|
|
||||||
|
public function new(tree: LogicTree) {
|
||||||
|
super(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
override function get(from: Int): Dynamic {
|
||||||
|
|
||||||
|
var probs: Array<Float> = [];
|
||||||
|
var probs_acum: Array<Float> = [];
|
||||||
|
var sum: Float = 0;
|
||||||
|
|
||||||
|
for (p in 0...inputs.length){
|
||||||
|
probs.push(inputs[p].get());
|
||||||
|
sum += probs[p];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sum > 1){
|
||||||
|
for (p in 0...probs.length)
|
||||||
|
probs[p] /= sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
sum = 0;
|
||||||
|
for (p in 0...probs.length){
|
||||||
|
sum += probs[p];
|
||||||
|
probs_acum.push(sum);
|
||||||
|
}
|
||||||
|
|
||||||
|
var rand: Float = Math.random();
|
||||||
|
|
||||||
|
for (p in 0...probs.length){
|
||||||
|
if (p == 0 && rand <= probs_acum[p]) return p;
|
||||||
|
else if (0 < p && p < probs.length-1 && probs_acum[p-1] < rand && rand <= probs_acum[p]) return p;
|
||||||
|
else if (p == probs.length-1 && probs_acum[p-1] < rand) return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
16
leenkx/Sources/leenkx/logicnode/ResolutionGetNode.hx
Normal file
16
leenkx/Sources/leenkx/logicnode/ResolutionGetNode.hx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package leenkx.logicnode;
|
||||||
|
|
||||||
|
class ResolutionGetNode extends LogicNode {
|
||||||
|
|
||||||
|
public function new(tree:LogicTree) {
|
||||||
|
super(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
override function get(from:Int):Dynamic {
|
||||||
|
return switch (from) {
|
||||||
|
case 0: leenkx.renderpath.Postprocess.resolution_uniforms[0];
|
||||||
|
case 1: leenkx.renderpath.Postprocess.resolution_uniforms[1];
|
||||||
|
default: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
33
leenkx/Sources/leenkx/logicnode/ResolutionSetNode.hx
Normal file
33
leenkx/Sources/leenkx/logicnode/ResolutionSetNode.hx
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package leenkx.logicnode;
|
||||||
|
|
||||||
|
import kha.graphics4.TextureFilter;
|
||||||
|
|
||||||
|
class ResolutionSetNode extends LogicNode {
|
||||||
|
|
||||||
|
public function new(tree:LogicTree) {
|
||||||
|
super(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
override function run(from:Int) {
|
||||||
|
|
||||||
|
var size: Int = inputs[1].get();
|
||||||
|
var filter: Int = inputs[2].get();
|
||||||
|
|
||||||
|
#if rp_resolution_filter
|
||||||
|
if (filter == 0)
|
||||||
|
iron.object.Uniforms.defaultFilter = TextureFilter.LinearFilter;
|
||||||
|
else
|
||||||
|
iron.object.Uniforms.defaultFilter = TextureFilter.PointFilter;
|
||||||
|
|
||||||
|
leenkx.renderpath.Postprocess.resolution_uniforms[0] = size;
|
||||||
|
leenkx.renderpath.Postprocess.resolution_uniforms[1] = filter;
|
||||||
|
|
||||||
|
var npath = leenkx.renderpath.RenderPathCreator.get();
|
||||||
|
var world = iron.Scene.active.raw.world_ref;
|
||||||
|
npath.loadShader("shader_datas/World_" + world + "/World_" + world);
|
||||||
|
iron.RenderPath.setActive(npath);
|
||||||
|
#end
|
||||||
|
|
||||||
|
runOutput(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
23
leenkx/Sources/leenkx/logicnode/SetAudioPositionNode.hx
Normal file
23
leenkx/Sources/leenkx/logicnode/SetAudioPositionNode.hx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package leenkx.logicnode;
|
||||||
|
|
||||||
|
import aura.Aura;
|
||||||
|
import aura.Types;
|
||||||
|
|
||||||
|
class SetAudioPositionNode extends LogicNode {
|
||||||
|
|
||||||
|
public function new(tree: LogicTree) {
|
||||||
|
super(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
override function run(from: Int) {
|
||||||
|
var audio = inputs[1].get();
|
||||||
|
if (audio == null) return;
|
||||||
|
|
||||||
|
var positionInSeconds:Float = inputs[2].get();
|
||||||
|
if (positionInSeconds < 0.0) positionInSeconds = 0.0;
|
||||||
|
|
||||||
|
audio.channel.floatPosition = positionInSeconds * audio.channel.sampleRate;
|
||||||
|
|
||||||
|
runOutput(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
38
leenkx/Sources/leenkx/logicnode/SetCameraRenderFilterNode.hx
Normal file
38
leenkx/Sources/leenkx/logicnode/SetCameraRenderFilterNode.hx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package leenkx.logicnode;
|
||||||
|
|
||||||
|
import iron.object.MeshObject;
|
||||||
|
import iron.object.CameraObject;
|
||||||
|
|
||||||
|
class SetCameraRenderFilterNode extends LogicNode {
|
||||||
|
|
||||||
|
public var property0: String;
|
||||||
|
|
||||||
|
public function new(tree: LogicTree) {
|
||||||
|
super(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
override function run(from: Int) {
|
||||||
|
var mo: MeshObject = cast inputs[1].get();
|
||||||
|
var camera: CameraObject = inputs[2].get();
|
||||||
|
|
||||||
|
assert(Error, Std.isOfType(camera, CameraObject), "Camera must be a camera object!");
|
||||||
|
|
||||||
|
if (camera == null || mo == null) return;
|
||||||
|
|
||||||
|
if (property0 == 'Add'){
|
||||||
|
if (mo.cameraList == null || mo.cameraList.indexOf(camera.name) == -1){
|
||||||
|
if (mo.cameraList == null) mo.cameraList = [];
|
||||||
|
mo.cameraList.push(camera.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
if (mo.cameraList != null){
|
||||||
|
mo.cameraList.remove(camera.name);
|
||||||
|
if (mo.cameraList.length == 0)
|
||||||
|
mo.cameraList = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
runOutput(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
21
leenkx/Sources/leenkx/logicnode/SetLightShadowNode.hx
Normal file
21
leenkx/Sources/leenkx/logicnode/SetLightShadowNode.hx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package leenkx.logicnode;
|
||||||
|
|
||||||
|
import iron.object.LightObject;
|
||||||
|
|
||||||
|
class SetLightShadowNode extends LogicNode {
|
||||||
|
|
||||||
|
public function new(tree: LogicTree) {
|
||||||
|
super(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
override function run(from: Int) {
|
||||||
|
var light: LightObject = inputs[1].get();
|
||||||
|
var shadow: Bool = inputs[2].get();
|
||||||
|
|
||||||
|
if (light == null) return;
|
||||||
|
|
||||||
|
light.data.raw.cast_shadow = shadow;
|
||||||
|
|
||||||
|
runOutput(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
206
leenkx/Sources/leenkx/logicnode/SetLookAtRotationNode.hx
Normal file
206
leenkx/Sources/leenkx/logicnode/SetLookAtRotationNode.hx
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
package leenkx.logicnode;
|
||||||
|
|
||||||
|
import iron.math.Vec4;
|
||||||
|
import iron.math.Quat;
|
||||||
|
import iron.math.Mat4;
|
||||||
|
import iron.object.Object;
|
||||||
|
|
||||||
|
class SetLookAtRotationNode extends LogicNode {
|
||||||
|
|
||||||
|
public var property0: String; // Axis to align
|
||||||
|
public var property1: String; // Use vector for target (true/false)
|
||||||
|
public var property2: String; // Use vector for source (true/false)
|
||||||
|
public var property3: String; // Damping value (backward compatibility, now input socket)
|
||||||
|
public var property4: String; // Disable rotation on aligning axis (true/false)
|
||||||
|
public var property5: String; // Use local space (true/false)
|
||||||
|
|
||||||
|
// Store the calculated rotation for output
|
||||||
|
var calculatedRotation: Quat = null;
|
||||||
|
// Store the previous rotation for smooth interpolation
|
||||||
|
var previousRotation: Quat = null;
|
||||||
|
|
||||||
|
public function new(tree: LogicTree) {
|
||||||
|
super(tree);
|
||||||
|
previousRotation = new Quat();
|
||||||
|
}
|
||||||
|
|
||||||
|
override function run(from: Int): Void {
|
||||||
|
// Determine if we're using a vector or an object as source
|
||||||
|
var useSourceVector: Bool = property2 == "true";
|
||||||
|
var objectToUse: Object = null;
|
||||||
|
var objectLoc: Vec4 = null;
|
||||||
|
|
||||||
|
if (useSourceVector) {
|
||||||
|
// Use tree.object as the object to rotate
|
||||||
|
objectToUse = tree.object;
|
||||||
|
if (objectToUse == null) {
|
||||||
|
runOutput(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the source location directly
|
||||||
|
objectLoc = inputs[1].get();
|
||||||
|
if (objectLoc == null) {
|
||||||
|
runOutput(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Get the source object (or fallback to tree.object)
|
||||||
|
objectToUse = (inputs.length > 1 && inputs[1] != null) ? inputs[1].get() : tree.object;
|
||||||
|
if (objectToUse == null) {
|
||||||
|
runOutput(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get source object's WORLD position (important for child objects)
|
||||||
|
objectLoc = new Vec4(objectToUse.transform.worldx(), objectToUse.transform.worldy(), objectToUse.transform.worldz());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine if we're using a vector or an object as target
|
||||||
|
var useTargetVector: Bool = property1 == "true";
|
||||||
|
var targetLoc: Vec4 = null;
|
||||||
|
|
||||||
|
if (useTargetVector) {
|
||||||
|
// Get the target location directly
|
||||||
|
targetLoc = inputs[2].get();
|
||||||
|
if (targetLoc == null) {
|
||||||
|
runOutput(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Get the target object
|
||||||
|
var targetObject: Object = inputs[2].get();
|
||||||
|
if (targetObject == null) {
|
||||||
|
runOutput(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get target object's WORLD position (important for child objects)
|
||||||
|
targetLoc = new Vec4(targetObject.transform.worldx(), targetObject.transform.worldy(), targetObject.transform.worldz());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate direction to target
|
||||||
|
var direction = new Vec4(
|
||||||
|
targetLoc.x - objectLoc.x,
|
||||||
|
targetLoc.y - objectLoc.y,
|
||||||
|
targetLoc.z - objectLoc.z
|
||||||
|
);
|
||||||
|
direction.normalize();
|
||||||
|
|
||||||
|
// Calculate target rotation based on selected axis
|
||||||
|
calculatedRotation = new Quat();
|
||||||
|
switch (property0) {
|
||||||
|
case "X":
|
||||||
|
calculatedRotation.fromTo(new Vec4(1, 0, 0), direction);
|
||||||
|
case "-X":
|
||||||
|
calculatedRotation.fromTo(new Vec4(-1, 0, 0), direction);
|
||||||
|
case "Y":
|
||||||
|
calculatedRotation.fromTo(new Vec4(0, 1, 0), direction);
|
||||||
|
case "-Y":
|
||||||
|
calculatedRotation.fromTo(new Vec4(0, -1, 0), direction);
|
||||||
|
case "Z":
|
||||||
|
calculatedRotation.fromTo(new Vec4(0, 0, 1), direction);
|
||||||
|
case "-Z":
|
||||||
|
calculatedRotation.fromTo(new Vec4(0, 0, -1), direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If disable rotation on aligning axis is enabled, constrain the target rotation
|
||||||
|
if (property4 == "true") {
|
||||||
|
// Apply constraint to the target rotation BEFORE damping to avoid jiggling
|
||||||
|
var eulerAngles = calculatedRotation.toEulerOrdered("XYZ");
|
||||||
|
|
||||||
|
// Set the rotation around the selected axis to 0
|
||||||
|
switch (property0) {
|
||||||
|
case "X", "-X":
|
||||||
|
eulerAngles.x = 0.0;
|
||||||
|
case "Y", "-Y":
|
||||||
|
eulerAngles.y = 0.0;
|
||||||
|
case "Z", "-Z":
|
||||||
|
eulerAngles.z = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert back to quaternion
|
||||||
|
calculatedRotation.fromEulerOrdered(eulerAngles, "XYZ");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert world rotation to local rotation if local space is enabled and object has a parent
|
||||||
|
var targetRotation = new Quat();
|
||||||
|
if (property5 == "true" && objectToUse.parent != null) {
|
||||||
|
// Get parent's world rotation
|
||||||
|
var parentWorldLoc = new Vec4();
|
||||||
|
var parentWorldRot = new Quat();
|
||||||
|
var parentWorldScale = new Vec4();
|
||||||
|
objectToUse.parent.transform.world.decompose(parentWorldLoc, parentWorldRot, parentWorldScale);
|
||||||
|
|
||||||
|
// Convert world rotation to local space by removing parent's rotation influence
|
||||||
|
// local_rotation = inverse(parent_world_rotation) * world_rotation
|
||||||
|
var invParentRot = new Quat().setFrom(parentWorldRot);
|
||||||
|
invParentRot.x = -invParentRot.x;
|
||||||
|
invParentRot.y = -invParentRot.y;
|
||||||
|
invParentRot.z = -invParentRot.z;
|
||||||
|
|
||||||
|
targetRotation.multquats(invParentRot, calculatedRotation);
|
||||||
|
} else {
|
||||||
|
// No local space conversion needed, use world rotation directly
|
||||||
|
targetRotation.setFrom(calculatedRotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply rotation with damping
|
||||||
|
var dampingValue: Float = 0.0;
|
||||||
|
|
||||||
|
// Try to get damping from input socket first (index 3), fallback to property
|
||||||
|
if (inputs.length > 3 && inputs[3] != null) {
|
||||||
|
var dampingInput: Dynamic = inputs[3].get();
|
||||||
|
if (dampingInput != null) {
|
||||||
|
dampingValue = dampingInput;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Fallback to property for backward compatibility
|
||||||
|
dampingValue = Std.parseFloat(property3);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dampingValue > 0.0) {
|
||||||
|
// Create a fixed interpolation rate that never reaches exactly 1.0
|
||||||
|
// Higher damping = slower rotation (smaller step)
|
||||||
|
var step = Math.max(0.001, (1.0 - dampingValue) * 0.2); // 0.001 to 0.2 range
|
||||||
|
|
||||||
|
// Get current local rotation as quaternion
|
||||||
|
var currentLocalRot = new Quat().setFrom(objectToUse.transform.rot);
|
||||||
|
|
||||||
|
// Calculate the difference between current and target rotation
|
||||||
|
var diffQuat = new Quat();
|
||||||
|
// q1 * inverse(q2) gives the rotation from q2 to q1
|
||||||
|
var invCurrent = new Quat().setFrom(currentLocalRot);
|
||||||
|
invCurrent.x = -invCurrent.x;
|
||||||
|
invCurrent.y = -invCurrent.y;
|
||||||
|
invCurrent.z = -invCurrent.z;
|
||||||
|
diffQuat.multquats(targetRotation, invCurrent);
|
||||||
|
|
||||||
|
// Convert to axis-angle representation
|
||||||
|
var axis = new Vec4();
|
||||||
|
var angle = diffQuat.toAxisAngle(axis);
|
||||||
|
|
||||||
|
// Apply only a portion of this rotation (step)
|
||||||
|
var partialAngle = angle * step;
|
||||||
|
|
||||||
|
// Create partial rotation quaternion
|
||||||
|
var partialRot = new Quat().fromAxisAngle(axis, partialAngle);
|
||||||
|
|
||||||
|
// Apply this partial rotation to current local rotation
|
||||||
|
var newLocalRot = new Quat();
|
||||||
|
newLocalRot.multquats(partialRot, currentLocalRot);
|
||||||
|
|
||||||
|
// Apply the new local rotation
|
||||||
|
objectToUse.transform.rot.setFrom(newLocalRot);
|
||||||
|
} else {
|
||||||
|
// No damping, apply instant rotation
|
||||||
|
objectToUse.transform.rot.setFrom(targetRotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
objectToUse.transform.buildMatrix();
|
||||||
|
|
||||||
|
runOutput(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// No output sockets needed - this node only performs actions
|
||||||
|
}
|
||||||
@ -0,0 +1,55 @@
|
|||||||
|
package leenkx.logicnode;
|
||||||
|
|
||||||
|
import iron.object.MeshObject;
|
||||||
|
import iron.data.MaterialData;
|
||||||
|
|
||||||
|
class SetMaterialTextureFilterNode extends LogicNode {
|
||||||
|
|
||||||
|
public function new(tree: LogicTree) {
|
||||||
|
super(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
override function run(from: Int) {
|
||||||
|
var object: MeshObject = inputs[1].get();
|
||||||
|
var mat: MaterialData = inputs[2].get();
|
||||||
|
var slot: Int = inputs[3].get();
|
||||||
|
var name: String = inputs[4].get();
|
||||||
|
var filter: Int = inputs[5].get();
|
||||||
|
|
||||||
|
if (object == null) return;
|
||||||
|
if (slot >= object.materials.length) return;
|
||||||
|
|
||||||
|
var mo = cast(object, iron.object.MeshObject);
|
||||||
|
|
||||||
|
for (i => node in mo.materials[slot].contexts[0].raw.bind_textures)
|
||||||
|
if (node.name == name){
|
||||||
|
var moImgt = mo.materials[slot].contexts[0].raw.bind_textures[i];
|
||||||
|
switch(filter){
|
||||||
|
case 0: //Linear
|
||||||
|
moImgt.min_filter = null;
|
||||||
|
moImgt.mag_filter = null;
|
||||||
|
moImgt.mipmap_filter = null;
|
||||||
|
moImgt.generate_mipmaps = null;
|
||||||
|
case 1: //Closest
|
||||||
|
moImgt.min_filter = 'point';
|
||||||
|
moImgt.mag_filter = 'point';
|
||||||
|
moImgt.mipmap_filter = null;
|
||||||
|
moImgt.generate_mipmaps = null;
|
||||||
|
case 2: //Cubic
|
||||||
|
moImgt.min_filter = null;
|
||||||
|
moImgt.mag_filter = null;
|
||||||
|
moImgt.mipmap_filter = 'linear';
|
||||||
|
moImgt.generate_mipmaps = true;
|
||||||
|
case 3: //Smart
|
||||||
|
moImgt.min_filter = 'anisotropic';
|
||||||
|
moImgt.mag_filter = null;
|
||||||
|
moImgt.mipmap_filter = 'linear';
|
||||||
|
moImgt.generate_mipmaps = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
runOutput(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,74 @@
|
|||||||
|
package leenkx.logicnode;
|
||||||
|
|
||||||
|
import iron.object.Object;
|
||||||
|
import iron.math.Vec4;
|
||||||
|
import iron.math.Mat4;
|
||||||
|
import iron.system.Time;
|
||||||
|
|
||||||
|
class SetObjectDelayedLocationNode extends LogicNode {
|
||||||
|
public var use_local_space: Bool = false;
|
||||||
|
|
||||||
|
private var initialOffset: Vec4 = null;
|
||||||
|
private var targetPos: Vec4 = new Vec4();
|
||||||
|
private var currentPos: Vec4 = new Vec4();
|
||||||
|
private var deltaVec: Vec4 = new Vec4();
|
||||||
|
private var tempVec: Vec4 = new Vec4();
|
||||||
|
|
||||||
|
private var lastParent: Object = null;
|
||||||
|
private var invParentMatrix: Mat4 = null;
|
||||||
|
|
||||||
|
public function new(tree: LogicTree) {
|
||||||
|
super(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
override function run(from: Int) {
|
||||||
|
var follower: Object = inputs[1].get();
|
||||||
|
var target: Object = inputs[2].get();
|
||||||
|
var delay: Float = inputs[3].get();
|
||||||
|
|
||||||
|
if (follower == null || target == null) return runOutput(0);
|
||||||
|
|
||||||
|
if (initialOffset == null) {
|
||||||
|
initialOffset = new Vec4();
|
||||||
|
var followerPos = follower.transform.world.getLoc();
|
||||||
|
var targetPos = target.transform.world.getLoc();
|
||||||
|
initialOffset.setFrom(followerPos);
|
||||||
|
initialOffset.sub(targetPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
targetPos.setFrom(target.transform.world.getLoc());
|
||||||
|
currentPos.setFrom(follower.transform.world.getLoc());
|
||||||
|
|
||||||
|
tempVec.setFrom(targetPos).add(initialOffset);
|
||||||
|
|
||||||
|
deltaVec.setFrom(tempVec).sub(currentPos);
|
||||||
|
|
||||||
|
if (deltaVec.length() < 0.001 && delay < 0.01) {
|
||||||
|
runOutput(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (delay == 0.0) {
|
||||||
|
currentPos.setFrom(tempVec);
|
||||||
|
} else {
|
||||||
|
var smoothFactor = Math.exp(-Time.delta / Math.max(0.0001, delay));
|
||||||
|
currentPos.x = tempVec.x + (currentPos.x - tempVec.x) * smoothFactor;
|
||||||
|
currentPos.y = tempVec.y + (currentPos.y - tempVec.y) * smoothFactor;
|
||||||
|
currentPos.z = tempVec.z + (currentPos.z - tempVec.z) * smoothFactor;
|
||||||
|
}
|
||||||
|
if (use_local_space && follower.parent != null) {
|
||||||
|
if (follower.parent != lastParent || invParentMatrix == null) {
|
||||||
|
lastParent = follower.parent;
|
||||||
|
invParentMatrix = Mat4.identity();
|
||||||
|
invParentMatrix.getInverse(follower.parent.transform.world);
|
||||||
|
}
|
||||||
|
tempVec.setFrom(currentPos);
|
||||||
|
tempVec.applymat(invParentMatrix);
|
||||||
|
follower.transform.loc.set(tempVec.x, tempVec.y, tempVec.z);
|
||||||
|
} else {
|
||||||
|
follower.transform.loc.set(currentPos.x, currentPos.y, currentPos.z);
|
||||||
|
}
|
||||||
|
follower.transform.buildMatrix();
|
||||||
|
runOutput(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -55,9 +55,9 @@ class SetParticleDataNode extends LogicNode {
|
|||||||
@:privateAccess psys.aligny = vel.y;
|
@:privateAccess psys.aligny = vel.y;
|
||||||
@:privateAccess psys.alignz = vel.z;
|
@:privateAccess psys.alignz = vel.z;
|
||||||
case 'Velocity Random':
|
case 'Velocity Random':
|
||||||
psys.r.factor_random = inputs[3].get();
|
@:privateAccess psys.r.factor_random = inputs[3].get();
|
||||||
case 'Weight Gravity':
|
case 'Weight Gravity':
|
||||||
psys.r.weight_gravity = inputs[3].get();
|
@:privateAccess psys.r.weight_gravity = inputs[3].get();
|
||||||
if (iron.Scene.active.raw.gravity != null) {
|
if (iron.Scene.active.raw.gravity != null) {
|
||||||
@:privateAccess psys.gx = iron.Scene.active.raw.gravity[0] * @:privateAccess psys.r.weight_gravity;
|
@:privateAccess psys.gx = iron.Scene.active.raw.gravity[0] * @:privateAccess psys.r.weight_gravity;
|
||||||
@:privateAccess psys.gy = iron.Scene.active.raw.gravity[1] * @:privateAccess psys.r.weight_gravity;
|
@:privateAccess psys.gy = iron.Scene.active.raw.gravity[1] * @:privateAccess psys.r.weight_gravity;
|
||||||
|
|||||||
39
leenkx/Sources/leenkx/logicnode/SetPositionSpeakerNode.hx
Normal file
39
leenkx/Sources/leenkx/logicnode/SetPositionSpeakerNode.hx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package leenkx.logicnode;
|
||||||
|
|
||||||
|
#if lnx_audio
|
||||||
|
import iron.object.SpeakerObject;
|
||||||
|
import kha.audio1.AudioChannel;
|
||||||
|
import iron.system.Audio;
|
||||||
|
#end
|
||||||
|
|
||||||
|
class SetPositionSpeakerNode extends LogicNode {
|
||||||
|
|
||||||
|
public function new(tree: LogicTree) {
|
||||||
|
super(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
override function run(from: Int) {
|
||||||
|
#if lnx_audio
|
||||||
|
var object: SpeakerObject = cast(inputs[1].get(), SpeakerObject);
|
||||||
|
if (object == null || object.sound == null) return;
|
||||||
|
|
||||||
|
var positionInSeconds:Float = inputs[2].get();
|
||||||
|
if (positionInSeconds < 0) positionInSeconds = 0;
|
||||||
|
|
||||||
|
var volume = object.data.volume;
|
||||||
|
var loop = object.data.loop;
|
||||||
|
var stream = object.data.stream;
|
||||||
|
|
||||||
|
object.stop();
|
||||||
|
|
||||||
|
var channel = Audio.play(object.sound, loop, stream);
|
||||||
|
if (channel != null) {
|
||||||
|
object.channels.push(channel);
|
||||||
|
channel.volume = volume;
|
||||||
|
@:privateAccess channel.set_position(positionInSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
#end
|
||||||
|
runOutput(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
23
leenkx/Sources/leenkx/logicnode/SetWorldNode.hx
Normal file
23
leenkx/Sources/leenkx/logicnode/SetWorldNode.hx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package leenkx.logicnode;
|
||||||
|
|
||||||
|
import iron.data.SceneFormat;
|
||||||
|
|
||||||
|
class SetWorldNode extends LogicNode {
|
||||||
|
|
||||||
|
public function new(tree: LogicTree) {
|
||||||
|
super(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
override function run(from: Int) {
|
||||||
|
var world: String = inputs[1].get();
|
||||||
|
|
||||||
|
if (world != null){
|
||||||
|
iron.Scene.active.raw.world_ref = world;
|
||||||
|
var npath = leenkx.renderpath.RenderPathCreator.get();
|
||||||
|
npath.loadShader("shader_datas/World_" + world + "/World_" + world);
|
||||||
|
iron.RenderPath.setActive(npath);
|
||||||
|
}
|
||||||
|
|
||||||
|
runOutput(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -780,7 +780,11 @@ class Inc {
|
|||||||
|
|
||||||
public static inline function getDisplayp(): Null<Int> {
|
public static inline function getDisplayp(): Null<Int> {
|
||||||
#if rp_resolution_filter // Custom resolution set
|
#if rp_resolution_filter // Custom resolution set
|
||||||
|
#if rp_pp
|
||||||
|
return leenkx.renderpath.Postprocess.resolution_uniforms[0];
|
||||||
|
#else
|
||||||
return Main.resolutionSize;
|
return Main.resolutionSize;
|
||||||
|
#end
|
||||||
#else
|
#else
|
||||||
return null;
|
return null;
|
||||||
#end
|
#end
|
||||||
|
|||||||
@ -133,6 +133,11 @@ class Postprocess {
|
|||||||
[50.0] //2: Volumetric Fog Amount B
|
[50.0] //2: Volumetric Fog Amount B
|
||||||
];
|
];
|
||||||
|
|
||||||
|
public static var resolution_uniforms = [
|
||||||
|
720, //0: Size
|
||||||
|
0 //1: Filter
|
||||||
|
];
|
||||||
|
|
||||||
public static function vec3Link(object: Object, mat: MaterialData, link: String): iron.math.Vec4 {
|
public static function vec3Link(object: Object, mat: MaterialData, link: String): iron.math.Vec4 {
|
||||||
var v:Vec4 = null;
|
var v:Vec4 = null;
|
||||||
|
|
||||||
|
|||||||
@ -672,18 +672,20 @@ class RenderPathForward {
|
|||||||
var framebuffer = "";
|
var framebuffer = "";
|
||||||
#end
|
#end
|
||||||
|
|
||||||
#if ((rp_antialiasing == "Off") || (rp_antialiasing == "FXAA"))
|
RenderPathCreator.finalTarget = path.currentTarget;
|
||||||
|
|
||||||
|
var target = "";
|
||||||
|
#if ((rp_antialiasing == "Off") || (rp_antialiasing == "FXAA") || (!rp_render_to_texture))
|
||||||
{
|
{
|
||||||
RenderPathCreator.finalTarget = path.currentTarget;
|
target = framebuffer;
|
||||||
path.setTarget(framebuffer);
|
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
{
|
{
|
||||||
path.setTarget("buf");
|
target = "buf";
|
||||||
RenderPathCreator.finalTarget = path.currentTarget;
|
|
||||||
}
|
}
|
||||||
#end
|
#end
|
||||||
|
path.setTarget(target);
|
||||||
|
|
||||||
#if rp_compositordepth
|
#if rp_compositordepth
|
||||||
{
|
{
|
||||||
path.bindTarget("_main", "gbufferD");
|
path.bindTarget("_main", "gbufferD");
|
||||||
@ -702,6 +704,15 @@ class RenderPathForward {
|
|||||||
}
|
}
|
||||||
#end
|
#end
|
||||||
|
|
||||||
|
#if rp_overlays
|
||||||
|
{
|
||||||
|
path.setTarget(target);
|
||||||
|
path.clearTarget(null, 1.0);
|
||||||
|
path.drawMeshes("overlay");
|
||||||
|
}
|
||||||
|
#end
|
||||||
|
|
||||||
|
|
||||||
#if ((rp_antialiasing == "SMAA") || (rp_antialiasing == "TAA"))
|
#if ((rp_antialiasing == "SMAA") || (rp_antialiasing == "TAA"))
|
||||||
{
|
{
|
||||||
path.setTarget("bufa");
|
path.setTarget("bufa");
|
||||||
@ -732,12 +743,6 @@ class RenderPathForward {
|
|||||||
}
|
}
|
||||||
#end
|
#end
|
||||||
|
|
||||||
#if rp_overlays
|
|
||||||
{
|
|
||||||
path.clearTarget(null, 1.0);
|
|
||||||
path.drawMeshes("overlay");
|
|
||||||
}
|
|
||||||
#end
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function setupDepthTexture() {
|
public static function setupDepthTexture() {
|
||||||
|
|||||||
@ -3,33 +3,35 @@ package leenkx.system;
|
|||||||
import haxe.Constraints.Function;
|
import haxe.Constraints.Function;
|
||||||
|
|
||||||
class Signal {
|
class Signal {
|
||||||
var callbacks:Array<Function> = [];
|
var callbacks: Array<Function> = [];
|
||||||
|
|
||||||
public function new() {
|
public function new() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function connect(callback:Function) {
|
public function connect(callback: Function) {
|
||||||
if (!callbacks.contains(callback)) callbacks.push(callback);
|
if (!callbacks.contains(callback)) callbacks.push(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function disconnect(callback:Function) {
|
public function disconnect(callback: Function) {
|
||||||
if (callbacks.contains(callback)) callbacks.remove(callback);
|
if (callbacks.contains(callback)) callbacks.remove(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function emit(...args:Any) {
|
public function emit(...args: Any) {
|
||||||
for (callback in callbacks) Reflect.callMethod(this, callback, args);
|
for (callback in callbacks.copy()) {
|
||||||
|
if (callbacks.contains(callback)) Reflect.callMethod(null, callback, args);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getConnections():Array<Function> {
|
public function getConnections(): Array<Function> {
|
||||||
return callbacks;
|
return callbacks;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isConnected(callBack:Function):Bool {
|
public function isConnected(callBack: Function):Bool {
|
||||||
return callbacks.contains(callBack);
|
return callbacks.contains(callBack);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isNull():Bool {
|
public function isNull(): Bool {
|
||||||
return callbacks.length == 0;
|
return callbacks.length == 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -61,7 +61,7 @@ class Starter {
|
|||||||
iron.Scene.getRenderPath = getRenderPath;
|
iron.Scene.getRenderPath = getRenderPath;
|
||||||
#end
|
#end
|
||||||
#if lnx_draworder_shader
|
#if lnx_draworder_shader
|
||||||
iron.RenderPath.active.drawOrder = iron.RenderPath.DrawOrder.Shader;
|
iron.RenderPath.active.drawOrder = iron.RenderPath.DrawOrder.Index;
|
||||||
#end // else Distance
|
#end // else Distance
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,87 +1,243 @@
|
|||||||
package leenkx.trait;
|
package leenkx.trait;
|
||||||
|
|
||||||
|
import iron.Trait;
|
||||||
import iron.math.Vec4;
|
import iron.math.Vec4;
|
||||||
import iron.system.Input;
|
import iron.system.Input;
|
||||||
import iron.object.Object;
|
import iron.object.Object;
|
||||||
import iron.object.CameraObject;
|
import iron.object.CameraObject;
|
||||||
import leenkx.trait.physics.PhysicsWorld;
|
import leenkx.trait.physics.PhysicsWorld;
|
||||||
import leenkx.trait.internal.CameraController;
|
import leenkx.trait.physics.RigidBody;
|
||||||
|
import kha.FastFloat;
|
||||||
|
|
||||||
class FirstPersonController extends CameraController {
|
class FirstPersonController extends Trait {
|
||||||
|
|
||||||
#if (!lnx_physics)
|
#if (!lnx_physics)
|
||||||
public function new() { super(); }
|
public function new() { super(); }
|
||||||
#else
|
#else
|
||||||
|
|
||||||
var head: Object;
|
@prop public var rotationSpeed:Float = 0.15;
|
||||||
static inline var rotationSpeed = 2.0;
|
@prop public var maxPitch:Float = 2.2;
|
||||||
|
@prop public var minPitch:Float = 0.5;
|
||||||
|
@prop public var enableJump:Bool = true;
|
||||||
|
@prop public var jumpForce:Float = 22.0;
|
||||||
|
@prop public var moveSpeed:Float = 500.0;
|
||||||
|
|
||||||
public function new() {
|
@prop public var forwardKey:String = "w";
|
||||||
super();
|
@prop public var backwardKey:String = "s";
|
||||||
|
@prop public var leftKey:String = "a";
|
||||||
|
@prop public var rightKey:String = "d";
|
||||||
|
@prop public var jumpKey:String = "space";
|
||||||
|
|
||||||
iron.Scene.active.notifyOnInit(init);
|
@prop public var allowAirJump:Bool = false;
|
||||||
}
|
|
||||||
|
|
||||||
function init() {
|
@prop public var canRun:Bool = true;
|
||||||
head = object.getChildOfType(CameraObject);
|
@prop public var runKey:String = "shift";
|
||||||
|
@prop public var runSpeed:Float = 1000.0;
|
||||||
|
|
||||||
PhysicsWorld.active.notifyOnPreUpdate(preUpdate);
|
// Sistema de estamina
|
||||||
notifyOnUpdate(update);
|
@prop public var stamina:Bool = false;
|
||||||
notifyOnRemove(removed);
|
@prop public var staminaBase:Float = 75.0;
|
||||||
}
|
@prop public var staRecoverPerSec:Float = 5.0;
|
||||||
|
@prop public var staDecreasePerSec:Float = 5.0;
|
||||||
|
@prop public var staRecoverTime:Float = 2.0;
|
||||||
|
@prop public var staDecreasePerJump:Float = 5.0;
|
||||||
|
@prop public var enableFatigue:Bool = false;
|
||||||
|
@prop public var fatigueSpeed:Float = 0.5; // the reduction of movement when fatigue is activated...
|
||||||
|
@prop public var fatigueThreshold:Float = 30.0; // Tiempo corriendo sin parar para la activacion // Time running non-stop for activation...
|
||||||
|
@prop public var fatRecoveryThreshold:Float = 7.5; // Tiempo sin correr/saltar para salir de fatiga // Time without running/jumping to get rid of fatigue...
|
||||||
|
|
||||||
|
|
||||||
var xVec = Vec4.xAxis();
|
// Var Privadas
|
||||||
var zVec = Vec4.zAxis();
|
var head:CameraObject;
|
||||||
function preUpdate() {
|
var pitch:Float = 0.0;
|
||||||
if (Input.occupied || !body.ready) return;
|
var body:RigidBody;
|
||||||
|
|
||||||
var mouse = Input.getMouse();
|
var moveForward:Bool = false;
|
||||||
var kb = Input.getKeyboard();
|
var moveBackward:Bool = false;
|
||||||
|
var moveLeft:Bool = false;
|
||||||
|
var moveRight:Bool = false;
|
||||||
|
var isRunning:Bool = false;
|
||||||
|
|
||||||
if (mouse.started() && !mouse.locked) mouse.lock();
|
var canJump:Bool = true;
|
||||||
else if (kb.started("escape") && mouse.locked) mouse.unlock();
|
var staminaValue:Float = 0.0;
|
||||||
|
var timeSinceStop:Float = 0.0;
|
||||||
|
|
||||||
if (mouse.locked || mouse.down()) {
|
var fatigueTimer:Float = 0.0;
|
||||||
head.transform.rotate(xVec, -mouse.movementY / 250 * rotationSpeed);
|
var fatigueCooldown:Float = 0.0;
|
||||||
transform.rotate(zVec, -mouse.movementX / 250 * rotationSpeed);
|
var isFatigueActive:Bool = false;
|
||||||
body.syncTransform();
|
|
||||||
|
public function new() {
|
||||||
|
super();
|
||||||
|
iron.Scene.active.notifyOnInit(init);
|
||||||
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
body = object.getTrait(RigidBody);
|
||||||
|
head = object.getChildOfType(CameraObject);
|
||||||
|
PhysicsWorld.active.notifyOnPreUpdate(preUpdate);
|
||||||
|
notifyOnUpdate(update);
|
||||||
|
notifyOnRemove(removed);
|
||||||
|
staminaValue = staminaBase;
|
||||||
|
}
|
||||||
|
|
||||||
|
function removed() {
|
||||||
|
PhysicsWorld.active.removePreUpdate(preUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
|
var zVec = Vec4.zAxis();
|
||||||
|
|
||||||
|
function preUpdate() {
|
||||||
|
if (Input.occupied || body == null) return;
|
||||||
|
var mouse = Input.getMouse();
|
||||||
|
var kb = Input.getKeyboard();
|
||||||
|
|
||||||
|
if (mouse.started() && !mouse.locked)
|
||||||
|
mouse.lock();
|
||||||
|
else if (kb.started("escape") && mouse.locked)
|
||||||
|
mouse.unlock();
|
||||||
|
|
||||||
|
if (mouse.locked || mouse.down()) {
|
||||||
|
var deltaTime:Float = iron.system.Time.delta;
|
||||||
|
object.transform.rotate(zVec, -mouse.movementX * rotationSpeed * deltaTime);
|
||||||
|
var deltaPitch:Float = -(mouse.movementY * rotationSpeed * deltaTime);
|
||||||
|
pitch += deltaPitch;
|
||||||
|
pitch = Math.max(minPitch, Math.min(maxPitch, pitch));
|
||||||
|
head.transform.setRotation(pitch, 0.0, 0.0);
|
||||||
|
body.syncTransform();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var dir:Vec4 = new Vec4();
|
||||||
|
|
||||||
|
function isFatigued():Bool {
|
||||||
|
return enableFatigue && isFatigueActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
function update() {
|
||||||
|
if (body == null) return;
|
||||||
|
var deltaTime:Float = iron.system.Time.delta;
|
||||||
|
var kb = Input.getKeyboard();
|
||||||
|
|
||||||
|
moveForward = kb.down(forwardKey);
|
||||||
|
moveBackward = kb.down(backwardKey);
|
||||||
|
moveLeft = kb.down(leftKey);
|
||||||
|
moveRight = kb.down(rightKey);
|
||||||
|
var isMoving = moveForward || moveBackward || moveLeft || moveRight;
|
||||||
|
|
||||||
|
var isGrounded:Bool = false;
|
||||||
|
#if lnx_physics
|
||||||
|
var vel = body.getLinearVelocity();
|
||||||
|
if (Math.abs(vel.z) < 0.1) {
|
||||||
|
isGrounded = true;
|
||||||
|
}
|
||||||
|
#end
|
||||||
|
|
||||||
|
// Dejo establecido el salto para tener en cuenta la (enableFatigue) si es que es false/true....
|
||||||
|
if (isGrounded && !isFatigued()) {
|
||||||
|
canJump = true;
|
||||||
}
|
}
|
||||||
}
|
// Saltar con estamina
|
||||||
|
if (enableJump && kb.started(jumpKey) && canJump) {
|
||||||
|
var jumpPower = jumpForce;
|
||||||
|
// Disminuir el salto al 50% si la (stamina) esta por debajo o en el 20%.
|
||||||
|
if (stamina) {
|
||||||
|
if (staminaValue <= 0) {
|
||||||
|
jumpPower = 0;
|
||||||
|
} else if (staminaValue <= staminaBase * 0.2) {
|
||||||
|
jumpPower *= 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
function removed() {
|
staminaValue -= staDecreasePerJump;
|
||||||
PhysicsWorld.active.removePreUpdate(preUpdate);
|
if (staminaValue < 0.0) staminaValue = 0.0;
|
||||||
}
|
timeSinceStop = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
var dir = new Vec4();
|
if (jumpPower > 0) {
|
||||||
function update() {
|
body.applyImpulse(new Vec4(0, 0, jumpPower));
|
||||||
if (!body.ready) return;
|
if (!allowAirJump) canJump = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (jump) {
|
// Control de estamina y correr
|
||||||
body.applyImpulse(new Vec4(0, 0, 16));
|
if (canRun && kb.down(runKey) && isMoving) {
|
||||||
jump = false;
|
if (stamina) {
|
||||||
|
if (staminaValue > 0.0) {
|
||||||
|
isRunning = true;
|
||||||
|
staminaValue -= staDecreasePerSec * deltaTime;
|
||||||
|
if (staminaValue < 0.0) staminaValue = 0.0;
|
||||||
|
} else {
|
||||||
|
isRunning = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
isRunning = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
isRunning = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// (temporizadores aparte)
|
||||||
|
if (isRunning) {
|
||||||
|
timeSinceStop = 0.0;
|
||||||
|
fatigueTimer += deltaTime;
|
||||||
|
fatigueCooldown = 0.0;
|
||||||
|
} else {
|
||||||
|
timeSinceStop += deltaTime;
|
||||||
|
fatigueCooldown += deltaTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evitar correr y saltar al estar fatigado...
|
||||||
|
if (isFatigued()) {
|
||||||
|
isRunning = false;
|
||||||
|
canJump = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move
|
// Activar fatiga despues de correr continuamente durante cierto umbral
|
||||||
dir.set(0, 0, 0);
|
if (enableFatigue && fatigueTimer >= fatigueThreshold) {
|
||||||
if (moveForward) dir.add(transform.look());
|
isFatigueActive = true;
|
||||||
if (moveBackward) dir.add(transform.look().mult(-1));
|
}
|
||||||
if (moveLeft) dir.add(transform.right().mult(-1));
|
|
||||||
if (moveRight) dir.add(transform.right());
|
|
||||||
|
|
||||||
// Push down
|
// Eliminar la fatiga despues de recuperarse
|
||||||
var btvec = body.getLinearVelocity();
|
if (enableFatigue && isFatigueActive && fatigueCooldown >= fatRecoveryThreshold) {
|
||||||
body.setLinearVelocity(0.0, 0.0, btvec.z - 1.0);
|
isFatigueActive = false;
|
||||||
|
fatigueTimer = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
if (moveForward || moveBackward || moveLeft || moveRight) {
|
// Recuperar estamina si no esta corriendo
|
||||||
var dirN = dir.normalize();
|
if (stamina && !isRunning && staminaValue < staminaBase && !isFatigued()) {
|
||||||
dirN.mult(6);
|
if (timeSinceStop >= staRecoverTime) {
|
||||||
body.activate();
|
staminaValue += staRecoverPerSec * deltaTime;
|
||||||
body.setLinearVelocity(dirN.x, dirN.y, btvec.z - 1.0);
|
if (staminaValue > staminaBase) staminaValue = staminaBase;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Keep vertical
|
// Movimiento ejes (local)
|
||||||
body.setAngularFactor(0, 0, 0);
|
dir.set(0, 0, 0);
|
||||||
camera.buildMatrix();
|
if (moveForward) dir.add(object.transform.look());
|
||||||
}
|
if (moveBackward) dir.add(object.transform.look().mult(-1));
|
||||||
#end
|
if (moveLeft) dir.add(object.transform.right().mult(-1));
|
||||||
|
if (moveRight) dir.add(object.transform.right());
|
||||||
|
|
||||||
|
var btvec = body.getLinearVelocity();
|
||||||
|
body.setLinearVelocity(0.0, 0.0, btvec.z - 1.0);
|
||||||
|
|
||||||
|
if (isMoving) {
|
||||||
|
var dirN = dir.normalize();
|
||||||
|
var baseSpeed = moveSpeed;
|
||||||
|
if (isRunning && moveForward) {
|
||||||
|
baseSpeed = runSpeed;
|
||||||
|
}
|
||||||
|
var currentSpeed = isFatigued() ? baseSpeed * fatigueSpeed : baseSpeed;
|
||||||
|
dirN.mult(currentSpeed * deltaTime);
|
||||||
|
body.activate();
|
||||||
|
body.setLinearVelocity(dirN.x, dirN.y, btvec.z - 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.setAngularFactor(0, 0, 0);
|
||||||
|
head.buildMatrix();
|
||||||
|
}
|
||||||
|
|
||||||
|
#end
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Stamina and fatigue system.....
|
||||||
@ -73,7 +73,17 @@ class PhysicsBreak extends Trait {
|
|||||||
collisionMargin: 0.04,
|
collisionMargin: 0.04,
|
||||||
linearDeactivationThreshold: 0.0,
|
linearDeactivationThreshold: 0.0,
|
||||||
angularDeactivationThrshold: 0.0,
|
angularDeactivationThrshold: 0.0,
|
||||||
deactivationTime: 0.0
|
deactivationTime: 0.0,
|
||||||
|
linearVelocityMin: 0.0,
|
||||||
|
linearVelocityMax: 0.0,
|
||||||
|
angularVelocityMin: 0.0,
|
||||||
|
angularVelocityMax: 0.0,
|
||||||
|
lockTranslationX: false,
|
||||||
|
lockTranslationY: false,
|
||||||
|
lockTranslationZ: false,
|
||||||
|
lockRotationX: false,
|
||||||
|
lockRotationY: false,
|
||||||
|
lockRotationZ: false
|
||||||
};
|
};
|
||||||
o.addTrait(new RigidBody(Shape.ConvexHull, ud.mass, ud.friction, 0, 1, params));
|
o.addTrait(new RigidBody(Shape.ConvexHull, ud.mass, ud.friction, 0, 1, params));
|
||||||
if (cast(o, MeshObject).data.geom.positions.values.length < 600) {
|
if (cast(o, MeshObject).data.geom.positions.values.length < 600) {
|
||||||
|
|||||||
@ -280,7 +280,11 @@ class DebugConsole extends Trait {
|
|||||||
|
|
||||||
function drawObjectNameInList(object: iron.object.Object, selected: Bool) {
|
function drawObjectNameInList(object: iron.object.Object, selected: Bool) {
|
||||||
var _y = ui._y;
|
var _y = ui._y;
|
||||||
ui.text(object.uid+'_'+object.name);
|
|
||||||
|
if (object.parent.name == 'Root' && object.raw == null)
|
||||||
|
ui.text(object.uid+'_'+object.name+' ('+iron.Scene.active.raw.world_ref+')');
|
||||||
|
else
|
||||||
|
ui.text(object.uid+'_'+object.name);
|
||||||
|
|
||||||
if (object == iron.Scene.active.camera) {
|
if (object == iron.Scene.active.camera) {
|
||||||
var tagWidth = 100;
|
var tagWidth = 100;
|
||||||
|
|||||||
98
leenkx/Sources/leenkx/trait/physics/PhysicsCache.hx
Normal file
98
leenkx/Sources/leenkx/trait/physics/PhysicsCache.hx
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
package leenkx.trait.physics;
|
||||||
|
|
||||||
|
import iron.object.Object;
|
||||||
|
|
||||||
|
class PhysicsCache {
|
||||||
|
#if lnx_physics
|
||||||
|
static var rbCache: Map<Int, Dynamic> = new Map();
|
||||||
|
static var contactsCache: Map<Int, Array<Dynamic>> = new Map();
|
||||||
|
static var physicsFrame: Int = 0;
|
||||||
|
static var lastQueryFrame: Int = -1;
|
||||||
|
#end
|
||||||
|
|
||||||
|
public static function getCachedRigidBody(object: Object): Dynamic {
|
||||||
|
#if (!lnx_physics)
|
||||||
|
return null;
|
||||||
|
#else
|
||||||
|
if (object == null) return null;
|
||||||
|
|
||||||
|
var cached = rbCache.get(object.uid);
|
||||||
|
if (cached != null) return cached;
|
||||||
|
|
||||||
|
#if lnx_bullet
|
||||||
|
var rb = object.getTrait(leenkx.trait.physics.bullet.RigidBody);
|
||||||
|
#else
|
||||||
|
var rb = object.getTrait(leenkx.trait.physics.oimo.RigidBody);
|
||||||
|
#end
|
||||||
|
|
||||||
|
if (rb != null) rbCache.set(object.uid, rb);
|
||||||
|
return rb;
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getCachedContacts(rb: Dynamic): Array<Dynamic> {
|
||||||
|
#if (!lnx_physics)
|
||||||
|
return null;
|
||||||
|
#else
|
||||||
|
if (rb == null) return null;
|
||||||
|
|
||||||
|
var rbObjectId = (rb.object != null) ? rb.object.uid : -1;
|
||||||
|
|
||||||
|
if (rbObjectId == -1) {
|
||||||
|
#if lnx_bullet
|
||||||
|
if (leenkx.trait.physics.bullet.PhysicsWorld.active == null) return null;
|
||||||
|
return leenkx.trait.physics.bullet.PhysicsWorld.active.getContacts(rb);
|
||||||
|
#else
|
||||||
|
if (leenkx.trait.physics.oimo.PhysicsWorld.active == null) return null;
|
||||||
|
return leenkx.trait.physics.oimo.PhysicsWorld.active.getContacts(rb);
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastQueryFrame == physicsFrame) {
|
||||||
|
var cached = contactsCache.get(rbObjectId);
|
||||||
|
if (cached != null) return cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastQueryFrame = physicsFrame;
|
||||||
|
|
||||||
|
var cached = contactsCache.get(rbObjectId);
|
||||||
|
if (cached != null) return cached;
|
||||||
|
|
||||||
|
#if lnx_bullet
|
||||||
|
if (leenkx.trait.physics.bullet.PhysicsWorld.active == null) return null;
|
||||||
|
var contacts = leenkx.trait.physics.bullet.PhysicsWorld.active.getContacts(rb);
|
||||||
|
#else
|
||||||
|
if (leenkx.trait.physics.oimo.PhysicsWorld.active == null) return null;
|
||||||
|
var contacts = leenkx.trait.physics.oimo.PhysicsWorld.active.getContacts(rb);
|
||||||
|
#end
|
||||||
|
|
||||||
|
if (contacts != null) {
|
||||||
|
contactsCache.set(rbObjectId, contacts);
|
||||||
|
}
|
||||||
|
|
||||||
|
return contacts;
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
|
||||||
|
public static inline function hasContactWith(contacts: Array<Dynamic>, target: Dynamic): Bool {
|
||||||
|
#if (!lnx_physics)
|
||||||
|
return false;
|
||||||
|
#else
|
||||||
|
return contacts != null && target != null && contacts.indexOf(target) >= 0;
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function clearCache() {
|
||||||
|
#if lnx_physics
|
||||||
|
rbCache.clear();
|
||||||
|
contactsCache.clear();
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function clearContactsCache() {
|
||||||
|
#if lnx_physics
|
||||||
|
physicsFrame++;
|
||||||
|
contactsCache.clear();
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -8,11 +8,9 @@ class PhysicsWorld extends iron.Trait { public function new() { super(); } }
|
|||||||
#else
|
#else
|
||||||
|
|
||||||
#if lnx_bullet
|
#if lnx_bullet
|
||||||
|
|
||||||
typedef PhysicsWorld = leenkx.trait.physics.bullet.PhysicsWorld;
|
typedef PhysicsWorld = leenkx.trait.physics.bullet.PhysicsWorld;
|
||||||
typedef Hit = leenkx.trait.physics.bullet.PhysicsWorld.Hit;
|
typedef Hit = leenkx.trait.physics.bullet.PhysicsWorld.Hit;
|
||||||
#else
|
#else
|
||||||
|
|
||||||
typedef PhysicsWorld = leenkx.trait.physics.oimo.PhysicsWorld;
|
typedef PhysicsWorld = leenkx.trait.physics.oimo.PhysicsWorld;
|
||||||
typedef Hit = leenkx.trait.physics.oimo.PhysicsWorld.Hit;
|
typedef Hit = leenkx.trait.physics.oimo.PhysicsWorld.Hit;
|
||||||
#end
|
#end
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import iron.system.Time;
|
|||||||
import iron.math.Vec4;
|
import iron.math.Vec4;
|
||||||
import iron.math.Quat;
|
import iron.math.Quat;
|
||||||
import iron.math.RayCaster;
|
import iron.math.RayCaster;
|
||||||
|
import leenkx.trait.physics.PhysicsCache;
|
||||||
|
|
||||||
class Hit {
|
class Hit {
|
||||||
|
|
||||||
@ -145,6 +146,7 @@ class PhysicsWorld extends Trait {
|
|||||||
|
|
||||||
iron.Scene.active.notifyOnRemove(function() {
|
iron.Scene.active.notifyOnRemove(function() {
|
||||||
sceneRemoved = true;
|
sceneRemoved = true;
|
||||||
|
PhysicsCache.clearCache();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -303,6 +305,8 @@ class PhysicsWorld extends Trait {
|
|||||||
var t = Time.fixedStep * timeScale * Time.scale;
|
var t = Time.fixedStep * timeScale * Time.scale;
|
||||||
if (t == 0.0) return; // Simulation paused
|
if (t == 0.0) return; // Simulation paused
|
||||||
|
|
||||||
|
PhysicsCache.clearContactsCache();
|
||||||
|
|
||||||
#if lnx_debug
|
#if lnx_debug
|
||||||
var startTime = kha.Scheduler.realTime();
|
var startTime = kha.Scheduler.realTime();
|
||||||
#end
|
#end
|
||||||
|
|||||||
@ -36,6 +36,18 @@ class RigidBody extends iron.Trait {
|
|||||||
var useDeactivation: Bool;
|
var useDeactivation: Bool;
|
||||||
var deactivationParams: Array<Float>;
|
var deactivationParams: Array<Float>;
|
||||||
var ccd = false; // Continuous collision detection
|
var ccd = false; // Continuous collision detection
|
||||||
|
// New velocity limiting properties
|
||||||
|
var linearVelocityMin: Float;
|
||||||
|
var linearVelocityMax: Float;
|
||||||
|
var angularVelocityMin: Float;
|
||||||
|
var angularVelocityMax: Float;
|
||||||
|
// New lock properties
|
||||||
|
var lockTranslationX: Bool;
|
||||||
|
var lockTranslationY: Bool;
|
||||||
|
var lockTranslationZ: Bool;
|
||||||
|
var lockRotationX: Bool;
|
||||||
|
var lockRotationY: Bool;
|
||||||
|
var lockRotationZ: Bool;
|
||||||
public var group = 1;
|
public var group = 1;
|
||||||
public var mask = 1;
|
public var mask = 1;
|
||||||
var trigger = false;
|
var trigger = false;
|
||||||
@ -120,7 +132,17 @@ class RigidBody extends iron.Trait {
|
|||||||
collisionMargin: 0.0,
|
collisionMargin: 0.0,
|
||||||
linearDeactivationThreshold: 0.0,
|
linearDeactivationThreshold: 0.0,
|
||||||
angularDeactivationThrshold: 0.0,
|
angularDeactivationThrshold: 0.0,
|
||||||
deactivationTime: 0.0
|
deactivationTime: 0.0,
|
||||||
|
linearVelocityMin: 0.0,
|
||||||
|
linearVelocityMax: 0.0,
|
||||||
|
angularVelocityMin: 0.0,
|
||||||
|
angularVelocityMax: 0.0,
|
||||||
|
lockTranslationX: false,
|
||||||
|
lockTranslationY: false,
|
||||||
|
lockTranslationZ: false,
|
||||||
|
lockRotationX: false,
|
||||||
|
lockRotationY: false,
|
||||||
|
lockRotationZ: false
|
||||||
};
|
};
|
||||||
|
|
||||||
if (flags == null) flags = {
|
if (flags == null) flags = {
|
||||||
@ -139,6 +161,18 @@ class RigidBody extends iron.Trait {
|
|||||||
this.angularFactors = [params.angularFactorsX, params.angularFactorsY, params.angularFactorsZ];
|
this.angularFactors = [params.angularFactorsX, params.angularFactorsY, params.angularFactorsZ];
|
||||||
this.collisionMargin = params.collisionMargin;
|
this.collisionMargin = params.collisionMargin;
|
||||||
this.deactivationParams = [params.linearDeactivationThreshold, params.angularDeactivationThrshold, params.deactivationTime];
|
this.deactivationParams = [params.linearDeactivationThreshold, params.angularDeactivationThrshold, params.deactivationTime];
|
||||||
|
// New velocity limiting properties
|
||||||
|
this.linearVelocityMin = params.linearVelocityMin;
|
||||||
|
this.linearVelocityMax = params.linearVelocityMax;
|
||||||
|
this.angularVelocityMin = params.angularVelocityMin;
|
||||||
|
this.angularVelocityMax = params.angularVelocityMax;
|
||||||
|
// New lock properties
|
||||||
|
this.lockTranslationX = params.lockTranslationX;
|
||||||
|
this.lockTranslationY = params.lockTranslationY;
|
||||||
|
this.lockTranslationZ = params.lockTranslationZ;
|
||||||
|
this.lockRotationX = params.lockRotationX;
|
||||||
|
this.lockRotationY = params.lockRotationY;
|
||||||
|
this.lockRotationZ = params.lockRotationZ;
|
||||||
this.animated = flags.animated;
|
this.animated = flags.animated;
|
||||||
this.trigger = flags.trigger;
|
this.trigger = flags.trigger;
|
||||||
this.ccd = flags.ccd;
|
this.ccd = flags.ccd;
|
||||||
@ -291,11 +325,25 @@ class RigidBody extends iron.Trait {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (linearFactors != null) {
|
if (linearFactors != null) {
|
||||||
setLinearFactor(linearFactors[0], linearFactors[1], linearFactors[2]);
|
// Apply lock properties by overriding factors
|
||||||
|
var lx = linearFactors[0];
|
||||||
|
var ly = linearFactors[1];
|
||||||
|
var lz = linearFactors[2];
|
||||||
|
if (lockTranslationX) lx = 0.0;
|
||||||
|
if (lockTranslationY) ly = 0.0;
|
||||||
|
if (lockTranslationZ) lz = 0.0;
|
||||||
|
setLinearFactor(lx, ly, lz);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (angularFactors != null) {
|
if (angularFactors != null) {
|
||||||
setAngularFactor(angularFactors[0], angularFactors[1], angularFactors[2]);
|
// Apply lock properties by overriding factors
|
||||||
|
var ax = angularFactors[0];
|
||||||
|
var ay = angularFactors[1];
|
||||||
|
var az = angularFactors[2];
|
||||||
|
if (lockRotationX) ax = 0.0;
|
||||||
|
if (lockRotationY) ay = 0.0;
|
||||||
|
if (lockRotationZ) az = 0.0;
|
||||||
|
setAngularFactor(ax, ay, az);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (trigger) bodyColl.setCollisionFlags(bodyColl.getCollisionFlags() | CF_NO_CONTACT_RESPONSE);
|
if (trigger) bodyColl.setCollisionFlags(bodyColl.getCollisionFlags() | CF_NO_CONTACT_RESPONSE);
|
||||||
@ -411,6 +459,55 @@ class RigidBody extends iron.Trait {
|
|||||||
var rbs = physics.getContacts(this);
|
var rbs = physics.getContacts(this);
|
||||||
if (rbs != null) for (rb in rbs) for (f in onContact) f(rb);
|
if (rbs != null) for (rb in rbs) for (f in onContact) f(rb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply velocity limiting if enabled
|
||||||
|
if (!animated && !staticObj) {
|
||||||
|
applyVelocityLimits();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyVelocityLimits() {
|
||||||
|
if (!ready) return;
|
||||||
|
|
||||||
|
// Check linear velocity limits
|
||||||
|
if (linearVelocityMin > 0.0 || linearVelocityMax > 0.0) {
|
||||||
|
var velocity = getLinearVelocity();
|
||||||
|
var speed = velocity.length();
|
||||||
|
|
||||||
|
if (linearVelocityMin > 0.0 && speed < linearVelocityMin) {
|
||||||
|
// Increase velocity to minimum
|
||||||
|
if (speed > 0.0) {
|
||||||
|
velocity.normalize();
|
||||||
|
velocity.mult(linearVelocityMin);
|
||||||
|
setLinearVelocity(velocity.x, velocity.y, velocity.z);
|
||||||
|
}
|
||||||
|
} else if (linearVelocityMax > 0.0 && speed > linearVelocityMax) {
|
||||||
|
// Clamp velocity to maximum
|
||||||
|
velocity.normalize();
|
||||||
|
velocity.mult(linearVelocityMax);
|
||||||
|
setLinearVelocity(velocity.x, velocity.y, velocity.z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check angular velocity limits
|
||||||
|
if (angularVelocityMin > 0.0 || angularVelocityMax > 0.0) {
|
||||||
|
var angularVel = getAngularVelocity();
|
||||||
|
var angularSpeed = angularVel.length();
|
||||||
|
|
||||||
|
if (angularVelocityMin > 0.0 && angularSpeed < angularVelocityMin) {
|
||||||
|
// Increase angular velocity to minimum
|
||||||
|
if (angularSpeed > 0.0) {
|
||||||
|
angularVel.normalize();
|
||||||
|
angularVel.mult(angularVelocityMin);
|
||||||
|
setAngularVelocity(angularVel.x, angularVel.y, angularVel.z);
|
||||||
|
}
|
||||||
|
} else if (angularVelocityMax > 0.0 && angularSpeed > angularVelocityMax) {
|
||||||
|
// Clamp angular velocity to maximum
|
||||||
|
angularVel.normalize();
|
||||||
|
angularVel.mult(angularVelocityMax);
|
||||||
|
setAngularVelocity(angularVel.x, angularVel.y, angularVel.z);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function disableCollision() {
|
public function disableCollision() {
|
||||||
@ -745,6 +842,16 @@ typedef RigidBodyParams = {
|
|||||||
var linearDeactivationThreshold: Float;
|
var linearDeactivationThreshold: Float;
|
||||||
var angularDeactivationThrshold: Float;
|
var angularDeactivationThrshold: Float;
|
||||||
var deactivationTime: Float;
|
var deactivationTime: Float;
|
||||||
|
var linearVelocityMin: Float;
|
||||||
|
var linearVelocityMax: Float;
|
||||||
|
var angularVelocityMin: Float;
|
||||||
|
var angularVelocityMax: Float;
|
||||||
|
var lockTranslationX: Bool;
|
||||||
|
var lockTranslationY: Bool;
|
||||||
|
var lockTranslationZ: Bool;
|
||||||
|
var lockRotationX: Bool;
|
||||||
|
var lockRotationY: Bool;
|
||||||
|
var lockRotationZ: Bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef RigidBodyFlags = {
|
typedef RigidBodyFlags = {
|
||||||
|
|||||||
Binary file not shown.
BIN
leenkx/blender/data/lnx_data_2.blend
Normal file
BIN
leenkx/blender/data/lnx_data_2.blend
Normal file
Binary file not shown.
@ -1,9 +1,17 @@
|
|||||||
import importlib
|
import importlib
|
||||||
import sys
|
import sys
|
||||||
import types
|
import types
|
||||||
|
import bpy
|
||||||
|
|
||||||
# This gets cleared if this package/the __init__ module is reloaded
|
# This gets cleared if this package/the __init__ module is reloaded
|
||||||
_module_cache: dict[str, types.ModuleType] = {}
|
if bpy.app.version < (2, 92, 0):
|
||||||
|
from typing import Dict
|
||||||
|
ModuleCacheType = Dict[str, types.ModuleType]
|
||||||
|
else:
|
||||||
|
ModuleCacheType = dict[str, types.ModuleType]
|
||||||
|
|
||||||
|
_module_cache: ModuleCacheType = {}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def enable_reload(module_name: str):
|
def enable_reload(module_name: str):
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user