forked from LeenkxTeam/LNXSDK
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5357b5ce25 | |||
| 1372c687f0 | |||
| 6a914782a5 | |||
| f4ada6ba50 | |||
| 4211317c03 | |||
| 9126175569 | |||
| 394ab38a80 | |||
| cc95912a7e | |||
| 85a44b930d | |||
| cd3090817a | |||
| d45c632dcd | |||
| 1c3c30e6ce | |||
| c9839c9be6 | |||
| 0adcafd697 | |||
| 423807c62f | |||
| 232ae3e7bc | |||
| a861665c98 | |||
| 4852a40848 |
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,3 +1,5 @@
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.DS_Store
|
||||
*.DS_Store
|
||||
**/workspace.xml
|
||||
**/vcs.xml
|
||||
@ -227,7 +227,7 @@ class SystemImpl {
|
||||
}
|
||||
|
||||
static inline var maxGamepads: Int = 4;
|
||||
static var frame: Framebuffer;
|
||||
public static var frame: Framebuffer;
|
||||
static var keyboard: Keyboard = null;
|
||||
static var mouse: kha.input.Mouse;
|
||||
static var surface: Surface;
|
||||
@ -388,7 +388,8 @@ class SystemImpl {
|
||||
{
|
||||
alpha: false,
|
||||
antialias: options.framebuffer.samplesPerPixel > 1,
|
||||
stencil: true
|
||||
stencil: true,
|
||||
xrCompatible: true
|
||||
}); // preserveDrawingBuffer: true } ); Warning: preserveDrawingBuffer can cause huge performance issues on mobile browsers
|
||||
SystemImpl.gl.pixelStorei(GL.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 1);
|
||||
|
||||
@ -417,7 +418,8 @@ class SystemImpl {
|
||||
{
|
||||
alpha: false,
|
||||
antialias: options.framebuffer.samplesPerPixel > 1,
|
||||
stencil: true
|
||||
stencil: true,
|
||||
xrCompatible: true
|
||||
}); // preserveDrawingBuffer: true } ); WARNING: preserveDrawingBuffer causes huge performance issues (on mobile browser)!
|
||||
SystemImpl.gl.pixelStorei(GL.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 1);
|
||||
SystemImpl.gl.getExtension("OES_texture_float");
|
||||
@ -547,6 +549,12 @@ class SystemImpl {
|
||||
];
|
||||
|
||||
function animate(timestamp) {
|
||||
if (untyped Browser.window._khaSkipWindowRender == true) {
|
||||
if (requestAnimationFrame != null)
|
||||
requestAnimationFrame(animate);
|
||||
return;
|
||||
}
|
||||
|
||||
if (requestAnimationFrame == null)
|
||||
Browser.window.setTimeout(animate, 1000.0 / 60.0);
|
||||
else
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -157,4 +157,5 @@ extern class Krom {
|
||||
static function getConstantLocationCompute(shader: Dynamic, name: String): Dynamic;
|
||||
static function getTextureUnitCompute(shader: Dynamic, name: String): Dynamic;
|
||||
static function compute(x: Int, y: Int, z: Int): Void;
|
||||
static function viewportSetCamera(posX: Float, posY: Float, posZ: Float, rotX: Float, rotY: Float, rotZ: Float, rotW: Float): Void;
|
||||
}
|
||||
|
||||
BIN
Krom/Krom.exe
BIN
Krom/Krom.exe
Binary file not shown.
@ -14,7 +14,7 @@ out vec4 fragColor;
|
||||
vec2 barrelDistortion(vec2 coord, float amt) {
|
||||
vec2 cc = coord - 0.5;
|
||||
float dist = dot(cc, cc);
|
||||
return coord + cc * dist * amt;
|
||||
return coord - cc * dist * amt;
|
||||
}
|
||||
float sat(float value)
|
||||
{
|
||||
@ -56,8 +56,6 @@ void main() {
|
||||
if (CAType == 1) {
|
||||
float reci_num_iter_f = 1.0 / float(num_iter);
|
||||
|
||||
vec2 resolution = vec2(1,1);
|
||||
vec2 uv = (texCoord.xy/resolution.xy);
|
||||
vec4 sumcol = vec4(0.0);
|
||||
vec4 sumw = vec4(0.0);
|
||||
for (int i=0; i < num_iter; ++i)
|
||||
@ -65,19 +63,21 @@ void main() {
|
||||
float t = float(i) * reci_num_iter_f;
|
||||
vec4 w = spectrum_offset(t);
|
||||
sumw += w;
|
||||
sumcol += w * texture(tex, barrelDistortion(uv, 0.6 * max_distort * t));
|
||||
vec2 distortedUV = barrelDistortion(texCoord, 0.6 * max_distort * t);
|
||||
sumcol += w * texture(tex, distortedUV);
|
||||
}
|
||||
|
||||
if (on == 1) fragColor = sumcol / sumw; else fragColor = texture(tex, texCoord);
|
||||
}
|
||||
|
||||
// Simple
|
||||
// inward sampling to avoid edge artifacts
|
||||
else {
|
||||
vec3 col = vec3(0.0);
|
||||
col.x = texture(tex, texCoord + ((vec2(0.0, 1.0) * max_distort) / vec2(1000.0))).x;
|
||||
col.y = texture(tex, texCoord + ((vec2(-0.85, -0.5) * max_distort) / vec2(1000.0))).y;
|
||||
col.z = texture(tex, texCoord + ((vec2(0.85, -0.5) * max_distort) / vec2(1000.0))).z;
|
||||
if (on == 1) fragColor = vec4(col.x, col.y, col.z, fragColor.w);
|
||||
vec2 toCenter = (vec2(0.5) - texCoord) * max_distort / 500.0;
|
||||
col.x = texture(tex, texCoord + toCenter * 0.0).x;
|
||||
col.y = texture(tex, texCoord + toCenter * 0.5).y;
|
||||
col.z = texture(tex, texCoord + toCenter * 1.0).z;
|
||||
if (on == 1) fragColor = vec4(col.x, col.y, col.z, 1.0);
|
||||
else fragColor = texture(tex, texCoord);
|
||||
}
|
||||
}
|
||||
|
||||
@ -357,6 +357,12 @@ void main() {
|
||||
#else
|
||||
fragColor = textureLod(tex, texCo, 0.0);
|
||||
#endif
|
||||
|
||||
// TODO: re-investigate white artifacts
|
||||
fragColor.rgb = clamp(fragColor.rgb, vec3(0.0), vec3(65504.0));
|
||||
if (any(isnan(fragColor.rgb)) || any(isinf(fragColor.rgb))) {
|
||||
fragColor.rgb = vec3(0.0);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@ -57,10 +57,10 @@ uniform vec3 backgroundCol;
|
||||
|
||||
#ifdef _SSAO
|
||||
uniform sampler2D ssaotex;
|
||||
#else
|
||||
#ifdef _SSGI
|
||||
uniform sampler2D ssaotex;
|
||||
#endif
|
||||
|
||||
#ifdef _SSGI
|
||||
uniform sampler2D ssgitex;
|
||||
#endif
|
||||
|
||||
#ifdef _SSS
|
||||
@ -102,8 +102,23 @@ uniform mat4 invVP;
|
||||
#endif
|
||||
|
||||
uniform vec2 cameraProj;
|
||||
#ifdef _VRStereo
|
||||
uniform vec3 eye; // center camera position
|
||||
uniform vec3 eyeLook; // center camera look
|
||||
uniform vec3 eyeLeft;
|
||||
uniform vec3 eyeRight;
|
||||
uniform vec3 eyeLookLeft;
|
||||
uniform vec3 eyeLookRight;
|
||||
uniform mat4 invVPLeft;
|
||||
uniform mat4 invVPRight;
|
||||
#ifdef _SinglePoint
|
||||
uniform vec3 pointPosLeft;
|
||||
uniform vec3 pointPosRight;
|
||||
#endif
|
||||
#else
|
||||
uniform vec3 eye;
|
||||
uniform vec3 eyeLook;
|
||||
#endif
|
||||
|
||||
#ifdef _Clusters
|
||||
uniform vec4 lightsArray[maxLights * 3];
|
||||
@ -200,7 +215,9 @@ uniform vec3 sunCol;
|
||||
#endif
|
||||
|
||||
#ifdef _SinglePoint // Fast path for single light
|
||||
#ifndef _VRStereo
|
||||
uniform vec3 pointPos;
|
||||
#endif
|
||||
uniform vec3 pointCol;
|
||||
#ifdef _ShadowMap
|
||||
uniform float pointBias;
|
||||
@ -225,6 +242,8 @@ out vec4 fragColor;
|
||||
|
||||
void main() {
|
||||
vec4 g0 = textureLod(gbuffer0, texCoord, 0.0); // Normal.xy, roughness, metallic/matid
|
||||
vec4 g1 = textureLod(gbuffer1, texCoord, 0.0); // Basecolor.rgb, spec/occ
|
||||
float depth = textureLod(gbufferD, texCoord, 0.0).r * 2.0 - 1.0;
|
||||
|
||||
vec3 n;
|
||||
n.z = 1.0 - abs(g0.x) - abs(g0.y);
|
||||
@ -236,14 +255,28 @@ void main() {
|
||||
uint matid;
|
||||
unpackFloatInt16(g0.a, metallic, matid);
|
||||
|
||||
vec4 g1 = textureLod(gbuffer1, texCoord, 0.0); // Basecolor.rgb, spec/occ
|
||||
vec2 occspec = unpackFloat2(g1.a);
|
||||
vec3 albedo = surfaceAlbedo(g1.rgb, metallic); // g1.rgb - basecolor
|
||||
vec3 f0 = surfaceF0(g1.rgb, metallic);
|
||||
|
||||
float depth = textureLod(gbufferD, texCoord, 0.0).r * 2.0 - 1.0;
|
||||
// re-investigate clamp basecolor to prevent extreme values causing glitches
|
||||
vec3 basecolor = min(g1.rgb, vec3(2.0));
|
||||
vec3 albedo = surfaceAlbedo(basecolor, metallic);
|
||||
vec3 f0 = surfaceF0(basecolor, metallic);
|
||||
|
||||
#ifdef _VRStereo
|
||||
bool isLeftEye = texCoord.x < 0.5;
|
||||
vec3 eyePos = isLeftEye ? eyeLeft : eyeRight;
|
||||
mat4 invVP_eye = isLeftEye ? invVPLeft : invVPRight;
|
||||
|
||||
vec2 eyeTexCoord = vec2(
|
||||
isLeftEye ? texCoord.x * 2.0 : (texCoord.x - 0.5) * 2.0,
|
||||
texCoord.y
|
||||
);
|
||||
|
||||
vec3 p = getPos2(invVP_eye, depth, eyeTexCoord);
|
||||
vec3 v = normalize(eyePos - p);
|
||||
#else
|
||||
vec3 p = getPos(eye, eyeLook, normalize(viewRay), depth, cameraProj);
|
||||
vec3 v = normalize(eye - p);
|
||||
#endif
|
||||
float dotNV = max(dot(n, v), 0.0);
|
||||
|
||||
#ifdef _gbuffer2
|
||||
@ -287,6 +320,7 @@ void main() {
|
||||
vec3 reflectionWorld = reflect(-v, n);
|
||||
float lod = getMipFromRoughness(roughness, envmapNumMipmaps);
|
||||
vec3 prefilteredColor = textureLod(senvmapRadiance, envMapEquirect(reflectionWorld), lod).rgb;
|
||||
prefilteredColor = min(prefilteredColor, vec3(20.0));
|
||||
#endif
|
||||
|
||||
#ifdef _EnvLDR
|
||||
@ -340,15 +374,12 @@ void main() {
|
||||
// fragColor.rgb = texture(ssaotex, texCoord).rrr;
|
||||
|
||||
#ifdef _SSAO
|
||||
// #ifdef _RTGI
|
||||
// fragColor.rgb *= textureLod(ssaotex, texCoord, 0.0).rgb;
|
||||
// #else
|
||||
fragColor.rgb *= textureLod(ssaotex, texCoord, 0.0).r;
|
||||
// #endif
|
||||
#else
|
||||
#ifdef _SSGI
|
||||
fragColor.rgb += textureLod(ssaotex, texCoord, 0.0).rgb;
|
||||
#endif
|
||||
|
||||
#ifdef _SSGI
|
||||
vec3 ssgiColor = textureLod(ssgitex, texCoord, 0.0).rgb;
|
||||
fragColor.rgb += ssgiColor * albedo;
|
||||
#endif
|
||||
|
||||
#ifdef _EmissionShadeless
|
||||
@ -381,62 +412,62 @@ void main() {
|
||||
#ifdef _ShadowMap
|
||||
#ifdef _CSM
|
||||
svisibility = shadowTestCascade(
|
||||
#ifdef _ShadowMapAtlas
|
||||
#ifdef _ShadowMapTransparent
|
||||
#ifndef _SingleAtlas
|
||||
shadowMapAtlasSun, shadowMapAtlasSunTransparent
|
||||
#else
|
||||
shadowMapAtlas, shadowMapAtlasTransparent
|
||||
#endif
|
||||
#else
|
||||
#ifndef _SingleAtlas
|
||||
shadowMapAtlasSun
|
||||
#else
|
||||
shadowMapAtlas
|
||||
#endif
|
||||
#endif
|
||||
#else
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMap, shadowMapTransparent
|
||||
#else
|
||||
shadowMap
|
||||
#endif
|
||||
#endif
|
||||
, eye, p + n * shadowsBias * 10, shadowsBias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, false
|
||||
#endif
|
||||
);
|
||||
#ifdef _ShadowMapAtlas
|
||||
#ifdef _ShadowMapTransparent
|
||||
#ifndef _SingleAtlas
|
||||
shadowMapAtlasSun, shadowMapAtlasSunTransparent
|
||||
#else
|
||||
shadowMapAtlas, shadowMapAtlasTransparent
|
||||
#endif
|
||||
#else
|
||||
#ifndef _SingleAtlas
|
||||
shadowMapAtlasSun
|
||||
#else
|
||||
shadowMapAtlas
|
||||
#endif
|
||||
#endif
|
||||
#else
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMap, shadowMapTransparent
|
||||
#else
|
||||
shadowMap
|
||||
#endif
|
||||
#endif
|
||||
, eye, p + n * shadowsBias * 2, shadowsBias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, false
|
||||
#endif
|
||||
);
|
||||
#else
|
||||
vec4 lPos = LWVP * vec4(p + n * shadowsBias * 100, 1.0);
|
||||
vec4 lPos = LWVP * vec4(p + n * shadowsBias * 2, 1.0);
|
||||
if (lPos.w > 0.0) {
|
||||
svisibility = shadowTest(
|
||||
#ifdef _ShadowMapAtlas
|
||||
#ifdef _ShadowMapTransparent
|
||||
#ifndef _SingleAtlas
|
||||
shadowMapAtlasSun, shadowMapAtlasSunTransparent
|
||||
#else
|
||||
shadowMapAtlas, shadowMapAtlasTransparent
|
||||
#endif
|
||||
#else
|
||||
#ifndef _SingleAtlas
|
||||
shadowMapAtlasSun
|
||||
#else
|
||||
shadowMapAtlas
|
||||
#endif
|
||||
#endif
|
||||
#else
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMap, shadowMapTransparent
|
||||
#else
|
||||
shadowMap
|
||||
#endif
|
||||
#endif
|
||||
, lPos.xyz / lPos.w, shadowsBias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, false
|
||||
#endif
|
||||
);
|
||||
#ifdef _ShadowMapAtlas
|
||||
#ifdef _ShadowMapTransparent
|
||||
#ifndef _SingleAtlas
|
||||
shadowMapAtlasSun, shadowMapAtlasSunTransparent
|
||||
#else
|
||||
shadowMapAtlas, shadowMapAtlasTransparent
|
||||
#endif
|
||||
#else
|
||||
#ifndef _SingleAtlas
|
||||
shadowMapAtlasSun
|
||||
#else
|
||||
shadowMapAtlas
|
||||
#endif
|
||||
#endif
|
||||
#else
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMap, shadowMapTransparent
|
||||
#else
|
||||
shadowMap
|
||||
#endif
|
||||
#endif
|
||||
, lPos.xyz / lPos.w, shadowsBias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, false
|
||||
#endif
|
||||
);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@ -498,8 +529,14 @@ void main() {
|
||||
|
||||
#ifdef _SinglePoint
|
||||
|
||||
#ifdef _VRStereo
|
||||
vec3 lightPos = pointPosLeft;
|
||||
#else
|
||||
vec3 lightPos = pointPos;
|
||||
#endif
|
||||
|
||||
fragColor.rgb += sampleLight(
|
||||
p, n, v, dotNV, pointPos, pointCol, albedo, roughness, occspec.y, f0
|
||||
p, n, v, dotNV, lightPos, pointCol, albedo, roughness, occspec.y, f0
|
||||
#ifdef _ShadowMap
|
||||
, 0, pointBias, true
|
||||
#ifdef _ShadowMapTransparent
|
||||
@ -522,7 +559,9 @@ void main() {
|
||||
|
||||
#ifdef _Spot
|
||||
#ifdef _SSS
|
||||
if (matid == 2) fragColor.rgb += fragColor.rgb * SSSSTransmittance(LWVPSpot[0], p, n, normalize(pointPos - p), lightPlane.y, shadowMapSpot[0]);//TODO implement transparent shadowmaps into the SSSSTransmittance()
|
||||
#ifdef _ShadowMap
|
||||
if (matid == 2) fragColor.rgb += fragColor.rgb * SSSSTransmittance(LWVPSpot[0], p, n, normalize(lightPos - p), lightPlane.y, shadowMapSpot[0]);//TODO implement transparent shadowmaps into the SSSSTransmittance()
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@ -582,5 +621,11 @@ void main() {
|
||||
);
|
||||
}
|
||||
#endif // _Clusters
|
||||
|
||||
fragColor.rgb = clamp(fragColor.rgb, vec3(0.0), vec3(65504.0));
|
||||
if (any(isnan(fragColor.rgb)) || any(isinf(fragColor.rgb))) {
|
||||
fragColor.rgb = vec3(0.0);
|
||||
}
|
||||
|
||||
fragColor.a = 1.0; // Mark as opaque
|
||||
}
|
||||
|
||||
@ -20,6 +20,36 @@
|
||||
"name": "eyeLook",
|
||||
"link": "_cameraLook"
|
||||
},
|
||||
{
|
||||
"name": "eyeLeft",
|
||||
"link": "_eyeLeft",
|
||||
"ifdef": ["_VRStereo"]
|
||||
},
|
||||
{
|
||||
"name": "eyeRight",
|
||||
"link": "_eyeRight",
|
||||
"ifdef": ["_VRStereo"]
|
||||
},
|
||||
{
|
||||
"name": "eyeLookLeft",
|
||||
"link": "_eyeLookLeft",
|
||||
"ifdef": ["_VRStereo"]
|
||||
},
|
||||
{
|
||||
"name": "eyeLookRight",
|
||||
"link": "_eyeLookRight",
|
||||
"ifdef": ["_VRStereo"]
|
||||
},
|
||||
{
|
||||
"name": "invVPLeft",
|
||||
"link": "_inverseViewProjectionMatrixLeft",
|
||||
"ifdef": ["_VRStereo"]
|
||||
},
|
||||
{
|
||||
"name": "invVPRight",
|
||||
"link": "_inverseViewProjectionMatrixRight",
|
||||
"ifdef": ["_VRStereo"]
|
||||
},
|
||||
{
|
||||
"name": "clipmaps",
|
||||
"link": "_clipmaps",
|
||||
@ -176,8 +206,19 @@
|
||||
{
|
||||
"name": "pointPos",
|
||||
"link": "_pointPosition",
|
||||
"ifndef": ["_VRStereo"],
|
||||
"ifdef": ["_SinglePoint"]
|
||||
},
|
||||
{
|
||||
"name": "pointPosLeft",
|
||||
"link": "_pointPositionLeft",
|
||||
"ifdef": ["_VRStereo", "_SinglePoint"]
|
||||
},
|
||||
{
|
||||
"name": "pointPosRight",
|
||||
"link": "_pointPositionRight",
|
||||
"ifdef": ["_VRStereo", "_SinglePoint"]
|
||||
},
|
||||
{
|
||||
"name": "pointCol",
|
||||
"link": "_pointColor",
|
||||
|
||||
@ -97,6 +97,31 @@
|
||||
"link": "_cascadeData",
|
||||
"ifdef": ["_Sun", "_ShadowMap", "_CSM"]
|
||||
},
|
||||
{
|
||||
"name": "eyeLookRight",
|
||||
"link": "_eyeLookRight",
|
||||
"ifdef": ["_VRStereo"]
|
||||
},
|
||||
{
|
||||
"name": "invVPLeft",
|
||||
"link": "_inverseViewProjectionMatrixLeft",
|
||||
"ifdef": ["_VRStereo"]
|
||||
},
|
||||
{
|
||||
"name": "invVPRight",
|
||||
"link": "_inverseViewProjectionMatrixRight",
|
||||
"ifdef": ["_VRStereo"]
|
||||
},
|
||||
{
|
||||
"name": "invVP",
|
||||
"link": "_viewProjectionMatrix",
|
||||
"ifdef": ["_SSRS"]
|
||||
},
|
||||
{
|
||||
"name": "smSizeUniform",
|
||||
"link": "_shadowMapSize",
|
||||
"ifdef": ["_SMSizeUniform"]
|
||||
},
|
||||
{
|
||||
"name": "lightPlane",
|
||||
"link": "_lightPlane",
|
||||
@ -108,8 +133,6 @@
|
||||
"ifdef": ["_SSRS"]
|
||||
},
|
||||
{
|
||||
"name": "smSizeUniform",
|
||||
"link": "_shadowMapSize",
|
||||
"ifdef": ["_SMSizeUniform"]
|
||||
},
|
||||
{
|
||||
@ -120,8 +143,19 @@
|
||||
{
|
||||
"name": "pointPos",
|
||||
"link": "_pointPosition",
|
||||
"ifndef": ["_VRStereo"],
|
||||
"ifdef": ["_SinglePoint"]
|
||||
},
|
||||
{
|
||||
"name": "pointPosLeft",
|
||||
"link": "_pointPositionLeft",
|
||||
"ifdef": ["_VRStereo", "_SinglePoint"]
|
||||
},
|
||||
{
|
||||
"name": "pointPosRight",
|
||||
"link": "_pointPositionRight",
|
||||
"ifdef": ["_VRStereo", "_SinglePoint"]
|
||||
},
|
||||
{
|
||||
"name": "pointCol",
|
||||
"link": "_pointColor",
|
||||
|
||||
9
leenkx/Shaders/fsr1_easu_pass/LICENSE.txt
Normal file
9
leenkx/Shaders/fsr1_easu_pass/LICENSE.txt
Normal file
@ -0,0 +1,9 @@
|
||||
https://gpuopen.com/manuals/fidelityfx_sdk/license/
|
||||
|
||||
Copyright © 2024 Advanced Micro Devices, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and /or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions :
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
157
leenkx/Shaders/fsr1_easu_pass/fsr1_easu_pass.frag.glsl
Normal file
157
leenkx/Shaders/fsr1_easu_pass/fsr1_easu_pass.frag.glsl
Normal file
@ -0,0 +1,157 @@
|
||||
#version 450
|
||||
|
||||
// AMD FidelityFX Super Resolution 1.0.2 - EASU (Edge Adaptive Spatial Upsampling)
|
||||
|
||||
#include "compiled.inc"
|
||||
|
||||
uniform sampler2D tex;
|
||||
uniform vec2 screenSize;
|
||||
|
||||
in vec2 texCoord;
|
||||
out vec4 fragColor;
|
||||
|
||||
// Helper functions from AMD ffx_a.h
|
||||
float APrxLoRcpF1(float a) {
|
||||
return uintBitsToFloat(uint(0x7ef07ebb) - floatBitsToUint(a));
|
||||
}
|
||||
|
||||
float AMax3F1(float x, float y, float z) {
|
||||
return max(x, max(y, z));
|
||||
}
|
||||
|
||||
float AMin3F1(float x, float y, float z) {
|
||||
return min(x, min(y, z));
|
||||
}
|
||||
|
||||
// Attempt to use textureGather for efficiency when available
|
||||
#if __VERSION__ >= 400
|
||||
|
||||
void FsrEasuTap(
|
||||
inout vec3 aC,
|
||||
inout float aW,
|
||||
vec2 off,
|
||||
vec2 dir,
|
||||
vec2 len,
|
||||
float lob,
|
||||
float clp,
|
||||
vec3 c
|
||||
) {
|
||||
vec2 v = off * dir;
|
||||
float d2 = v.x + v.y;
|
||||
d2 = clamp(d2 * APrxLoRcpF1(max(abs(v.x), abs(v.y))), 0.0, 1.0);
|
||||
d2 = d2 * d2;
|
||||
d2 = d2 * len.x + len.y;
|
||||
float wB = 2.0 / 5.0 * d2 - 1.0;
|
||||
float wA = lob * d2 - 1.0;
|
||||
wB *= wB;
|
||||
wA *= wA;
|
||||
float w = 25.0 / 16.0 * wA * wB;
|
||||
w = min(w, clp);
|
||||
w = max(w, 0.0);
|
||||
aC += c * w;
|
||||
aW += w;
|
||||
}
|
||||
|
||||
vec3 FsrEasuF(vec2 ip) {
|
||||
vec2 inputSize = textureSize(tex, 0);
|
||||
vec2 inputRcp = 1.0 / inputSize;
|
||||
|
||||
// Position in input pixels
|
||||
vec2 pp = ip * inputSize - 0.5;
|
||||
vec2 fp = floor(pp);
|
||||
pp -= fp;
|
||||
|
||||
// 12-tap kernel
|
||||
// b c
|
||||
// e f g h
|
||||
// i j k l
|
||||
// n o
|
||||
ivec2 sp = ivec2(fp);
|
||||
|
||||
vec3 b = texelFetch(tex, sp + ivec2(0, -1), 0).rgb;
|
||||
vec3 c = texelFetch(tex, sp + ivec2(1, -1), 0).rgb;
|
||||
vec3 e = texelFetch(tex, sp + ivec2(-1, 0), 0).rgb;
|
||||
vec3 f = texelFetch(tex, sp + ivec2(0, 0), 0).rgb;
|
||||
vec3 g = texelFetch(tex, sp + ivec2(1, 0), 0).rgb;
|
||||
vec3 h = texelFetch(tex, sp + ivec2(2, 0), 0).rgb;
|
||||
vec3 i = texelFetch(tex, sp + ivec2(-1, 1), 0).rgb;
|
||||
vec3 j = texelFetch(tex, sp + ivec2(0, 1), 0).rgb;
|
||||
vec3 k = texelFetch(tex, sp + ivec2(1, 1), 0).rgb;
|
||||
vec3 l = texelFetch(tex, sp + ivec2(2, 1), 0).rgb;
|
||||
vec3 n = texelFetch(tex, sp + ivec2(0, 2), 0).rgb;
|
||||
vec3 o = texelFetch(tex, sp + ivec2(1, 2), 0).rgb;
|
||||
|
||||
// Luma for edge detection (using green channel approximation)
|
||||
float bL = b.g + 0.5 * (b.r + b.b);
|
||||
float cL = c.g + 0.5 * (c.r + c.b);
|
||||
float eL = e.g + 0.5 * (e.r + e.b);
|
||||
float fL = f.g + 0.5 * (f.r + f.b);
|
||||
float gL = g.g + 0.5 * (g.r + g.b);
|
||||
float hL = h.g + 0.5 * (h.r + h.b);
|
||||
float iL = i.g + 0.5 * (i.r + i.b);
|
||||
float jL = j.g + 0.5 * (j.r + j.b);
|
||||
float kL = k.g + 0.5 * (k.r + k.b);
|
||||
float lL = l.g + 0.5 * (l.r + l.b);
|
||||
float nL = n.g + 0.5 * (n.r + n.b);
|
||||
float oL = o.g + 0.5 * (o.r + o.b);
|
||||
|
||||
// Gradient detection
|
||||
float dirX = (cL - bL) + (gL - fL) + (kL - jL) + (oL - nL);
|
||||
float dirY = (eL - iL) + (fL - jL) + (gL - kL) + (hL - lL);
|
||||
|
||||
// Normalize direction
|
||||
float dirR = APrxLoRcpF1(max(abs(dirX), abs(dirY)));
|
||||
dirX *= dirR;
|
||||
dirY *= dirR;
|
||||
|
||||
// Calculate stretch based on edge direction
|
||||
float len = length(vec2(dirX, dirY));
|
||||
len = len * 0.5;
|
||||
len *= len;
|
||||
float stretch = (dirX * dirX + dirY * dirY) * APrxLoRcpF1(max(abs(dirX), abs(dirY)));
|
||||
vec2 len2 = vec2(1.0 + (stretch - 1.0) * len, 1.0 - 0.5 * len);
|
||||
float lob = 0.5 + (0.25 - 0.04 - 0.5) * len;
|
||||
float clp = APrxLoRcpF1(lob);
|
||||
|
||||
// Accumulate samples
|
||||
vec3 aC = vec3(0.0);
|
||||
float aW = 0.0;
|
||||
vec2 dir = vec2(dirX, dirY);
|
||||
|
||||
FsrEasuTap(aC, aW, vec2(0.0, -1.0) - pp, dir, len2, lob, clp, b);
|
||||
FsrEasuTap(aC, aW, vec2(1.0, -1.0) - pp, dir, len2, lob, clp, c);
|
||||
FsrEasuTap(aC, aW, vec2(-1.0, 0.0) - pp, dir, len2, lob, clp, e);
|
||||
FsrEasuTap(aC, aW, vec2(0.0, 0.0) - pp, dir, len2, lob, clp, f);
|
||||
FsrEasuTap(aC, aW, vec2(1.0, 0.0) - pp, dir, len2, lob, clp, g);
|
||||
FsrEasuTap(aC, aW, vec2(2.0, 0.0) - pp, dir, len2, lob, clp, h);
|
||||
FsrEasuTap(aC, aW, vec2(-1.0, 1.0) - pp, dir, len2, lob, clp, i);
|
||||
FsrEasuTap(aC, aW, vec2(0.0, 1.0) - pp, dir, len2, lob, clp, j);
|
||||
FsrEasuTap(aC, aW, vec2(1.0, 1.0) - pp, dir, len2, lob, clp, k);
|
||||
FsrEasuTap(aC, aW, vec2(2.0, 1.0) - pp, dir, len2, lob, clp, l);
|
||||
FsrEasuTap(aC, aW, vec2(0.0, 2.0) - pp, dir, len2, lob, clp, n);
|
||||
FsrEasuTap(aC, aW, vec2(1.0, 2.0) - pp, dir, len2, lob, clp, o);
|
||||
|
||||
// Normalize
|
||||
vec3 pix = aC / aW;
|
||||
|
||||
// Clamp to neighborhood min/max to prevent ringing
|
||||
vec3 mn = min(min(min(f, g), j), k);
|
||||
vec3 mx = max(max(max(f, g), j), k);
|
||||
pix = clamp(pix, mn, mx);
|
||||
|
||||
return pix;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
// Fallback for older GLSL - simple bilinear
|
||||
vec3 FsrEasuF(vec2 ip) {
|
||||
return texture(tex, ip).rgb;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void main() {
|
||||
vec3 col = FsrEasuF(texCoord);
|
||||
fragColor = vec4(col, 1.0);
|
||||
}
|
||||
19
leenkx/Shaders/fsr1_easu_pass/fsr1_easu_pass.json
Normal file
19
leenkx/Shaders/fsr1_easu_pass/fsr1_easu_pass.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"contexts": [
|
||||
{
|
||||
"name": "fsr1_easu_pass",
|
||||
"depth_write": false,
|
||||
"compare_mode": "always",
|
||||
"cull_mode": "none",
|
||||
"links": [
|
||||
{
|
||||
"name": "screenSize",
|
||||
"link": "_screenSize"
|
||||
}
|
||||
],
|
||||
"texture_params": [],
|
||||
"vertex_shader": "../include/pass.vert.glsl",
|
||||
"fragment_shader": "fsr1_easu_pass.frag.glsl"
|
||||
}
|
||||
]
|
||||
}
|
||||
9
leenkx/Shaders/fsr1_rcas_pass/LICENSE.txt
Normal file
9
leenkx/Shaders/fsr1_rcas_pass/LICENSE.txt
Normal file
@ -0,0 +1,9 @@
|
||||
https://gpuopen.com/manuals/fidelityfx_sdk/license/
|
||||
|
||||
Copyright © 2024 Advanced Micro Devices, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and /or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions :
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
116
leenkx/Shaders/fsr1_rcas_pass/fsr1_rcas_pass.frag.glsl
Normal file
116
leenkx/Shaders/fsr1_rcas_pass/fsr1_rcas_pass.frag.glsl
Normal file
@ -0,0 +1,116 @@
|
||||
#version 450
|
||||
|
||||
// AMD FidelityFX Super Resolution 1.0.2 - RCAS (Robust Contrast Adaptive Sharpening)
|
||||
|
||||
#include "compiled.inc"
|
||||
|
||||
uniform sampler2D tex;
|
||||
|
||||
// Sharpness in "stops": 0.0 = maximum sharpness, higher = less sharp
|
||||
// Converted to linear via exp2(-sharpness)
|
||||
#ifdef _FSR1_Ultra_Quality
|
||||
const float SHARPNESS_STOPS = 0.0;
|
||||
#elif defined(_FSR1_Balanced)
|
||||
const float SHARPNESS_STOPS = 1.0;
|
||||
#elif defined(_FSR1_Performance)
|
||||
const float SHARPNESS_STOPS = 2.0;
|
||||
#elif defined(_FSR1_Custom)
|
||||
uniform vec4 PPComp15;
|
||||
#define SHARPNESS_STOPS (PPComp15.x * 2.0)
|
||||
#else
|
||||
const float SHARPNESS_STOPS = 0.5; // Quality (default)
|
||||
#endif
|
||||
|
||||
// FSR RCAS limit - prevents unnatural sharpening artifacts
|
||||
#define FSR_RCAS_LIMIT (0.25 - (1.0 / 16.0))
|
||||
|
||||
in vec2 texCoord;
|
||||
out vec4 fragColor;
|
||||
|
||||
// AMD helper functions from ffx_a.h
|
||||
float AMin3F1(float x, float y, float z) { return min(x, min(y, z)); }
|
||||
float AMax3F1(float x, float y, float z) { return max(x, max(y, z)); }
|
||||
|
||||
// High precision reciprocal (required for limiters per AMD docs)
|
||||
// Added epsilon to prevent division by zero in dark areas
|
||||
float ARcpF1(float a) {
|
||||
return 1.0 / max(a, 1e-8);
|
||||
}
|
||||
|
||||
// Medium precision reciprocal approximation (from AMD ffx_a.h)
|
||||
// Only used for noise detection and final resolve
|
||||
float APrxMedRcpF1(float a) {
|
||||
return uintBitsToFloat(uint(0x7ef19fff) - floatBitsToUint(a));
|
||||
}
|
||||
|
||||
void main() {
|
||||
// Get texture size and texel offset
|
||||
vec2 texSize = vec2(textureSize(tex, 0));
|
||||
vec2 texelSize = 1.0 / texSize;
|
||||
|
||||
// Algorithm uses minimal 3x3 pixel neighborhood
|
||||
// b
|
||||
// d e f
|
||||
// h
|
||||
// Clamp inputs to [0,1] - FSR expects sRGB normalized input
|
||||
vec3 b = clamp(texture(tex, texCoord + vec2(0.0, -texelSize.y)).rgb, 0.0, 1.0);
|
||||
vec3 d = clamp(texture(tex, texCoord + vec2(-texelSize.x, 0.0)).rgb, 0.0, 1.0);
|
||||
vec4 ee = texture(tex, texCoord);
|
||||
vec3 e = clamp(ee.rgb, 0.0, 1.0);
|
||||
vec3 f = clamp(texture(tex, texCoord + vec2(texelSize.x, 0.0)).rgb, 0.0, 1.0);
|
||||
vec3 h = clamp(texture(tex, texCoord + vec2(0.0, texelSize.y)).rgb, 0.0, 1.0);
|
||||
|
||||
// Luma times 2 (AMD's luma calculation: B*0.5 + R*0.5 + G)
|
||||
float bL = b.b * 0.5 + (b.r * 0.5 + b.g);
|
||||
float dL = d.b * 0.5 + (d.r * 0.5 + d.g);
|
||||
float eL = e.b * 0.5 + (e.r * 0.5 + e.g);
|
||||
float fL = f.b * 0.5 + (f.r * 0.5 + f.g);
|
||||
float hL = h.b * 0.5 + (h.r * 0.5 + h.g);
|
||||
|
||||
// Noise detection (official AMD algorithm with safety for flat areas)
|
||||
float nz = 0.25 * bL + 0.25 * dL + 0.25 * fL + 0.25 * hL - eL;
|
||||
float range = AMax3F1(AMax3F1(bL, dL, eL), fL, hL) - AMin3F1(AMin3F1(bL, dL, eL), fL, hL);
|
||||
// Use safe division instead of APrxMedRcpF1 for range to avoid NaN in flat areas
|
||||
nz = clamp(abs(nz) / max(range, 1e-5), 0.0, 1.0);
|
||||
nz = -0.5 * nz + 1.0;
|
||||
|
||||
// Min and max of ring (per channel)
|
||||
float mn4R = min(AMin3F1(b.r, d.r, f.r), h.r);
|
||||
float mn4G = min(AMin3F1(b.g, d.g, f.g), h.g);
|
||||
float mn4B = min(AMin3F1(b.b, d.b, f.b), h.b);
|
||||
float mx4R = max(AMax3F1(b.r, d.r, f.r), h.r);
|
||||
float mx4G = max(AMax3F1(b.g, d.g, f.g), h.g);
|
||||
float mx4B = max(AMax3F1(b.b, d.b, f.b), h.b);
|
||||
|
||||
// Immediate constants for peak range
|
||||
vec2 peakC = vec2(1.0, -4.0);
|
||||
|
||||
// Limiters - these need HIGH PRECISION reciprocals (per AMD docs)
|
||||
float hitMinR = min(mn4R, e.r) * ARcpF1(4.0 * mx4R);
|
||||
float hitMinG = min(mn4G, e.g) * ARcpF1(4.0 * mx4G);
|
||||
float hitMinB = min(mn4B, e.b) * ARcpF1(4.0 * mx4B);
|
||||
float hitMaxR = (peakC.x - max(mx4R, e.r)) * ARcpF1(4.0 * mn4R + peakC.y);
|
||||
float hitMaxG = (peakC.x - max(mx4G, e.g)) * ARcpF1(4.0 * mn4G + peakC.y);
|
||||
float hitMaxB = (peakC.x - max(mx4B, e.b)) * ARcpF1(4.0 * mn4B + peakC.y);
|
||||
float lobeR = max(-hitMinR, hitMaxR);
|
||||
float lobeG = max(-hitMinG, hitMaxG);
|
||||
float lobeB = max(-hitMinB, hitMaxB);
|
||||
|
||||
// Apply sharpness (convert from stops to linear)
|
||||
float sharpness = exp2(-SHARPNESS_STOPS);
|
||||
float lobe = max(-FSR_RCAS_LIMIT, min(AMax3F1(lobeR, lobeG, lobeB), 0.0)) * sharpness;
|
||||
|
||||
// Apply noise removal
|
||||
lobe *= nz;
|
||||
|
||||
// Resolve using safe reciprocal to avoid any edge case issues
|
||||
float denom = 4.0 * lobe + 1.0;
|
||||
float rcpL = 1.0 / max(denom, 0.25); // denom should be in [0.25, 1.0] range
|
||||
vec3 pix;
|
||||
pix.r = (lobe * b.r + lobe * d.r + lobe * h.r + lobe * f.r + e.r) * rcpL;
|
||||
pix.g = (lobe * b.g + lobe * d.g + lobe * h.g + lobe * f.g + e.g) * rcpL;
|
||||
pix.b = (lobe * b.b + lobe * d.b + lobe * h.b + lobe * f.b + e.b) * rcpL;
|
||||
|
||||
// Ensure output is clamped to valid range
|
||||
fragColor = vec4(clamp(pix, 0.0, 1.0), ee.a);
|
||||
}
|
||||
24
leenkx/Shaders/fsr1_rcas_pass/fsr1_rcas_pass.json
Normal file
24
leenkx/Shaders/fsr1_rcas_pass/fsr1_rcas_pass.json
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
"contexts": [
|
||||
{
|
||||
"name": "fsr1_rcas_pass",
|
||||
"depth_write": false,
|
||||
"compare_mode": "always",
|
||||
"cull_mode": "none",
|
||||
"links": [
|
||||
{
|
||||
"name": "screenSize",
|
||||
"link": "_screenSize"
|
||||
},
|
||||
{
|
||||
"name": "PPComp15",
|
||||
"link": "_PPComp15",
|
||||
"ifdef": ["_FSR1_Custom"]
|
||||
}
|
||||
],
|
||||
"texture_params": [],
|
||||
"vertex_shader": "../include/pass.vert.glsl",
|
||||
"fragment_shader": "fsr1_rcas_pass.frag.glsl"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -8,6 +8,7 @@ uniform sampler2D gbufferD;
|
||||
uniform sampler2D gbuffer0;
|
||||
uniform sampler2D gbuffer1;
|
||||
uniform mat4 invVP;
|
||||
uniform mat4 invW;
|
||||
uniform vec3 probep;
|
||||
uniform vec3 eye;
|
||||
|
||||
@ -25,19 +26,27 @@ void main() {
|
||||
|
||||
float roughness = g0.b;
|
||||
if (roughness > 0.95) {
|
||||
fragColor.rgb = vec3(0.0);
|
||||
fragColor = vec4(0.0);
|
||||
return;
|
||||
}
|
||||
|
||||
float spec = fract(textureLod(gbuffer1, texCoord, 0.0).a);
|
||||
if (spec == 0.0) {
|
||||
fragColor.rgb = vec3(0.0);
|
||||
fragColor = vec4(0.0);
|
||||
return;
|
||||
}
|
||||
|
||||
float depth = textureLod(gbufferD, texCoord, 0.0).r * 2.0 - 1.0;
|
||||
vec3 wp = getPos2(invVP, depth, texCoord);
|
||||
|
||||
vec3 localPos = (invW * vec4(wp, 1.0)).xyz;
|
||||
|
||||
// return if surface is inside probe volume bounds
|
||||
if (abs(localPos.x) > 1.0 || abs(localPos.y) > 1.0 || abs(localPos.z) > 1.0) {
|
||||
fragColor = vec4(0.0);
|
||||
return;
|
||||
}
|
||||
|
||||
vec2 enc = g0.rg;
|
||||
vec3 n;
|
||||
n.z = 1.0 - abs(enc.x) - abs(enc.y);
|
||||
@ -50,5 +59,5 @@ void main() {
|
||||
r.y = -r.y;
|
||||
#endif
|
||||
float intensity = clamp((1.0 - roughness) * dot(wp - probep, n), 0.0, 1.0);
|
||||
fragColor.rgb = texture(probeTex, r).rgb * intensity;
|
||||
fragColor = vec4(texture(probeTex, r).rgb * intensity, 1.0);
|
||||
}
|
||||
|
||||
@ -20,6 +20,10 @@
|
||||
"name": "invVP",
|
||||
"link": "_inverseViewProjectionMatrix"
|
||||
},
|
||||
{
|
||||
"name": "invW",
|
||||
"link": "_inverseWorldMatrix"
|
||||
},
|
||||
{
|
||||
"name": "probep",
|
||||
"link": "_probePosition"
|
||||
|
||||
@ -25,13 +25,13 @@ void main() {
|
||||
|
||||
float roughness = g0.b;
|
||||
if (roughness > 0.95) {
|
||||
fragColor.rgb = vec3(0.0);
|
||||
fragColor = vec4(0.0);
|
||||
return;
|
||||
}
|
||||
|
||||
float spec = fract(textureLod(gbuffer1, texCoord, 0.0).a);
|
||||
if (spec == 0.0) {
|
||||
fragColor.rgb = vec3(0.0);
|
||||
fragColor = vec4(0.0);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -50,5 +50,5 @@ void main() {
|
||||
n = normalize(n);
|
||||
|
||||
float intensity = clamp((1.0 - roughness) * dot(n, proben), 0.0, 1.0);
|
||||
fragColor.rgb = texture(probeTex, tc).rgb * intensity;
|
||||
fragColor = vec4(texture(probeTex, tc).rgb * intensity, 1.0);
|
||||
}
|
||||
|
||||
@ -5,42 +5,56 @@
|
||||
|
||||
uniform sampler2D tex;
|
||||
uniform sampler2D gbuffer0;
|
||||
uniform sampler2D gbufferD;
|
||||
|
||||
uniform vec2 dirInv; // texStep
|
||||
|
||||
in vec2 texCoord;
|
||||
out float fragColor;
|
||||
out vec3 fragColor;
|
||||
|
||||
const float blurWeights[5] = float[] (0.227027, 0.1945946, 0.1216216, 0.054054, 0.016216);
|
||||
// const float blurWeights[10] = float[] (0.132572, 0.125472, 0.106373, 0.08078, 0.05495, 0.033482, 0.018275, 0.008934, 0.003912, 0.001535);
|
||||
const float discardThreshold = 0.95;
|
||||
|
||||
float doBlur(const float blurWeight, const int pos, const vec3 nor, const vec2 texCoord) {
|
||||
const float posadd = pos + 0.5;
|
||||
|
||||
vec3 nor2 = getNor(textureLod(gbuffer0, texCoord + pos * dirInv, 0.0).rg);
|
||||
float influenceFactor = step(discardThreshold, dot(nor2, nor));
|
||||
float col = textureLod(tex, texCoord + posadd * dirInv, 0.0).r;
|
||||
fragColor += col * blurWeight * influenceFactor;
|
||||
float weight = blurWeight * influenceFactor;
|
||||
|
||||
nor2 = getNor(textureLod(gbuffer0, texCoord - pos * dirInv, 0.0).rg);
|
||||
influenceFactor = step(discardThreshold, dot(nor2, nor));
|
||||
col = textureLod(tex, texCoord - posadd * dirInv, 0.0).r;
|
||||
fragColor += col * blurWeight * influenceFactor;
|
||||
weight += blurWeight * influenceFactor;
|
||||
|
||||
return weight;
|
||||
}
|
||||
const int KERNEL_SIZE = 13;
|
||||
const float blurWeights[13] = float[](0.1, 0.09, 0.08, 0.07, 0.06, 0.05, 0.04, 0.03, 0.025, 0.02, 0.015, 0.01, 0.005);
|
||||
|
||||
void main() {
|
||||
vec3 nor = getNor(textureLod(gbuffer0, texCoord, 0.0).rg);
|
||||
vec3 centerNor = getNor(textureLod(gbuffer0, texCoord, 0.0).rg);
|
||||
float centerDepth = textureLod(gbufferD, texCoord, 0.0).r;
|
||||
|
||||
fragColor = textureLod(tex, texCoord, 0.0).r * blurWeights[0];
|
||||
float weight = blurWeights[0];
|
||||
for (int i = 1; i < 5; i++) {
|
||||
weight += doBlur(blurWeights[i], i, nor, texCoord);
|
||||
// skip sky pixels
|
||||
if (centerDepth == 1.0) {
|
||||
fragColor = vec3(0.0);
|
||||
return;
|
||||
}
|
||||
|
||||
fragColor = textureLod(tex, texCoord, 0.0).rgb * blurWeights[0];
|
||||
float totalWeight = blurWeights[0];
|
||||
|
||||
for (int i = 1; i < KERNEL_SIZE; i++) {
|
||||
vec2 offset = float(i) * dirInv;
|
||||
|
||||
vec2 uvPos = texCoord + offset;
|
||||
vec3 norPos = getNor(textureLod(gbuffer0, uvPos, 0.0).rg);
|
||||
float depthPos = textureLod(gbufferD, uvPos, 0.0).r;
|
||||
|
||||
float normalWeight = max(0.0, dot(norPos, centerNor));
|
||||
normalWeight = pow(normalWeight, 8.0); // Softer normal falloff for better blending
|
||||
float depthWeight = 1.0 - smoothstep(0.0, 0.02, abs(depthPos - centerDepth));
|
||||
float w = blurWeights[i] * normalWeight * depthWeight;
|
||||
|
||||
fragColor += textureLod(tex, uvPos, 0.0).rgb * w;
|
||||
totalWeight += w;
|
||||
|
||||
vec2 uvNeg = texCoord - offset;
|
||||
vec3 norNeg = getNor(textureLod(gbuffer0, uvNeg, 0.0).rg);
|
||||
float depthNeg = textureLod(gbufferD, uvNeg, 0.0).r;
|
||||
|
||||
normalWeight = max(0.0, dot(norNeg, centerNor));
|
||||
normalWeight = pow(normalWeight, 8.0);
|
||||
depthWeight = 1.0 - smoothstep(0.0, 0.02, abs(depthNeg - centerDepth));
|
||||
w = blurWeights[i] * normalWeight * depthWeight;
|
||||
|
||||
fragColor += textureLod(tex, uvNeg, 0.0).rgb * w;
|
||||
totalWeight += w;
|
||||
}
|
||||
|
||||
fragColor = fragColor / weight;
|
||||
fragColor /= totalWeight;
|
||||
}
|
||||
@ -1,25 +1,8 @@
|
||||
#version 450
|
||||
|
||||
#include "compiled.inc"
|
||||
#include "std/gbuffer.glsl"
|
||||
#include "std/brdf.glsl"
|
||||
#include "std/math.glsl"
|
||||
#ifdef _Clusters
|
||||
#include "std/clusters.glsl"
|
||||
#endif
|
||||
#ifdef _ShadowMap
|
||||
#include "std/shadows.glsl"
|
||||
#endif
|
||||
#ifdef _LTC
|
||||
#include "std/ltc.glsl"
|
||||
#endif
|
||||
#ifdef _LightIES
|
||||
#include "std/ies.glsl"
|
||||
#endif
|
||||
#ifdef _Spot
|
||||
#include "std/light_common.glsl"
|
||||
#endif
|
||||
#include "std/constants.glsl"
|
||||
#include "std/gbuffer.glsl"
|
||||
|
||||
uniform sampler2D gbuffer0;
|
||||
uniform sampler2D gbuffer1;
|
||||
@ -27,480 +10,179 @@ uniform sampler2D gbufferD;
|
||||
#ifdef _EmissionShaded
|
||||
uniform sampler2D gbufferEmission;
|
||||
#endif
|
||||
uniform sampler2D sveloc;
|
||||
uniform vec2 cameraProj;
|
||||
uniform vec3 eye;
|
||||
uniform vec3 eyeLook;
|
||||
uniform vec2 screenSize;
|
||||
uniform mat4 invVP;
|
||||
|
||||
in vec2 texCoord;
|
||||
in vec3 viewRay;
|
||||
out vec3 fragColor;
|
||||
|
||||
float metallic;
|
||||
uint matid;
|
||||
|
||||
#ifdef _SMSizeUniform
|
||||
//!uniform vec2 smSizeUniform;
|
||||
#endif
|
||||
|
||||
#ifdef _Clusters
|
||||
uniform vec4 lightsArray[maxLights * 3];
|
||||
#ifdef _Spot
|
||||
uniform vec4 lightsArraySpot[maxLights * 2];
|
||||
#endif
|
||||
uniform sampler2D clustersData;
|
||||
uniform vec2 cameraPlane;
|
||||
#endif
|
||||
|
||||
#ifdef _SinglePoint // Fast path for single light
|
||||
uniform vec3 pointPos;
|
||||
uniform vec3 pointCol;
|
||||
#ifdef _ShadowMap
|
||||
uniform float pointBias;
|
||||
#endif
|
||||
#ifdef _Spot
|
||||
uniform vec3 spotDir;
|
||||
uniform vec3 spotRight;
|
||||
uniform vec4 spotData;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef _CPostprocess
|
||||
uniform vec3 PPComp12;
|
||||
#endif
|
||||
|
||||
#ifdef _ShadowMap
|
||||
#ifdef _SinglePoint
|
||||
#ifdef _Spot
|
||||
#ifndef _LTC
|
||||
uniform sampler2DShadow shadowMapSpot[1];
|
||||
uniform sampler2D shadowMapSpotTransparent[1];
|
||||
uniform mat4 LWVPSpot[1];
|
||||
#endif
|
||||
#else
|
||||
uniform samplerCubeShadow shadowMapPoint[1];
|
||||
uniform samplerCube shadowMapPointTransparent[1];
|
||||
uniform vec2 lightProj;
|
||||
#endif
|
||||
#endif
|
||||
#ifdef _Clusters
|
||||
#ifdef _SingleAtlas
|
||||
uniform sampler2DShadow shadowMapAtlas;
|
||||
uniform sampler2D shadowMapAtlasTransparent;
|
||||
#endif
|
||||
uniform vec2 lightProj;
|
||||
#ifdef _ShadowMapAtlas
|
||||
#ifndef _SingleAtlas
|
||||
uniform sampler2DShadow shadowMapAtlasPoint;
|
||||
uniform sampler2D shadowMapAtlasPointTransparent;
|
||||
//!uniform vec4 pointLightDataArray[maxLightsCluster * 6];
|
||||
#else
|
||||
uniform samplerCubeShadow shadowMapPoint[4];
|
||||
uniform samplerCube shadowMapPointTransparent[4];
|
||||
#endif
|
||||
#endif
|
||||
#ifdef _Spot
|
||||
#ifdef _ShadowMapAtlas
|
||||
#ifndef _SingleAtlas
|
||||
uniform sampler2DShadow shadowMapAtlasSpot;
|
||||
uniform sampler2D shadowMapAtlasSpotTransparent;
|
||||
#endif
|
||||
#else
|
||||
uniform sampler2DShadow shadowMapSpot[4];
|
||||
uniform sampler2D shadowMapSpotTransparent[4];
|
||||
#endif
|
||||
uniform mat4 LWVPSpotArray[maxLightsCluster];
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef _LTC
|
||||
uniform vec3 lightArea0;
|
||||
uniform vec3 lightArea1;
|
||||
uniform vec3 lightArea2;
|
||||
uniform vec3 lightArea3;
|
||||
uniform sampler2D sltcMat;
|
||||
uniform sampler2D sltcMag;
|
||||
#ifdef _ShadowMap
|
||||
#ifndef _Spot
|
||||
#ifdef _SinglePoint
|
||||
uniform sampler2DShadow shadowMapSpot[1];
|
||||
uniform sampler2D shadowMapSpotTransparent[1];
|
||||
uniform mat4 LWVPSpot[1];
|
||||
#endif
|
||||
#ifdef _Clusters
|
||||
uniform sampler2DShadow shadowMapSpot[maxLightsCluster];
|
||||
uniform mat4 LWVPSpotArray[maxLightsCluster];
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
uniform mat4 P;
|
||||
uniform mat4 invP;
|
||||
uniform mat3 V3;
|
||||
|
||||
#ifdef _Sun
|
||||
uniform vec3 sunDir;
|
||||
uniform vec3 sunCol;
|
||||
#ifdef _ShadowMap
|
||||
#ifdef _ShadowMapAtlas
|
||||
#ifndef _SingleAtlas
|
||||
uniform sampler2DShadow shadowMapAtlasSun;
|
||||
uniform sampler2D shadowMapAtlasSunTransparent;
|
||||
#endif
|
||||
#else
|
||||
uniform sampler2DShadow shadowMap;
|
||||
uniform sampler2D shadowMapTransparent;
|
||||
#endif
|
||||
uniform float shadowsBias;
|
||||
#ifdef _CSM
|
||||
//!uniform vec4 casData[shadowmapCascades * 4 + 4];
|
||||
#else
|
||||
uniform mat4 LWVP;
|
||||
#endif
|
||||
#endif // _ShadowMap
|
||||
#endif
|
||||
|
||||
vec3 sampleLight(const vec3 p, const vec3 n, const vec3 lp, const vec3 lightCol
|
||||
#ifdef _ShadowMap
|
||||
, int index, float bias, bool receiveShadow, bool transparent
|
||||
#endif
|
||||
#ifdef _Spot
|
||||
, const bool isSpot, const float spotSize, float spotBlend, vec3 spotDir, vec2 scale, vec3 right
|
||||
#endif
|
||||
) {
|
||||
|
||||
vec3 ld = lp - p;
|
||||
vec3 l = normalize(ld);
|
||||
|
||||
vec3 visibility = lightCol;
|
||||
visibility *= attenuate(distance(p, lp));
|
||||
|
||||
#ifdef _LTC
|
||||
#ifdef _ShadowMap
|
||||
if (receiveShadow) {
|
||||
#ifdef _SinglePoint
|
||||
vec4 lPos = LWVPSpotArray[0] * vec4(p + n * bias * 10, 1.0);
|
||||
visibility *= shadowTest(shadowMapSpot[0],
|
||||
shadowMapSpotTransparent[0],
|
||||
lPos.xyz / lPos.w, bias, transparent);
|
||||
#endif
|
||||
#ifdef _Clusters
|
||||
vec4 lPos = LWVPSpotArray[index] * vec4(p + n * bias * 10, 1.0);
|
||||
if (index == 0) visibility *= shadowTest(shadowMapSpot[0],
|
||||
shadowMapSpotTransparent[0],
|
||||
lPos.xyz / lPos.w, bias, transparent);
|
||||
else if (index == 1) visibility *= shadowTest(shadowMapSpot[1],
|
||||
shadowMapSpotTransparent[1],
|
||||
, lPos.xyz / lPos.w, bias, transparent);
|
||||
else if (index == 2) visibility *= shadowTest(shadowMapSpot[2],
|
||||
shadowMapSpotTransparent[2],
|
||||
lPos.xyz / lPos.w, bias, transparent);
|
||||
else if (index == 3) visibility *= shadowTest(shadowMapSpot[3],
|
||||
shadowMapSpotTransparent[3],
|
||||
lPos.xyz / lPos.w, bias, transparent);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
return visibility;
|
||||
#endif
|
||||
|
||||
#ifdef _Spot
|
||||
if (isSpot) {
|
||||
visibility *= spotlightMask(l, spotDir, right, scale, spotSize, spotBlend);
|
||||
|
||||
#ifdef _ShadowMap
|
||||
if (receiveShadow) {
|
||||
#ifdef _SinglePoint
|
||||
vec4 lPos = LWVPSpot[0] * vec4(p + n * bias * 10, 1.0);
|
||||
visibility *= shadowTest(shadowMapSpot[0],
|
||||
shadowMapSpotTransparent[0],
|
||||
lPos.xyz / lPos.w, bias, transparent);
|
||||
#endif
|
||||
#ifdef _Clusters
|
||||
vec4 lPos = LWVPSpotArray[index] * vec4(p + n * bias * 10, 1.0);
|
||||
#ifdef _ShadowMapAtlas
|
||||
visibility *= shadowTest(
|
||||
#ifndef _SingleAtlas
|
||||
shadowMapAtlasSpot, shadowMapAtlasSpotTransparent
|
||||
#else
|
||||
shadowMapAtlas, shadowMapAtlasTransparent
|
||||
#endif
|
||||
, lPos.xyz / lPos.w, bias, transparent
|
||||
);
|
||||
#else
|
||||
if (index == 0) visibility *= shadowTest(shadowMapSpot[0],
|
||||
shadowMapSpotTransparent[0],
|
||||
lPos.xyz / lPos.w, bias, transparent);
|
||||
else if (index == 1) visibility *= shadowTest(shadowMapSpot[1],
|
||||
shadowMapSpotTransparent[1],
|
||||
lPos.xyz / lPos.w, bias, transparent);
|
||||
else if (index == 2) visibility *= shadowTest(shadowMapSpot[2],
|
||||
shadowMapSpotTransparent[2],
|
||||
lPos.xyz / lPos.w, bias, transparent);
|
||||
else if (index == 3) visibility *= shadowTest(shadowMapSpot[3],
|
||||
shadowMapSpotTransparent[3],
|
||||
lPos.xyz / lPos.w, bias, transparent);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
return visibility;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _LightIES
|
||||
visibility *= iesAttenuation(-l);
|
||||
#endif
|
||||
|
||||
#ifdef _ShadowMap
|
||||
if (receiveShadow) {
|
||||
#ifdef _SinglePoint
|
||||
#ifndef _Spot
|
||||
visibility *= PCFCube(shadowMapPoint[0],
|
||||
shadowMapPointTransparent[0],
|
||||
ld, -l, bias, lightProj, n, transparent);
|
||||
#endif
|
||||
#endif
|
||||
#ifdef _Clusters
|
||||
#ifdef _ShadowMapAtlas
|
||||
visibility *= PCFFakeCube(
|
||||
#ifndef _SingleAtlas
|
||||
shadowMapAtlasPoint, shadowMapAtlasPointTransparent
|
||||
#else
|
||||
shadowMapAtlas, shadowMapAtlasTransparent
|
||||
#endif
|
||||
, ld, -l, bias, lightProj, n, index, transparent
|
||||
);
|
||||
#else
|
||||
if (index == 0) visibility *= PCFCube(shadowMapPoint[0],
|
||||
shadowMapPointTransparent[0],
|
||||
ld, -l, bias, lightProj, n, transparent);
|
||||
else if (index == 1) visibility *= PCFCube(shadowMapPoint[1],
|
||||
shadowMapPointTransparent[1],
|
||||
ld, -l, bias, lightProj, n, transparent);
|
||||
else if (index == 2) visibility *= PCFCube(shadowMapPoint[2],
|
||||
shadowMapPointTransparent[2],
|
||||
ld, -l, bias, lightProj, n, transparent);
|
||||
else if (index == 3) visibility *= PCFCube(shadowMapPoint[3],
|
||||
shadowMapPointTransparent[3],
|
||||
ld, -l, bias, lightProj, n, transparent);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
return visibility;
|
||||
}
|
||||
|
||||
vec3 getVisibility(vec3 p, vec3 n, float depth, vec2 uv) {
|
||||
vec3 visibility = vec3(0.0);
|
||||
#ifdef _Sun
|
||||
#ifdef _ShadowMap
|
||||
#ifdef _CSM
|
||||
visibility = shadowTestCascade(
|
||||
#ifdef _ShadowMapAtlas
|
||||
#ifndef _SingleAtlas
|
||||
shadowMapAtlasSun, shadowMapAtlasSunTransparent
|
||||
#else
|
||||
shadowMapAtlas, shadowMapAtlasTransparent
|
||||
#endif
|
||||
#else
|
||||
shadowMap, shadowMapTransparent
|
||||
#endif
|
||||
, eye, p + n * shadowsBias * 10, shadowsBias, false
|
||||
);
|
||||
#else
|
||||
vec4 lPos = LWVP * vec4(p + n * shadowsBias * 100, 1.0);
|
||||
if (lPos.w > 0.0) {
|
||||
visibility = shadowTest(
|
||||
#ifdef _ShadowMapAtlas
|
||||
#ifndef _SingleAtlas
|
||||
shadowMapAtlasSun, shadowMapAtlasSunTransparent
|
||||
#else
|
||||
shadowMapAtlas, shadowMapAtlasTransparent
|
||||
#endif
|
||||
#else
|
||||
shadowMap, shadowMapTransparent
|
||||
#endif
|
||||
, lPos.xyz / lPos.w, shadowsBias, false
|
||||
);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
#ifdef _CPostprocess
|
||||
uniform vec3 PPComp12;
|
||||
#endif
|
||||
|
||||
#ifdef _SinglePoint
|
||||
visibility += sampleLight(
|
||||
p, n, pointPos, pointCol
|
||||
#ifdef _ShadowMap
|
||||
, 0, pointBias, true, false
|
||||
#endif
|
||||
#ifdef _Spot
|
||||
, true, spotData.x, spotData.y, spotDir, spotData.zw, spotRight
|
||||
#endif
|
||||
);
|
||||
#endif
|
||||
in vec2 texCoord;
|
||||
out vec4 fragColor;
|
||||
|
||||
#ifdef _Clusters
|
||||
float viewz = linearize(depth, cameraProj);
|
||||
int clusterI = getClusterI(uv, viewz, cameraPlane);
|
||||
int numLights = int(texelFetch(clustersData, ivec2(clusterI, 0), 0).r * 255);
|
||||
|
||||
#ifdef HLSL
|
||||
viewz += textureLod(clustersData, vec2(0.0), 0.0).r * 1e-9; // TODO: krafix bug, needs to generate sampler
|
||||
#endif
|
||||
|
||||
#ifdef _Spot
|
||||
int numSpots = int(texelFetch(clustersData, ivec2(clusterI, 1 + maxLightsCluster), 0).r * 255);
|
||||
int numPoints = numLights - numSpots;
|
||||
#endif
|
||||
|
||||
for (int i = 0; i < min(numLights, maxLightsCluster); i++) {
|
||||
int li = int(texelFetch(clustersData, ivec2(clusterI, i + 1), 0).r * 255);
|
||||
visibility += sampleLight(
|
||||
p,
|
||||
n,
|
||||
lightsArray[li * 3].xyz, // lp
|
||||
lightsArray[li * 3 + 1].xyz // lightCol
|
||||
#ifdef _ShadowMap
|
||||
// light index, shadow bias, cast_shadows
|
||||
, li, lightsArray[li * 3 + 2].x, lightsArray[li * 3 + 2].z != 0.0, false
|
||||
#endif
|
||||
#ifdef _Spot
|
||||
, lightsArray[li * 3 + 2].y != 0.0
|
||||
, lightsArray[li * 3 + 2].y // spot size (cutoff)
|
||||
, lightsArraySpot[li * 2].w // spot blend (exponent)
|
||||
, lightsArraySpot[li * 2].xyz // spotDir
|
||||
, vec2(lightsArray[li * 3].w, lightsArray[li * 3 + 1].w) // scale
|
||||
, lightsArraySpot[li * 2 + 1].xyz // right
|
||||
#endif
|
||||
);
|
||||
}
|
||||
#endif // _Clusters
|
||||
return visibility;
|
||||
}
|
||||
|
||||
vec3 getWorldPos(vec2 uv, float depth) {
|
||||
vec4 pos = invVP * vec4(uv * 2.0 - 1.0, depth * 2.0 - 1.0, 1.0);
|
||||
return pos.xyz / pos.w;
|
||||
}
|
||||
|
||||
vec3 getNormal(vec2 uv) {
|
||||
vec4 g0 = textureLod(gbuffer0, uv, 0.0);
|
||||
vec2 enc = g0.rg;
|
||||
vec3 n;
|
||||
n.z = 1.0 - abs(enc.x) - abs(enc.y);
|
||||
n.xy = n.z >= 0.0 ? enc.xy : octahedronWrap(enc.xy);
|
||||
return normalize(n);
|
||||
}
|
||||
|
||||
vec3 calculateIndirectLight(vec2 uv, vec3 pos, vec3 normal, float depth) {
|
||||
// Simplified visibility - replace with your full visibility function if needed
|
||||
vec3 sampleColor = textureLod(gbuffer1, uv, 0.0).rgb * getVisibility(pos, normal, depth, uv);
|
||||
|
||||
#ifdef _EmissionShadeless
|
||||
if (matid == 1) { // pure emissive material, color stored in basecol
|
||||
sampleColor += textureLod(gbuffer1, uv, 0.0).rgb;
|
||||
}
|
||||
#endif
|
||||
#ifdef _EmissionShaded
|
||||
#ifdef _EmissionShadeless
|
||||
else {
|
||||
#endif
|
||||
vec3 sampleEmission = textureLod(gbufferEmission, uv, 0.0).rgb;
|
||||
sampleColor += sampleEmission; // Emission should be added directly
|
||||
#ifdef _EmissionShadeless
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
return sampleColor;
|
||||
}
|
||||
|
||||
// Improved sampling parameters
|
||||
const float GOLDEN_ANGLE = 2.39996323;
|
||||
const float MAX_DEPTH_DIFFERENCE = 0.9; // More conservative depth threshold
|
||||
const float SAMPLE_BIAS = 0.01; // Small offset to avoid self-occlusion
|
||||
const int RAY_STEPS = 12;
|
||||
|
||||
vec2 getProjectedCoord(const vec3 viewPos) {
|
||||
vec4 projectedCoord = P * vec4(viewPos, 1.0);
|
||||
projectedCoord.xy /= projectedCoord.w;
|
||||
projectedCoord.xy = projectedCoord.xy * 0.5 + 0.5;
|
||||
#ifdef _InvY
|
||||
projectedCoord.y = 1.0 - projectedCoord.y;
|
||||
#endif
|
||||
return projectedCoord.xy;
|
||||
}
|
||||
|
||||
vec3 cosineSampleHemisphere(vec3 n, vec2 rand) {
|
||||
float phi = PI * 2.0 * rand.x;
|
||||
float cosTheta = sqrt(1.0 - rand.y);
|
||||
float sinTheta = sqrt(rand.y);
|
||||
|
||||
vec3 h = vec3(cos(phi) * sinTheta, sin(phi) * sinTheta, cosTheta);
|
||||
|
||||
vec3 tangent, bitangent;
|
||||
vec3 absN = abs(n);
|
||||
|
||||
if (absN.x <= absN.y && absN.x <= absN.z) {
|
||||
tangent = normalize(cross(n, vec3(1.0, 0.0, 0.0)));
|
||||
} else if (absN.y <= absN.z) {
|
||||
tangent = normalize(cross(n, vec3(0.0, 1.0, 0.0)));
|
||||
} else {
|
||||
tangent = normalize(cross(n, vec3(0.0, 0.0, 1.0)));
|
||||
}
|
||||
bitangent = cross(n, tangent);
|
||||
|
||||
return normalize(tangent * h.x + bitangent * h.y + n * h.z);
|
||||
}
|
||||
|
||||
vec3 traceRay(vec3 origin, vec3 dir, float maxDist, float minDist) {
|
||||
float stepSize = maxDist / float(RAY_STEPS);
|
||||
vec3 pos = origin + dir * minDist;
|
||||
|
||||
float prevDepthDiff = 0.0;
|
||||
float hadValidPrev = 0.0;
|
||||
|
||||
for (int i = 1; i <= RAY_STEPS; i++) {
|
||||
pos += dir * stepSize;
|
||||
vec2 uv = getProjectedCoord(pos);
|
||||
|
||||
vec2 sampleUV = clamp(uv, vec2(0.001), vec2(0.999));
|
||||
|
||||
float sampleDepth = textureLod(gbufferD, sampleUV, 0.0).r * 2.0 - 1.0;
|
||||
if (sampleDepth == 1.0) {
|
||||
hadValidPrev = 0.0;
|
||||
continue;
|
||||
}
|
||||
|
||||
vec3 sampleViewPos = getPosView2(invP, sampleDepth, sampleUV);
|
||||
float depthDiff = pos.z - sampleViewPos.z;
|
||||
float rayDist = length(pos - origin);
|
||||
float thickness = 0.15 + rayDist * 0.25;
|
||||
|
||||
float crossed = hadValidPrev * step(0.0, prevDepthDiff) * step(depthDiff, 0.0);
|
||||
float withinThickness = step(abs(depthDiff), thickness);
|
||||
|
||||
if (crossed > 0.5 || withinThickness > 0.5) {
|
||||
float distWeight = 1.0 - (rayDist / maxDist);
|
||||
distWeight = max(0.0, distWeight * distWeight);
|
||||
|
||||
return vec3(sampleUV, distWeight);
|
||||
}
|
||||
|
||||
prevDepthDiff = depthDiff;
|
||||
hadValidPrev = 1.0;
|
||||
}
|
||||
|
||||
return vec3(-1.0);
|
||||
}
|
||||
|
||||
void main() {
|
||||
float depth = textureLod(gbufferD, texCoord, 0.0).r;
|
||||
if (depth >= 1.0) {
|
||||
fragColor = vec3(0.0);
|
||||
return;
|
||||
}
|
||||
|
||||
vec4 g0 = textureLod(gbuffer0, texCoord, 0.0); // Normal.xy, roughness, metallic/matid
|
||||
unpackFloatInt16(g0.a, metallic, matid);
|
||||
|
||||
vec2 velocity = -textureLod(sveloc, texCoord, 0.0).rg;
|
||||
|
||||
vec3 n;
|
||||
n.z = 1.0 - abs(g0.x) - abs(g0.y);
|
||||
n.xy = n.z >= 0.0 ? g0.xy : octahedronWrap(g0.xy);
|
||||
n = normalize(n);
|
||||
|
||||
vec3 pos = getWorldPos(texCoord, depth);
|
||||
vec3 normal = getNormal(texCoord);
|
||||
vec3 centerColor = textureLod(gbuffer1, texCoord, 0.0).rgb;
|
||||
|
||||
float radius = ssaoRadius;
|
||||
|
||||
vec3 gi = vec3(0.0);
|
||||
float totalWeight = 0.0;
|
||||
float angle = fract(sin(dot(texCoord, vec2(12.9898, 78.233))) * 100.0);
|
||||
|
||||
for (int i = 0; i < ssgiSamples; i++) {
|
||||
// Use quasi-random sequence for better coverage
|
||||
float r = sqrt((float(i) + 0.5) / float(ssgiSamples)) * radius;
|
||||
float a = (float(i) * GOLDEN_ANGLE) + angle;
|
||||
|
||||
vec2 offset = vec2(cos(a), sin(a)) * r * radius;
|
||||
vec2 sampleUV = clamp(texCoord + offset * (BayerMatrix8[int(gl_FragCoord.x + velocity.x) % 8][int(gl_FragCoord.y + velocity.y) % 8] - 0.5) / screenSize, vec2(0.001), vec2(0.999));
|
||||
|
||||
float sampleDepth = textureLod(gbufferD, sampleUV, 0.0).r;
|
||||
if (sampleDepth >= 1.0) continue;
|
||||
|
||||
vec3 samplePos = getWorldPos(sampleUV, sampleDepth);
|
||||
vec3 sampleNormal = getNormal(sampleUV);
|
||||
|
||||
// Apply small bias to sample position to avoid self-occlusion
|
||||
samplePos += sampleNormal * SAMPLE_BIAS;
|
||||
|
||||
vec3 dir = pos - samplePos;
|
||||
float dist = length(dir);
|
||||
|
||||
if (abs(pos.z - samplePos.z) > MAX_DEPTH_DIFFERENCE) continue;;
|
||||
|
||||
vec3 sampleColor = calculateIndirectLight(sampleUV, samplePos, sampleNormal, sampleDepth);
|
||||
float weight = 1.0 / (1.0 + dist * dist * 2.0) * max(dot(sampleNormal, n), 0.0);
|
||||
|
||||
gi += sampleColor * weight;
|
||||
totalWeight += weight;
|
||||
float depth = textureLod(gbufferD, texCoord, 0.0).r * 2.0 - 1.0;
|
||||
if (depth == 1.0) {
|
||||
fragColor = vec4(0.0, 0.0, 0.0, 1.0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Normalize and apply intensity
|
||||
if (totalWeight > 0.0) {
|
||||
gi /= totalWeight;
|
||||
#ifdef _CPostprocess
|
||||
gi *= PPComp12.x;
|
||||
#else
|
||||
gi *= ssaoStrength;
|
||||
#endif
|
||||
}
|
||||
vec4 g0 = textureLod(gbuffer0, texCoord, 0.0);
|
||||
vec2 enc = g0.rg;
|
||||
vec3 n;
|
||||
n.z = 1.0 - abs(enc.x) - abs(enc.y);
|
||||
n.xy = n.z >= 0.0 ? enc.xy : octahedronWrap(enc.xy);
|
||||
n = normalize(n);
|
||||
|
||||
#ifdef _EmissionShadeless
|
||||
if (matid == 1) { // pure emissive material, color stored in basecol
|
||||
gi += textureLod(gbuffer1, texCoord, 0.0).rgb;
|
||||
}
|
||||
vec3 viewNormal = V3 * n;
|
||||
vec3 viewPos = getPosView2(invP, depth, texCoord);
|
||||
|
||||
#ifdef _CPostprocess
|
||||
float radius = PPComp12.y;
|
||||
float strength = PPComp12.x;
|
||||
#else
|
||||
float radius = ssgiRadius;
|
||||
float strength = ssgiStrength;
|
||||
#endif
|
||||
|
||||
float noise = fract(52.9829189 * fract(0.06711056 * texCoord.x * 1000.0 + 0.00583715 * texCoord.y * 1000.0));
|
||||
|
||||
vec3 gi = vec3(0.0);
|
||||
int validSamples = 0;
|
||||
|
||||
// min distance to avoid self shadowing artiffacts
|
||||
float minDist = radius * 0.05;
|
||||
|
||||
for (int i = 0; i < ssgiSamples; i++) {
|
||||
float fi = float(i) + noise;
|
||||
vec2 rand = vec2(
|
||||
fract(fi * 0.7548776662 + noise),
|
||||
fract(fi * 0.5698402909 + noise * 1.5)
|
||||
);
|
||||
|
||||
vec3 rayDir = cosineSampleHemisphere(viewNormal, rand);
|
||||
vec3 hitResult = traceRay(viewPos, rayDir, radius, minDist);
|
||||
|
||||
if (hitResult.x < 0.0) continue;
|
||||
|
||||
vec2 hitUV = hitResult.xy;
|
||||
float distWeight = hitResult.z;
|
||||
|
||||
vec3 hitAlbedo = textureLod(gbuffer1, hitUV, 1.0).rgb;
|
||||
|
||||
#ifdef _Sun
|
||||
vec4 hitG0 = textureLod(gbuffer0, hitUV, 0.0);
|
||||
vec2 hitEnc = hitG0.rg;
|
||||
vec3 hitN;
|
||||
hitN.z = 1.0 - abs(hitEnc.x) - abs(hitEnc.y);
|
||||
hitN.xy = hitN.z >= 0.0 ? hitEnc.xy : octahedronWrap(hitEnc.xy);
|
||||
hitN = normalize(hitN);
|
||||
float hitNdotL = max(0.0, dot(hitN, sunDir));
|
||||
vec3 hitRadiance = hitAlbedo * sunCol * hitNdotL;
|
||||
#else
|
||||
vec3 hitRadiance = hitAlbedo * 0.5;
|
||||
#endif
|
||||
|
||||
#ifdef _EmissionShaded
|
||||
hitRadiance += textureLod(gbufferEmission, hitUV, 0.0).rgb;
|
||||
#endif
|
||||
|
||||
gi += hitRadiance * distWeight;
|
||||
validSamples++;
|
||||
}
|
||||
|
||||
if (validSamples > 0) {
|
||||
gi /= float(validSamples);
|
||||
}
|
||||
|
||||
gi *= strength;
|
||||
|
||||
#ifdef _EmissionShaded
|
||||
#ifdef _EmissionShadeless
|
||||
else {
|
||||
#endif
|
||||
gi += textureLod(gbufferEmission, texCoord, 0.0).rgb;
|
||||
#ifdef _EmissionShadeless
|
||||
}
|
||||
#endif
|
||||
gi += textureLod(gbufferEmission, texCoord, 0.0).rgb * 0.3;
|
||||
#endif
|
||||
fragColor = gi / (gi + vec3(1.0)); // Reinhard tone mapping
|
||||
|
||||
fragColor = vec4(min(gi, vec3(2.0)), 1.0);
|
||||
}
|
||||
|
||||
@ -6,60 +6,18 @@
|
||||
"compare_mode": "always",
|
||||
"cull_mode": "none",
|
||||
"links": [
|
||||
{
|
||||
"name": "invVP",
|
||||
"link": "_inverseViewProjectionMatrix"
|
||||
},
|
||||
{
|
||||
"name": "P",
|
||||
"link": "_projectionMatrix"
|
||||
},
|
||||
{
|
||||
"name": "invP",
|
||||
"link": "_inverseProjectionMatrix"
|
||||
},
|
||||
{
|
||||
"name": "V3",
|
||||
"link": "_viewMatrix3"
|
||||
},
|
||||
{
|
||||
"name": "eye",
|
||||
"link": "_cameraPosition"
|
||||
},
|
||||
{
|
||||
"name": "eyeLook",
|
||||
"link": "_cameraLook"
|
||||
},
|
||||
{
|
||||
"name": "cameraProj",
|
||||
"link": "_cameraPlaneProj"
|
||||
|
||||
},
|
||||
{
|
||||
"name": "screenSize",
|
||||
"link": "_screenSize"
|
||||
},
|
||||
{
|
||||
"name": "PPComp12",
|
||||
"link": "_PPComp12",
|
||||
"ifdef": ["_CPostprocess"]
|
||||
},
|
||||
{
|
||||
"name": "lightsArraySpot",
|
||||
"link": "_lightsArraySpot",
|
||||
"ifdef": ["_Clusters", "_Spot"]
|
||||
},
|
||||
{
|
||||
"name": "lightsArray",
|
||||
"link": "_lightsArray",
|
||||
"ifdef": ["_Clusters"]
|
||||
},
|
||||
{
|
||||
"name": "clustersData",
|
||||
"link": "_clustersData",
|
||||
"ifdef": ["_Clusters"]
|
||||
},
|
||||
{
|
||||
"name": "cameraPlane",
|
||||
"link": "_cameraPlane",
|
||||
"ifdef": ["_Clusters"]
|
||||
},
|
||||
{
|
||||
"name": "sunDir",
|
||||
"link": "_sunDirection",
|
||||
@ -71,128 +29,13 @@
|
||||
"ifdef": ["_Sun"]
|
||||
},
|
||||
{
|
||||
"name": "shadowsBias",
|
||||
"link": "_sunShadowsBias",
|
||||
"ifdef": ["_Sun", "_ShadowMap"]
|
||||
},
|
||||
{
|
||||
"name": "LWVP",
|
||||
"link": "_biasLightWorldViewProjectionMatrixSun",
|
||||
"ifndef": ["_CSM"],
|
||||
"ifdef": ["_Sun", "_ShadowMap"]
|
||||
},
|
||||
{
|
||||
"name": "casData",
|
||||
"link": "_cascadeData",
|
||||
"ifdef": ["_Sun", "_ShadowMap", "_CSM"]
|
||||
},
|
||||
{
|
||||
"name": "lightArea0",
|
||||
"link": "_lightArea0",
|
||||
"ifdef": ["_LTC"]
|
||||
},
|
||||
{
|
||||
"name": "lightArea1",
|
||||
"link": "_lightArea1",
|
||||
"ifdef": ["_LTC"]
|
||||
},
|
||||
{
|
||||
"name": "lightArea2",
|
||||
"link": "_lightArea2",
|
||||
"ifdef": ["_LTC"]
|
||||
},
|
||||
{
|
||||
"name": "lightArea3",
|
||||
"link": "_lightArea3",
|
||||
"ifdef": ["_LTC"]
|
||||
},
|
||||
{
|
||||
"name": "sltcMat",
|
||||
"link": "_ltcMat",
|
||||
"ifdef": ["_LTC"]
|
||||
},
|
||||
{
|
||||
"name": "sltcMag",
|
||||
"link": "_ltcMag",
|
||||
"ifdef": ["_LTC"]
|
||||
},
|
||||
{
|
||||
"name": "smSizeUniform",
|
||||
"link": "_shadowMapSize",
|
||||
"ifdef": ["_SMSizeUniform"]
|
||||
},
|
||||
{
|
||||
"name": "lightProj",
|
||||
"link": "_lightPlaneProj",
|
||||
"ifdef": ["_ShadowMap"]
|
||||
},
|
||||
{
|
||||
"name": "pointPos",
|
||||
"link": "_pointPosition",
|
||||
"ifdef": ["_SinglePoint"]
|
||||
},
|
||||
{
|
||||
"name": "pointCol",
|
||||
"link": "_pointColor",
|
||||
"ifdef": ["_SinglePoint"]
|
||||
},
|
||||
{
|
||||
"name": "pointBias",
|
||||
"link": "_pointShadowsBias",
|
||||
"ifdef": ["_SinglePoint", "_ShadowMap"]
|
||||
},
|
||||
{
|
||||
"name": "spotDir",
|
||||
"link": "_spotDirection",
|
||||
"ifdef": ["_SinglePoint", "_Spot"]
|
||||
},
|
||||
{
|
||||
"name": "spotData",
|
||||
"link": "_spotData",
|
||||
"ifdef": ["_SinglePoint", "_Spot"]
|
||||
},
|
||||
{
|
||||
"name": "spotRight",
|
||||
"link": "_spotRight",
|
||||
"ifdef": ["_SinglePoint", "_Spot"]
|
||||
},
|
||||
{
|
||||
"name": "LWVPSpotArray",
|
||||
"link": "_biasLightWorldViewProjectionMatrixSpotArray",
|
||||
"ifdef": ["_Clusters", "_ShadowMap", "_Spot"]
|
||||
},
|
||||
{
|
||||
"name": "pointLightDataArray",
|
||||
"link": "_pointLightsAtlasArray",
|
||||
"ifdef": ["_Clusters", "_ShadowMap", "_ShadowMapAtlas"]
|
||||
},
|
||||
{
|
||||
"name": "LWVPSpot[0]",
|
||||
"link": "_biasLightWorldViewProjectionMatrixSpot0",
|
||||
"ifndef": ["_ShadowMapAtlas"],
|
||||
"ifdef": ["_LTC", "_ShadowMap"]
|
||||
},
|
||||
{
|
||||
"name": "LWVPSpot[1]",
|
||||
"link": "_biasLightWorldViewProjectionMatrixSpot1",
|
||||
"ifndef": ["_ShadowMapAtlas"],
|
||||
"ifdef": ["_LTC", "_ShadowMap"]
|
||||
},
|
||||
{
|
||||
"name": "LWVPSpot[2]",
|
||||
"link": "_biasLightWorldViewProjectionMatrixSpot2",
|
||||
"ifndef": ["_ShadowMapAtlas"],
|
||||
"ifdef": ["_LTC", "_ShadowMap"]
|
||||
},
|
||||
{
|
||||
"name": "LWVPSpot[3]",
|
||||
"link": "_biasLightWorldViewProjectionMatrixSpot3",
|
||||
"ifndef": ["_ShadowMapAtlas"],
|
||||
"ifdef": ["_LTC", "_ShadowMap"]
|
||||
"name": "PPComp12",
|
||||
"link": "_PPComp12",
|
||||
"ifdef": ["_CPostprocess"]
|
||||
}
|
||||
],
|
||||
"texture_params": [],
|
||||
"vertex_shader": "../include/pass_viewray.vert.glsl",
|
||||
"vertex_shader": "../include/pass.vert.glsl",
|
||||
"fragment_shader": "ssgi_pass.frag.glsl"
|
||||
}
|
||||
]
|
||||
|
||||
@ -64,21 +64,26 @@ vec4 rayCast(vec3 dir) {
|
||||
ddepth = getDeltaDepth(hitCoord);
|
||||
if (ddepth > 0.0) return binarySearch(dir);
|
||||
}
|
||||
return vec4(texCoord, 0.0, 1.0);
|
||||
return vec4(texCoord, 0.0, 0.0);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec4 g0 = textureLod(gbuffer0, texCoord, 0.0);
|
||||
float roughness = g0.z;
|
||||
vec4 gr = textureLod(gbuffer_refraction, texCoord, 0.0);
|
||||
float ior = gr.x;
|
||||
float opac = 1.0 - gr.y;
|
||||
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) {
|
||||
fragColor.rgb = textureLod(tex1, texCoord, 0.0).rgb;
|
||||
fragColor.a = opac;
|
||||
float transmittance = gr.y;
|
||||
float surfaceDepth = gr.z;
|
||||
float d = surfaceDepth * 2.0 - 1.0;
|
||||
|
||||
vec4 sceneSample = textureLod(tex, texCoord, 0.0);
|
||||
if (surfaceDepth == 0.0 || transmittance == 0.0 || ior == 1.0) {
|
||||
vec3 background = textureLod(tex1, texCoord, 0.0).rgb;
|
||||
fragColor.rgb = sceneSample.rgb + background * (1.0 - sceneSample.a);
|
||||
fragColor.a = 1.0;
|
||||
return;
|
||||
}
|
||||
|
||||
vec4 g0 = textureLod(gbuffer0, texCoord, 0.0);
|
||||
float roughness = g0.z;
|
||||
vec2 enc = g0.rg;
|
||||
vec3 n;
|
||||
n.z = 1.0 - abs(enc.x) - abs(enc.y);
|
||||
@ -87,24 +92,32 @@ void main() {
|
||||
|
||||
vec3 viewNormal = V3 * n;
|
||||
vec3 viewPos = getPosView(viewRay, d, cameraProj);
|
||||
vec3 refracted = refract(normalize(viewPos), viewNormal, 1.0 / ior);
|
||||
vec3 incident = normalize(viewPos);
|
||||
vec3 refracted = refract(incident, viewNormal, 1.0 / ior);
|
||||
if (length(refracted) < 0.001) {
|
||||
vec3 background = textureLod(tex1, texCoord, 0.0).rgb;
|
||||
fragColor.rgb = sceneSample.rgb + background * (1.0 - sceneSample.a);
|
||||
fragColor.a = 1.0;
|
||||
return;
|
||||
}
|
||||
|
||||
hitCoord = viewPos;
|
||||
|
||||
vec3 dir = refracted * (1.0 - rand(texCoord) * ss_refractionJitter * roughness) * 2.0;
|
||||
vec4 coords = rayCast(dir);
|
||||
vec2 deltaCoords = abs(vec2(0.5, 0.5) - coords.xy);
|
||||
float screenEdgeFactor = clamp(1.0 - (deltaCoords.x + deltaCoords.y), 0.0, 1.0);
|
||||
|
||||
vec2 screenEdge = smoothstep(0.0, 0.1, coords.xy) * smoothstep(0.0, 0.1, 1.0 - coords.xy);
|
||||
float screenEdgeFactor = screenEdge.x * screenEdge.y;
|
||||
float refractivity = 1.0 - roughness;
|
||||
float intensity = pow(refractivity, ss_refractionFalloffExp) * screenEdgeFactor * \
|
||||
clamp(-refracted.z, 0.0, 1.0) * clamp((length(viewPos - hitCoord)), 0.0, 1.0) * coords.w;
|
||||
|
||||
float intensity = pow(refractivity, ss_refractionFalloffExp) * screenEdgeFactor * coords.w;
|
||||
intensity = clamp(intensity, 0.0, 1.0);
|
||||
|
||||
vec4 refractionCol = textureLod(tex1, coords.xy, 0.0).rgba;
|
||||
refractionCol.a = opac;
|
||||
//refractionCol *= intensity;
|
||||
vec4 color = textureLod(tex, texCoord.xy, 0.0).rgba;
|
||||
color.a = opac;
|
||||
|
||||
fragColor.rgba = mix(refractionCol, color, opac);
|
||||
fragColor.a = opac;
|
||||
vec3 refractedBackground = textureLod(tex1, coords.xy, 0.0).rgb;
|
||||
vec3 straightBackground = textureLod(tex1, texCoord, 0.0).rgb;
|
||||
|
||||
vec3 behindColor = mix(straightBackground, refractedBackground, intensity);
|
||||
|
||||
fragColor.rgb = sceneSample.rgb + behindColor * (1.0 - sceneSample.a);
|
||||
fragColor.a = 1.0;
|
||||
}
|
||||
|
||||
@ -5,12 +5,9 @@
|
||||
"depth_write": false,
|
||||
"compare_mode": "always",
|
||||
"cull_mode": "none",
|
||||
"blend_source": "source_alpha",
|
||||
"blend_destination": "inverse_source_alpha",
|
||||
"blend_source": "blend_one",
|
||||
"blend_destination": "blend_zero",
|
||||
"blend_operation": "add",
|
||||
"alpha_blend_source": "blend_one",
|
||||
"alpha_blend_destination": "blend_one",
|
||||
"alpha_blend_operation": "add",
|
||||
"links": [
|
||||
{
|
||||
"name": "P",
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
//
|
||||
// Copyright (C) 2012 Jorge Jimenez (jorge@iryoku.com)
|
||||
// Copyright (C) 2012 Diego Gutierrez (diegog@unizar.es)
|
||||
// Copyright (C) 2025 Onek8 (info@leenkx.com)
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
@ -34,17 +33,10 @@
|
||||
// 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
|
||||
|
||||
#include "compiled.inc"
|
||||
#include "std/gbuffer.glsl"
|
||||
|
||||
uniform sampler2D gbufferD;
|
||||
uniform sampler2D gbuffer0;
|
||||
@ -56,7 +48,8 @@ uniform vec2 cameraProj;
|
||||
in vec2 texCoord;
|
||||
out vec4 fragColor;
|
||||
|
||||
const float SSSS_FOVY = 108.0;
|
||||
const vec3 SKIN_SSS_RADIUS = vec3(4.8, 2.4, 1.5);
|
||||
const float SSS_DISTANCE_SCALE = 0.001;
|
||||
|
||||
// Temp hash func -
|
||||
float hash13(vec3 p3) {
|
||||
@ -69,63 +62,54 @@ vec4 SSSSBlur() {
|
||||
const int SSSS_N_SAMPLES = 15;
|
||||
vec4 kernel[SSSS_N_SAMPLES];
|
||||
|
||||
// color neutral kernel weights to prevent color shifting
|
||||
kernel[0] = vec4(0.2, 0.2, 0.2, 0.0);
|
||||
kernel[1] = vec4(0.12, 0.12, 0.12, 0.2);
|
||||
kernel[2] = vec4(0.09, 0.09, 0.09, 0.4);
|
||||
kernel[3] = vec4(0.06, 0.06, 0.06, 0.8);
|
||||
kernel[4] = vec4(0.04, 0.04, 0.04, 1.2);
|
||||
kernel[5] = vec4(0.025, 0.025, 0.025, 1.6);
|
||||
kernel[6] = vec4(0.015, 0.015, 0.015, 2.0);
|
||||
kernel[7] = vec4(0.005, 0.005, 0.005, 2.5);
|
||||
kernel[8] = vec4(0.12, 0.12, 0.12, -0.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);
|
||||
kernel[0] = vec4(0.233, 0.455, 0.649, 0.0); // Center sample
|
||||
kernel[1] = vec4(0.100, 0.336, 0.344, 0.37); // +0.37mm
|
||||
kernel[2] = vec4(0.118, 0.198, 0.0, 0.97); // +0.97mm
|
||||
kernel[3] = vec4(0.113, 0.007, 0.007, 1.93); // +1.93mm
|
||||
kernel[4] = vec4(0.358, 0.004, 0.0, 3.87); // +3.87mm
|
||||
kernel[5] = vec4(0.078, 0.0, 0.0, 6.53); // +6.53mm (red only)
|
||||
kernel[6] = vec4(0.0, 0.0, 0.0, 0.0); // Unused
|
||||
kernel[7] = vec4(0.0, 0.0, 0.0, 0.0); // Unused
|
||||
kernel[8] = vec4(0.100, 0.336, 0.344, -0.37); // -0.37mm
|
||||
kernel[9] = vec4(0.118, 0.198, 0.0, -0.97); // -0.97mm
|
||||
kernel[10] = vec4(0.113, 0.007, 0.007, -1.93); // -1.93mm
|
||||
kernel[11] = vec4(0.358, 0.004, 0.0, -3.87); // -3.87mm
|
||||
kernel[12] = vec4(0.078, 0.0, 0.0, -6.53); // -6.53mm (red only)
|
||||
kernel[13] = vec4(0.0, 0.0, 0.0, 0.0); // Unused
|
||||
kernel[14] = vec4(0.0, 0.0, 0.0, 0.0); // Unused
|
||||
|
||||
vec4 colorM = textureLod(tex, texCoord, 0.0);
|
||||
|
||||
float depth = textureLod(gbufferD, texCoord, 0.0).r;
|
||||
float depthM = cameraProj.y / (depth - cameraProj.x);
|
||||
|
||||
float distanceToProjectionWindow = 1.0 / tan(0.5 * radians(SSSS_FOVY));
|
||||
float scale = distanceToProjectionWindow / depthM;
|
||||
|
||||
vec2 finalStep = sssWidth * scale * dir;
|
||||
float distanceScale = 1.0 / max(depthM, 0.1);
|
||||
|
||||
vec2 finalStep = sssWidth * distanceScale * dir * SSS_DISTANCE_SCALE;
|
||||
|
||||
|
||||
vec3 jitterSeed = vec3(texCoord.xy * 1000.0, fract(cameraProj.x * 0.0001));
|
||||
float jitterOffset = (hash13(jitterSeed) * 2.0 - 1.0) * 0.15; // 15% jitteR
|
||||
float jitterOffset = (hash13(jitterSeed) * 2.0 - 1.0) * 0.15;
|
||||
|
||||
finalStep *= (1.0 + jitterOffset);
|
||||
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++) {
|
||||
float sampleJitter = hash13(vec3(texCoord.xy * 720.0, float(i) * 37.45)) * 0.1 - 0.05;
|
||||
|
||||
vec2 offset = texCoord + (kernel[i].a + sampleJitter) * finalStep;
|
||||
vec4 color = textureLod(tex, offset, 0.0);
|
||||
const float DEPTH_THRESHOLD = 0.05;
|
||||
float sampleDepth = textureLod(gbufferD, offset, 0.0).r;
|
||||
float sampleDepthM = cameraProj.y / (sampleDepth - cameraProj.x);
|
||||
|
||||
// ADJUST FOR SURFACE FOLLOWING
|
||||
// 0.0 = disabled (maximum SSS but with bleeding), 1.0 = fully enabled (prevents bleeding but might reduce SSS effect)
|
||||
const float SURFACE_FOLLOWING_STRENGTH = 0.15; // Reduced to preserve more SSS effect
|
||||
float depthDiff = abs(depthM - sampleDepthM);
|
||||
float depthWeight = exp(-depthDiff * 10.0);
|
||||
|
||||
if (SURFACE_FOLLOWING_STRENGTH > 0.0) {
|
||||
float sampleDepth = textureLod(gbufferD, offset, 0.0).r;
|
||||
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);
|
||||
}
|
||||
if (depthDiff > DEPTH_THRESHOLD) {
|
||||
color.rgb = mix(colorM.rgb, color.rgb, depthWeight);
|
||||
}
|
||||
|
||||
colorBlurred += color.rgb * kernel[i].rgb;
|
||||
@ -133,17 +117,22 @@ vec4 SSSSBlur() {
|
||||
}
|
||||
vec3 normalizedColor = colorBlurred / max(weightSum, vec3(0.00001));
|
||||
float dither = hash13(vec3(texCoord * 1333.0, 0.0)) * 0.003 - 0.0015;
|
||||
return vec4(normalizedColor + vec3(dither), colorM.a);
|
||||
normalizedColor = max(normalizedColor + vec3(dither), vec3(0.0));
|
||||
return vec4(normalizedColor, colorM.a);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec4 g0 = textureLod(gbuffer0, texCoord, 0.0);
|
||||
float metallic;
|
||||
uint matid;
|
||||
unpackFloatInt16(g0.a, metallic, matid);
|
||||
|
||||
if (textureLod(gbuffer0, texCoord, 0.0).a == 8192.0) {
|
||||
if (matid == 2u) {
|
||||
vec4 originalColor = textureLod(tex, texCoord, 0.0);
|
||||
vec4 blurredColor = SSSSBlur();
|
||||
vec4 finalColor = mix(blurredColor, originalColor, 0.15);
|
||||
|
||||
fragColor = clamp(finalColor, 0.0, 1.0);
|
||||
vec4 sssContribution = blurredColor - originalColor;
|
||||
vec4 combined = originalColor + max(vec4(0.0), sssContribution) * 0.8;
|
||||
fragColor = max(vec4(0.0), min(combined, vec4(10.0)));
|
||||
} else {
|
||||
fragColor = textureLod(tex, texCoord, 0.0);
|
||||
}
|
||||
|
||||
@ -36,7 +36,8 @@ float d_ggx(const float nh, const float a) {
|
||||
|
||||
vec3 specularBRDF(const vec3 f0, const float roughness, const float nl, const float nh, const float nv, const float vh) {
|
||||
float a = roughness * roughness;
|
||||
return d_ggx(nh, a) * g2_approx(nl, nv, a) * f_schlick(f0, vh) / max(4.0 * nv, 1e-5); //NdotL cancels out later
|
||||
vec3 result = d_ggx(nh, a) * g2_approx(nl, nv, a) * f_schlick(f0, vh) / max(4.0 * nv, 1e-5); //NdotL cancels out later
|
||||
return min(result, vec3(200.0));
|
||||
}
|
||||
|
||||
// John Hable - Optimizing GGX Shaders
|
||||
|
||||
@ -24,7 +24,7 @@ const int DIFFUSE_CONE_COUNT = 16;
|
||||
|
||||
const float SHADOW_CONE_APERTURE = radians(15.0);
|
||||
|
||||
const float DIFFUSE_CONE_APERTURE = 0.872665; // 50 degrees in radians
|
||||
const float DIFFUSE_CONE_APERTURE = 0.872665;
|
||||
|
||||
mat3 makeTangentBasis(const vec3 normal) {
|
||||
// Create a tangent basis from normal vector
|
||||
|
||||
@ -8,10 +8,10 @@
|
||||
// const float compoDOFLength = 160.0; // Focal length in mm 18-200
|
||||
// const float compoDOFFstop = 128.0; // F-stop value
|
||||
|
||||
const int samples = 6; // Samples on the first ring
|
||||
const int samples = 8; // Samples on the first ring
|
||||
const int rings = 6; // Ring count
|
||||
const vec2 focus = vec2(0.5, 0.5);
|
||||
const float coc = 0.11; // Circle of confusion size in mm (35mm film = 0.03mm)
|
||||
const float coc = 0.03; // Circle of confusion size in mm (35mm film = 0.03mm)
|
||||
const float maxblur = 1.0;
|
||||
const float threshold = 0.5; // Highlight threshold
|
||||
const float gain = 2.0; // Highlight gain
|
||||
@ -55,21 +55,26 @@ vec3 dof(
|
||||
float f = DOFLength; // Focal length in mm
|
||||
float d = fDepth * 1000.0; // Focal plane in mm
|
||||
float o = depth * 1000.0; // Depth in mm
|
||||
float a = (o * f) / (o - f);
|
||||
float b = (d * f) / (d - f);
|
||||
float c = (d - f) / (d * DOFFStop * coc);
|
||||
float a = (o > f) ? (o * f) / (o - f) : 0.0;
|
||||
float b = (d > f) ? (d * f) / (d - f) : 0.0;
|
||||
float sensorSize = max(DOFFStop, 10.0);
|
||||
float c = (d - f) / (d * sensorSize * coc);
|
||||
|
||||
float blur = abs(a - b) * c;
|
||||
blur = clamp(blur, 0.0, 1.0);
|
||||
|
||||
vec2 noise = rand2(texCoord) * namount * blur;
|
||||
float w = (texStep.x) * blur * maxblur + noise.x;
|
||||
float h = (texStep.y) * blur * maxblur + noise.y;
|
||||
vec3 col = vec3(0.0);
|
||||
if (blur < 0.05) {
|
||||
col = textureLod(tex, texCoord, 0.0).rgb;
|
||||
}
|
||||
else {
|
||||
col = textureLod(tex, texCoord, 0.0).rgb;
|
||||
vec3 sharpCol = textureLod(tex, texCoord, 0.0).rgb;
|
||||
vec3 col = sharpCol;
|
||||
float blurThreshold = 0.02;
|
||||
float blurRange = 0.06;
|
||||
|
||||
if (blur > blurThreshold) {
|
||||
float blurAmount = smoothstep(blurThreshold, blurThreshold + blurRange, blur);
|
||||
|
||||
vec3 blurredCol = sharpCol;
|
||||
float s = 1.0;
|
||||
int ringsamples;
|
||||
|
||||
@ -81,11 +86,12 @@ vec3 dof(
|
||||
float ph = (sin(float(j) * step) * float(i));
|
||||
float p = 1.0;
|
||||
// if (pentagon) p = penta(vec2(pw, ph));
|
||||
col += color(texCoord + vec2(pw * w, ph * h), blur, tex, texStep) * mix(1.0, (float(i)) / (float(rings)), bias) * p;
|
||||
blurredCol += color(texCoord + vec2(pw * w, ph * h), blur, tex, texStep) * mix(1.0, (float(i)) / (float(rings)), bias) * p;
|
||||
s += 1.0 * mix(1.0, (float(i)) / (float(rings)), bias) * p;
|
||||
}
|
||||
}
|
||||
col /= s;
|
||||
blurredCol /= s;
|
||||
col = mix(sharpCol, blurredCol, blurAmount);
|
||||
}
|
||||
return col;
|
||||
}
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
#ifndef _GBUFFER_GLSL_
|
||||
#define _GBUFFER_GLSL_
|
||||
|
||||
vec2 octahedronWrap(const vec2 v) {
|
||||
vec2 octahedronWrap(vec2 v) {
|
||||
return (1.0 - abs(v.yx)) * (vec2(v.x >= 0.0 ? 1.0 : -1.0, v.y >= 0.0 ? 1.0 : -1.0));
|
||||
}
|
||||
|
||||
vec3 getNor(const vec2 enc) {
|
||||
vec3 getNor(vec2 enc) {
|
||||
vec3 n;
|
||||
n.z = 1.0 - abs(enc.x) - abs(enc.y);
|
||||
n.xy = n.z >= 0.0 ? enc.xy : octahedronWrap(enc.xy);
|
||||
@ -13,13 +13,13 @@ vec3 getNor(const vec2 enc) {
|
||||
return n;
|
||||
}
|
||||
|
||||
vec3 getPosView(const vec3 viewRay, const float depth, const vec2 cameraProj) {
|
||||
vec3 getPosView(vec3 viewRay, float depth, vec2 cameraProj) {
|
||||
float linearDepth = cameraProj.y / (cameraProj.x - depth);
|
||||
//float linearDepth = cameraProj.y / ((depth * 0.5 + 0.5) - cameraProj.x);
|
||||
return viewRay * linearDepth;
|
||||
}
|
||||
|
||||
vec3 getPos(const vec3 eye, const vec3 eyeLook, const vec3 viewRay, const float depth, const vec2 cameraProj) {
|
||||
vec3 getPos(vec3 eye, vec3 eyeLook, vec3 viewRay, float depth, vec2 cameraProj) {
|
||||
// eyeLook, viewRay should be normalized
|
||||
float linearDepth = cameraProj.y / ((depth * 0.5 + 0.5) - cameraProj.x);
|
||||
float viewZDist = dot(eyeLook, viewRay);
|
||||
@ -27,7 +27,7 @@ vec3 getPos(const vec3 eye, const vec3 eyeLook, const vec3 viewRay, const float
|
||||
return wposition;
|
||||
}
|
||||
|
||||
vec3 getPosNoEye(const vec3 eyeLook, const vec3 viewRay, const float depth, const vec2 cameraProj) {
|
||||
vec3 getPosNoEye(vec3 eyeLook, vec3 viewRay, float depth, vec2 cameraProj) {
|
||||
// eyeLook, viewRay should be normalized
|
||||
float linearDepth = cameraProj.y / ((depth * 0.5 + 0.5) - cameraProj.x);
|
||||
float viewZDist = dot(eyeLook, viewRay);
|
||||
@ -36,10 +36,10 @@ vec3 getPosNoEye(const vec3 eyeLook, const vec3 viewRay, const float depth, cons
|
||||
}
|
||||
|
||||
#if defined(HLSL) || defined(METAL)
|
||||
vec3 getPos2(const mat4 invVP, const float depth, vec2 coord) {
|
||||
vec3 getPos2(mat4 invVP, float depth, vec2 coord) {
|
||||
coord.y = 1.0 - coord.y;
|
||||
#else
|
||||
vec3 getPos2(const mat4 invVP, const float depth, const vec2 coord) {
|
||||
vec3 getPos2(mat4 invVP, float depth, vec2 coord) {
|
||||
#endif
|
||||
vec4 pos = vec4(coord * 2.0 - 1.0, depth, 1.0);
|
||||
pos = invVP * pos;
|
||||
@ -48,10 +48,10 @@ vec3 getPos2(const mat4 invVP, const float depth, const vec2 coord) {
|
||||
}
|
||||
|
||||
#if defined(HLSL) || defined(METAL)
|
||||
vec3 getPosView2(const mat4 invP, const float depth, vec2 coord) {
|
||||
vec3 getPosView2(mat4 invP, float depth, vec2 coord) {
|
||||
coord.y = 1.0 - coord.y;
|
||||
#else
|
||||
vec3 getPosView2(const mat4 invP, const float depth, const vec2 coord) {
|
||||
vec3 getPosView2(mat4 invP, float depth, vec2 coord) {
|
||||
#endif
|
||||
vec4 pos = vec4(coord * 2.0 - 1.0, depth, 1.0);
|
||||
pos = invP * pos;
|
||||
@ -60,10 +60,10 @@ vec3 getPosView2(const mat4 invP, const float depth, const vec2 coord) {
|
||||
}
|
||||
|
||||
#if defined(HLSL) || defined(METAL)
|
||||
vec3 getPos2NoEye(const vec3 eye, const mat4 invVP, const float depth, vec2 coord) {
|
||||
vec3 getPos2NoEye(vec3 eye, mat4 invVP, float depth, vec2 coord) {
|
||||
coord.y = 1.0 - coord.y;
|
||||
#else
|
||||
vec3 getPos2NoEye(const vec3 eye, const mat4 invVP, const float depth, const vec2 coord) {
|
||||
vec3 getPos2NoEye(vec3 eye, mat4 invVP, float depth, vec2 coord) {
|
||||
#endif
|
||||
vec4 pos = vec4(coord * 2.0 - 1.0, depth, 1.0);
|
||||
pos = invVP * pos;
|
||||
@ -71,24 +71,24 @@ vec3 getPos2NoEye(const vec3 eye, const mat4 invVP, const float depth, const vec
|
||||
return pos.xyz - eye;
|
||||
}
|
||||
|
||||
float packFloat(const float f1, const float f2) {
|
||||
float packFloat(float f1, float f2) {
|
||||
return floor(f1 * 100.0) + min(f2, 1.0 - 1.0 / 100.0);
|
||||
}
|
||||
|
||||
vec2 unpackFloat(const float f) {
|
||||
vec2 unpackFloat(float f) {
|
||||
return vec2(floor(f) / 100.0, fract(f));
|
||||
}
|
||||
|
||||
float packFloat2(const float f1, const float f2) {
|
||||
float packFloat2(float f1, float f2) {
|
||||
// Higher f1 = less precise f2
|
||||
return floor(f1 * 255.0) + min(f2, 1.0 - 1.0 / 100.0);
|
||||
}
|
||||
|
||||
vec2 unpackFloat2(const float f) {
|
||||
vec2 unpackFloat2(float f) {
|
||||
return vec2(floor(f) / 255.0, fract(f));
|
||||
}
|
||||
|
||||
vec4 encodeRGBM(const vec3 rgb) {
|
||||
vec4 encodeRGBM(vec3 rgb) {
|
||||
const float maxRange = 6.0;
|
||||
float maxRGB = max(rgb.x, max(rgb.g, rgb.b));
|
||||
float m = maxRGB / maxRange;
|
||||
@ -96,7 +96,7 @@ vec4 encodeRGBM(const vec3 rgb) {
|
||||
return vec4(rgb / (m * maxRange), m);
|
||||
}
|
||||
|
||||
vec3 decodeRGBM(const vec4 rgbm) {
|
||||
vec3 decodeRGBM(vec4 rgbm) {
|
||||
const float maxRange = 6.0;
|
||||
return rgbm.rgb * rgbm.a * maxRange;
|
||||
}
|
||||
@ -150,7 +150,7 @@ vec3 decNor(uint val) {
|
||||
/**
|
||||
Packs a float in [0, 1] and an integer in [0..15] into a single 16 bit float value.
|
||||
**/
|
||||
float packFloatInt16(const float f, const uint i) {
|
||||
float packFloatInt16(float f, uint i) {
|
||||
const uint numBitFloat = 12;
|
||||
const float maxValFloat = float((1 << numBitFloat) - 1);
|
||||
|
||||
@ -160,7 +160,7 @@ float packFloatInt16(const float f, const uint i) {
|
||||
return float(bitsInt | bitsFloat);
|
||||
}
|
||||
|
||||
void unpackFloatInt16(const float val, out float f, out uint i) {
|
||||
void unpackFloatInt16(float val, out float f, out uint i) {
|
||||
const uint numBitFloat = 12;
|
||||
const float maxValFloat = float((1 << numBitFloat) - 1);
|
||||
|
||||
|
||||
@ -158,7 +158,7 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co
|
||||
#endif
|
||||
|
||||
direct *= attenuate(distance(p, lp));
|
||||
direct *= lightCol;
|
||||
direct *= min(lightCol, vec3(100.0));
|
||||
|
||||
#ifdef _MicroShadowing
|
||||
direct *= clamp(dotNL + 2.0 * occ * occ - 1.0, 0.0, 1.0);
|
||||
@ -181,55 +181,55 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co
|
||||
#ifdef _ShadowMap
|
||||
if (receiveShadow) {
|
||||
#ifdef _SinglePoint
|
||||
vec4 lPos = LWVPSpot[0] * vec4(p + n * bias * 10, 1.0);
|
||||
vec4 lPos = LWVPSpot[0] * vec4(p + n * bias * 2, 1.0);
|
||||
direct *= shadowTest(shadowMapSpot[0],
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapSpotTransparent[0],
|
||||
#endif
|
||||
lPos.xyz / lPos.w, bias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapSpotTransparent[0],
|
||||
#endif
|
||||
lPos.xyz / lPos.w, bias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#endif
|
||||
#ifdef _Clusters
|
||||
vec4 lPos = LWVPSpot[index] * vec4(p + n * bias * 10, 1.0);
|
||||
vec4 lPos = LWVPSpot[index] * vec4(p + n * bias * 2, 1.0);
|
||||
if (index == 0) direct *= shadowTest(shadowMapSpot[0],
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapSpotTransparent[0],
|
||||
#endif
|
||||
lPos.xyz / lPos.w, bias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapSpotTransparent[0],
|
||||
#endif
|
||||
lPos.xyz / lPos.w, bias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
else if (index == 1) direct *= shadowTest(shadowMapSpot[1],
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapSpotTransparent[1],
|
||||
#endif
|
||||
lPos.xyz / lPos.w, bias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapSpotTransparent[1],
|
||||
#endif
|
||||
lPos.xyz / lPos.w, bias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
else if (index == 2) direct *= shadowTest(shadowMapSpot[2],
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapSpotTransparent[2],
|
||||
#endif
|
||||
lPos.xyz / lPos.w, bias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapSpotTransparent[2],
|
||||
#endif
|
||||
lPos.xyz / lPos.w, bias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
else if (index == 3) direct *= shadowTest(shadowMapSpot[3],
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapSpotTransparent[3],
|
||||
#endif
|
||||
lPos.xyz / lPos.w, bias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapSpotTransparent[3],
|
||||
#endif
|
||||
lPos.xyz / lPos.w, bias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
@ -243,76 +243,76 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co
|
||||
#ifdef _ShadowMap
|
||||
if (receiveShadow) {
|
||||
#ifdef _SinglePoint
|
||||
vec4 lPos = LWVPSpotArray[0] * vec4(p + n * bias * 10, 1.0);
|
||||
vec4 lPos = LWVPSpotArray[0] * vec4(p + n * bias * 2, 1.0);
|
||||
direct *= shadowTest(shadowMapSpot[0],
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapSpotTransparent[0],
|
||||
#endif
|
||||
lPos.xyz / lPos.w, bias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapSpotTransparent[0],
|
||||
#endif
|
||||
lPos.xyz / lPos.w, bias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#endif
|
||||
#ifdef _Clusters
|
||||
vec4 lPos = LWVPSpotArray[index] * vec4(p + n * bias * 10, 1.0);
|
||||
vec4 lPos = LWVPSpotArray[index] * vec4(p + n * bias * 2, 1.0);
|
||||
#ifdef _ShadowMapAtlas
|
||||
direct *= shadowTest(
|
||||
#ifdef _ShadowMapTransparent
|
||||
#ifndef _SingleAtlas
|
||||
shadowMapAtlasSpot, shadowMapAtlasSpotTransparent
|
||||
#else
|
||||
shadowMapAtlas, shadowMapAtlasTransparent
|
||||
#endif
|
||||
#else
|
||||
#ifndef _SingleAtlas
|
||||
shadowMapAtlasSpot
|
||||
#else
|
||||
shadowMapAtlas
|
||||
#endif
|
||||
#endif
|
||||
, lPos.xyz / lPos.w, bias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#ifdef _ShadowMapTransparent
|
||||
#ifndef _SingleAtlas
|
||||
shadowMapAtlasSpot, shadowMapAtlasSpotTransparent
|
||||
#else
|
||||
shadowMapAtlas, shadowMapAtlasTransparent
|
||||
#endif
|
||||
#else
|
||||
#ifndef _SingleAtlas
|
||||
shadowMapAtlasSpot
|
||||
#else
|
||||
shadowMapAtlas
|
||||
#endif
|
||||
#endif
|
||||
, lPos.xyz / lPos.w, bias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#else
|
||||
if (index == 0) direct *= shadowTest(shadowMapSpot[0],
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapSpotTransparent[0],
|
||||
#endif
|
||||
lPos.xyz / lPos.w, bias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
if (index == 0) direct *= shadowTest(shadowMapSpot[0],
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapSpotTransparent[0],
|
||||
#endif
|
||||
lPos.xyz / lPos.w, bias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
else if (index == 1) direct *= shadowTest(shadowMapSpot[1],
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapSpotTransparent[1],
|
||||
#endif
|
||||
lPos.xyz / lPos.w, bias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapSpotTransparent[1],
|
||||
#endif
|
||||
lPos.xyz / lPos.w, bias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
else if (index == 2) direct *= shadowTest(shadowMapSpot[2],
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapSpotTransparent[2],
|
||||
#endif
|
||||
lPos.xyz / lPos.w, bias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapSpotTransparent[2],
|
||||
#endif
|
||||
lPos.xyz / lPos.w, bias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
else if (index == 3) direct *= shadowTest(shadowMapSpot[3],
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapSpotTransparent[3],
|
||||
#endif
|
||||
lPos.xyz / lPos.w, bias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapSpotTransparent[3],
|
||||
#endif
|
||||
lPos.xyz / lPos.w, bias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
@ -330,74 +330,74 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co
|
||||
#ifdef _SinglePoint
|
||||
#ifndef _Spot
|
||||
direct *= PCFCube(shadowMapPoint[0],
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapPointTransparent[0],
|
||||
#endif
|
||||
ld, -l, bias, lightProj, n
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapPointTransparent[0],
|
||||
#endif
|
||||
ld, -l, bias, lightProj, n
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#endif
|
||||
#endif
|
||||
#ifdef _Clusters
|
||||
#ifdef _ShadowMapAtlas
|
||||
direct *= PCFFakeCube(
|
||||
#ifdef _ShadowMapTransparent
|
||||
#ifndef _SingleAtlas
|
||||
shadowMapAtlasPoint, shadowMapAtlasPointTransparent
|
||||
#else
|
||||
shadowMapAtlas, shadowMapAtlasTransparent
|
||||
#endif
|
||||
#else
|
||||
#ifndef _SingleAtlas
|
||||
shadowMapAtlasPoint
|
||||
#else
|
||||
shadowMapAtlas
|
||||
#endif
|
||||
#endif
|
||||
, ld, -l, bias, lightProj, n, index
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#ifdef _ShadowMapTransparent
|
||||
#ifndef _SingleAtlas
|
||||
shadowMapAtlasPoint, shadowMapAtlasPointTransparent
|
||||
#else
|
||||
shadowMapAtlas, shadowMapAtlasTransparent
|
||||
#endif
|
||||
#else
|
||||
#ifndef _SingleAtlas
|
||||
shadowMapAtlasPoint
|
||||
#else
|
||||
shadowMapAtlas
|
||||
#endif
|
||||
#endif
|
||||
, ld, -l, bias, lightProj, n, index
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#else
|
||||
if (index == 0) direct *= PCFCube(shadowMapPoint[0],
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapPointTransparent[0],
|
||||
#endif
|
||||
ld, -l, bias, lightProj, n
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
if (index == 0) direct *= PCFCube(shadowMapPoint[0],
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapPointTransparent[0],
|
||||
#endif
|
||||
ld, -l, bias, lightProj, n
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
else if (index == 1) direct *= PCFCube(shadowMapPoint[1],
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapPointTransparent[1],
|
||||
#endif
|
||||
ld, -l, bias, lightProj, n
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapPointTransparent[1],
|
||||
#endif
|
||||
ld, -l, bias, lightProj, n
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
else if (index == 2) direct *= PCFCube(shadowMapPoint[2],
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapPointTransparent[2],
|
||||
#endif
|
||||
ld, -l, bias, lightProj, n
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapPointTransparent[2],
|
||||
#endif
|
||||
ld, -l, bias, lightProj, n
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
else if (index == 3) direct *= PCFCube(shadowMapPoint[3],
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapPointTransparent[3],
|
||||
#endif
|
||||
ld, -l, bias, lightProj, n
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapPointTransparent[3],
|
||||
#endif
|
||||
ld, -l, bias, lightProj, n
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
@ -445,61 +445,62 @@ vec3 sampleLightVoxels(const vec3 p, const vec3 n, const vec3 v, const float dot
|
||||
#endif
|
||||
|
||||
direct *= attenuate(distance(p, lp));
|
||||
direct *= lightCol;
|
||||
// CRITICAL: Clamp light color to prevent extreme HDR values causing white sphere artifacts
|
||||
direct *= min(lightCol, vec3(100.0));
|
||||
|
||||
#ifdef _LTC
|
||||
#ifdef _ShadowMap
|
||||
if (receiveShadow) {
|
||||
#ifdef _SinglePoint
|
||||
vec4 lPos = LWVPSpot[0] * vec4(p + n * bias * 10, 1.0);
|
||||
vec4 lPos = LWVPSpot[0] * vec4(p + n * bias * 2, 1.0);
|
||||
direct *= shadowTest(shadowMapSpot[0],
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapSpotTransparent[0],
|
||||
#endif
|
||||
lPos.xyz / lPos.w, bias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapSpotTransparent[0],
|
||||
#endif
|
||||
lPos.xyz / lPos.w, bias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#endif
|
||||
#ifdef _Clusters
|
||||
vec4 lPos = LWVPSpot[index] * vec4(p + n * bias * 10, 1.0);
|
||||
vec4 lPos = LWVPSpot[index] * vec4(p + n * bias * 2, 1.0);
|
||||
if (index == 0) direct *= shadowTest(shadowMapSpot[0],
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapSpotTransparent[0],
|
||||
#endif
|
||||
lPos.xyz / lPos.w, bias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapSpotTransparent[0],
|
||||
#endif
|
||||
lPos.xyz / lPos.w, bias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
else if (index == 1) direct *= shadowTest(shadowMapSpot[1],
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapSpotTransparent[1],
|
||||
#endif
|
||||
lPos.xyz / lPos.w, bias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapSpotTransparent[1],
|
||||
#endif
|
||||
lPos.xyz / lPos.w, bias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
else if (index == 2) direct *= shadowTest(shadowMapSpot[2],
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapSpotTransparent[2],
|
||||
#endif
|
||||
lPos.xyz / lPos.w, bias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapSpotTransparent[2],
|
||||
#endif
|
||||
lPos.xyz / lPos.w, bias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
else if (index == 3) direct *= shadowTest(shadowMapSpot[3],
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapSpotTransparent[3],
|
||||
#endif
|
||||
lPos.xyz / lPos.w, bias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapSpotTransparent[3],
|
||||
#endif
|
||||
lPos.xyz / lPos.w, bias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
@ -513,76 +514,76 @@ vec3 sampleLightVoxels(const vec3 p, const vec3 n, const vec3 v, const float dot
|
||||
#ifdef _ShadowMap
|
||||
if (receiveShadow) {
|
||||
#ifdef _SinglePoint
|
||||
vec4 lPos = LWVPSpotArray[0] * vec4(p + n * bias * 10, 1.0);
|
||||
vec4 lPos = LWVPSpotArray[0] * vec4(p + n * bias * 2, 1.0);
|
||||
direct *= shadowTest(shadowMapSpot[0],
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapSpotTransparent[0],
|
||||
#endif
|
||||
lPos.xyz / lPos.w, bias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapSpotTransparent[0],
|
||||
#endif
|
||||
lPos.xyz / lPos.w, bias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#endif
|
||||
#ifdef _Clusters
|
||||
vec4 lPos = LWVPSpotArray[index] * vec4(p + n * bias * 10, 1.0);
|
||||
vec4 lPos = LWVPSpotArray[index] * vec4(p + n * bias * 2, 1.0);
|
||||
#ifdef _ShadowMapAtlas
|
||||
direct *= shadowTest(
|
||||
#ifdef _ShadowMapTransparent
|
||||
#ifndef _SingleAtlas
|
||||
shadowMapAtlasSpot, shadowMapAtlasSpotTransparent
|
||||
#else
|
||||
shadowMapAtlas, shadowMapAtlasTransparent
|
||||
#endif
|
||||
#else
|
||||
#ifndef _SingleAtlas
|
||||
shadowMapAtlasSpot
|
||||
#else
|
||||
shadowMapAtlas
|
||||
#endif
|
||||
#endif
|
||||
, lPos.xyz / lPos.w, bias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#ifdef _ShadowMapTransparent
|
||||
#ifndef _SingleAtlas
|
||||
shadowMapAtlasSpot, shadowMapAtlasSpotTransparent
|
||||
#else
|
||||
shadowMapAtlas, shadowMapAtlasTransparent
|
||||
#endif
|
||||
#else
|
||||
#ifndef _SingleAtlas
|
||||
shadowMapAtlasSpot
|
||||
#else
|
||||
shadowMapAtlas
|
||||
#endif
|
||||
#endif
|
||||
, lPos.xyz / lPos.w, bias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#else
|
||||
if (index == 0) direct *= shadowTest(shadowMapSpot[0],
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapSpotTransparent[0],
|
||||
#endif
|
||||
lPos.xyz / lPos.w, bias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
if (index == 0) direct *= shadowTest(shadowMapSpot[0],
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapSpotTransparent[0],
|
||||
#endif
|
||||
lPos.xyz / lPos.w, bias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
else if (index == 1) direct *= shadowTest(shadowMapSpot[1],
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapSpotTransparent[1],
|
||||
#endif
|
||||
lPos.xyz / lPos.w, bias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapSpotTransparent[1],
|
||||
#endif
|
||||
lPos.xyz / lPos.w, bias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
else if (index == 2) direct *= shadowTest(shadowMapSpot[2],
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapSpotTransparent[2],
|
||||
#endif
|
||||
lPos.xyz / lPos.w, bias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapSpotTransparent[2],
|
||||
#endif
|
||||
lPos.xyz / lPos.w, bias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
else if (index == 3) direct *= shadowTest(shadowMapSpot[3],
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapSpotTransparent[3],
|
||||
#endif
|
||||
lPos.xyz / lPos.w, bias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapSpotTransparent[3],
|
||||
#endif
|
||||
lPos.xyz / lPos.w, bias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
@ -600,74 +601,74 @@ vec3 sampleLightVoxels(const vec3 p, const vec3 n, const vec3 v, const float dot
|
||||
#ifdef _SinglePoint
|
||||
#ifndef _Spot
|
||||
direct *= PCFCube(shadowMapPoint[0],
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapPointTransparent[0],
|
||||
#endif
|
||||
ld, -l, bias, lightProj, n
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapPointTransparent[0],
|
||||
#endif
|
||||
ld, -l, bias, lightProj, n
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#endif
|
||||
#endif
|
||||
#ifdef _Clusters
|
||||
#ifdef _ShadowMapAtlas
|
||||
direct *= PCFFakeCube(
|
||||
#ifdef _ShadowMapTransparent
|
||||
#ifndef _SingleAtlas
|
||||
shadowMapAtlasPoint, shadowMapAtlasPointTransparent
|
||||
#else
|
||||
shadowMapAtlas, shadowMapAtlasTransparent
|
||||
#endif
|
||||
#else
|
||||
#ifndef _SingleAtlas
|
||||
shadowMapAtlasPoint
|
||||
#else
|
||||
shadowMapAtlas
|
||||
#endif
|
||||
#endif
|
||||
, ld, -l, bias, lightProj, n, index
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#ifdef _ShadowMapTransparent
|
||||
#ifndef _SingleAtlas
|
||||
shadowMapAtlasPoint, shadowMapAtlasPointTransparent
|
||||
#else
|
||||
shadowMapAtlas, shadowMapAtlasTransparent
|
||||
#endif
|
||||
#else
|
||||
#ifndef _SingleAtlas
|
||||
shadowMapAtlasPoint
|
||||
#else
|
||||
shadowMapAtlas
|
||||
#endif
|
||||
#endif
|
||||
, ld, -l, bias, lightProj, n, index
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#else
|
||||
if (index == 0) direct *= PCFCube(shadowMapPoint[0],
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapPointTransparent[0],
|
||||
#endif
|
||||
ld, -l, bias, lightProj, n
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
if (index == 0) direct *= PCFCube(shadowMapPoint[0],
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapPointTransparent[0],
|
||||
#endif
|
||||
ld, -l, bias, lightProj, n
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
else if (index == 1) direct *= PCFCube(shadowMapPoint[1],
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapPointTransparent[1],
|
||||
#endif
|
||||
ld, -l, bias, lightProj, n
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapPointTransparent[1],
|
||||
#endif
|
||||
ld, -l, bias, lightProj, n
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
else if (index == 2) direct *= PCFCube(shadowMapPoint[2],
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapPointTransparent[2],
|
||||
#endif
|
||||
ld, -l, bias, lightProj, n
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapPointTransparent[2],
|
||||
#endif
|
||||
ld, -l, bias, lightProj, n
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
else if (index == 3) direct *= PCFCube(shadowMapPoint[3],
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapPointTransparent[3],
|
||||
#endif
|
||||
ld, -l, bias, lightProj, n
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapPointTransparent[3],
|
||||
#endif
|
||||
ld, -l, bias, lightProj, n
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ uniform vec2 morphDataDim;
|
||||
uniform vec4 morphWeights[8];
|
||||
|
||||
void getMorphedVertex(vec2 uvCoord, inout vec3 A){
|
||||
vec3 totalDelta = vec3(0.0);
|
||||
for(int i = 0; i<8; i++ )
|
||||
{
|
||||
vec4 tempCoordY = vec4( uvCoord.y - (i * 4) * morphDataDim.y,
|
||||
@ -13,21 +14,28 @@ void getMorphedVertex(vec2 uvCoord, inout vec3 A){
|
||||
uvCoord.y - (i * 4 + 3) * morphDataDim.y);
|
||||
|
||||
vec3 morph = texture(morphDataPos, vec2(uvCoord.x, tempCoordY.x)).rgb * morphScaleOffset.x + morphScaleOffset.y;
|
||||
A += morphWeights[i].x * morph;
|
||||
totalDelta += morphWeights[i].x * morph;
|
||||
|
||||
morph = texture(morphDataPos, vec2(uvCoord.x, tempCoordY.y)).rgb * morphScaleOffset.x + morphScaleOffset.y;
|
||||
A += morphWeights[i].y * morph;
|
||||
totalDelta += morphWeights[i].y * morph;
|
||||
|
||||
morph = texture(morphDataPos, vec2(uvCoord.x, tempCoordY.z)).rgb * morphScaleOffset.x + morphScaleOffset.y;
|
||||
A += morphWeights[i].z * morph;
|
||||
totalDelta += morphWeights[i].z * morph;
|
||||
|
||||
morph = texture(morphDataPos, vec2(uvCoord.x, tempCoordY.w)).rgb * morphScaleOffset.x + morphScaleOffset.y;
|
||||
A += morphWeights[i].w * morph;
|
||||
totalDelta += morphWeights[i].w * morph;
|
||||
}
|
||||
//float deltaLength = length(totalDelta);
|
||||
//if (deltaLength > 5.0) {
|
||||
// clamp corrupted data
|
||||
//totalDelta = normalize(totalDelta) * 5.0;
|
||||
//}
|
||||
A += totalDelta;
|
||||
}
|
||||
|
||||
void getMorphedNormal(vec2 uvCoord, vec3 oldNor, inout vec3 morphNor){
|
||||
|
||||
|
||||
vec3 normalDelta = vec3(0.0);
|
||||
for(int i = 0; i<8; i++ )
|
||||
{
|
||||
vec4 tempCoordY = vec4( uvCoord.y - (i * 4) * morphDataDim.y,
|
||||
@ -35,19 +43,11 @@ void getMorphedNormal(vec2 uvCoord, vec3 oldNor, inout vec3 morphNor){
|
||||
uvCoord.y - (i * 4 + 2) * morphDataDim.y,
|
||||
uvCoord.y - (i * 4 + 3) * morphDataDim.y);
|
||||
|
||||
vec3 norm = oldNor + morphWeights[i].x * (texture(morphDataNor, vec2(uvCoord.x, tempCoordY.x)).rgb * 2.0 - 1.0);
|
||||
morphNor += norm;
|
||||
|
||||
norm = oldNor + morphWeights[i].y * (texture(morphDataNor, vec2(uvCoord.x, tempCoordY.y)).rgb * 2.0 - 1.0);
|
||||
morphNor += norm;
|
||||
|
||||
norm = oldNor + morphWeights[i].z * (texture(morphDataNor, vec2(uvCoord.x, tempCoordY.z)).rgb * 2.0 - 1.0);
|
||||
morphNor += norm;
|
||||
|
||||
norm = oldNor + morphWeights[i].w * (texture(morphDataNor, vec2(uvCoord.x, tempCoordY.w)).rgb * 2.0 - 1.0);
|
||||
morphNor += norm;
|
||||
|
||||
normalDelta += morphWeights[i].x * (texture(morphDataNor, vec2(uvCoord.x, tempCoordY.x)).rgb * 2.0 - 1.0);
|
||||
normalDelta += morphWeights[i].y * (texture(morphDataNor, vec2(uvCoord.x, tempCoordY.y)).rgb * 2.0 - 1.0);
|
||||
normalDelta += morphWeights[i].z * (texture(morphDataNor, vec2(uvCoord.x, tempCoordY.z)).rgb * 2.0 - 1.0);
|
||||
normalDelta += morphWeights[i].w * (texture(morphDataNor, vec2(uvCoord.x, tempCoordY.w)).rgb * 2.0 - 1.0);
|
||||
}
|
||||
|
||||
morphNor = normalize(morphNor);
|
||||
|
||||
morphNor = normalize(oldNor + normalDelta);
|
||||
}
|
||||
1
leenkx/Shaders/std/shader_datas.lnx
Normal file
1
leenkx/Shaders/std/shader_datas.lnx
Normal file
@ -0,0 +1 @@
|
||||
<EFBFBD><EFBFBD>shader_datas<EFBFBD><EFBFBD><EFBFBD>name<EFBFBD>copy_pass<EFBFBD>contexts<EFBFBD><EFBFBD><EFBFBD>name<EFBFBD>copy_pass<EFBFBD>constants<EFBFBD><EFBFBD>texture_units<EFBFBD><EFBFBD><EFBFBD>name<EFBFBD>tex<EFBFBD>vertex_elements<EFBFBD><EFBFBD><EFBFBD>data<EFBFBD>float2<EFBFBD>name<EFBFBD>pos<EFBFBD>vertex_shader<EFBFBD>pass.vert<72>fragment_shader<65>pass_copy.frag<61>depth_write¬compare_mode<64>always<79>cull_mode<64>none<6E><65>name<6D>compositor_pass<73>contexts<74><73><EFBFBD>name<6D>compositor_pass<73>constants<74><73>texture_units<74><73><EFBFBD>name<6D>tex<65>vertex_elements<74><73><EFBFBD>data<74>float2<74>name<6D>pos<6F>vertex_shader<65>compositor_pass.vert<72>fragment_shader<65>compositor_pass.frag<61>depth_write¬compare_mode<64>always<79>cull_mode<64>none<6E><65>name<6D>deferred_light<68>contexts<74><73><EFBFBD>name<6D>deferred_light<68>constants<74><73><EFBFBD>type<70>mat4<74>name<6D>invVP<56>link<6E>_inverseViewProjectionMatrix<69><78>type<70>vec3<63>name<6D>eye<79>link<6E>_cameraPosition<6F><6E>type<70>float<61>name<6D>envmapStrength<74>link<6E>_envmapStrength<74><68>type<70>floats<74>name<6D>shirr<72>link<6E>_envmapIrradiance<63><65>type<70>int<6E>name<6D>envmapNumMipmaps<70>link<6E>_envmapNumMipmaps<70><73>type<70>vec2<63>name<6D>cameraProj<6F>link<6E>_cameraPlaneProj<6F><6A>type<70>vec3<63>name<6D>eyeLook<6F>link<6E>_cameraLook<6F><6B>type<70>vec2<63>name<6D>lightProj<6F>link<6E>_lightPlaneProj<6F><6A>type<70>vec3<63>name<6D>pointPos<6F>link<6E>_pointPosition<6F><6E>type<70>vec3<63>name<6D>pointCol<6F>link<6E>_pointColor<6F><72>type<70>float<61>name<6D>pointBias<61>link<6E>_pointShadowsBias<61>texture_units<74><73><EFBFBD>name<6D>gbufferD<72><44>name<6D>gbuffer0<72><30>name<6D>gbuffer1<72><31>name<6D>senvmapBrdf<64>link<6E>$brdf.png<6E><67>name<6D>senvmapRadiance<63>link<6E>_envmapRadiance<63><65>name<6D>shadowMapPoint[0]<5D>vertex_elements<74><73><EFBFBD>data<74>float2<74>name<6D>pos<6F>vertex_shader<65>pass_viewray.vert<72>fragment_shader<65>deferred_light.frag<61>color_attachments<74><73>RGBA64<36>depth_write¬compare_mode<64>always<79>cull_mode<64>none<6E><65>name<6D>water_pass<73>contexts<74><73><EFBFBD>name<6D>water_pass<73>constants<74><73><EFBFBD>type<70>mat4<74>name<6D>invVP<56><50>type<70>vec3<63>name<6D>eye<79>link<6E>_cameraPosition<6F><6E>type<70>float<61>name<6D>time<6D>link<6E>_time<6D><65>type<70>float<61>name<6D>holoOverallStrength<74><68>type<70>vec2<63>name<6D>cameraProj<6F>link<6E>_cameraPlaneProj<6F><6A>type<70>vec3<63>name<6D>eyeLook<6F>link<6E>_cameraLook<6F><6B>type<70>vec3<63>name<6D>ld<6C>link<6E>_lightDirection<6F>texture_units<74><73><EFBFBD>name<6D>tex<65><78>name<6D>gbufferD<72><44>name<6D>gbuffer0<72>vertex_elements<74><73><EFBFBD>data<74>float2<74>name<6D>pos<6F>vertex_shader<65>pass_viewray.vert<72>fragment_shader<65>water_pass.frag<61>depth_write¬compare_mode<64>always<79>cull_mode<64>none<6E>blend_source<63>source_alpha<68>blend_destination<6F>inverse_source_alpha<68>blend_operation<6F>add<64>alpha_blend_source<63>blend_one<6E>alpha_blend_destination<6F>blend_one<6E>alpha_blend_operation<6F>add<64><64>contexts<74><73><EFBFBD>name<6D>World_World<6C>depth_write¬compare_mode<64>less<73>cull_mode<64>clockwise<73>vertex_elements<74><73><EFBFBD>name<6D>pos<6F>data<74>float3<74><33>name<6D>nor<6F>data<74>float3<74>color_attachments<74><73>_HDR<44>texture_units<74><73>constants<74><73><EFBFBD>name<6D>SMVP<56>type<70>mat4<74>link<6E>_skydomeMatrix<69>vertex_shader<65>World_World.vert<72>fragment_shader<65>World_World.frag<61>name<6D>World_World
|
||||
@ -23,6 +23,59 @@ uniform vec2 smSizeUniform;
|
||||
#endif
|
||||
|
||||
#ifdef _ShadowMapAtlas
|
||||
// PCF that clamps samples to tile boundaries to prevent bleeding
|
||||
vec3 PCFTileAware(sampler2DShadow shadowMap,
|
||||
#ifdef _ShadowMapTransparent
|
||||
sampler2D shadowMapTransparent,
|
||||
#endif
|
||||
const vec2 uv, const float compare, const vec2 smSize,
|
||||
const vec2 tileMin, const vec2 tileMax
|
||||
#ifdef _ShadowMapTransparent
|
||||
, const bool transparent
|
||||
#endif
|
||||
) {
|
||||
vec3 result = vec3(0.0);
|
||||
vec2 offset;
|
||||
|
||||
offset = vec2(-1.0, -1.0) / smSize;
|
||||
result.x = texture(shadowMap, vec3(clamp(uv + offset, tileMin, tileMax), compare));
|
||||
|
||||
offset = vec2(-1.0, 0.0) / smSize;
|
||||
result.x += texture(shadowMap, vec3(clamp(uv + offset, tileMin, tileMax), compare));
|
||||
|
||||
offset = vec2(-1.0, 1.0) / smSize;
|
||||
result.x += texture(shadowMap, vec3(clamp(uv + offset, tileMin, tileMax), compare));
|
||||
|
||||
offset = vec2(0.0, -1.0) / smSize;
|
||||
result.x += texture(shadowMap, vec3(clamp(uv + offset, tileMin, tileMax), compare));
|
||||
|
||||
result.x += texture(shadowMap, vec3(uv, compare));
|
||||
|
||||
offset = vec2(0.0, 1.0) / smSize;
|
||||
result.x += texture(shadowMap, vec3(clamp(uv + offset, tileMin, tileMax), compare));
|
||||
|
||||
offset = vec2(1.0, -1.0) / smSize;
|
||||
result.x += texture(shadowMap, vec3(clamp(uv + offset, tileMin, tileMax), compare));
|
||||
|
||||
offset = vec2(1.0, 0.0) / smSize;
|
||||
result.x += texture(shadowMap, vec3(clamp(uv + offset, tileMin, tileMax), compare));
|
||||
|
||||
offset = vec2(1.0, 1.0) / smSize;
|
||||
result.x += texture(shadowMap, vec3(clamp(uv + offset, tileMin, tileMax), compare));
|
||||
|
||||
result = result.xxx / 9.0;
|
||||
|
||||
#ifdef _ShadowMapTransparent
|
||||
if (transparent == false) {
|
||||
vec4 shadowmap_transparent = texture(shadowMapTransparent, uv);
|
||||
if (shadowmap_transparent.a < compare)
|
||||
result *= shadowmap_transparent.rgb;
|
||||
}
|
||||
#endif
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// https://www.khronos.org/registry/OpenGL/specs/gl/glspec20.pdf // p:168
|
||||
// https://www.gamedev.net/forums/topic/687535-implementing-a-cube-map-lookup-function/5337472/
|
||||
vec2 sampleCube(vec3 dir, out int faceIndex) {
|
||||
@ -251,7 +304,7 @@ vec3 PCFFakeCube(sampler2DShadow shadowMap,
|
||||
#endif
|
||||
|
||||
if (any(lessThan(uvtiled, vec2(0.0))) || any(greaterThan(uvtiled, vec2(1.0)))) {
|
||||
return vec3(1.0); // Handle edge cases by returning full light
|
||||
return vec3(1.0);
|
||||
}
|
||||
|
||||
vec3 result = vec3(0.0);
|
||||
@ -334,30 +387,55 @@ vec3 PCFFakeCube(sampler2DShadow shadowMap,
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _ShadowMapAtlas
|
||||
uniform vec4 tileBounds;
|
||||
#endif
|
||||
|
||||
vec3 shadowTest(sampler2DShadow shadowMap,
|
||||
#ifdef _ShadowMapTransparent
|
||||
sampler2D shadowMapTransparent,
|
||||
#endif
|
||||
const vec3 lPos, const float shadowsBias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, const bool transparent
|
||||
#endif
|
||||
) {
|
||||
#ifdef _ShadowMapTransparent
|
||||
sampler2D shadowMapTransparent,
|
||||
#endif
|
||||
const vec3 lPos, const float shadowsBias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, const bool transparent
|
||||
#endif
|
||||
) {
|
||||
if (lPos.x < 0.0 || lPos.y < 0.0 || lPos.x > 1.0 || lPos.y > 1.0) return vec3(1.0);
|
||||
|
||||
#ifdef _ShadowMapAtlas
|
||||
// use tile PCF
|
||||
#ifdef _SMSizeUniform
|
||||
vec2 smSizeAtlas = smSizeUniform;
|
||||
#else
|
||||
const vec2 smSizeAtlas = shadowmapSize;
|
||||
#endif
|
||||
return PCFTileAware(shadowMap,
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapTransparent,
|
||||
#endif
|
||||
lPos.xy, lPos.z - shadowsBias, smSizeAtlas,
|
||||
tileBounds.xy, tileBounds.zw
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#else
|
||||
// use PCF for non-atlas shadows
|
||||
#ifdef _SMSizeUniform
|
||||
vec2 smSize = smSizeUniform;
|
||||
#else
|
||||
const vec2 smSize = shadowmapSize;
|
||||
#endif
|
||||
if (lPos.x < 0.0 || lPos.y < 0.0 || lPos.x > 1.0 || lPos.y > 1.0) return vec3(1.0);
|
||||
return PCF(shadowMap,
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapTransparent,
|
||||
#endif
|
||||
lPos.xy, lPos.z - shadowsBias, smSize
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapTransparent,
|
||||
#endif
|
||||
lPos.xy, lPos.z - shadowsBias, smSize
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef _CSM
|
||||
@ -390,14 +468,14 @@ mat4 getCascadeMat(const float d, out int casi, out int casIndex) {
|
||||
}
|
||||
|
||||
vec3 shadowTestCascade(sampler2DShadow shadowMap,
|
||||
#ifdef _ShadowMapTransparent
|
||||
sampler2D shadowMapTransparent,
|
||||
#endif
|
||||
const vec3 eye, const vec3 p, const float shadowsBias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, const bool transparent
|
||||
#endif
|
||||
) {
|
||||
#ifdef _ShadowMapTransparent
|
||||
sampler2D shadowMapTransparent,
|
||||
#endif
|
||||
const vec3 eye, const vec3 p, const float shadowsBias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, const bool transparent
|
||||
#endif
|
||||
) {
|
||||
#ifdef _SMSizeUniform
|
||||
vec2 smSize = smSizeUniform;
|
||||
#else
|
||||
@ -413,14 +491,14 @@ vec3 shadowTestCascade(sampler2DShadow shadowMap,
|
||||
|
||||
vec3 visibility = vec3(1.0);
|
||||
if (lPos.w > 0.0) visibility = PCF(shadowMap,
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapTransparent,
|
||||
#endif
|
||||
lPos.xy, lPos.z - shadowsBias, smSize
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapTransparent,
|
||||
#endif
|
||||
lPos.xy, lPos.z - shadowsBias, smSize
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
|
||||
// Blend cascade
|
||||
// https://github.com/TheRealMJP/Shadows
|
||||
@ -439,15 +517,16 @@ vec3 shadowTestCascade(sampler2DShadow shadowMap,
|
||||
vec4 lPos2 = LWVP2 * vec4(p, 1.0);
|
||||
lPos2.xyz /= lPos2.w;
|
||||
vec3 visibility2 = vec3(1.0);
|
||||
// use lPos2 coordinates for second cascade, not lPos
|
||||
if (lPos2.w > 0.0) visibility2 = PCF(shadowMap,
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapTransparent,
|
||||
#endif
|
||||
lPos.xy, lPos.z - shadowsBias, smSize
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapTransparent,
|
||||
#endif
|
||||
lPos2.xy, lPos2.z - shadowsBias, smSize
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
|
||||
float lerpAmt = smoothstep(0.0, blendThres, splitDist);
|
||||
return mix(visibility2, visibility, lerpAmt);
|
||||
|
||||
56
leenkx/Shaders/std/voxels_constants.glsl
Normal file
56
leenkx/Shaders/std/voxels_constants.glsl
Normal file
@ -0,0 +1,56 @@
|
||||
/*
|
||||
Copyright (c) 2024 Turánszki János
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
const int DIFFUSE_CONE_COUNT = 16;
|
||||
const float DIFFUSE_CONE_APERTURE = radians(45.0);
|
||||
|
||||
const vec3 DIFFUSE_CONE_DIRECTIONS[16] = {
|
||||
vec3(0.0000, 0.0000, 1.0000), // Central direction
|
||||
vec3(0.3827, 0.0000, 0.9239), // Ring 1
|
||||
vec3(-0.3827, 0.0000, 0.9239),
|
||||
vec3(0.0000, 0.3827, 0.9239),
|
||||
vec3(0.0000, -0.3827, 0.9239),
|
||||
vec3(0.2706, 0.2706, 0.9239), // Ring 2
|
||||
vec3(-0.2706, 0.2706, 0.9239),
|
||||
vec3(0.2706, -0.2706, 0.9239),
|
||||
vec3(-0.2706, -0.2706, 0.9239),
|
||||
vec3(0.1802, 0.3604, 0.9239), // Ring 3
|
||||
vec3(-0.1802, 0.3604, 0.9239),
|
||||
vec3(0.1802, -0.3604, 0.9239),
|
||||
vec3(-0.1802, -0.3604, 0.9239),
|
||||
vec3(0.3604, 0.1802, 0.9239),
|
||||
vec3(-0.3604, 0.1802, 0.9239),
|
||||
vec3(0.3604, -0.1802, 0.9239)
|
||||
};
|
||||
|
||||
|
||||
const float BayerMatrix8[8][8] =
|
||||
{
|
||||
{ 1.0 / 65.0, 49.0 / 65.0, 13.0 / 65.0, 61.0 / 65.0, 4.0 / 65.0, 52.0 / 65.0, 16.0 / 65.0, 64.0 / 65.0 },
|
||||
{ 33.0 / 65.0, 17.0 / 65.0, 45.0 / 65.0, 29.0 / 65.0, 36.0 / 65.0, 20.0 / 65.0, 48.0 / 65.0, 32.0 / 65.0 },
|
||||
{ 9.0 / 65.0, 57.0 / 65.0, 5.0 / 65.0, 53.0 / 65.0, 12.0 / 65.0, 60.0 / 65.0, 8.0 / 65.0, 56.0 / 65.0 },
|
||||
{ 41.0 / 65.0, 25.0 / 65.0, 37.0 / 65.0, 21.0 / 65.0, 44.0 / 65.0, 28.0 / 65.0, 40.0 / 65.0, 24.0 / 65.0 },
|
||||
{ 3.0 / 65.0, 51.0 / 65.0, 15.0 / 65.0, 63.0 / 65.0, 2.0 / 65.0, 50.0 / 65.0, 14.0 / 65.0, 62.0 / 65.0 },
|
||||
{ 35.0 / 65.0, 19.0 / 65.0, 47.0 / 65.0, 31.0 / 65.0, 34.0 / 65.0, 18.0 / 65.0, 46.0 / 65.0, 30.0 / 65.0 },
|
||||
{ 11.0 / 65.0, 59.0 / 65.0, 7.0 / 65.0, 55.0 / 65.0, 10.0 / 65.0, 58.0 / 65.0, 6.0 / 65.0, 54.0 / 65.0 },
|
||||
{ 43.0 / 65.0, 27.0 / 65.0, 39.0 / 65.0, 23.0 / 65.0, 42.0 / 65.0, 26.0 / 65.0, 38.0 / 65.0, 22.0 / 65.0 }
|
||||
};
|
||||
@ -13,32 +13,80 @@ out vec4 fragColor;
|
||||
|
||||
const float SMAA_REPROJECTION_WEIGHT_SCALE = 30.0;
|
||||
|
||||
// TODO: Move to a utility
|
||||
bool isInvalidValue(float v) {
|
||||
return (v != v) || (v > 65000.0) || (v < -65000.0);
|
||||
}
|
||||
|
||||
bool hasInvalidValues(vec3 v) {
|
||||
return isInvalidValue(v.x) || isInvalidValue(v.y) || isInvalidValue(v.z);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec4 current = textureLod(tex, texCoord, 0.0);
|
||||
|
||||
current.rgb = clamp(current.rgb, vec3(0.0), vec3(10.0));
|
||||
current.a = clamp(current.a, 0.0, 1.0);
|
||||
if (hasInvalidValues(current.rgb)) {
|
||||
current = vec4(0.0, 0.0, 0.0, 1.0);
|
||||
}
|
||||
|
||||
#ifdef _Veloc
|
||||
// Velocity is assumed to be calculated for motion blur, so we need to inverse it for reprojection
|
||||
vec2 velocity = -textureLod(sveloc, texCoord, 0.0).rg;
|
||||
|
||||
velocity = clamp(velocity, vec2(-1.0), vec2(1.0));
|
||||
if (isInvalidValue(velocity.x) || isInvalidValue(velocity.y)) {
|
||||
velocity = vec2(0.0);
|
||||
}
|
||||
|
||||
#ifdef _InvY
|
||||
velocity.y = -velocity.y;
|
||||
#endif
|
||||
|
||||
// Reproject current coordinates and fetch previous pixel
|
||||
vec4 previous = textureLod(tex2, texCoord + velocity, 0.0);
|
||||
vec2 prevCoord = texCoord + velocity;
|
||||
prevCoord = clamp(prevCoord, vec2(0.0), vec2(1.0));
|
||||
vec4 previous = textureLod(tex2, prevCoord, 0.0);
|
||||
|
||||
previous.rgb = clamp(previous.rgb, vec3(0.0), vec3(10.0));
|
||||
previous.a = clamp(previous.a, 0.0, 1.0);
|
||||
if (hasInvalidValues(previous.rgb)) {
|
||||
previous = current; // Fallback to current frame if previous is corrupted
|
||||
}
|
||||
|
||||
// Attenuate the previous pixel if the velocity is different
|
||||
#ifdef _SMAA
|
||||
float delta = abs(current.a * current.a - previous.a * previous.a) / 5.0;
|
||||
float currentAlpha = clamp(current.a, 0.0, 1.0);
|
||||
float previousAlpha = clamp(previous.a, 0.0, 1.0);
|
||||
float delta = abs(currentAlpha * currentAlpha - previousAlpha * previousAlpha) / 5.0;
|
||||
delta = clamp(delta, 0.0, 1.0); // Ensure delta is in valid range
|
||||
#else
|
||||
const float delta = 0.0;
|
||||
#endif
|
||||
float weight = 0.5 * clamp(1.0 - sqrt(delta) * SMAA_REPROJECTION_WEIGHT_SCALE, 0.0, 1.0);
|
||||
|
||||
float weight = 0.5 * clamp(1.0 - sqrt(max(delta, 0.0)) * SMAA_REPROJECTION_WEIGHT_SCALE, 0.0, 1.0);
|
||||
|
||||
// Blend the pixels according to the calculated weight:
|
||||
fragColor = vec4(mix(current.rgb, previous.rgb, weight), 1.0);
|
||||
vec3 blended = mix(current.rgb, previous.rgb, weight);
|
||||
blended = clamp(blended, vec3(0.0), vec3(10.0));
|
||||
if (hasInvalidValues(blended)) {
|
||||
blended = current.rgb;
|
||||
}
|
||||
|
||||
fragColor = vec4(blended, 1.0);
|
||||
#else
|
||||
vec4 previous = textureLod(tex2, texCoord, 0.0);
|
||||
fragColor = vec4(mix(current.rgb, previous.rgb, 0.5), 1.0);
|
||||
|
||||
previous.rgb = clamp(previous.rgb, vec3(0.0), vec3(10.0));
|
||||
if (hasInvalidValues(previous.rgb)) {
|
||||
previous.rgb = current.rgb;
|
||||
}
|
||||
|
||||
vec3 blended = mix(current.rgb, previous.rgb, 0.5);
|
||||
blended = clamp(blended, vec3(0.0), vec3(10.0));
|
||||
if (hasInvalidValues(blended)) {
|
||||
blended = current.rgb;
|
||||
}
|
||||
|
||||
fragColor = vec4(blended, 1.0);
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -0,0 +1,79 @@
|
||||
/*
|
||||
Copyright (c) 2024 Turánszki János
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#version 450
|
||||
|
||||
layout (local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
|
||||
|
||||
#include "compiled.inc"
|
||||
#include "std/math.glsl"
|
||||
#include "std/gbuffer.glsl"
|
||||
#include "std/imageatomic.glsl"
|
||||
#include "std/conetrace.glsl"
|
||||
|
||||
uniform sampler2D gbufferD;
|
||||
uniform sampler2D gbuffer0;
|
||||
uniform sampler3D voxels;
|
||||
uniform sampler3D voxelsSDF;
|
||||
uniform sampler2D gbuffer_refraction;
|
||||
uniform layout(rgba8) image2D voxels_refraction;
|
||||
|
||||
uniform float clipmaps[voxelgiClipmapCount * 10];
|
||||
uniform mat4 InvVP;
|
||||
uniform vec2 cameraProj;
|
||||
uniform vec3 eye;
|
||||
uniform vec3 eyeLook;
|
||||
uniform vec2 postprocess_resolution;
|
||||
|
||||
void main() {
|
||||
const vec2 pixel = gl_GlobalInvocationID.xy;
|
||||
vec2 uv = (pixel + 0.5) / postprocess_resolution;
|
||||
#ifdef _InvY
|
||||
uv.y = 1.0 - uv.y
|
||||
#endif
|
||||
|
||||
float depth = textureLod(gbufferD, uv, 0.0).r * 2.0 - 1.0;
|
||||
if (depth == 0) return;
|
||||
|
||||
vec2 ior_opac = textureLod(gbuffer_refraction, uv, 0.0).xy;
|
||||
|
||||
float x = uv.x * 2 - 1;
|
||||
float y = uv.y * 2 - 1;
|
||||
vec4 v = vec4(x, y, 1.0, 1.0);
|
||||
v = vec4(InvVP * v);
|
||||
v.xyz /= v.w;
|
||||
vec3 viewRay = v.xyz - eye;
|
||||
|
||||
vec3 P = getPos(eye, eyeLook, normalize(viewRay), depth, cameraProj);
|
||||
|
||||
vec4 g0 = textureLod(gbuffer0, uv, 0.0);
|
||||
vec3 n;
|
||||
n.z = 1.0 - abs(g0.x) - abs(g0.y);
|
||||
n.xy = n.z >= 0.0 ? g0.xy : octahedronWrap(g0.xy);
|
||||
n = normalize(n);
|
||||
|
||||
vec3 color = vec3(0.0);
|
||||
if(ior_opac.y < 1.0)
|
||||
color = traceRefraction(P, n, voxels, voxelsSDF, normalize(eye - P), ior_opac.x, g0.b, clipmaps, pixel).rgb;
|
||||
|
||||
imageStore(voxels_refraction, ivec2(pixel), vec4(color, 1.0));
|
||||
}
|
||||
@ -0,0 +1,75 @@
|
||||
/*
|
||||
Copyright (c) 2024 Turánszki János
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#version 450
|
||||
|
||||
layout (local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
|
||||
|
||||
#include "compiled.inc"
|
||||
#include "std/math.glsl"
|
||||
#include "std/gbuffer.glsl"
|
||||
#include "std/imageatomic.glsl"
|
||||
#include "std/conetrace.glsl"
|
||||
|
||||
uniform sampler3D voxels;
|
||||
uniform sampler3D voxelsSDF;
|
||||
uniform sampler2D gbufferD;
|
||||
uniform sampler2D gbuffer0;
|
||||
uniform layout(r16) image2D voxels_shadows;
|
||||
|
||||
uniform float clipmaps[voxelgiClipmapCount * 10];
|
||||
uniform mat4 InvVP;
|
||||
uniform vec2 cameraProj;
|
||||
uniform vec3 eye;
|
||||
uniform vec3 eyeLook;
|
||||
uniform vec2 postprocess_resolution;
|
||||
uniform vec3 lPos;
|
||||
|
||||
void main() {
|
||||
const vec2 pixel = gl_GlobalInvocationID.xy;
|
||||
vec2 uv = (pixel + 0.5) / postprocess_resolution;
|
||||
#ifdef _InvY
|
||||
uv.y = 1.0 - uv.y;
|
||||
#endif
|
||||
|
||||
float depth = textureLod(gbufferD, uv, 0.0).r * 2.0 - 1.0;
|
||||
if (depth == 0) return;
|
||||
|
||||
float x = uv.x * 2 - 1;
|
||||
float y = uv.y * 2 - 1;
|
||||
vec4 v = vec4(x, y, 1.0, 1.0);
|
||||
v = vec4(InvVP * v);
|
||||
v.xyz /= v.w;
|
||||
vec3 viewRay = v.xyz - eye;
|
||||
|
||||
vec3 P = getPos(eye, eyeLook, normalize(viewRay), depth, cameraProj);
|
||||
|
||||
vec4 g0 = textureLod(gbuffer0, uv, 0.0);
|
||||
vec3 n;
|
||||
n.z = 1.0 - abs(g0.x) - abs(g0.y);
|
||||
n.xy = n.z >= 0.0 ? g0.xy : octahedronWrap(g0.xy);
|
||||
n = normalize(n);
|
||||
|
||||
float occ = 1.0 - traceShadow(P, n, voxels, voxelsSDF, normalize(lPos - P), clipmaps, pixel);
|
||||
|
||||
imageStore(voxels_shadows, ivec2(pixel), vec4(occ));
|
||||
}
|
||||
@ -75,17 +75,16 @@ vec4 binarySearch(vec3 dir) {
|
||||
}
|
||||
|
||||
vec4 rayCast(vec3 dir) {
|
||||
float ddepth;
|
||||
dir *= ss_refractionRayStep;
|
||||
for (int i = 0; i < maxSteps; i++) {
|
||||
hitCoord += dir;
|
||||
ddepth = getDeltaDepth(hitCoord);
|
||||
if (ddepth > 0.0)
|
||||
return binarySearch(dir);
|
||||
}
|
||||
// No hit — fallback to projecting the ray to UV space
|
||||
vec2 fallbackUV = getProjectedCoord(hitCoord);
|
||||
return vec4(fallbackUV, 0.0, 0.5); // We set .w lower to indicate fallback
|
||||
#ifdef _CPostprocess
|
||||
dir *= PPComp9.x;
|
||||
#else
|
||||
dir *= ssrRayStep;
|
||||
#endif
|
||||
for (int i = 0; i < maxSteps; i++) {
|
||||
hitCoord += dir;
|
||||
if (getDeltaDepth(hitCoord) > 0.0) return binarySearch(dir);
|
||||
}
|
||||
return vec4(0.0);
|
||||
}
|
||||
#endif //SSR
|
||||
|
||||
|
||||
@ -53,6 +53,21 @@ class App {
|
||||
static function update() {
|
||||
if (Scene.active == null || !Scene.active.ready) return;
|
||||
|
||||
// VR is handling it so we prevent double updates
|
||||
// TODO: avoid js.Syntax
|
||||
#if (kha_webgl && lnx_vr)
|
||||
var vrActive = false;
|
||||
js.Syntax.code("
|
||||
if (typeof kha !== 'undefined' && kha.vr && kha.vr.VrInterface) {
|
||||
const vr = kha.vr.VrInterface.instance;
|
||||
if (vr && vr.IsPresenting && vr.IsPresenting()) {
|
||||
{0} = true;
|
||||
}
|
||||
}
|
||||
", vrActive);
|
||||
if (vrActive) return;
|
||||
#end
|
||||
|
||||
iron.system.Time.update();
|
||||
|
||||
if (lastw == -1) {
|
||||
@ -138,6 +153,21 @@ class App {
|
||||
traitInits.splice(0, traitInits.length);
|
||||
}
|
||||
|
||||
// skip for XR callback to handle rendering
|
||||
// TODO: avoid js Syntax
|
||||
#if (kha_webgl && lnx_vr)
|
||||
var vrActive = false;
|
||||
js.Syntax.code("
|
||||
if (typeof kha !== 'undefined' && kha.vr && kha.vr.VrInterface) {
|
||||
const vr = kha.vr.VrInterface.instance;
|
||||
if (vr && vr.IsPresenting && vr.IsPresenting()) {
|
||||
{0} = true;
|
||||
}
|
||||
}
|
||||
", vrActive);
|
||||
|
||||
if (!vrActive) {
|
||||
#end
|
||||
Scene.active.renderFrame(frame.g4);
|
||||
|
||||
for (f in traitRenders) {
|
||||
@ -146,6 +176,10 @@ class App {
|
||||
|
||||
render2D(frame);
|
||||
|
||||
#if (kha_webgl && lnx_vr)
|
||||
}
|
||||
#end
|
||||
|
||||
#if lnx_debug
|
||||
renderPathTime = kha.Scheduler.realTime() - startTime;
|
||||
#end
|
||||
|
||||
@ -18,10 +18,44 @@ import iron.object.LightObject;
|
||||
import iron.object.MeshObject;
|
||||
import iron.object.Uniforms;
|
||||
import iron.object.Clipmap;
|
||||
#if lnx_vr
|
||||
import iron.math.Vec4;
|
||||
import iron.math.Mat4;
|
||||
import iron.math.Quat;
|
||||
#end
|
||||
|
||||
class RenderPath {
|
||||
|
||||
public static var active: RenderPath;
|
||||
#if lnx_vr
|
||||
static var vrSimulateMode: Bool = false;
|
||||
static var vrCameraOffsetSet:Bool = false;
|
||||
static var vrCameraOffset:Vec4 = new Vec4();
|
||||
static var wasVRPresenting:Bool = false;
|
||||
public static var vrCalibrationPosition:Vec4 = null;
|
||||
public static var vrCalibrationRotation:iron.math.Quat = null;
|
||||
public static var vrCalibrationSaved:Bool = false;
|
||||
|
||||
public static var vrCenterCameraWorld:Mat4 = null;
|
||||
|
||||
static var vrOriginalSuperSample:Float = -1.0;
|
||||
public static inline function isVRPresenting(): Bool {
|
||||
#if (kha_webgl && lnx_vr)
|
||||
return kha.vr.VrInterface.instance != null && kha.vr.VrInterface.instance.IsPresenting();
|
||||
#else
|
||||
return false;
|
||||
#end
|
||||
}
|
||||
|
||||
public static inline function isVRSimulateMode(): Bool {
|
||||
return vrSimulateMode;
|
||||
}
|
||||
|
||||
// TODO: done remove safely
|
||||
public static inline function debugLog(msg: String, once: Bool = true): Void {
|
||||
return;
|
||||
}
|
||||
#end
|
||||
|
||||
public var frameScissor = false;
|
||||
public var frameScissorX = 0;
|
||||
@ -43,9 +77,15 @@ class RenderPath {
|
||||
public var isProbe = false;
|
||||
public var currentG: Graphics = null;
|
||||
public var frameG: Graphics;
|
||||
#if lnx_vr
|
||||
var beginCalled = false;
|
||||
var scissorSet = false;
|
||||
var viewportScaled = false;
|
||||
var renderToXRFramebuffer = false;
|
||||
#end
|
||||
public var drawOrder = DrawOrder.Distance;
|
||||
public var paused = false;
|
||||
public var ready(get, null): Bool;
|
||||
public var ready(get, never): Bool;
|
||||
function get_ready(): Bool { return loading == 0; }
|
||||
public var commands: Void->Void = null;
|
||||
public var setupDepthTexture: Void->Void = null;
|
||||
@ -123,9 +163,93 @@ class RenderPath {
|
||||
public function renderFrame(g: Graphics) {
|
||||
if (!ready || paused || iron.App.w() == 0 || iron.App.h() == 0) return;
|
||||
|
||||
if (lastW > 0 && (lastW != iron.App.w() || lastH != iron.App.h())) resize();
|
||||
lastW = iron.App.w();
|
||||
lastH = iron.App.h();
|
||||
var appW = iron.App.w();
|
||||
var appH = iron.App.h();
|
||||
|
||||
// use native XR framebuffer dimensions
|
||||
#if (kha_webgl && lnx_vr)
|
||||
if (kha.vr.VrInterface.instance != null) {
|
||||
var vr = kha.vr.VrInterface.instance;
|
||||
var isPresenting = vr != null && vr.IsPresenting();
|
||||
|
||||
// save/restore camera position between modes
|
||||
if (!wasVRPresenting && isPresenting) {
|
||||
if (Scene.active != null && Scene.active.camera != null) {
|
||||
if (vrCalibrationPosition == null) vrCalibrationPosition = new Vec4();
|
||||
if (vrCalibrationRotation == null) vrCalibrationRotation = new Quat();
|
||||
|
||||
vrCalibrationPosition.setFrom(Scene.active.camera.transform.loc);
|
||||
vrCalibrationRotation.setFrom(Scene.active.camera.transform.rot);
|
||||
vrCalibrationSaved = true;
|
||||
}
|
||||
|
||||
// save original super sampling for later
|
||||
vrOriginalSuperSample = leenkx.renderpath.Inc.superSample;
|
||||
|
||||
// compositeToXR function handles blitting to VR framebuffer
|
||||
|
||||
var xrVr: kha.js.vr.VrInterface = cast vr;
|
||||
if (xrVr.xrGLLayer != null) {
|
||||
var vrWidth = untyped xrVr.xrGLLayer.framebufferWidth;
|
||||
var vrHeight = untyped xrVr.xrGLLayer.framebufferHeight;
|
||||
}
|
||||
}
|
||||
else if (wasVRPresenting && !isPresenting) {
|
||||
// reset VR frame time before anything else
|
||||
#if (kha_webgl && lnx_vr)
|
||||
iron.system.Time.vrFrameTime = -1.0;
|
||||
#end
|
||||
|
||||
if (vrCalibrationSaved && Scene.active != null && Scene.active.camera != null) {
|
||||
Scene.active.camera.transform.loc.setFrom(vrCalibrationPosition);
|
||||
Scene.active.camera.transform.rot.setFrom(vrCalibrationRotation);
|
||||
Scene.active.camera.buildMatrix();
|
||||
Scene.active.camera.buildProjection();
|
||||
}
|
||||
|
||||
// restore original super sampling from simulate mode
|
||||
if (vrOriginalSuperSample >= 0.0) {
|
||||
leenkx.renderpath.Inc.superSample = vrOriginalSuperSample;
|
||||
|
||||
for (rt in renderTargets) {
|
||||
if (rt.raw.width == 0 && rt.raw.scale != null) {
|
||||
rt.raw.scale = vrOriginalSuperSample;
|
||||
}
|
||||
}
|
||||
resize();
|
||||
vrOriginalSuperSample = -1.0;
|
||||
}
|
||||
|
||||
// reset offset for next session
|
||||
vrCameraOffsetSet = false;
|
||||
vrCameraOffset = null;
|
||||
}
|
||||
wasVRPresenting = isPresenting;
|
||||
|
||||
if (isPresenting) {
|
||||
// TODO: re-investigate using super sampling to avoid pixelation in simulate mode while giving max quality in headset
|
||||
if (vrOriginalSuperSample >= 0.0 && leenkx.renderpath.Inc.superSample != 4.0) {
|
||||
leenkx.renderpath.Inc.superSample = 4.0;
|
||||
for (rt in renderTargets) {
|
||||
if (rt.raw.width == 0 && rt.raw.scale != null) {
|
||||
rt.raw.scale = 4.0;
|
||||
}
|
||||
}
|
||||
resize();
|
||||
}
|
||||
|
||||
var xrVr: kha.js.vr.VrInterface = cast vr;
|
||||
if (xrVr.xrGLLayer != null) {
|
||||
appW = xrVr.xrGLLayer.framebufferWidth;
|
||||
appH = xrVr.xrGLLayer.framebufferHeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
||||
if (lastW > 0 && (lastW != appW || lastH != appH)) resize();
|
||||
lastW = appW;
|
||||
lastH = appH;
|
||||
|
||||
frameTime = Time.time() - lastFrameTime;
|
||||
lastFrameTime = Time.time();
|
||||
@ -191,7 +315,9 @@ class RenderPath {
|
||||
}
|
||||
light = Scene.active.lights[0];
|
||||
|
||||
commands();
|
||||
if (commands != null) {
|
||||
commands();
|
||||
}
|
||||
|
||||
if (!isProbe) frame++;
|
||||
}
|
||||
@ -207,13 +333,13 @@ class RenderPath {
|
||||
begin(frameG, Scene.active.camera.currentFace);
|
||||
}
|
||||
else { // Screen, planar probe
|
||||
currentW = iron.App.w();
|
||||
currentH = iron.App.h();
|
||||
currentW = kha.System.windowWidth();
|
||||
currentH = kha.System.windowHeight();
|
||||
if (frameScissor) setFrameScissor();
|
||||
begin(frameG);
|
||||
if (!isProbe) {
|
||||
setCurrentViewport(iron.App.w(), iron.App.h());
|
||||
setCurrentScissor(iron.App.w(), iron.App.h());
|
||||
setCurrentViewport(kha.System.windowWidth(), kha.System.windowHeight());
|
||||
setCurrentScissor(kha.System.windowWidth(), kha.System.windowHeight());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -258,16 +384,42 @@ class RenderPath {
|
||||
if (currentG != null) end();
|
||||
currentG = g;
|
||||
additionalTargets = additionalRenderTargets;
|
||||
face >= 0 ? g.beginFace(face) : g.begin(additionalRenderTargets);
|
||||
|
||||
// we still bind but skip begin() when explicitly rendering to XR framebuffer (renderToXRFramebuffer flag)
|
||||
#if lnx_vr
|
||||
if (!renderToXRFramebuffer) {
|
||||
face >= 0 ? g.beginFace(face) : g.begin(additionalRenderTargets);
|
||||
beginCalled = true;
|
||||
} else {
|
||||
// XR framebuffer is already bound by VrInterface so we dont rebind
|
||||
beginCalled = false;
|
||||
}
|
||||
#else
|
||||
face >= 0 ? g.beginFace(face) : g.begin(additionalRenderTargets);
|
||||
#end
|
||||
}
|
||||
|
||||
inline function end() {
|
||||
if (currentG == null) return;
|
||||
if (scissorSet) {
|
||||
currentG.disableScissor();
|
||||
scissorSet = false;
|
||||
}
|
||||
|
||||
#if lnx_vr
|
||||
if (beginCalled) {
|
||||
currentG.end();
|
||||
beginCalled = false;
|
||||
}
|
||||
// persist for rendering both eyes
|
||||
if (!isVRPresenting()) {
|
||||
currentG = null;
|
||||
additionalTargets = null;
|
||||
}
|
||||
#else
|
||||
currentG.end();
|
||||
currentG = null;
|
||||
#end
|
||||
bindParams = null;
|
||||
}
|
||||
|
||||
@ -341,8 +493,8 @@ class RenderPath {
|
||||
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; });
|
||||
return a.data.name >= b.data.name ? 1 : -1;
|
||||
});
|
||||
}
|
||||
|
||||
public function drawMeshes(context: String) {
|
||||
@ -521,44 +673,208 @@ class RenderPath {
|
||||
return Reflect.field(kha.Shaders, handle + "_comp");
|
||||
}
|
||||
|
||||
#if lnx_vr
|
||||
// blits to each eyes viewport in the XR framebuffer.
|
||||
public function compositeToXR(sourceTarget: String) {
|
||||
#if (kha_webgl && lnx_vr)
|
||||
|
||||
var vr: kha.js.vr.VrInterface = cast kha.vr.VrInterface.instance;
|
||||
if (vr == null || vr._glContext == null || vr.xrGLLayer == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var gl: js.html.webgl.WebGL2RenderingContext = cast vr._glContext;
|
||||
var source = renderTargets.get(sourceTarget);
|
||||
if (source == null) {
|
||||
return;
|
||||
}
|
||||
var sourceFB: js.html.webgl.Framebuffer = untyped source.image.g4.renderTargetFrameBuffer;
|
||||
if (sourceFB == null) {
|
||||
return;
|
||||
}
|
||||
// trace('Framebuffer OK');
|
||||
|
||||
renderToXRFramebuffer = true;
|
||||
gl.bindFramebuffer(js.html.webgl.WebGL2RenderingContext.DRAW_FRAMEBUFFER, vr.xrGLLayer.framebuffer);
|
||||
gl.bindFramebuffer(js.html.webgl.WebGL2RenderingContext.READ_FRAMEBUFFER, sourceFB);
|
||||
|
||||
var readStatus = gl.checkFramebufferStatus(js.html.webgl.WebGL2RenderingContext.READ_FRAMEBUFFER);
|
||||
var drawStatus = gl.checkFramebufferStatus(js.html.webgl.WebGL2RenderingContext.DRAW_FRAMEBUFFER);
|
||||
if (readStatus != js.html.webgl.WebGL2RenderingContext.FRAMEBUFFER_COMPLETE ||
|
||||
drawStatus != js.html.webgl.WebGL2RenderingContext.FRAMEBUFFER_COMPLETE) {
|
||||
return;
|
||||
}
|
||||
|
||||
var halfWidth = Std.int(source.image.width / 2);
|
||||
var fullHeight = source.image.height;
|
||||
|
||||
if (vr._leftViewport != null) {
|
||||
var vp = vr._leftViewport;
|
||||
gl.blitFramebuffer(
|
||||
0, 0, halfWidth, fullHeight,
|
||||
vp.x, vp.y, vp.x + vp.width, vp.y + vp.height,
|
||||
js.html.webgl.WebGL2RenderingContext.COLOR_BUFFER_BIT,
|
||||
js.html.webgl.WebGL2RenderingContext.LINEAR
|
||||
);
|
||||
}
|
||||
if (vr._rightViewport != null) {
|
||||
var vp = vr._rightViewport;
|
||||
gl.blitFramebuffer(
|
||||
halfWidth, 0, source.image.width, fullHeight,
|
||||
vp.x, vp.y, vp.x + vp.width, vp.y + vp.height,
|
||||
js.html.webgl.WebGL2RenderingContext.COLOR_BUFFER_BIT,
|
||||
js.html.webgl.WebGL2RenderingContext.LINEAR
|
||||
);
|
||||
}
|
||||
|
||||
gl.bindFramebuffer(js.html.webgl.WebGL2RenderingContext.FRAMEBUFFER, null);
|
||||
renderToXRFramebuffer = false;
|
||||
#end
|
||||
}
|
||||
#end
|
||||
#if lnx_vr
|
||||
public function drawStereo(drawMeshes: Void->Void) {
|
||||
var vr = kha.vr.VrInterface.instance;
|
||||
vrSimulateMode = false;
|
||||
|
||||
if (currentG == null && frameG != null) {
|
||||
currentG = frameG;
|
||||
}
|
||||
|
||||
var appw = iron.App.w();
|
||||
var apph = iron.App.h();
|
||||
var halfw = Std.int(appw / 2);
|
||||
var g = currentG;
|
||||
|
||||
// get render target dimensions not App.w/h gbuffer is scaled in simulate mode with supersampling
|
||||
|
||||
if (vr != null && vr.IsPresenting()) {
|
||||
// Left eye
|
||||
Scene.active.camera.V.setFrom(Scene.active.camera.leftV);
|
||||
var gbuffer0 = renderTargets.get("gbuffer0");
|
||||
var actualWidth = (gbuffer0 != null && gbuffer0.image != null) ? gbuffer0.image.width : appw;
|
||||
var actualHeight = (gbuffer0 != null && gbuffer0.image != null) ? gbuffer0.image.height : apph;
|
||||
var actualHalfWidth = Std.int(actualWidth / 2);
|
||||
|
||||
var vrFBWidth = actualWidth;
|
||||
var vrFBHeight = actualHeight;
|
||||
var vrHalfWidth = actualHalfWidth;
|
||||
var isVRPresenting = false;
|
||||
vrSimulateMode = false;
|
||||
|
||||
var vr:Dynamic = null;
|
||||
var vrExists = false;
|
||||
#if (kha_webgl && lnx_vr)
|
||||
if (kha.vr.VrInterface.instance != null) {
|
||||
vr = kha.vr.VrInterface.instance;
|
||||
vrExists = true;
|
||||
}
|
||||
#end
|
||||
|
||||
if (vrExists && vr != null && vr.IsPresenting()) {
|
||||
vrSimulateMode = false;
|
||||
isVRPresenting = true;
|
||||
|
||||
// get framebuffer dimensions from XR layer
|
||||
#if (kha_webgl && lnx_vr)
|
||||
var xrVr: kha.js.vr.VrInterface = cast vr;
|
||||
if (xrVr.xrGLLayer != null) {
|
||||
vrFBWidth = untyped xrVr.xrGLLayer.framebufferWidth;
|
||||
vrFBHeight = untyped xrVr.xrGLLayer.framebufferHeight;
|
||||
vrHalfWidth = Std.int(vrFBWidth / 2);
|
||||
}
|
||||
#end
|
||||
|
||||
if (Scene.active == null || Scene.active.camera == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
#if (kha_webgl && lnx_vr)
|
||||
if (vrCenterCameraWorld == null) vrCenterCameraWorld = Mat4.identity();
|
||||
vrCenterCameraWorld.setFrom(Scene.active.camera.transform.world);
|
||||
#end
|
||||
|
||||
// LEFT EYE
|
||||
// HMD center for room scale position tracking
|
||||
#if (kha_webgl && lnx_vr)
|
||||
var xrVr: kha.js.vr.VrInterface = cast vr;
|
||||
if (xrVr.currentViewerPose != null) {
|
||||
var viewerTransform = untyped xrVr.currentViewerPose.transform;
|
||||
if (viewerTransform != null && viewerTransform.position != null) {
|
||||
// VR present calibration is used to position objects in world space not the camera
|
||||
var pos = viewerTransform.position;
|
||||
|
||||
// camera follows headset directly in local floor space
|
||||
Scene.active.camera.transform.loc.set(pos.x, pos.y, pos.z);
|
||||
|
||||
if (viewerTransform.orientation != null) {
|
||||
Scene.active.camera.transform.rot.set(
|
||||
viewerTransform.orientation.x,
|
||||
viewerTransform.orientation.y,
|
||||
viewerTransform.orientation.z,
|
||||
viewerTransform.orientation.w
|
||||
);
|
||||
}
|
||||
Scene.active.camera.transform.buildMatrix();
|
||||
}
|
||||
}
|
||||
iron.system.VRController.updatePoses();
|
||||
#end
|
||||
|
||||
Scene.active.camera.V.self = vr.GetViewMatrix(0);
|
||||
Scene.active.camera.P.self = vr.GetProjectionMatrix(0);
|
||||
g.viewport(0, 0, halfw, apph);
|
||||
Scene.active.camera.VP.setFrom(Scene.active.camera.P);
|
||||
Scene.active.camera.VP.multmat(Scene.active.camera.V);
|
||||
Scene.active.camera.buildMatrix(); // update frustum for culling
|
||||
|
||||
var renderWidth = actualWidth;
|
||||
var renderHeight = actualHeight;
|
||||
var renderHalfWidth = actualHalfWidth;
|
||||
|
||||
// left half of render target
|
||||
g.viewport(0, 0, renderHalfWidth, renderHeight);
|
||||
g.scissor(0, 0, renderHalfWidth, renderHeight);
|
||||
drawMeshes();
|
||||
|
||||
// Right eye
|
||||
begin(g, additionalTargets);
|
||||
Scene.active.camera.V.setFrom(Scene.active.camera.rightV);
|
||||
// RIGHT EYE
|
||||
Scene.active.camera.V.self = vr.GetViewMatrix(1);
|
||||
Scene.active.camera.P.self = vr.GetProjectionMatrix(1);
|
||||
g.viewport(halfw, 0, halfw, apph);
|
||||
Scene.active.camera.VP.setFrom(Scene.active.camera.P);
|
||||
Scene.active.camera.VP.multmat(Scene.active.camera.V);
|
||||
Scene.active.camera.buildMatrix();
|
||||
|
||||
// right half of render target
|
||||
g.viewport(renderHalfWidth, 0, renderHalfWidth, renderHeight);
|
||||
g.scissor(renderHalfWidth, 0, renderHalfWidth, renderHeight);
|
||||
drawMeshes();
|
||||
|
||||
// restore for post-processing
|
||||
g.disableScissor();
|
||||
g.viewport(0, 0, renderWidth, renderHeight);
|
||||
}
|
||||
else { // Simulate
|
||||
Scene.active.camera.buildProjection(halfw / apph);
|
||||
vrSimulateMode = true;
|
||||
var ipd_offset = 0.032 * 35.0;
|
||||
|
||||
// Left eye
|
||||
g.viewport(0, 0, halfw, apph);
|
||||
#if (kha_webgl && lnx_vr)
|
||||
if (vrCenterCameraWorld == null) vrCenterCameraWorld = Mat4.identity();
|
||||
vrCenterCameraWorld.setFrom(Scene.active.camera.transform.world);
|
||||
#end
|
||||
|
||||
Scene.active.camera.buildProjection(actualHalfWidth / actualHeight);
|
||||
|
||||
Scene.active.camera.transform.move(Scene.active.camera.right(), -ipd_offset);
|
||||
Scene.active.camera.buildMatrix();
|
||||
g.viewport(0, 0, actualHalfWidth, actualHeight);
|
||||
g.scissor(0, 0, actualHalfWidth, actualHeight);
|
||||
drawMeshes();
|
||||
|
||||
// Right eye
|
||||
begin(g, additionalTargets);
|
||||
Scene.active.camera.transform.move(Scene.active.camera.right(), 0.032);
|
||||
Scene.active.camera.transform.move(Scene.active.camera.right(), ipd_offset * 2.0);
|
||||
Scene.active.camera.buildMatrix();
|
||||
g.viewport(halfw, 0, halfw, apph);
|
||||
g.viewport(actualHalfWidth, 0, actualHalfWidth, actualHeight);
|
||||
g.scissor(actualHalfWidth, 0, actualHalfWidth, actualHeight);
|
||||
drawMeshes();
|
||||
|
||||
Scene.active.camera.transform.move(Scene.active.camera.right(), -0.032);
|
||||
Scene.active.camera.transform.move(Scene.active.camera.right(), -ipd_offset);
|
||||
Scene.active.camera.buildMatrix();
|
||||
g.disableScissor();
|
||||
g.viewport(0, 0, actualWidth, actualHeight);
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
||||
@ -954,7 +954,14 @@ class Scene {
|
||||
static function createTraitClassInstance(traitName: String, args: Array<Dynamic>): Dynamic {
|
||||
var cname = Type.resolveClass(traitName);
|
||||
if (cname == null) return null;
|
||||
return Type.createInstance(cname, args);
|
||||
var trait:Dynamic;
|
||||
try {
|
||||
trait = Type.createInstance(cname, args);
|
||||
} catch(e) {
|
||||
trace("Error creating trait: " + traitName + " - " + e);
|
||||
trait = null;
|
||||
}
|
||||
return trait;
|
||||
}
|
||||
|
||||
function loadEmbeddedData(datas: Array<String>, done: Void->Void) {
|
||||
|
||||
@ -37,7 +37,9 @@ class Armature {
|
||||
}
|
||||
|
||||
public function getAction(name: String): TAction {
|
||||
for (a in actions) if (a.name == name) return a;
|
||||
for (a in actions) {
|
||||
if (a.name == name) return a;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@ -141,6 +141,7 @@ class Animation {
|
||||
sampler.cacheSet = false;
|
||||
sampler.trackEnd = false;
|
||||
|
||||
if (anim == null || anim.tracks == null || anim.tracks.length == 0) return;
|
||||
var track = anim.tracks[0];
|
||||
|
||||
if (frameIndex == -1) {
|
||||
@ -442,7 +443,12 @@ class ActionSampler {
|
||||
*/
|
||||
public inline function setObjectAction(actionData: TObj) {
|
||||
this.actionData = [actionData];
|
||||
this.totalFrames = actionData.anim.tracks[0].frames.length;
|
||||
if (actionData != null && actionData.anim != null && actionData.anim.tracks != null && actionData.anim.tracks.length > 0) {
|
||||
this.totalFrames = actionData.anim.tracks[0].frames.length;
|
||||
}
|
||||
else {
|
||||
this.totalFrames = 0;
|
||||
}
|
||||
actionDataInit = true;
|
||||
}
|
||||
|
||||
|
||||
@ -108,9 +108,11 @@ class BoneAnimation extends Animation {
|
||||
object.transform.rot.set(0, 0, 0, 1);
|
||||
object.transform.buildMatrix();
|
||||
|
||||
var refs = mo.parent.raw.bone_actions;
|
||||
if (refs != null && refs.length > 0) {
|
||||
Data.getSceneRaw(refs[0], function(action: TSceneFormat) { play(action.name); });
|
||||
if (mo.parent != null && mo.parent.raw != null && mo.parent.raw.bone_actions != null) {
|
||||
var refs = mo.parent.raw.bone_actions;
|
||||
if (refs.length > 0) {
|
||||
Data.getSceneRaw(refs[0], function(action: TSceneFormat) { play(action.name); });
|
||||
}
|
||||
}
|
||||
}
|
||||
if (armatureObject.raw.relative_bone_constraints) relativeBoneConstraints = true;
|
||||
@ -183,8 +185,10 @@ class BoneAnimation extends Animation {
|
||||
}
|
||||
|
||||
function setAction(action: String) {
|
||||
if (armature == null) return;
|
||||
armature.initMats();
|
||||
var a = armature.getAction(action);
|
||||
if (a == null) return;
|
||||
skeletonBones = a.bones;
|
||||
skeletonMats = a.mats;
|
||||
if(! rootMotionCacheInit) skeletonMats.push(Mat4.identity());
|
||||
@ -193,8 +197,11 @@ class BoneAnimation extends Animation {
|
||||
}
|
||||
|
||||
function getAction(action: String): Array<TObj> {
|
||||
if (armature == null) return null;
|
||||
armature.initMats();
|
||||
return armature.getAction(action).bones;
|
||||
var a = armature.getAction(action);
|
||||
if (a == null) return null;
|
||||
return a.bones;
|
||||
}
|
||||
|
||||
function multParent(i: Int, fasts: Array<Mat4>, bones: Array<TObj>, mats: Array<Mat4>) {
|
||||
@ -225,9 +232,9 @@ class BoneAnimation extends Animation {
|
||||
}
|
||||
|
||||
override public function play(action = "", onComplete: Void->Void = null, blendTime = 0.2, speed = 1.0, loop = true) {
|
||||
super.play(action, onComplete, blendTime, speed, loop);
|
||||
if (action != "") {
|
||||
setAction(action);
|
||||
super.play(action, onComplete, blendTime, speed, loop);
|
||||
var tempAnimParam = new ActionSampler(action);
|
||||
registerAction("tempAction", tempAnimParam);
|
||||
updateAnimation = function(mats){
|
||||
@ -239,6 +246,10 @@ class BoneAnimation extends Animation {
|
||||
override public function update(delta: FastFloat) {
|
||||
this.delta = delta;
|
||||
if (!isSkinned && skeletonBones == null) setAction(armature.actions[0].name);
|
||||
|
||||
// TODO: double check skip culling for skinned meshes if they need animation updates for bounds
|
||||
// if (object != null && !object.visible) return;
|
||||
|
||||
if (object != null && (!object.visible || object.culled)) return;
|
||||
if (skeletonBones == null || skeletonBones.length == 0) return;
|
||||
|
||||
@ -248,7 +259,6 @@ class BoneAnimation extends Animation {
|
||||
|
||||
super.update(delta);
|
||||
if(updateAnimation != null) {
|
||||
|
||||
updateAnimation(skeletonMats);
|
||||
}
|
||||
|
||||
@ -401,6 +411,7 @@ class BoneAnimation extends Animation {
|
||||
}
|
||||
|
||||
var bones = sampler.getBoneAction();
|
||||
if (bones == null) return;
|
||||
for(b in bones){
|
||||
if (b.anim != null) {
|
||||
updateTrack(b.anim, sampler);
|
||||
@ -410,13 +421,14 @@ class BoneAnimation extends Animation {
|
||||
}
|
||||
|
||||
public function sampleAction(sampler: ActionSampler, actionMats: Array<Mat4>) {
|
||||
|
||||
if(! sampler.actionDataInit) {
|
||||
var bones = getAction(sampler.action);
|
||||
sampler.setBoneAction(bones);
|
||||
}
|
||||
|
||||
var bones = sampler.getBoneAction();
|
||||
if (bones == null) return;
|
||||
|
||||
actionMats[skeletonBones.length].setIdentity();
|
||||
var rootMotionEnabled = sampler.rootMotionPos || sampler.rootMotionRot;
|
||||
for (i in 0...bones.length) {
|
||||
@ -427,7 +439,6 @@ class BoneAnimation extends Animation {
|
||||
updateAnimSampled(bones[i].anim, actionMats[i], sampler);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function updateAnimSampled(anim: TAnimation, mm: Mat4, sampler: ActionSampler) {
|
||||
@ -588,6 +599,9 @@ class BoneAnimation extends Animation {
|
||||
|
||||
public override function getTotalFrames(sampler: ActionSampler): Int {
|
||||
var bones = getAction(sampler.action);
|
||||
if (bones == null){
|
||||
return 0;
|
||||
}
|
||||
var track = bones[0].anim.tracks[0];
|
||||
return Std.int(track.frames[track.frames.length - 1] - track.frames[0]);
|
||||
}
|
||||
@ -1048,9 +1062,9 @@ class BoneAnimation extends Animation {
|
||||
var rootLen = root.bone_length * rootMat.getScale().x;
|
||||
|
||||
// Get distance form root to goal
|
||||
var goalLen = Math.abs(Vec4.distance(rootMat.getLoc(), goal));
|
||||
var goalLen: FastFloat = Math.abs(Vec4.distance(rootMat.getLoc(), goal));
|
||||
|
||||
var totalLength = effectorLen + rootLen;
|
||||
var totalLength: FastFloat = effectorLen + rootLen;
|
||||
|
||||
// Get tip location of effector bone
|
||||
var effectorTipPos = new Vec4().setFrom(effectorMat.look()).normalize();
|
||||
@ -1070,7 +1084,7 @@ class BoneAnimation extends Animation {
|
||||
|
||||
// Get unit vector of effector bone
|
||||
var vectorEffector = new Vec4().setFrom(effectorMat.look()).normalize();
|
||||
|
||||
|
||||
// Get dot product of vectors
|
||||
var dot = new Vec4().setFrom(vectorRootEffector).dot(vectorRoot);
|
||||
// Calmp between -1 and 1
|
||||
|
||||
@ -42,9 +42,10 @@ class CameraObject extends Object {
|
||||
|
||||
this.data = data;
|
||||
|
||||
#if lnx_vr
|
||||
iron.system.VR.initButton();
|
||||
#end
|
||||
// dont just auto initialize VR button - headset trait controls VR
|
||||
// #if lnx_vr
|
||||
// iron.system.VR.initButton();
|
||||
// #end
|
||||
|
||||
buildProjection();
|
||||
|
||||
@ -85,7 +86,14 @@ class CameraObject extends Object {
|
||||
projectionJitter();
|
||||
#end
|
||||
|
||||
// matrices are set by VR system so avoid rebuilding transforms unless its in preview/not presenting
|
||||
#if (kha_webgl && lnx_vr)
|
||||
if (@:privateAccess !RenderPath.isVRPresenting()) {
|
||||
buildMatrix();
|
||||
}
|
||||
#else
|
||||
buildMatrix();
|
||||
#end
|
||||
|
||||
RenderPath.active.renderFrame(g);
|
||||
|
||||
|
||||
@ -59,6 +59,9 @@ class LightObject extends Object {
|
||||
public static var clustersData: kha.Image = null;
|
||||
static var lpos = new Vec4();
|
||||
public static var LWVPMatrixArray: Float32Array = null;
|
||||
#if lnx_vr
|
||||
static var originalLightPositions: Float32Array = null;
|
||||
#end
|
||||
#end // lnx_clusters
|
||||
|
||||
public var V: Mat4 = Mat4.identity();
|
||||
@ -519,7 +522,7 @@ class LightObject extends Object {
|
||||
updateLightsArray(); // TODO: only update on light change
|
||||
}
|
||||
|
||||
static function updateLightsArray() {
|
||||
public static function updateLightsArray() {
|
||||
if (lightsArray == null) { // vec4x3 - 1: pos, a, color, b, 2: dir, c
|
||||
lightsArray = new Float32Array(maxLights * 4 * 3);
|
||||
#if lnx_spot
|
||||
@ -578,6 +581,49 @@ class LightObject extends Object {
|
||||
}
|
||||
}
|
||||
|
||||
// VR deferred stereo we save original light positions before adjusting for per-eye rendering
|
||||
#if lnx_vr
|
||||
public static function saveOriginalLightPositions() {
|
||||
if (lightsArray == null) return;
|
||||
|
||||
if (originalLightPositions == null) {
|
||||
originalLightPositions = new Float32Array(lightsArray.length);
|
||||
}
|
||||
|
||||
for (i in 0...lightsArray.length) {
|
||||
originalLightPositions[i] = lightsArray[i];
|
||||
}
|
||||
}
|
||||
|
||||
// negative for left eye, positive for right eye
|
||||
public static function adjustLightPositionsForVREye(offsetX: Float, rightVec: Vec4) {
|
||||
if (lightsArray == null) return;
|
||||
|
||||
var lights = Scene.active.lights;
|
||||
var n = lights.length > maxLights ? maxLights : lights.length;
|
||||
var i = 0;
|
||||
|
||||
for (l in lights) {
|
||||
if (discardLightCulled(l)) continue;
|
||||
if (i >= n) break;
|
||||
|
||||
lightsArray[i * 12 ] = originalLightPositions[i * 12 ] + rightVec.x * offsetX;
|
||||
lightsArray[i * 12 + 1] = originalLightPositions[i * 12 + 1] + rightVec.y * offsetX;
|
||||
lightsArray[i * 12 + 2] = originalLightPositions[i * 12 + 2] + rightVec.z * offsetX;
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
public static function restoreOriginalLightPositions() {
|
||||
if (lightsArray == null || originalLightPositions == null) return;
|
||||
|
||||
for (i in 0...lightsArray.length) {
|
||||
lightsArray[i] = originalLightPositions[i];
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
||||
public static function updateLWVPMatrixArray(object: Object, type: String) {
|
||||
if (LWVPMatrixArray == null) {
|
||||
LWVPMatrixArray = new Float32Array(maxLightsCluster * 16);
|
||||
@ -629,8 +675,8 @@ class LightObject extends Object {
|
||||
LWVPMatrixArray[i * 16 + 13] = m._31;
|
||||
LWVPMatrixArray[i * 16 + 14] = m._32;
|
||||
LWVPMatrixArray[i * 16 + 15] = m._33;
|
||||
i++; // only increment in light type
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return LWVPMatrixArray;
|
||||
}
|
||||
|
||||
@ -20,6 +20,13 @@ class MorphTarget {
|
||||
public var morphDataPos: Image;
|
||||
public var morphDataNor: Image;
|
||||
public var morphMap: Map<String, Int> = null;
|
||||
|
||||
public var isDirty: Bool = true;
|
||||
var previousWeights: Float32Array;
|
||||
var changeThreshold: FastFloat = 0.001; // skip smaller
|
||||
var pendingUpdates: Map<Int, Float> = null;
|
||||
var batchUpdateEnabled: Bool = true;
|
||||
var lastFlushFrame: Int = 0;
|
||||
|
||||
public function new(data: TMorphTarget) {
|
||||
initWeights(data.morph_target_defaults);
|
||||
@ -42,6 +49,14 @@ class MorphTarget {
|
||||
morphMap.set(name, i);
|
||||
i++;
|
||||
}
|
||||
|
||||
previousWeights = new Float32Array(morphWeights.length);
|
||||
for (i in 0...morphWeights.length) {
|
||||
previousWeights.set(i, morphWeights.get(i));
|
||||
}
|
||||
|
||||
// batch system
|
||||
pendingUpdates = new Map<Int, Float>();
|
||||
}
|
||||
|
||||
inline function initWeights(defaults: Float32Array) {
|
||||
@ -54,9 +69,96 @@ class MorphTarget {
|
||||
public function setMorphValue(name: String, value: Float) {
|
||||
var i = morphMap.get(name);
|
||||
if (i != null) {
|
||||
morphWeights.set(i, value);
|
||||
if (batchUpdateEnabled) {
|
||||
pendingUpdates.set(i, value);
|
||||
} else {
|
||||
setMorphValueDirect(i, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// faster indexed access
|
||||
public inline function setMorphValueDirect(index: Int, value: Float) {
|
||||
var current = morphWeights.get(index);
|
||||
|
||||
// allow explicit zero values to reset
|
||||
if (value == 0.0 && current != 0.0) {
|
||||
morphWeights.set(index, value);
|
||||
isDirty = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var delta = value - current;
|
||||
|
||||
if (delta < -changeThreshold || delta > changeThreshold) {
|
||||
morphWeights.set(index, value);
|
||||
isDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
// flush pending batch
|
||||
public function flushBatchedUpdates() {
|
||||
if (pendingUpdates.keys().hasNext()) {
|
||||
var anyChanged = false;
|
||||
var hasZeros = false;
|
||||
|
||||
for (index in pendingUpdates.keys()) {
|
||||
var value = pendingUpdates.get(index);
|
||||
if (value == null) continue;
|
||||
|
||||
if (value == 0.0) hasZeros = true;
|
||||
|
||||
var current = morphWeights.get(index);
|
||||
var delta = value - current;
|
||||
|
||||
if (value == 0.0 && current != 0.0) {
|
||||
try{
|
||||
morphWeights.set(index, cast value);
|
||||
}catch(e){
|
||||
trace("ERROR: " + e);
|
||||
}
|
||||
anyChanged = true;
|
||||
}
|
||||
else if (delta < -changeThreshold || delta > changeThreshold) {
|
||||
morphWeights.set(index, cast value);
|
||||
anyChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
pendingUpdates.clear();
|
||||
|
||||
if (anyChanged || hasZeros) {
|
||||
isDirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public inline function markClean() {
|
||||
isDirty = false;
|
||||
for (i in 0...morphWeights.length) {
|
||||
previousWeights.set(i, morphWeights.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
public inline function markDirty() {
|
||||
isDirty = true;
|
||||
}
|
||||
|
||||
// toggle batch mode
|
||||
public inline function setBatchMode(enabled: Bool) {
|
||||
if (!enabled && batchUpdateEnabled) {
|
||||
flushBatchedUpdates();
|
||||
}
|
||||
batchUpdateEnabled = enabled;
|
||||
}
|
||||
|
||||
public function resetAllWeights() {
|
||||
for (i in 0...morphWeights.length) {
|
||||
morphWeights.set(i, 0.0);
|
||||
}
|
||||
pendingUpdates.clear();
|
||||
isDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
#end
|
||||
|
||||
@ -210,8 +210,12 @@ class Object {
|
||||
}
|
||||
|
||||
#if lnx_skin
|
||||
public function getBoneAnimation(armatureUid): BoneAnimation {
|
||||
for (a in Scene.active.animations) if (a.armature != null && a.armature.uid == armatureUid) return cast a;
|
||||
public function getBoneAnimation(armatureUid: Int): BoneAnimation {
|
||||
for (a in Scene.active.animations) {
|
||||
if (a.armature != null && a.armature.uid == armatureUid) {
|
||||
return cast a;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
#else
|
||||
|
||||
@ -97,7 +97,9 @@ class ObjectAnimation extends Animation {
|
||||
}
|
||||
|
||||
public override function getTotalFrames(sampler: ActionSampler): Int {
|
||||
var track = getAction(sampler.action).anim.tracks[0];
|
||||
var action = getAction(sampler.action);
|
||||
if (action == null || action.anim == null || action.anim.tracks == null || action.anim.tracks.length == 0) return 0;
|
||||
var track = action.anim.tracks[0];
|
||||
return Std.int(track.frames[track.frames.length - 1] - track.frames[0]);
|
||||
}
|
||||
|
||||
|
||||
@ -7,16 +7,20 @@ import kha.graphics4.TextureFilter;
|
||||
import kha.graphics4.MipMapFilter;
|
||||
import kha.arrays.Float32Array;
|
||||
import iron.math.Vec4;
|
||||
import iron.math.Mat4;
|
||||
import iron.math.Quat;
|
||||
import iron.math.Mat3;
|
||||
import iron.math.Mat4;
|
||||
import iron.data.WorldData;
|
||||
import iron.data.MaterialData;
|
||||
import iron.data.ShaderData;
|
||||
import iron.data.SceneFormat;
|
||||
import iron.data.WorldData;
|
||||
import iron.data.SceneFormat.TShaderConstant;
|
||||
import iron.data.SceneFormat.TBindConstant;
|
||||
import iron.object.Transform;
|
||||
import iron.object.LightObject;
|
||||
import iron.Scene;
|
||||
import iron.RenderPath;
|
||||
import iron.system.Input;
|
||||
import iron.system.Time;
|
||||
import iron.RenderPath;
|
||||
using StringTools;
|
||||
|
||||
// Structure for setting shader uniforms
|
||||
@ -38,6 +42,7 @@ class Uniforms {
|
||||
public static var helpMat = Mat4.identity();
|
||||
public static var helpMat2 = Mat4.identity();
|
||||
public static var helpMat3 = Mat3.identity();
|
||||
public static var helpMat4 = Mat4.identity();
|
||||
public static var helpVec = new Vec4();
|
||||
public static var helpVec2 = new Vec4();
|
||||
public static var helpQuat = new Quat(); // Keep at identity
|
||||
@ -47,6 +52,10 @@ class Uniforms {
|
||||
public static var externalVec4Links: Array<Object->MaterialData->String->Vec4> = null;
|
||||
public static var externalVec3Links: Array<Object->MaterialData->String->Vec4> = null;
|
||||
public static var externalVec2Links: Array<Object->MaterialData->String->Vec4> = null;
|
||||
|
||||
public static var eyeLeftCallCount = 0;
|
||||
public static var lastFrameChecked = -1;
|
||||
|
||||
public static var externalFloatLinks: Array<Object->MaterialData->String->Null<kha.FastFloat>> = null;
|
||||
public static var externalFloatsLinks: Array<Object->MaterialData->String->Float32Array> = null;
|
||||
public static var externalIntLinks: Array<Object->MaterialData->String->Null<Int>> = null;
|
||||
@ -59,6 +68,10 @@ class Uniforms {
|
||||
public static var defaultFilter = TextureFilter.LinearFilter;
|
||||
#end
|
||||
|
||||
#if lnx_morph_target
|
||||
public static var forceUploadMorphWeights: Bool = false;
|
||||
#end
|
||||
|
||||
public static function setContextConstants(g: Graphics, context: ShaderContext, bindParams: Array<String>) {
|
||||
if (context.raw.constants != null) {
|
||||
for (i in 0...context.raw.constants.length) {
|
||||
@ -290,6 +303,89 @@ class Uniforms {
|
||||
helpMat.getInverse(helpMat);
|
||||
m = helpMat;
|
||||
}
|
||||
#if lnx_vr
|
||||
case "_inverseViewProjectionMatrixLeft": {
|
||||
var vr = kha.vr.VrInterface.instance;
|
||||
if (vr != null && vr.IsPresenting()) {
|
||||
var leftView = vr.GetViewMatrix(0);
|
||||
var leftProj = vr.GetProjectionMatrix(0);
|
||||
helpMat._00 = leftView._00; helpMat._01 = leftView._01; helpMat._02 = leftView._02; helpMat._03 = leftView._03;
|
||||
helpMat._10 = leftView._10; helpMat._11 = leftView._11; helpMat._12 = leftView._12; helpMat._13 = leftView._13;
|
||||
helpMat._20 = leftView._20; helpMat._21 = leftView._21; helpMat._22 = leftView._22; helpMat._23 = leftView._23;
|
||||
helpMat._30 = leftView._30; helpMat._31 = leftView._31; helpMat._32 = leftView._32; helpMat._33 = leftView._33;
|
||||
helpMat2._00 = leftProj._00; helpMat2._01 = leftProj._01; helpMat2._02 = leftProj._02; helpMat2._03 = leftProj._03;
|
||||
helpMat2._10 = leftProj._10; helpMat2._11 = leftProj._11; helpMat2._12 = leftProj._12; helpMat2._13 = leftProj._13;
|
||||
helpMat2._20 = leftProj._20; helpMat2._21 = leftProj._21; helpMat2._22 = leftProj._22; helpMat2._23 = leftProj._23;
|
||||
helpMat2._30 = leftProj._30; helpMat2._31 = leftProj._31; helpMat2._32 = leftProj._32; helpMat2._33 = leftProj._33;
|
||||
helpMat.multmat(helpMat2);
|
||||
helpMat.getInverse(helpMat);
|
||||
} else if (iron.RenderPath.isVRSimulateMode()) {
|
||||
var ipd_offset = 0.032 * 35.0; // Match eye offset
|
||||
var rightVec = camera.rightWorld();
|
||||
|
||||
var eyeLeftX = camera.transform.worldx() - rightVec.x * ipd_offset;
|
||||
var eyeLeftY = camera.transform.worldy() - rightVec.y * ipd_offset;
|
||||
var eyeLeftZ = camera.transform.worldz() - rightVec.z * ipd_offset;
|
||||
|
||||
helpMat.setFrom(camera.transform.world);
|
||||
helpMat._30 = eyeLeftX;
|
||||
helpMat._31 = eyeLeftY;
|
||||
helpMat._32 = eyeLeftZ;
|
||||
helpMat.getInverse(helpMat); // Now it's a view matrix
|
||||
|
||||
helpMat.multmat(camera.P);
|
||||
helpMat.getInverse(helpMat);
|
||||
} else {
|
||||
helpMat.setFrom(camera.V);
|
||||
helpMat.multmat(camera.P);
|
||||
helpMat.getInverse(helpMat);
|
||||
}
|
||||
m = helpMat;
|
||||
}
|
||||
case "_inverseViewProjectionMatrixRight": {
|
||||
var vr = kha.vr.VrInterface.instance;
|
||||
if (vr != null && vr.IsPresenting()) {
|
||||
var rightView = vr.GetViewMatrix(1);
|
||||
var rightProj = vr.GetProjectionMatrix(1);
|
||||
// kha.math.FastMatrix4 to iron.math.Mat4
|
||||
helpMat2._00 = rightView._00; helpMat2._01 = rightView._01; helpMat2._02 = rightView._02; helpMat2._03 = rightView._03;
|
||||
helpMat2._10 = rightView._10; helpMat2._11 = rightView._11; helpMat2._12 = rightView._12; helpMat2._13 = rightView._13;
|
||||
helpMat2._20 = rightView._20; helpMat2._21 = rightView._21; helpMat2._22 = rightView._22; helpMat2._23 = rightView._23;
|
||||
helpMat2._30 = rightView._30; helpMat2._31 = rightView._31; helpMat2._32 = rightView._32; helpMat2._33 = rightView._33;
|
||||
helpMat4._00 = rightProj._00; helpMat4._01 = rightProj._01; helpMat4._02 = rightProj._02; helpMat4._03 = rightProj._03;
|
||||
helpMat4._10 = rightProj._10; helpMat4._11 = rightProj._11; helpMat4._12 = rightProj._12; helpMat4._13 = rightProj._13;
|
||||
helpMat4._20 = rightProj._20; helpMat4._21 = rightProj._21; helpMat4._22 = rightProj._22; helpMat4._23 = rightProj._23;
|
||||
helpMat4._30 = rightProj._30; helpMat4._31 = rightProj._31; helpMat4._32 = rightProj._32; helpMat4._33 = rightProj._33;
|
||||
helpMat2.multmat(helpMat4);
|
||||
helpMat2.getInverse(helpMat2);
|
||||
m = helpMat2;
|
||||
} else if (iron.RenderPath.isVRSimulateMode()) {
|
||||
var ipd_offset = 0.032 * 35.0;
|
||||
var rightVec = camera.rightWorld();
|
||||
|
||||
// calculate right eye position in world space
|
||||
var eyeRightX = camera.transform.worldx() + rightVec.x * ipd_offset;
|
||||
var eyeRightY = camera.transform.worldy() + rightVec.y * ipd_offset;
|
||||
var eyeRightZ = camera.transform.worldz() + rightVec.z * ipd_offset;
|
||||
|
||||
helpMat2.setFrom(camera.transform.world);
|
||||
helpMat2._30 = eyeRightX;
|
||||
helpMat2._31 = eyeRightY;
|
||||
helpMat2._32 = eyeRightZ;
|
||||
helpMat2.getInverse(helpMat2);
|
||||
|
||||
helpMat2.multmat(camera.P);
|
||||
helpMat2.getInverse(helpMat2);
|
||||
m = helpMat2;
|
||||
} else {
|
||||
// fallback to center camera
|
||||
helpMat2.setFrom(camera.V);
|
||||
helpMat2.multmat(camera.P);
|
||||
helpMat2.getInverse(helpMat2);
|
||||
m = helpMat2;
|
||||
}
|
||||
}
|
||||
#end
|
||||
case "_viewProjectionMatrix": {
|
||||
#if lnx_centerworld
|
||||
m = vmat(camera.V);
|
||||
@ -402,6 +498,28 @@ class Uniforms {
|
||||
v = helpVec;
|
||||
}
|
||||
}
|
||||
#if lnx_vr
|
||||
case "_pointPositionLeft": {
|
||||
var point = RenderPath.active.point;
|
||||
if (point != null) {
|
||||
var lightWorldX = point.transform.worldx();
|
||||
var lightWorldY = point.transform.worldy();
|
||||
var lightWorldZ = point.transform.worldz();
|
||||
helpVec.set(lightWorldX, lightWorldY, lightWorldZ);
|
||||
v = helpVec;
|
||||
}
|
||||
}
|
||||
case "_pointPositionRight": {
|
||||
var point = RenderPath.active.point;
|
||||
if (point != null) {
|
||||
var lightWorldX = point.transform.worldx();
|
||||
var lightWorldY = point.transform.worldy();
|
||||
var lightWorldZ = point.transform.worldz();
|
||||
helpVec.set(lightWorldX, lightWorldY, lightWorldZ);
|
||||
v = helpVec;
|
||||
}
|
||||
}
|
||||
#end
|
||||
#if lnx_spot
|
||||
case "_spotDirection": {
|
||||
var point = RenderPath.active.point;
|
||||
@ -488,6 +606,84 @@ class Uniforms {
|
||||
helpVec = camera.rightWorld().normalize();
|
||||
v = helpVec;
|
||||
}
|
||||
#if lnx_vr
|
||||
case "_eyeLeft": {
|
||||
var currentFrame = iron.RenderPath.active.frame;
|
||||
if (currentFrame != lastFrameChecked) {
|
||||
eyeLeftCallCount = 0;
|
||||
lastFrameChecked = currentFrame;
|
||||
}
|
||||
eyeLeftCallCount++;
|
||||
|
||||
var vr = kha.vr.VrInterface.instance;
|
||||
if (vr != null && vr.IsPresenting()) {
|
||||
var leftViewMatrix = vr.GetViewMatrix(0);
|
||||
var invLeft = leftViewMatrix.inverse();
|
||||
helpVec.set(invLeft._30, invLeft._31, invLeft._32);
|
||||
// trace("eyeLeft: " + helpVec.x + ", " + helpVec.y + ", " + helpVec.z);
|
||||
} else if (iron.RenderPath.isVRSimulateMode()) {
|
||||
var ipd_offset = 0.032 * 35.0;
|
||||
var rightVec = camera.rightWorld();
|
||||
var centerX = camera.transform.worldx();
|
||||
var centerY = camera.transform.worldy();
|
||||
var centerZ = camera.transform.worldz();
|
||||
helpVec.set(
|
||||
centerX - rightVec.x * ipd_offset,
|
||||
centerY - rightVec.y * ipd_offset,
|
||||
centerZ - rightVec.z * ipd_offset
|
||||
);
|
||||
} else {
|
||||
helpVec.set(camera.transform.worldx(), camera.transform.worldy(), camera.transform.worldz());
|
||||
}
|
||||
v = helpVec;
|
||||
}
|
||||
case "_eyeRight": {
|
||||
var vr = kha.vr.VrInterface.instance;
|
||||
if (vr != null && vr.IsPresenting()) {
|
||||
var rightViewMatrix = vr.GetViewMatrix(1);
|
||||
var invRight = rightViewMatrix.inverse();
|
||||
helpVec.set(invRight._30, invRight._31, invRight._32);
|
||||
} else if (iron.RenderPath.isVRSimulateMode()) {
|
||||
var ipd_offset = 0.032 * 35.0;
|
||||
var rightVec = camera.rightWorld();
|
||||
var centerX = camera.transform.worldx();
|
||||
var centerY = camera.transform.worldy();
|
||||
var centerZ = camera.transform.worldz();
|
||||
helpVec.set(
|
||||
centerX + rightVec.x * ipd_offset,
|
||||
centerY + rightVec.y * ipd_offset,
|
||||
centerZ + rightVec.z * ipd_offset
|
||||
);
|
||||
} else {
|
||||
helpVec.set(camera.transform.worldx(), camera.transform.worldy(), camera.transform.worldz());
|
||||
}
|
||||
v = helpVec;
|
||||
}
|
||||
case "_eyeLookLeft": {
|
||||
var vr = kha.vr.VrInterface.instance;
|
||||
if (vr != null && vr.IsPresenting()) {
|
||||
var leftViewMatrix = vr.GetViewMatrix(0);
|
||||
var invLeft = leftViewMatrix.inverse();
|
||||
helpVec.set(-invLeft._20, -invLeft._21, -invLeft._22);
|
||||
helpVec.normalize();
|
||||
} else {
|
||||
helpVec = camera.lookWorld().normalize();
|
||||
}
|
||||
v = helpVec;
|
||||
}
|
||||
case "_eyeLookRight": {
|
||||
var vr = kha.vr.VrInterface.instance;
|
||||
if (vr != null && vr.IsPresenting()) {
|
||||
var rightViewMatrix = vr.GetViewMatrix(1);
|
||||
var invRight = rightViewMatrix.inverse();
|
||||
helpVec.set(-invRight._20, -invRight._21, -invRight._22);
|
||||
helpVec.normalize();
|
||||
} else {
|
||||
helpVec = camera.lookWorld().normalize();
|
||||
}
|
||||
v = helpVec;
|
||||
}
|
||||
#end
|
||||
case "_backgroundCol": {
|
||||
if (camera.data.raw.clear_color != null) helpVec.set(camera.data.raw.clear_color[0], camera.data.raw.clear_color[1], camera.data.raw.clear_color[2]);
|
||||
v = helpVec;
|
||||
@ -1165,7 +1361,19 @@ class Uniforms {
|
||||
#end // lnx_clusters
|
||||
#if lnx_morph_target
|
||||
case "_morphWeights": {
|
||||
fa = cast(object, MeshObject).morphTarget.morphWeights;
|
||||
var morphTarget = cast(object, MeshObject).morphTarget;
|
||||
morphTarget.flushBatchedUpdates();
|
||||
if (forceUploadMorphWeights) {
|
||||
fa = morphTarget.morphWeights;
|
||||
}
|
||||
else {
|
||||
if (morphTarget.isDirty) {
|
||||
fa = morphTarget.morphWeights;
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
#end
|
||||
}
|
||||
@ -1179,6 +1387,12 @@ class Uniforms {
|
||||
|
||||
if (fa == null) return;
|
||||
g.setFloats(location, fa);
|
||||
|
||||
#if lnx_morph_target
|
||||
if (c.link == "_morphWeights") {
|
||||
cast(object, MeshObject).morphTarget.markClean();
|
||||
}
|
||||
#end
|
||||
}
|
||||
else if (c.type == "int") {
|
||||
var i: Null<Int> = null;
|
||||
@ -1207,6 +1421,7 @@ class Uniforms {
|
||||
if (materialContext.raw.bind_constants != null) {
|
||||
for (i in 0...materialContext.raw.bind_constants.length) {
|
||||
var matc = materialContext.raw.bind_constants[i];
|
||||
if (matc == null) continue;
|
||||
var pos = -1;
|
||||
for (i in 0...context.raw.constants.length) {
|
||||
if (context.raw.constants[i].name == matc.name) {
|
||||
|
||||
@ -3,6 +3,14 @@ package iron.system;
|
||||
class Time {
|
||||
public static var scale = 1.0;
|
||||
|
||||
// TODO: VR Frame Time Override - used to sync physics with VR headset refresh rate
|
||||
#if lnx_vr
|
||||
public static var vrFrameTime: Float = -1.0; // VR frame time in seconds (-1 = not in VR)
|
||||
static var lastVRFrameTime: Float = 0.0;
|
||||
static var vrFrameCount: Int = 0;
|
||||
static var normalModeLogged: Bool = false;
|
||||
#end
|
||||
|
||||
static var frequency: Null<Int> = null;
|
||||
static function initFrequency() {
|
||||
frequency = kha.Display.primary != null ? kha.Display.primary.frequency : 60;
|
||||
@ -47,6 +55,24 @@ class Time {
|
||||
}
|
||||
|
||||
public static function update() {
|
||||
#if lnx_vr
|
||||
// TODO: use VR frame time when in VR present mode to sync physics with headset refresh
|
||||
if (vrFrameTime >= 0.0) {
|
||||
if (lastVRFrameTime > 0.0) {
|
||||
_delta = vrFrameTime - lastVRFrameTime;
|
||||
} else {
|
||||
_delta = 1.0 / 90.0; // Default to 90Hz for first VR frame
|
||||
}
|
||||
lastVRFrameTime = vrFrameTime;
|
||||
|
||||
return;
|
||||
} else {
|
||||
if (!normalModeLogged) {
|
||||
normalModeLogged = true;
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
||||
_delta = realTime() - lastTime;
|
||||
lastTime = realTime();
|
||||
}
|
||||
|
||||
138
leenkx/Sources/iron/system/VRController.hx
Normal file
138
leenkx/Sources/iron/system/VRController.hx
Normal file
@ -0,0 +1,138 @@
|
||||
package iron.system;
|
||||
|
||||
#if lnx_vr
|
||||
import iron.math.Vec4;
|
||||
import iron.math.Quat;
|
||||
|
||||
class VRController {
|
||||
|
||||
public static var leftHandPosition: Vec4 = new Vec4();
|
||||
public static var leftHandRotation: Quat = new Quat();
|
||||
public static var rightHandPosition: Vec4 = new Vec4();
|
||||
public static var rightHandRotation: Quat = new Quat();
|
||||
|
||||
public static var leftHandActive: Bool = false;
|
||||
public static var rightHandActive: Bool = false;
|
||||
|
||||
public static var leftThumbstickX: Float = 0.0;
|
||||
public static var leftThumbstickY: Float = 0.0;
|
||||
public static var rightThumbstickX: Float = 0.0;
|
||||
public static var rightThumbstickY: Float = 0.0;
|
||||
|
||||
public static var leftTrigger: Float = 0.0;
|
||||
public static var rightTrigger: Float = 0.0;
|
||||
public static var leftGrip: Float = 0.0;
|
||||
public static var rightGrip: Float = 0.0;
|
||||
|
||||
public static var leftButtonX: Bool = false;
|
||||
public static var leftButtonY: Bool = false;
|
||||
public static var rightButtonA: Bool = false;
|
||||
public static var rightButtonB: Bool = false;
|
||||
|
||||
public static var debugLog:Bool = false;
|
||||
|
||||
public static function enableDebug() {
|
||||
debugLog = true;
|
||||
}
|
||||
|
||||
public static function disableDebug() {
|
||||
debugLog = false;
|
||||
}
|
||||
|
||||
public static function updatePoses() {
|
||||
var vr: kha.js.vr.VrInterface = cast kha.vr.VrInterface.instance;
|
||||
if (vr == null || !vr.IsPresenting()) {
|
||||
if (debugLog) trace("[VRController] Not presenting or VR null");
|
||||
leftHandActive = false;
|
||||
rightHandActive = false;
|
||||
return;
|
||||
}
|
||||
|
||||
untyped window._vrControllerFrame = (untyped window._vrControllerFrame || 0) + 1;
|
||||
|
||||
leftHandActive = false;
|
||||
rightHandActive = false;
|
||||
|
||||
leftButtonX = false;
|
||||
leftButtonY = false;
|
||||
rightButtonA = false;
|
||||
rightButtonB = false;
|
||||
|
||||
var refSpace = untyped vr.xrRefSpace;
|
||||
if (vr.currentInputSources == null || vr.currentFrame == null || refSpace == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var inputSources = vr.currentInputSources;
|
||||
var frame = vr.currentFrame;
|
||||
|
||||
var sourceCount:Int = untyped inputSources.length;
|
||||
|
||||
for (i in 0...sourceCount) {
|
||||
var inputSource = untyped inputSources[i];
|
||||
if (inputSource == null) continue;
|
||||
|
||||
var handedness = untyped inputSource.handedness; // "left", "right", or "none"
|
||||
|
||||
var gripSpace = untyped inputSource.gripSpace;
|
||||
var targetRaySpace = untyped inputSource.targetRaySpace;
|
||||
// use targetRaySpace first laser/pointer and fall back to gripSpace
|
||||
var space = (targetRaySpace != null) ? targetRaySpace : gripSpace;
|
||||
|
||||
if (space == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var pose = untyped frame.getPose(space, refSpace);
|
||||
if (pose == null || pose.transform == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var transform = pose.transform;
|
||||
var pos = transform.position;
|
||||
var orient = transform.orientation;
|
||||
|
||||
if (handedness == "left") {
|
||||
leftHandPosition.set(pos.x, pos.y, pos.z);
|
||||
leftHandRotation.set(orient.x, orient.y, orient.z, orient.w);
|
||||
leftHandActive = true;
|
||||
|
||||
var gamepad = untyped inputSource.gamepad;
|
||||
if (gamepad != null) {
|
||||
// [0]=thumbstickX [1]=thumbstickY [2]=touchpadX [3]=touchpadY
|
||||
if (gamepad.axes != null && gamepad.axes.length >= 2) {
|
||||
leftThumbstickX = gamepad.axes[0];
|
||||
leftThumbstickY = gamepad.axes[1];
|
||||
}
|
||||
// [0]=trigger [1]=grip [4]=X [5]=Y
|
||||
if (gamepad.buttons != null) {
|
||||
if (gamepad.buttons.length > 0) leftTrigger = gamepad.buttons[0].value;
|
||||
if (gamepad.buttons.length > 1) leftGrip = gamepad.buttons[1].value;
|
||||
if (gamepad.buttons.length > 4) leftButtonX = gamepad.buttons[4].pressed;
|
||||
if (gamepad.buttons.length > 5) leftButtonY = gamepad.buttons[5].pressed;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (handedness == "right") {
|
||||
rightHandPosition.set(pos.x, pos.y, pos.z);
|
||||
rightHandRotation.set(orient.x, orient.y, orient.z, orient.w);
|
||||
rightHandActive = true;
|
||||
|
||||
var gamepad = untyped inputSource.gamepad;
|
||||
if (gamepad != null) {
|
||||
if (gamepad.axes != null && gamepad.axes.length >= 2) {
|
||||
rightThumbstickX = gamepad.axes[0];
|
||||
rightThumbstickY = gamepad.axes[1];
|
||||
}
|
||||
if (gamepad.buttons != null) {
|
||||
if (gamepad.buttons.length > 0) rightTrigger = gamepad.buttons[0].value;
|
||||
if (gamepad.buttons.length > 1) rightGrip = gamepad.buttons[1].value;
|
||||
if (gamepad.buttons.length > 4) rightButtonA = gamepad.buttons[4].pressed;
|
||||
if (gamepad.buttons.length > 5) rightButtonB = gamepad.buttons[5].pressed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#end
|
||||
@ -44,6 +44,7 @@ typedef TConfig = {
|
||||
@:optional var rp_supersample: Null<Float>;
|
||||
@:optional var rp_shadowmap_cube: Null<Int>; // size
|
||||
@:optional var rp_shadowmap_cascade: Null<Int>; // size for single cascade
|
||||
@:optional var rp_ssao: Null<Bool>;
|
||||
@:optional var rp_ssgi: Null<Bool>;
|
||||
@:optional var rp_ssr: Null<Bool>;
|
||||
@:optional var rp_ssrefr: Null<Bool>;
|
||||
|
||||
@ -2,13 +2,16 @@ package leenkx.logicnode;
|
||||
|
||||
import iron.object.Object;
|
||||
|
||||
#if lnx_bullet
|
||||
import leenkx.trait.physics.PhysicsConstraint;
|
||||
import leenkx.trait.physics.bullet.PhysicsConstraint.ConstraintType;
|
||||
#elseif lnx_oimo
|
||||
// TODO
|
||||
#if lnx_physics
|
||||
import leenkx.trait.physics.PhysicsConstraint;
|
||||
#if lnx_bullet
|
||||
import leenkx.trait.physics.bullet.PhysicsConstraint.ConstraintType;
|
||||
#elseif lnx_jolt
|
||||
import leenkx.trait.physics.jolt.PhysicsConstraint.ConstraintType;
|
||||
#else
|
||||
import leenkx.trait.physics.oimo.PhysicsConstraint.ConstraintType;
|
||||
#end
|
||||
#end
|
||||
|
||||
class AddPhysicsConstraintNode extends LogicNode {
|
||||
|
||||
public var property0: String;//Type
|
||||
@ -27,7 +30,7 @@ class AddPhysicsConstraintNode extends LogicNode {
|
||||
|
||||
if (pivotObject == null || rb1 == null || rb2 == null) return;
|
||||
|
||||
#if lnx_bullet
|
||||
#if lnx_physics
|
||||
|
||||
var disableCollisions: Bool = inputs[4].get();
|
||||
var breakable: Bool = inputs[5].get();
|
||||
@ -110,8 +113,6 @@ class AddPhysicsConstraintNode extends LogicNode {
|
||||
}
|
||||
pivotObject.addTrait(con);
|
||||
}
|
||||
#elseif lnx_oimo
|
||||
// TODO
|
||||
#end
|
||||
runOutput(0);
|
||||
}
|
||||
|
||||
@ -37,86 +37,90 @@ class CreateLeenkxNode extends LogicNode {
|
||||
|
||||
function onEvent() {
|
||||
#if js
|
||||
var window:haxe.DynamicAccess<Dynamic> = untyped js.Browser.window;
|
||||
var lxCxNew = window.get('lxNew');
|
||||
var lnxjs:Dynamic = js.Lib.global;
|
||||
var lnxCxNew = lnxjs.lnxNew;
|
||||
|
||||
if (lnxCxNew == null) {
|
||||
trace("ERROR: lnxNew not available");
|
||||
return;
|
||||
}
|
||||
|
||||
var lxCn:Dynamic = lxCxNew(net_Url);
|
||||
|
||||
lxCn.on("connections", function(c) {
|
||||
leenkx.network.Leenkx.data.set(net_Url, c + 1);
|
||||
leenkx.network.Leenkx.connections[net_Url].onconnections();
|
||||
});
|
||||
lxCn.on("message", function(address,message) {
|
||||
leenkx.network.Leenkx.data.set(net_Url, message);
|
||||
leenkx.network.Leenkx.id.set(net_Url, address);
|
||||
leenkx.network.Leenkx.connections[net_Url].onmessage();
|
||||
});
|
||||
lxCn.on("seen", function(address) {
|
||||
leenkx.network.Leenkx.id.set(net_Url, address);
|
||||
leenkx.network.Leenkx.connections[net_Url].onseen();
|
||||
});
|
||||
lxCn.on("left", function(address) {
|
||||
leenkx.network.Leenkx.id.set(net_Url, address);
|
||||
leenkx.network.Leenkx.connections[net_Url].onleft();
|
||||
});
|
||||
lxCn.on("server", function(address) {
|
||||
leenkx.network.Leenkx.id.set(net_Url, address);
|
||||
leenkx.network.Leenkx.connections[net_Url].onserver();
|
||||
});
|
||||
lxCn.on("ping", function(address) {
|
||||
leenkx.network.Leenkx.id.set(net_Url, address);
|
||||
leenkx.network.Leenkx.connections[net_Url].onping();
|
||||
});
|
||||
lxCn.on("timeout", function(address) {
|
||||
leenkx.network.Leenkx.id.set(net_Url, address);
|
||||
leenkx.network.Leenkx.connections[net_Url].ontimeout();
|
||||
});
|
||||
lxCn.on("rpc", function(address, call, args, nonce) {
|
||||
leenkx.network.Leenkx.data.set(net_Url, call);
|
||||
leenkx.network.Leenkx.id.set(net_Url, address);
|
||||
call(args);
|
||||
leenkx.network.Leenkx.connections[net_Url].onrpc();
|
||||
});
|
||||
lxCn.on("rpc-response", function(address, nonce, response) {
|
||||
leenkx.network.Leenkx.id.set(net_Url, address);
|
||||
leenkx.network.Leenkx.connections[net_Url].onrpcresponse();
|
||||
});
|
||||
lxCn.on("wireleft", function(wirecount, wire) {
|
||||
leenkx.network.Leenkx.data.set(net_Url, wirecount);
|
||||
leenkx.network.Leenkx.id.set(net_Url, wire.peerId);
|
||||
leenkx.network.Leenkx.connections[net_Url].onwireleft();
|
||||
});
|
||||
lxCn.on("wireseen", function(wirecount, wire) {
|
||||
leenkx.network.Leenkx.data.set(net_Url, wirecount);
|
||||
leenkx.network.Leenkx.id.set(net_Url, wire.peerId);
|
||||
leenkx.network.Leenkx.connections[net_Url].onwireseen();
|
||||
});
|
||||
lxCn.on("torrent", function(identifier, torrent) {
|
||||
leenkx.network.Leenkx.data.set(net_Url, torrent);
|
||||
leenkx.network.Leenkx.id.set(net_Url, identifier);
|
||||
leenkx.network.Leenkx.connections[net_Url].ontorrent();
|
||||
});
|
||||
|
||||
lxCn.on("tracker", function(identifier) {
|
||||
leenkx.network.Leenkx.id.set(net_Url, identifier);
|
||||
leenkx.network.Leenkx.connections[net_Url].ontracker();
|
||||
});
|
||||
|
||||
lxCn.on("announce", function(identifier) {
|
||||
leenkx.network.Leenkx.id.set(net_Url, identifier);
|
||||
leenkx.network.Leenkx.connections[net_Url].onannounce();
|
||||
});
|
||||
window.set("lx_" + net_Url, lxCn);
|
||||
Leenkx.connections[net_Url].client = lxCn;
|
||||
var script = '
|
||||
window.addEventListener("beforeunload", function (e) {
|
||||
leenkx.network.Leenkx.connections.h["' + net_Url + '"].client.destroy();
|
||||
delete e["returnValue"];
|
||||
});
|
||||
';
|
||||
js.Syntax.code('(1, eval)({0})', script.toString());
|
||||
runOutput(0);
|
||||
#end
|
||||
var lnxCn:Dynamic = lnxCxNew(net_Url);
|
||||
|
||||
lnxCn.on("connections", function(c) {
|
||||
leenkx.network.Leenkx.data.set(net_Url, c + 1);
|
||||
leenkx.network.Leenkx.connections[net_Url].onconnections();
|
||||
});
|
||||
lnxCn.on("message", function(address, message) {
|
||||
leenkx.network.Leenkx.data.set(net_Url, message);
|
||||
leenkx.network.Leenkx.id.set(net_Url, address);
|
||||
leenkx.network.Leenkx.connections[net_Url].onmessage();
|
||||
});
|
||||
lnxCn.on("seen", function(address) {
|
||||
leenkx.network.Leenkx.id.set(net_Url, address);
|
||||
leenkx.network.Leenkx.connections[net_Url].onseen();
|
||||
});
|
||||
lnxCn.on("left", function(address) {
|
||||
leenkx.network.Leenkx.id.set(net_Url, address);
|
||||
leenkx.network.Leenkx.connections[net_Url].onleft();
|
||||
});
|
||||
lnxCn.on("server", function(address) {
|
||||
leenkx.network.Leenkx.id.set(net_Url, address);
|
||||
leenkx.network.Leenkx.connections[net_Url].onserver();
|
||||
});
|
||||
lnxCn.on("ping", function(address) {
|
||||
leenkx.network.Leenkx.id.set(net_Url, address);
|
||||
leenkx.network.Leenkx.connections[net_Url].onping();
|
||||
});
|
||||
lnxCn.on("timeout", function(address) {
|
||||
leenkx.network.Leenkx.id.set(net_Url, address);
|
||||
leenkx.network.Leenkx.connections[net_Url].ontimeout();
|
||||
});
|
||||
lnxCn.on("rpc", function(address, call, args, nonce) {
|
||||
leenkx.network.Leenkx.data.set(net_Url, call);
|
||||
leenkx.network.Leenkx.id.set(net_Url, address);
|
||||
call(args);
|
||||
leenkx.network.Leenkx.connections[net_Url].onrpc();
|
||||
});
|
||||
lnxCn.on("rpc-response", function(address, nonce, response) {
|
||||
leenkx.network.Leenkx.id.set(net_Url, address);
|
||||
leenkx.network.Leenkx.connections[net_Url].onrpcresponse();
|
||||
});
|
||||
lnxCn.on("wireleft", function(wirecount, wire) {
|
||||
leenkx.network.Leenkx.data.set(net_Url, wirecount);
|
||||
leenkx.network.Leenkx.id.set(net_Url, wire.peerId);
|
||||
leenkx.network.Leenkx.connections[net_Url].onwireleft();
|
||||
});
|
||||
lnxCn.on("wireseen", function(wirecount, wire) {
|
||||
leenkx.network.Leenkx.data.set(net_Url, wirecount);
|
||||
leenkx.network.Leenkx.id.set(net_Url, wire.peerId);
|
||||
leenkx.network.Leenkx.connections[net_Url].onwireseen();
|
||||
});
|
||||
lnxCn.on("torrent", function(identifier, torrent) {
|
||||
leenkx.network.Leenkx.data.set(net_Url, torrent);
|
||||
leenkx.network.Leenkx.id.set(net_Url, identifier);
|
||||
leenkx.network.Leenkx.connections[net_Url].ontorrent();
|
||||
});
|
||||
lnxCn.on("tracker", function(identifier) {
|
||||
leenkx.network.Leenkx.id.set(net_Url, identifier);
|
||||
leenkx.network.Leenkx.connections[net_Url].ontracker();
|
||||
});
|
||||
lnxCn.on("announce", function(identifier) {
|
||||
leenkx.network.Leenkx.id.set(net_Url, identifier);
|
||||
leenkx.network.Leenkx.connections[net_Url].onannounce();
|
||||
});
|
||||
|
||||
Reflect.setField(lnxjs, "lnx_" + net_Url, lnxCn);
|
||||
Leenkx.connections[net_Url].client = lnxCn;
|
||||
|
||||
var script = 'globalThis.addEventListener("beforeunload", function (e) {
|
||||
leenkx.network.Leenkx.connections.h["' + net_Url + '"].client.destroy();
|
||||
delete e["returnValue"];
|
||||
});';
|
||||
js.Syntax.code('(1, eval)({0})', script);
|
||||
|
||||
runOutput(0);
|
||||
#end
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -32,10 +32,15 @@ class LeenkxCloseConnectionNode extends LogicNode {
|
||||
} else {
|
||||
var script = '
|
||||
try{
|
||||
leenkx.network.Leenkx.connections.h["1008"].client.torrent._peers[p].conn._pc.close();
|
||||
leenkx.network.Leenkx.connections.h["1008"].client.torrent._peers[p].conn.destroy();
|
||||
leenkx.network.Leenkx.id.set("1008",p);
|
||||
leenkx.network.Leenkx.connections.h["1008"].onclose();
|
||||
var lnxConn = leenkx.network.Leenkx.connections.h["' + connection._url + '"];
|
||||
if (lnxConn && lnxConn.client && lnxConn.client.torrent && lnxConn.client.torrent._peers) {
|
||||
for (var p in lnxConn.client.torrent._peers) {
|
||||
lnxConn.client.torrent._peers[p].conn._pc.close();
|
||||
lnxConn.client.torrent._peers[p].conn.destroy();
|
||||
leenkx.network.Leenkx.id.set("' + connection._url + '", p);
|
||||
lnxConn.onclose();
|
||||
}
|
||||
}
|
||||
}catch(error){
|
||||
console.log("Error: " + error);
|
||||
}
|
||||
|
||||
@ -40,7 +40,7 @@ class LeenkxEventNode extends LogicNode {
|
||||
default: throw "Failed to set client event type.";
|
||||
}
|
||||
} else if (property0 == "host") {
|
||||
#if sys
|
||||
#if (sys || kha_krom)
|
||||
var net_Domain = inputs[0].get();
|
||||
var net_Port = inputs[1].get();
|
||||
net_Url = "ws://" + net_Domain + ":" + Std.string(net_Port);
|
||||
@ -53,7 +53,7 @@ class LeenkxEventNode extends LogicNode {
|
||||
}
|
||||
#end
|
||||
} else if (property0 == "securehost"){
|
||||
#if sys
|
||||
#if (sys || kha_krom)
|
||||
var net_Domain = inputs[0].get();
|
||||
var net_Port = inputs[1].get();
|
||||
net_Url = "wss://" + net_Domain + ":" + Std.string(net_Port);
|
||||
|
||||
@ -27,9 +27,9 @@ class LeenkxSendMessageNode extends LogicNode {
|
||||
|
||||
try {
|
||||
if(inputs[5].get() == true){
|
||||
var script = 'for (p in lx_' + connection._url +'.torrent._peers){
|
||||
var script = 'for (p in lnx_' + connection._url +'.torrent._peers){
|
||||
try{
|
||||
lx_' + connection._url +'.torrent._peers[p].conn.send(`'+ api + message + '`);
|
||||
lnx_' + connection._url +'.torrent._peers[p].conn.send(`'+ api + message + '`);
|
||||
}catch(error){
|
||||
console.log("Error: " + error);
|
||||
}
|
||||
@ -37,7 +37,7 @@ class LeenkxSendMessageNode extends LogicNode {
|
||||
js.Syntax.code('(1, eval)({0})', script.toString());
|
||||
runOutput(0);
|
||||
} else {
|
||||
var script = 'lx_' + connection._url +'.send(`' + inputs[4].get() + '`, `'+ api + message + '` );';
|
||||
var script = 'lnx_' + connection._url +'.send(`' + inputs[4].get() + '`, `'+ api + message + '` );';
|
||||
js.Syntax.code('(1, eval)({0})', script.toString());
|
||||
runOutput(0);
|
||||
}
|
||||
@ -58,9 +58,9 @@ class LeenkxSendMessageNode extends LogicNode {
|
||||
try {
|
||||
connection.buffer = buffer;
|
||||
if(inputs[5].get() == true){
|
||||
var script = 'for (p in lx_' + connection._url +'.torrent._peers){
|
||||
var script = 'for (p in lnx_' + connection._url +'.torrent._peers){
|
||||
try{
|
||||
lx_' + connection._url +'.torrent._peers[p].conn.send(leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );
|
||||
lnx_' + connection._url +'.torrent._peers[p].conn.send(leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );
|
||||
}catch(error){
|
||||
console.log("Error: " + error);
|
||||
}
|
||||
@ -68,7 +68,7 @@ class LeenkxSendMessageNode extends LogicNode {
|
||||
js.Syntax.code('(1, eval)({0})', script.toString());
|
||||
runOutput(0);
|
||||
} else {
|
||||
var script = 'lx_' + connection._url +'.send("' + inputs[4].get() + '",leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );';
|
||||
var script = 'lnx_' + connection._url +'.send("' + inputs[4].get() + '",leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );';
|
||||
js.Syntax.code('(1, eval)({0})', script.toString());
|
||||
runOutput(0);
|
||||
}
|
||||
@ -77,15 +77,15 @@ class LeenkxSendMessageNode extends LogicNode {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var window:haxe.DynamicAccess<Dynamic> = untyped js.Browser.window;
|
||||
var lxCn = window.get('lx_' + connection._url);
|
||||
if(inputs[5].get() == true){
|
||||
lxCn.send(api+message);
|
||||
}else{
|
||||
lxCn.send(inputs[4].get(), api+message);
|
||||
}
|
||||
runOutput(0);
|
||||
return;
|
||||
if(inputs[5].get() == true){
|
||||
var script = 'lnx_' + connection._url + '.send(`' + api + message + '`);';
|
||||
js.Syntax.code('(1, eval)({0})', script);
|
||||
}else{
|
||||
var script = 'lnx_' + connection._url + '.send(`' + inputs[4].get() + '`, `' + api + message + '`);';
|
||||
js.Syntax.code('(1, eval)({0})', script);
|
||||
}
|
||||
runOutput(0);
|
||||
return;
|
||||
}
|
||||
case "vector":
|
||||
if(property0 == "client"){
|
||||
@ -103,9 +103,9 @@ class LeenkxSendMessageNode extends LogicNode {
|
||||
try {
|
||||
connection.buffer = buffer;
|
||||
if(inputs[5].get() == true){
|
||||
var script = 'for (p in lx_' + connection._url +'.torrent._peers){
|
||||
var script = 'for (p in lnx_' + connection._url +'.torrent._peers){
|
||||
try{
|
||||
lx_' + connection._url +'.torrent._peers[p].conn.send("' + api + message + '");
|
||||
lnx_' + connection._url +'.torrent._peers[p].conn.send("' + api + message + '");
|
||||
}catch(error){
|
||||
console.log("Error: " + error);
|
||||
}
|
||||
@ -113,7 +113,7 @@ class LeenkxSendMessageNode extends LogicNode {
|
||||
js.Syntax.code('(1, eval)({0})', script.toString());
|
||||
runOutput(0);
|
||||
} else {
|
||||
var script = 'lx_' + connection._url +'.send("' + inputs[4].get() + '",leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );';
|
||||
var script = 'lnx_' + connection._url +'.send("' + inputs[4].get() + '",leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );';
|
||||
js.Syntax.code('(1, eval)({0})', script.toString());
|
||||
runOutput(0);
|
||||
}
|
||||
@ -121,12 +121,12 @@ class LeenkxSendMessageNode extends LogicNode {
|
||||
trace("Error: " + error);
|
||||
}
|
||||
} else {
|
||||
var window:haxe.DynamicAccess<Dynamic> = untyped js.Browser.window;
|
||||
var lxCn = window.get('lx_' + connection._url);
|
||||
if(inputs[5].get() == true){
|
||||
lxCn.send(api+message);
|
||||
var script = 'lnx_' + connection._url + '.send(`' + api + message + '`);';
|
||||
js.Syntax.code('(1, eval)({0})', script);
|
||||
}else{
|
||||
lxCn.send(inputs[4].get(), api+message);
|
||||
var script = 'lnx_' + connection._url + '.send(`' + inputs[4].get() + '`, `' + api + message + '`);';
|
||||
js.Syntax.code('(1, eval)({0})', script);
|
||||
}
|
||||
runOutput(0);
|
||||
return;
|
||||
@ -143,9 +143,9 @@ class LeenkxSendMessageNode extends LogicNode {
|
||||
try {
|
||||
connection.buffer = buffer;
|
||||
if(inputs[5].get() == true){
|
||||
var script = 'for (p in lx_' + connection._url +'.torrent._peers){
|
||||
var script = 'for (p in lnx_' + connection._url +'.torrent._peers){
|
||||
try{
|
||||
lx_' + connection._url +'.torrent._peers[p].conn.send(leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );
|
||||
lnx_' + connection._url +'.torrent._peers[p].conn.send(leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );
|
||||
}catch(error){
|
||||
console.log("Error: " + error);
|
||||
}
|
||||
@ -153,7 +153,7 @@ class LeenkxSendMessageNode extends LogicNode {
|
||||
js.Syntax.code('(1, eval)({0})', script.toString());
|
||||
runOutput(0);
|
||||
} else {
|
||||
var script = 'lx_' + connection._url +'.send("' + inputs[4].get() + '",leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );';
|
||||
var script = 'lnx_' + connection._url +'.send("' + inputs[4].get() + '",leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );';
|
||||
js.Syntax.code('(1, eval)({0})', script.toString());
|
||||
runOutput(0);
|
||||
}
|
||||
@ -161,12 +161,12 @@ class LeenkxSendMessageNode extends LogicNode {
|
||||
trace("Error: " + error);
|
||||
}
|
||||
} else {
|
||||
var window:haxe.DynamicAccess<Dynamic> = untyped js.Browser.window;
|
||||
var lxCn = window.get('lx_' + connection._url);
|
||||
if(inputs[5].get() == true){
|
||||
lxCn.send(api+message);
|
||||
var script = 'lnx_' + connection._url + '.send(`' + api + message + '`);';
|
||||
js.Syntax.code('(1, eval)({0})', script);
|
||||
}else{
|
||||
lxCn.send(inputs[4].get(), api+message);
|
||||
var script = 'lnx_' + connection._url + '.send(`' + inputs[4].get() + '`, `' + api + message + '`);';
|
||||
js.Syntax.code('(1, eval)({0})', script);
|
||||
}
|
||||
runOutput(0);
|
||||
return;
|
||||
@ -183,9 +183,9 @@ class LeenkxSendMessageNode extends LogicNode {
|
||||
try {
|
||||
connection.buffer = buffer;
|
||||
if(inputs[5].get() == true){
|
||||
var script = 'for (p in lx_' + connection._url +'.torrent._peers){
|
||||
var script = 'for (p in lnx_' + connection._url +'.torrent._peers){
|
||||
try{
|
||||
lx_' + connection._url +'.torrent._peers[p].conn.send(leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );
|
||||
lnx_' + connection._url +'.torrent._peers[p].conn.send(leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );
|
||||
}catch(error){
|
||||
console.log("Error: " + error);
|
||||
}
|
||||
@ -193,7 +193,7 @@ class LeenkxSendMessageNode extends LogicNode {
|
||||
js.Syntax.code('(1, eval)({0})', script.toString());
|
||||
runOutput(0);
|
||||
} else {
|
||||
var script = 'lx_' + connection._url +'.send("' + inputs[4].get() + '",leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );';
|
||||
var script = 'lnx_' + connection._url +'.send("' + inputs[4].get() + '",leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );';
|
||||
js.Syntax.code('(1, eval)({0})', script.toString());
|
||||
runOutput(0);
|
||||
}
|
||||
@ -201,12 +201,12 @@ class LeenkxSendMessageNode extends LogicNode {
|
||||
trace("Error: " + error);
|
||||
}
|
||||
} else {
|
||||
var window:haxe.DynamicAccess<Dynamic> = untyped js.Browser.window;
|
||||
var lxCn = window.get('lx_' + connection._url);
|
||||
if(inputs[5].get() == true){
|
||||
lxCn.send(api+message);
|
||||
var script = 'lnx_' + connection._url + '.send(`' + api + message + '`);';
|
||||
js.Syntax.code('(1, eval)({0})', script);
|
||||
}else{
|
||||
lxCn.send(inputs[4].get(), api+message);
|
||||
var script = 'lnx_' + connection._url + '.send(`' + inputs[4].get() + '`, `' + api + message + '`);';
|
||||
js.Syntax.code('(1, eval)({0})', script);
|
||||
}
|
||||
runOutput(0);
|
||||
return;
|
||||
@ -225,9 +225,9 @@ class LeenkxSendMessageNode extends LogicNode {
|
||||
try {
|
||||
connection.buffer = buffer;
|
||||
if(inputs[5].get() == true){
|
||||
var script = 'for (p in lx_' + connection._url +'.torrent._peers){
|
||||
var script = 'for (p in lnx_' + connection._url +'.torrent._peers){
|
||||
try{
|
||||
lx_' + connection._url +'.torrent._peers[p].conn.send(leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );
|
||||
lnx_' + connection._url +'.torrent._peers[p].conn.send(leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );
|
||||
}catch(error){
|
||||
console.log("Error: " + error);
|
||||
}
|
||||
@ -235,7 +235,7 @@ class LeenkxSendMessageNode extends LogicNode {
|
||||
js.Syntax.code('(1, eval)({0})', script.toString());
|
||||
runOutput(0);
|
||||
} else {
|
||||
var script = 'lx_' + connection._url +'.send("' + inputs[4].get() + '",leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );';
|
||||
var script = 'lnx_' + connection._url +'.send("' + inputs[4].get() + '",leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );';
|
||||
js.Syntax.code('(1, eval)({0})', script.toString());
|
||||
runOutput(0);
|
||||
}
|
||||
@ -243,12 +243,12 @@ class LeenkxSendMessageNode extends LogicNode {
|
||||
trace("Error: " + error);
|
||||
}
|
||||
} else {
|
||||
var window:haxe.DynamicAccess<Dynamic> = untyped js.Browser.window;
|
||||
var lxCn = window.get('lx_' + connection._url);
|
||||
if(inputs[5].get() == true){
|
||||
lxCn.send(api+message);
|
||||
var script = 'lnx_' + connection._url + '.send(`' + api + message + '`);';
|
||||
js.Syntax.code('(1, eval)({0})', script);
|
||||
}else{
|
||||
lxCn.send(inputs[4].get(), api+message);
|
||||
var script = 'lnx_' + connection._url + '.send(`' + inputs[4].get() + '`, `' + api + message + '`);';
|
||||
js.Syntax.code('(1, eval)({0})', script);
|
||||
}
|
||||
runOutput(0);
|
||||
return;
|
||||
@ -283,10 +283,10 @@ class LeenkxSendMessageNode extends LogicNode {
|
||||
connection.buffer = buffer;
|
||||
|
||||
if(inputs[5].get() == true){
|
||||
var script = 'for (p in lx_' + connection._url +'.torrent._peers){
|
||||
var script = 'for (p in lnx_' + connection._url +'.torrent._peers){
|
||||
try{
|
||||
//console.log("Mine: " + lx_8001.torrent.discovery.peerId + " || Incomming: " + p);
|
||||
lx_' + connection._url +'.torrent._peers[p].conn.send(leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );
|
||||
lnx_' + connection._url +'.torrent._peers[p].conn.send(leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );
|
||||
}catch(error){
|
||||
console.log("Error: " + error);
|
||||
}
|
||||
@ -294,8 +294,8 @@ class LeenkxSendMessageNode extends LogicNode {
|
||||
js.Syntax.code('(1, eval)({0})', script.toString());
|
||||
runOutput(0);
|
||||
} else {
|
||||
//var script = 'lx_' + connection._url +'.send("' + inputs[4].get() + '",leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );';
|
||||
var script = 'lx_' + connection._url +'.send(leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );';
|
||||
//var script = 'lnx_' + connection._url +'.send("' + inputs[4].get() + '",leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );';
|
||||
var script = 'lnx_' + connection._url +'.send(leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );';
|
||||
js.Syntax.code('(1, eval)({0})', script.toString());
|
||||
runOutput(0);
|
||||
}
|
||||
@ -319,9 +319,9 @@ class LeenkxSendMessageNode extends LogicNode {
|
||||
try {
|
||||
connection.buffer = buffer;
|
||||
if(inputs[5].get() == true){
|
||||
var script = 'for (p in lx_' + connection._url +'.torrent._peers){
|
||||
var script = 'for (p in lnx_' + connection._url +'.torrent._peers){
|
||||
try{
|
||||
lx_' + connection._url +'.torrent._peers[p].conn.send(leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );
|
||||
lnx_' + connection._url +'.torrent._peers[p].conn.send(leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );
|
||||
}catch(error){
|
||||
console.log("Error: " + error);
|
||||
}
|
||||
@ -329,7 +329,7 @@ class LeenkxSendMessageNode extends LogicNode {
|
||||
js.Syntax.code('(1, eval)({0})', script.toString());
|
||||
runOutput(0);
|
||||
} else {
|
||||
var script = 'lx_' + connection._url +'.send("' + inputs[4].get() + '",leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );';
|
||||
var script = 'lnx_' + connection._url +'.send("' + inputs[4].get() + '",leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );';
|
||||
js.Syntax.code('(1, eval)({0})', script.toString());
|
||||
runOutput(0);
|
||||
}
|
||||
@ -337,12 +337,12 @@ class LeenkxSendMessageNode extends LogicNode {
|
||||
trace("Error: " + error);
|
||||
}
|
||||
} else {
|
||||
var window:haxe.DynamicAccess<Dynamic> = untyped js.Browser.window;
|
||||
var lxCn = window.get('lx_' + connection._url);
|
||||
if(inputs[5].get() == true){
|
||||
lxCn.send(api+message);
|
||||
var script = 'lnx_' + connection._url + '.send(`' + api + message + '`);';
|
||||
js.Syntax.code('(1, eval)({0})', script);
|
||||
}else{
|
||||
lxCn.send(inputs[4].get(), api+message);
|
||||
var script = 'lnx_' + connection._url + '.send(`' + inputs[4].get() + '`, `' + api + message + '`);';
|
||||
js.Syntax.code('(1, eval)({0})', script);
|
||||
}
|
||||
runOutput(0);
|
||||
return;
|
||||
@ -360,9 +360,9 @@ class LeenkxSendMessageNode extends LogicNode {
|
||||
try {
|
||||
|
||||
if(inputs[5].get() == true){
|
||||
var script = 'for (p in lx_' + connection._url +'.torrent._peers){
|
||||
var script = 'for (p in lnx_' + connection._url +'.torrent._peers){
|
||||
try{
|
||||
lx_' + connection._url +'.torrent._peers[p].conn.send("' + api + message + '");
|
||||
lnx_' + connection._url +'.torrent._peers[p].conn.send("' + api + message + '");
|
||||
}catch(error){
|
||||
console.log("Error: " + error);
|
||||
}
|
||||
@ -370,7 +370,7 @@ class LeenkxSendMessageNode extends LogicNode {
|
||||
js.Syntax.code('(1, eval)({0})', script.toString());
|
||||
runOutput(0);
|
||||
} else {
|
||||
var script = 'for (p in lx_' + connection._url +'.torrent._peers){
|
||||
var script = 'for (p in lnx_' + connection._url +'.torrent._peers){
|
||||
try{
|
||||
|
||||
lx_' + connection._url +'.torrent._peers[' + inputs[4].get() + '].conn.send("' + api + message + '");
|
||||
|
||||
@ -16,7 +16,7 @@ class NetworkCloseConnectionNode extends LogicNode {
|
||||
if(property1 == "client") {
|
||||
var connection = cast(inputs[1].get(), leenkx.network.WebSocket);
|
||||
if (connection == null) return;
|
||||
#if sys
|
||||
#if (sys || kha_krom)
|
||||
try{
|
||||
var net_Url = connection._protocol + "://" + connection._host + ":" + connection._port;
|
||||
connection.close();
|
||||
@ -44,7 +44,7 @@ class NetworkCloseConnectionNode extends LogicNode {
|
||||
}
|
||||
#end
|
||||
} else if(property1 == "securehost"){
|
||||
#if sys
|
||||
#if (sys || kha_krom)
|
||||
var connection = cast(inputs[1].get(), leenkx.network.WebSocketSecureServer<leenkx.network.Connect.HostHandler>);
|
||||
if (connection == null) return;
|
||||
var net_Url = "wss://" + @:privateAccess connection._host + ":" + @:privateAccess connection._port;
|
||||
@ -56,7 +56,7 @@ class NetworkCloseConnectionNode extends LogicNode {
|
||||
}
|
||||
#end
|
||||
} else {
|
||||
#if sys
|
||||
#if (sys || kha_krom)
|
||||
var connection = cast(inputs[1].get(), leenkx.network.WebSocketServer<leenkx.network.Connect.HostHandler>);
|
||||
if (connection == null) return;
|
||||
var net_Url = "ws://" + @:privateAccess connection._host + ":" + @:privateAccess connection._port;
|
||||
|
||||
@ -28,7 +28,7 @@ class NetworkEventNode extends LogicNode {
|
||||
default: throw "Failed to set client event type.";
|
||||
}
|
||||
} else if (property0 == "host") {
|
||||
#if sys
|
||||
#if (sys || kha_krom)
|
||||
var net_Domain = inputs[0].get();
|
||||
var net_Port = inputs[1].get();
|
||||
net_Url = "ws://" + net_Domain + ":" + Std.string(net_Port);
|
||||
@ -41,7 +41,7 @@ class NetworkEventNode extends LogicNode {
|
||||
}
|
||||
#end
|
||||
} else if (property0 == "securehost"){
|
||||
#if sys
|
||||
#if (sys || kha_krom)
|
||||
var net_Domain = inputs[0].get();
|
||||
var net_Port = inputs[1].get();
|
||||
net_Url = "wss://" + net_Domain + ":" + Std.string(net_Port);
|
||||
|
||||
@ -11,7 +11,7 @@ class NetworkHostCloseClientNode extends LogicNode {
|
||||
}
|
||||
|
||||
override function run(from:Int) {
|
||||
#if sys
|
||||
#if (sys || kha_krom)
|
||||
if(property0 == false){
|
||||
var connection = cast(inputs[1].get(), leenkx.network.WebSocketServer<HostHandler>);
|
||||
if (connection == null) return;
|
||||
|
||||
@ -12,7 +12,7 @@ class NetworkHostGetIpNode extends LogicNode {
|
||||
}
|
||||
|
||||
override function run(from:Int) {
|
||||
#if sys
|
||||
#if (sys || kha_krom)
|
||||
if(property0 == false){
|
||||
var connection = cast(inputs[1].get(), leenkx.network.WebSocketServer<HostHandler>);
|
||||
if (connection == null) return;
|
||||
|
||||
@ -12,7 +12,7 @@ class NetworkHostNode extends LogicNode {
|
||||
}
|
||||
|
||||
override function run(from:Int) {
|
||||
#if sys
|
||||
#if (sys || kha_krom)
|
||||
if(property0 == false) {
|
||||
final net_Object: Object = tree.object;
|
||||
var net_Domain: String = inputs[1].get();
|
||||
@ -49,7 +49,7 @@ class NetworkHostNode extends LogicNode {
|
||||
#end
|
||||
}
|
||||
|
||||
#if sys
|
||||
#if (sys || kha_krom)
|
||||
override function get(from: Int): Dynamic {
|
||||
if(property0 == false) {
|
||||
return switch (from) {
|
||||
|
||||
@ -85,10 +85,6 @@ class NetworkHttpRequestNode extends LogicNode {
|
||||
} catch( e : Dynamic ) {
|
||||
trace("Could not complete request: " + e);
|
||||
}
|
||||
|
||||
callbackType = 0;
|
||||
runOutput(0);
|
||||
|
||||
}
|
||||
|
||||
override function get(from: Int): Dynamic {
|
||||
|
||||
@ -19,7 +19,7 @@ class NetworkOpenConnectionNode extends LogicNode {
|
||||
var connection = cast(inputs[1].get(), leenkx.network.WebSocket);
|
||||
if (connection == null) return;
|
||||
var object = tree.object;
|
||||
#if sys
|
||||
#if (sys || kha_krom)
|
||||
net_Url = connection._protocol + "://" + connection._host + ":" + connection._port;
|
||||
Client.connections[net_Url] = null;
|
||||
var client = new leenkx.network.Connect.Client(net_Url, object);
|
||||
@ -94,7 +94,7 @@ class NetworkOpenConnectionNode extends LogicNode {
|
||||
#end
|
||||
runOutput(0);
|
||||
} else if (property0 == "securehost"){
|
||||
#if sys
|
||||
#if (sys || kha_krom)
|
||||
var connection = cast(inputs[1].get(), leenkx.network.WebSocketSecureServer<leenkx.network.Connect.HostHandler>);
|
||||
if (connection == null) return;
|
||||
net_Url = "wss://" + @:privateAccess connection._host + ":" + @:privateAccess connection._port;
|
||||
@ -108,7 +108,7 @@ class NetworkOpenConnectionNode extends LogicNode {
|
||||
runOutput(0);
|
||||
#end
|
||||
} else {
|
||||
#if sys
|
||||
#if (sys || kha_krom)
|
||||
var connection = cast(inputs[1].get(), leenkx.network.WebSocketServer<leenkx.network.Connect.HostHandler>);
|
||||
if (connection == null) return;
|
||||
net_Url = "ws://" + @:privateAccess connection._host + ":" + @:privateAccess connection._port;
|
||||
@ -126,7 +126,7 @@ class NetworkOpenConnectionNode extends LogicNode {
|
||||
|
||||
override function get(from: Int): Dynamic {
|
||||
return switch (property0) {
|
||||
#if sys
|
||||
#if (sys || kha_krom)
|
||||
case "host": Host.connections[net_Url];
|
||||
case "securehost": SecureHost.connections[net_Url];
|
||||
#end
|
||||
|
||||
@ -38,7 +38,7 @@ class NetworkSendMessageNode extends LogicNode {
|
||||
trace("Error: " + error);
|
||||
}
|
||||
}
|
||||
#if sys
|
||||
#if (sys || kha_krom)
|
||||
else if(inputs[5].get() == true){
|
||||
if(property0 == "securehost"){
|
||||
var connection = cast(inputs[1].get(), leenkx.network.WebSocketSecureServer<SecureHostHandler>);
|
||||
@ -127,7 +127,7 @@ class NetworkSendMessageNode extends LogicNode {
|
||||
trace("Error: " + error);
|
||||
}
|
||||
}
|
||||
#if sys
|
||||
#if (sys || kha_krom)
|
||||
else if(inputs[5].get() == true){
|
||||
if(property0 == "securehost"){
|
||||
var connection = cast(inputs[1].get(), leenkx.network.WebSocketSecureServer<SecureHostHandler>);
|
||||
@ -233,7 +233,7 @@ class NetworkSendMessageNode extends LogicNode {
|
||||
trace("Error: " + error);
|
||||
}
|
||||
}
|
||||
#if sys
|
||||
#if (sys || kha_krom)
|
||||
else if(inputs[5].get() == true){
|
||||
if(property0 == "securehost"){
|
||||
var connection = cast(inputs[1].get(), leenkx.network.WebSocketSecureServer<SecureHostHandler>);
|
||||
@ -327,7 +327,7 @@ class NetworkSendMessageNode extends LogicNode {
|
||||
trace("Error: " + error);
|
||||
}
|
||||
}
|
||||
#if sys
|
||||
#if (sys || kha_krom)
|
||||
else if(inputs[5].get() == true){
|
||||
if(property0 == "securehost"){
|
||||
var connection = cast(inputs[1].get(), leenkx.network.WebSocketSecureServer<SecureHostHandler>);
|
||||
@ -423,7 +423,7 @@ class NetworkSendMessageNode extends LogicNode {
|
||||
trace("Error: " + error);
|
||||
}
|
||||
}
|
||||
#if sys
|
||||
#if (sys || kha_krom)
|
||||
else if(inputs[5].get() == true){
|
||||
if(property0 == "securehost"){
|
||||
var connection = cast(inputs[1].get(), leenkx.network.WebSocketSecureServer<SecureHostHandler>);
|
||||
@ -541,7 +541,7 @@ class NetworkSendMessageNode extends LogicNode {
|
||||
trace("Error: " + error);
|
||||
}
|
||||
}
|
||||
#if sys
|
||||
#if (sys || kha_krom)
|
||||
else if(inputs[5].get() == true){
|
||||
if(property0 == "securehost"){
|
||||
var connection = cast(inputs[1].get(), leenkx.network.WebSocketSecureServer<SecureHostHandler>);
|
||||
@ -702,7 +702,7 @@ class NetworkSendMessageNode extends LogicNode {
|
||||
trace("Error: " + error);
|
||||
}
|
||||
}
|
||||
#if sys
|
||||
#if (sys || kha_krom)
|
||||
else if(inputs[5].get() == true){
|
||||
if(property0 == "securehost"){
|
||||
var connection = cast(inputs[1].get(), leenkx.network.WebSocketSecureServer<SecureHostHandler>);
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
package leenkx.network;
|
||||
|
||||
#if sys
|
||||
#if (sys || kha_krom)
|
||||
import leenkx.network.WebSocketServer;
|
||||
import leenkx.network.WebSocketSecureServer;
|
||||
import leenkx.network.SocketImpl;
|
||||
#end
|
||||
#if sys
|
||||
import sys.ssl.Key;
|
||||
import sys.ssl.Certificate;
|
||||
import leenkx.network.SocketImpl;
|
||||
#end
|
||||
import leenkx.network.WebSocket;
|
||||
import leenkx.network.Types;
|
||||
@ -115,7 +117,7 @@ class Host extends Connect {
|
||||
public static var onErrorEvent: String = "Host.onError";
|
||||
public static var onCloseEvent: String = "Host.onClose";
|
||||
public static var object: Object = null;
|
||||
#if sys
|
||||
#if (sys || kha_krom)
|
||||
public static var connections:Map<String, WebSocketServer<HostHandler>> = [];
|
||||
#else
|
||||
public static var connections = null;
|
||||
@ -131,14 +133,15 @@ class Host extends Connect {
|
||||
object = net_object;
|
||||
net_Url = "ws://" + net_Domain + ":" + net_Port;
|
||||
|
||||
#if sys
|
||||
if (connections[net_Url] != null) return;
|
||||
connections[net_Url] = new WebSocketServer<HostHandler>(net_Domain, net_Port, net_Max);
|
||||
#if (sys || kha_krom)
|
||||
if (connections[net_Url] == null) {
|
||||
connections[net_Url] = new WebSocketServer<HostHandler>(net_Domain, net_Port, net_Max);
|
||||
}
|
||||
#end
|
||||
}
|
||||
}
|
||||
|
||||
#if sys
|
||||
#if (sys || kha_krom)
|
||||
class HostHandler extends WebSocketHandler {
|
||||
|
||||
public function new(s: SocketImpl) {
|
||||
@ -217,7 +220,7 @@ class SecureHost extends Connect {
|
||||
public static var onCloseEvent: String = "SecureHost.onClose";
|
||||
public static var object: Object = null;
|
||||
public static var net_Url: String;
|
||||
#if sys
|
||||
#if (sys || kha_krom)
|
||||
public static var connections:Map<String, WebSocketSecureServer<SecureHostHandler>> = [];
|
||||
#else
|
||||
public static var connections = null;
|
||||
@ -235,13 +238,18 @@ class SecureHost extends Connect {
|
||||
#if sys
|
||||
var cert = Certificate.loadFile(net_Cert);
|
||||
var key = Key.loadFile(net_Key);
|
||||
if (connections[net_Url] != null) return;
|
||||
connections[net_Url] = new WebSocketSecureServer<SecureHostHandler>(net_Domain, net_Port, cert, key, cert, net_Max);
|
||||
if (connections[net_Url] == null) {
|
||||
connections[net_Url] = new WebSocketSecureServer<SecureHostHandler>(net_Domain, net_Port, cert, key, cert, net_Max);
|
||||
}
|
||||
#elseif kha_krom
|
||||
if (connections[net_Url] == null) {
|
||||
connections[net_Url] = new WebSocketSecureServer<SecureHostHandler>(net_Domain, net_Port, net_Cert, net_Key, net_Cert, net_Max);
|
||||
}
|
||||
#end
|
||||
}
|
||||
}
|
||||
|
||||
#if sys
|
||||
#if (sys || kha_krom)
|
||||
class SecureHostHandler extends WebSocketHandler {
|
||||
|
||||
public function new(s: SocketImpl) {
|
||||
@ -313,3 +321,4 @@ class SecureHostHandler extends WebSocketHandler {
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
||||
|
||||
@ -2,7 +2,6 @@ package leenkx.network;
|
||||
|
||||
import leenkx.network.Types;
|
||||
import haxe.io.Bytes;
|
||||
import js.Browser;
|
||||
import iron.object.Object;
|
||||
import leenkx.system.Event;
|
||||
import leenkx.network.Buffer;
|
||||
@ -30,11 +29,13 @@ class Leenkx {
|
||||
public static var onAnnounceEvent: String = "Leenkx.onAnnounce";
|
||||
public static var onTorrentDoneEvent: String = "Leenkx.onTorrentDone";
|
||||
public static var connections:Map<String, leenkx.network.LeenkxSocket> = [];
|
||||
#if js
|
||||
public static var peers:js.lib.Map<String, String> = new js.lib.Map<String,String>();
|
||||
public static var data:js.lib.Map<String, Dynamic> = new js.lib.Map<String,Dynamic>();
|
||||
public static var id:js.lib.Map<String, String> = new js.lib.Map<String,String>();
|
||||
public static var torrent:js.lib.Map<String, Dynamic> = new js.lib.Map<String,Dynamic>();
|
||||
public static var file:js.lib.Map<String, Dynamic> = new js.lib.Map<String,Dynamic>();
|
||||
public static var file:js.lib.Map<String, Dynamic> = new js.lib.Map<String,Dynamic>();
|
||||
#end
|
||||
public static var lxNew:Void->Void;
|
||||
|
||||
public function new(net_Url: String, net_object: Object) {
|
||||
@ -53,10 +54,6 @@ class Leenkx {
|
||||
}
|
||||
|
||||
if (object != null) {
|
||||
//var script = "const scope = '/';const sw = navigator.serviceWorker.register(`LeenkxFS.js`, { scope });";
|
||||
//js.Syntax.code('(1, eval)({0})', script);
|
||||
|
||||
|
||||
final loadEvent = Event.get(Leenkx.onLoadEvent);
|
||||
final openEvent = Event.get(Leenkx.onOpenEvent);
|
||||
final messageEvent = Event.get(Leenkx.onMessageEvent);
|
||||
@ -77,197 +74,204 @@ class Leenkx {
|
||||
final announceEvent = Event.get(Leenkx.onAnnounceEvent);
|
||||
final torrentDoneEvent = Event.get(Leenkx.onTorrentDoneEvent);
|
||||
|
||||
Leenkx.connections[net_Url].onopen = function() {
|
||||
if (openEvent != null) {
|
||||
for (e in openEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Leenkx.connections[net_Url].onmessage = function() {
|
||||
if (messageEvent != null) {
|
||||
for (e in messageEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Leenkx.connections[net_Url].onerror = function() {
|
||||
if (errorEvent != null) {
|
||||
for (e in errorEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Leenkx.connections[net_Url].onclose = function() {
|
||||
if (closeEvent != null) {
|
||||
for (e in closeEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Leenkx.connections[net_Url].onseen = function() {
|
||||
if (seenEvent != null) {
|
||||
for (e in seenEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Leenkx.connections[net_Url].onserver = function() {
|
||||
if (serverEvent != null) {
|
||||
for (e in serverEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Leenkx.connections[net_Url].onconnections = function() {
|
||||
if (connectionsEvent != null) {
|
||||
for (e in connectionsEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Leenkx.connections[net_Url].onping = function() {
|
||||
if (pingEvent != null) {
|
||||
for (e in pingEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Leenkx.connections[net_Url].onleft = function() {
|
||||
if (leftEvent != null) {
|
||||
for (e in leftEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Leenkx.connections[net_Url].ontimeout = function() {
|
||||
if (timeoutEvent != null) {
|
||||
for (e in timeoutEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Leenkx.connections[net_Url].onrpc = function() {
|
||||
if (rpcEvent != null) {
|
||||
for (e in rpcEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Leenkx.connections[net_Url].onrpcresponse = function() {
|
||||
if (rpcresponseEvent != null) {
|
||||
for (e in rpcresponseEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Leenkx.connections[net_Url].onwireleft = function() {
|
||||
if (wireleftEvent != null) {
|
||||
for (e in wireleftEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Leenkx.connections[net_Url].onwireseen = function() {
|
||||
if (wireseenEvent != null) {
|
||||
for (e in wireseenEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Leenkx.connections[net_Url].ontorrent = function() {
|
||||
if (torrentEvent != null) {
|
||||
for (e in torrentEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Leenkx.connections[net_Url].ontracker = function() {
|
||||
if (trackerEvent != null) {
|
||||
for (e in trackerEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Leenkx.connections[net_Url].onannounce = function() {
|
||||
if (announceEvent != null) {
|
||||
for (e in announceEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Leenkx.connections[net_Url].onload = function() {
|
||||
if (loadEvent != null) {
|
||||
for (e in loadEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Leenkx.connections[net_Url].ontorrentdone = function() {
|
||||
if (torrentDoneEvent != null) {
|
||||
for (e in torrentDoneEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var lnkx = Browser.document.createScriptElement();
|
||||
lnkx.type = "text/javascript";
|
||||
lnkx.src = "Leenkx.js";
|
||||
lnkx.onload = function() {
|
||||
|
||||
js.Syntax.code('(1, eval)({0})', 'lxNew =function(url){ var cx = new Leenkx(url); return cx;}');
|
||||
|
||||
Leenkx.connections[net_Url].onopen = function() {
|
||||
if (openEvent != null) {
|
||||
for (e in openEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
#if js
|
||||
var lnxjs:Dynamic = js.Lib.global;
|
||||
var connectionUrl = net_Url;
|
||||
|
||||
if (lnxjs.Leenkx != null) {
|
||||
if (lnxjs.lnxNew == null) {
|
||||
js.Syntax.code('globalThis.lnxNew = function(url) { return new Leenkx(url); }');
|
||||
}
|
||||
Leenkx.connections[net_Url].onmessage = function() {
|
||||
if (messageEvent != null) {
|
||||
for (e in messageEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
Leenkx.connections[connectionUrl].onload();
|
||||
} else {
|
||||
kha.Assets.loadBlobFromPath("Leenkx.js", function(b: kha.Blob) {
|
||||
if (b != null) {
|
||||
js.Syntax.code("(1,eval)({0})", b.toString());
|
||||
if (lnxjs.Leenkx != null && lnxjs.lnxNew == null) {
|
||||
js.Syntax.code('globalThis.lnxNew = function(url) { return new Leenkx(url); }');
|
||||
}
|
||||
} else {
|
||||
trace("Warning: Leenkx.js blob is null - file may not be in assets");
|
||||
}
|
||||
}
|
||||
Leenkx.connections[net_Url].onerror = function() {
|
||||
if (errorEvent != null) {
|
||||
for (e in errorEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Leenkx.connections[net_Url].onclose = function() {
|
||||
if (closeEvent != null) {
|
||||
for (e in closeEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Leenkx.connections[net_Url].onseen = function() {
|
||||
if (seenEvent != null) {
|
||||
for (e in seenEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Leenkx.connections[net_Url].onserver = function() {
|
||||
if (serverEvent != null) {
|
||||
for (e in serverEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Leenkx.connections[net_Url].onconnections = function() {
|
||||
if (connectionsEvent != null) {
|
||||
for (e in connectionsEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Leenkx.connections[net_Url].onping = function() {
|
||||
if (pingEvent != null) {
|
||||
for (e in pingEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Leenkx.connections[net_Url].onleft = function() {
|
||||
if (leftEvent != null) {
|
||||
for (e in leftEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Leenkx.connections[net_Url].ontimeout = function() {
|
||||
if (timeoutEvent != null) {
|
||||
for (e in timeoutEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Leenkx.connections[net_Url].onrpc = function() {
|
||||
if (rpcEvent != null) {
|
||||
for (e in rpcEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Leenkx.connections[net_Url].onrpcresponse = function() {
|
||||
if (rpcresponseEvent != null) {
|
||||
for (e in rpcresponseEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Leenkx.connections[net_Url].onwireleft = function() {
|
||||
if (wireleftEvent != null) {
|
||||
for (e in wireleftEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Leenkx.connections[net_Url].onwireseen = function() {
|
||||
if (wireseenEvent != null) {
|
||||
for (e in wireseenEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Leenkx.connections[net_Url].ontorrent = function() {
|
||||
if (torrentEvent != null) {
|
||||
for (e in torrentEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Leenkx.connections[net_Url].ontracker = function() {
|
||||
if (trackerEvent != null) {
|
||||
for (e in trackerEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Leenkx.connections[net_Url].onannounce = function() {
|
||||
if (announceEvent != null) {
|
||||
for (e in announceEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Leenkx.connections[net_Url].onload = function() {
|
||||
if (loadEvent != null) {
|
||||
for (e in loadEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Leenkx.connections[net_Url].ontorrentdone = function() {
|
||||
if (torrentDoneEvent != null) {
|
||||
for (e in torrentDoneEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Leenkx.connections[net_Url].onload();
|
||||
//var script = 'leenkx.network.Leenkx.connections.h["' + net_Url + '"].onload();';
|
||||
//js.Syntax.code('(1, eval)({0})', script);
|
||||
|
||||
}
|
||||
|
||||
lnkx.onerror = function(error, i) {
|
||||
trace("ERROR - " + error + " | " + i);
|
||||
}
|
||||
|
||||
Browser.document.head.appendChild(lnkx);
|
||||
Leenkx.connections[connectionUrl].onload();
|
||||
}, function(err: kha.AssetError) {
|
||||
trace("ERROR loading Leenkx.js: " + err.url + " - " + err.error);
|
||||
Leenkx.connections[connectionUrl].onload();
|
||||
});
|
||||
}
|
||||
#end
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,8 +7,8 @@ class Log {
|
||||
|
||||
public static var mask:Int = 0;
|
||||
|
||||
#if sys
|
||||
public static var logFn:Dynamic->Void = Sys.println;
|
||||
#if (sys || kha_krom)
|
||||
public static var logFn:Dynamic->Void = function(data:Dynamic) { trace(data); };
|
||||
#elseif js
|
||||
public static var logFn:Dynamic->Void = js.html.Console.log;
|
||||
#end
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
package leenkx.network;
|
||||
|
||||
#if kha_krom
|
||||
typedef SecureSocketImpl = leenkx.network.krom.KromSecureSocket;
|
||||
#else
|
||||
typedef SecureSocketImpl = sys.ssl.Socket;
|
||||
#end
|
||||
|
||||
@ -12,6 +12,10 @@ typedef SocketImpl = leenkx.network.cs.NonBlockingSocket;
|
||||
|
||||
typedef SocketImpl = leenkx.network.nodejs.NodeSocket;
|
||||
|
||||
#elseif kha_krom
|
||||
|
||||
typedef SocketImpl = leenkx.network.krom.KromSocket;
|
||||
|
||||
#else
|
||||
|
||||
typedef SocketImpl = sys.net.Socket;
|
||||
|
||||
@ -2,7 +2,128 @@ package leenkx.network;
|
||||
|
||||
import leenkx.network.Types;
|
||||
|
||||
#if js
|
||||
#if kha_krom
|
||||
|
||||
import haxe.io.Bytes;
|
||||
|
||||
#if (haxe_ver < 4)
|
||||
typedef JsBuffer = js.html.ArrayBuffer;
|
||||
#else
|
||||
typedef JsBuffer = js.lib.ArrayBuffer;
|
||||
#end
|
||||
|
||||
@:native("WebSocket")
|
||||
extern class NativeWebSocket {
|
||||
var binaryType:String;
|
||||
var onopen:Dynamic;
|
||||
var onclose:Dynamic;
|
||||
var onerror:Dynamic;
|
||||
var onmessage:Dynamic;
|
||||
var readyState:Int;
|
||||
function new(url:String):Void;
|
||||
function close(?code:Int, ?reason:String):Void;
|
||||
function send(data:Dynamic):Void;
|
||||
}
|
||||
|
||||
class WebSocket {
|
||||
public var _protocol:String;
|
||||
public var _host:String;
|
||||
public var _port:Int = 0;
|
||||
public var _path:String;
|
||||
private var _url:String;
|
||||
|
||||
private var _ws:NativeWebSocket = null;
|
||||
|
||||
public var binaryType:Dynamic;
|
||||
|
||||
public var onopen:Void->Void;
|
||||
public var onclose:Void->Void;
|
||||
public var onerror:Dynamic->Void;
|
||||
public var onmessage:MessageType->Void;
|
||||
|
||||
public function new(url:String, immediateOpen:Bool = true) {
|
||||
_url = url;
|
||||
parseUrl(url);
|
||||
if (immediateOpen) {
|
||||
open();
|
||||
}
|
||||
}
|
||||
|
||||
private function parseUrl(url:String):Void {
|
||||
var urlArr = url.split(":");
|
||||
if (urlArr.length < 2) return;
|
||||
_protocol = urlArr[0];
|
||||
var hostPart = urlArr[1];
|
||||
if (hostPart.substr(0, 2) == "//") hostPart = hostPart.substr(2);
|
||||
_host = hostPart;
|
||||
if (urlArr.length >= 3) {
|
||||
var portPathPart = urlArr[2];
|
||||
var slashIndex = portPathPart.indexOf("/");
|
||||
if (slashIndex >= 0) {
|
||||
_port = Std.parseInt(portPathPart.substr(0, slashIndex));
|
||||
_path = portPathPart.substr(slashIndex);
|
||||
} else {
|
||||
_port = Std.parseInt(portPathPart);
|
||||
_path = "/";
|
||||
}
|
||||
} else {
|
||||
_port = (_protocol == "wss") ? 443 : 80;
|
||||
_path = "/";
|
||||
}
|
||||
if (_port == null || _port == 0) _port = (_protocol == "wss") ? 443 : 80;
|
||||
}
|
||||
|
||||
public function open():Void {
|
||||
if (_ws != null) {
|
||||
throw "Socket already connected";
|
||||
}
|
||||
_ws = new NativeWebSocket(_url);
|
||||
_ws.binaryType = "arraybuffer";
|
||||
|
||||
_ws.onopen = function() {
|
||||
if (onopen != null) onopen();
|
||||
};
|
||||
_ws.onclose = function() {
|
||||
if (onclose != null) onclose();
|
||||
};
|
||||
_ws.onerror = function(err:Dynamic) {
|
||||
if (onerror != null) onerror(err);
|
||||
};
|
||||
_ws.onmessage = function(event:Dynamic) {
|
||||
if (onmessage != null) {
|
||||
if (Std.isOfType(event.data, JsBuffer)) {
|
||||
var buffer = new Buffer();
|
||||
buffer.writeBytes(Bytes.ofData(event.data));
|
||||
onmessage(MessageType.BytesMessage(buffer));
|
||||
} else {
|
||||
onmessage(MessageType.StrMessage(event.data));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public function close():Void {
|
||||
if (_ws != null) {
|
||||
_ws.close();
|
||||
_ws = null;
|
||||
}
|
||||
}
|
||||
|
||||
public function send(msg:Dynamic):Void {
|
||||
if (_ws == null) return;
|
||||
if (Std.isOfType(msg, Bytes)) {
|
||||
var bytes:Bytes = cast msg;
|
||||
_ws.send(bytes.getData());
|
||||
} else if (Std.isOfType(msg, Buffer)) {
|
||||
var buffer:Buffer = cast msg;
|
||||
_ws.send(buffer.readAllAvailableBytes().getData());
|
||||
} else {
|
||||
_ws.send(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#elseif js
|
||||
|
||||
import haxe.Constraints.Function;
|
||||
import haxe.io.Bytes;
|
||||
|
||||
@ -7,13 +7,22 @@ class WebSocketHandler extends Handler {
|
||||
|
||||
public function new(socket:SocketImpl) {
|
||||
super(socket);
|
||||
#if kha_krom
|
||||
_creationTime = kha.Scheduler.time();
|
||||
#else
|
||||
_creationTime = Sys.time();
|
||||
#end
|
||||
_socket.setBlocking(false);
|
||||
Log.debug('New socket handler', id);
|
||||
}
|
||||
|
||||
public override function handle() {
|
||||
if (this.state == State.Handshake && Sys.time() - _creationTime > (MAX_WAIT_TIME / 1000)) {
|
||||
#if kha_krom
|
||||
var currentTime = kha.Scheduler.time();
|
||||
#else
|
||||
var currentTime = Sys.time();
|
||||
#end
|
||||
if (this.state == State.Handshake && currentTime - _creationTime > (MAX_WAIT_TIME / 1000)) {
|
||||
Log.info('No handshake detected in ${MAX_WAIT_TIME}ms, closing connection', id);
|
||||
this.close();
|
||||
return;
|
||||
|
||||
@ -2,8 +2,10 @@ package leenkx.network;
|
||||
|
||||
import haxe.Constraints;
|
||||
|
||||
#if !kha_krom
|
||||
import sys.ssl.Key;
|
||||
import sys.ssl.Certificate;
|
||||
#end
|
||||
|
||||
@:generic
|
||||
class WebSocketSecureServer
|
||||
@ -14,11 +16,21 @@ class WebSocketSecureServer
|
||||
#end
|
||||
extends WebSocketServer<T> {
|
||||
|
||||
#if kha_krom
|
||||
private var _cert:Dynamic;
|
||||
private var _key:Dynamic;
|
||||
private var _caChain:Dynamic;
|
||||
#else
|
||||
private var _cert:Certificate;
|
||||
private var _key:Key;
|
||||
private var _caChain:Certificate;
|
||||
#end
|
||||
|
||||
#if kha_krom
|
||||
public function new(host:String, port:Int, cert:Dynamic, key:Dynamic, caChain:Dynamic, maxConnections:Int = 1) {
|
||||
#else
|
||||
public function new(host:String, port:Int, cert:Certificate, key:Key, caChain:Certificate, maxConnections:Int = 1) {
|
||||
#end
|
||||
super(host, port, maxConnections);
|
||||
|
||||
_cert=cert;
|
||||
|
||||
@ -3,6 +3,9 @@ package leenkx.network;
|
||||
import haxe.Constraints;
|
||||
import haxe.MainLoop;
|
||||
import haxe.io.Error;
|
||||
#if kha_krom
|
||||
import leenkx.network.krom.KromSocket.KromHost;
|
||||
#end
|
||||
|
||||
@:generic
|
||||
class WebSocketServer
|
||||
@ -51,11 +54,20 @@ class WebSocketServer
|
||||
_stopServer = false;
|
||||
_serverSocket = createSocket();
|
||||
_serverSocket.setBlocking(false);
|
||||
#if kha_krom
|
||||
_serverSocket.bind(new KromHost(_host), _port);
|
||||
#else
|
||||
_serverSocket.bind(new sys.net.Host(_host), _port);
|
||||
#end
|
||||
_serverSocket.listen(_maxConnections);
|
||||
Log.info('Starting server - ${_host}:${_port} (maxConnections: ${_maxConnections})');
|
||||
|
||||
#if cs
|
||||
#if kha_krom
|
||||
kha.Scheduler.addTimeTask(function() {
|
||||
tick();
|
||||
}, 0, sleepAmount);
|
||||
|
||||
#elseif cs
|
||||
while (true) {
|
||||
var continueLoop = tick();
|
||||
if (continueLoop == false) {
|
||||
|
||||
62
leenkx/Sources/leenkx/network/krom/KromSecureSocket.hx
Normal file
62
leenkx/Sources/leenkx/network/krom/KromSecureSocket.hx
Normal file
@ -0,0 +1,62 @@
|
||||
package leenkx.network.krom;
|
||||
|
||||
import haxe.io.Bytes;
|
||||
import haxe.io.BytesOutput;
|
||||
import haxe.io.Error;
|
||||
import leenkx.network.krom.KromSocket.KromHost;
|
||||
|
||||
@:native("krom_socket_enable_ssl") extern function krom_socket_enable_ssl(id:Int):Bool;
|
||||
|
||||
class KromSecureSocket extends KromSocket {
|
||||
private var _sslEnabled:Bool = false;
|
||||
private var _hostname:String = null;
|
||||
private var _certPath:String = null;
|
||||
private var _keyPath:String = null;
|
||||
private var _caPath:String = null;
|
||||
public var verifyCert:Bool = true;
|
||||
|
||||
public function new() {
|
||||
super();
|
||||
}
|
||||
|
||||
public function setHostname(hostname:String):Void {
|
||||
_hostname = hostname;
|
||||
}
|
||||
|
||||
public function setCA(ca:Dynamic):Void {
|
||||
if (ca != null) {
|
||||
_caPath = Std.string(ca);
|
||||
}
|
||||
}
|
||||
|
||||
public function setCertificate(cert:Dynamic, key:Dynamic):Void {
|
||||
if (cert != null) {
|
||||
_certPath = Std.string(cert);
|
||||
}
|
||||
if (key != null) {
|
||||
_keyPath = Std.string(key);
|
||||
}
|
||||
}
|
||||
|
||||
public function enableSSL():Bool {
|
||||
if (getSocketId() >= 0 && !_sslEnabled) {
|
||||
var result = krom_socket_enable_ssl(getSocketId());
|
||||
if (result) {
|
||||
_sslEnabled = true;
|
||||
} else {
|
||||
trace("SecureSocket: Failed to enable SSL for socket " + getSocketId());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return _sslEnabled;
|
||||
}
|
||||
|
||||
override public function connect(host:KromHost, port:Int):Void {
|
||||
super.connect(host, port);
|
||||
enableSSL();
|
||||
}
|
||||
|
||||
public function isSSLEnabled():Bool {
|
||||
return _sslEnabled;
|
||||
}
|
||||
}
|
||||
249
leenkx/Sources/leenkx/network/krom/KromSocket.hx
Normal file
249
leenkx/Sources/leenkx/network/krom/KromSocket.hx
Normal file
@ -0,0 +1,249 @@
|
||||
package leenkx.network.krom;
|
||||
|
||||
import haxe.io.Bytes;
|
||||
import haxe.io.BytesInput;
|
||||
import haxe.io.BytesOutput;
|
||||
import haxe.io.Error;
|
||||
|
||||
#if (haxe_ver < 4)
|
||||
typedef JsBuffer = js.html.ArrayBuffer;
|
||||
#else
|
||||
typedef JsBuffer = js.lib.ArrayBuffer;
|
||||
#end
|
||||
|
||||
@:native("krom_socket_create") extern function krom_socket_create():Int;
|
||||
@:native("krom_socket_close") extern function krom_socket_close(id:Int):Void;
|
||||
@:native("krom_socket_bind") extern function krom_socket_bind(id:Int, addr:String, port:Int):Bool;
|
||||
@:native("krom_socket_listen") extern function krom_socket_listen(id:Int, backlog:Int):Bool;
|
||||
@:native("krom_socket_accept") extern function krom_socket_accept(id:Int):Int;
|
||||
@:native("krom_socket_connect") extern function krom_socket_connect(id:Int, host:String, port:Int):Bool;
|
||||
@:native("krom_socket_send") extern function krom_socket_send(id:Int, data:JsBuffer):Int;
|
||||
@:native("krom_socket_recv") extern function krom_socket_recv(id:Int, maxLen:Int):Dynamic;
|
||||
@:native("krom_socket_set_blocking") extern function krom_socket_set_blocking(id:Int, blocking:Bool):Void;
|
||||
@:native("krom_socket_is_connected") extern function krom_socket_is_connected(id:Int):Bool;
|
||||
|
||||
class KromSocket {
|
||||
private var _socketId:Int = -1;
|
||||
private var _host:KromHost = null;
|
||||
private var _port:Int = 0;
|
||||
private var _blocking:Bool = true;
|
||||
|
||||
public var input:KromSocketInput = null;
|
||||
public var output:KromSocketOutput = null;
|
||||
|
||||
private static var _connections:Array<KromSocket> = [];
|
||||
private var _newConnections:Array<KromSocket> = [];
|
||||
|
||||
public function new() {
|
||||
_socketId = krom_socket_create();
|
||||
if (_socketId >= 0) {
|
||||
input = new KromSocketInput(this);
|
||||
output = new KromSocketOutput(this);
|
||||
}
|
||||
}
|
||||
|
||||
private function setSocketId(id:Int):Void {
|
||||
_socketId = id;
|
||||
if (_socketId >= 0) {
|
||||
input = new KromSocketInput(this);
|
||||
output = new KromSocketOutput(this);
|
||||
}
|
||||
}
|
||||
|
||||
public function getSocketId():Int {
|
||||
return _socketId;
|
||||
}
|
||||
|
||||
public function bind(host:KromHost, port:Int):Void {
|
||||
_host = host;
|
||||
_port = port;
|
||||
if (_socketId >= 0) {
|
||||
if (!krom_socket_bind(_socketId, host.host, port)) {
|
||||
trace("Failed to bind to " + host.host + ":" + port);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function listen(connections:Int):Void {
|
||||
if (_host == null) {
|
||||
throw "You must bind the Socket to an address!";
|
||||
}
|
||||
if (_socketId >= 0) {
|
||||
if (!krom_socket_listen(_socketId, connections)) {
|
||||
trace("Failed to listen");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function accept():KromSocket {
|
||||
if (_socketId < 0) {
|
||||
throw "Blocking";
|
||||
}
|
||||
|
||||
var clientId = krom_socket_accept(_socketId);
|
||||
if (clientId < 0) {
|
||||
throw "Blocking";
|
||||
}
|
||||
|
||||
var clientSocket = new KromSocket();
|
||||
clientSocket.setSocketId(clientId);
|
||||
_connections.push(clientSocket);
|
||||
return clientSocket;
|
||||
}
|
||||
|
||||
public function connect(host:KromHost, port:Int):Void {
|
||||
if (_socketId >= 0) {
|
||||
if (!krom_socket_connect(_socketId, host.host, port)) {
|
||||
throw "Connection failed to " + host.host + ":" + port;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function setBlocking(blocking:Bool):Void {
|
||||
_blocking = blocking;
|
||||
if (_socketId >= 0) {
|
||||
krom_socket_set_blocking(_socketId, blocking);
|
||||
}
|
||||
}
|
||||
|
||||
public function setTimeout(timeout:Int):Void {
|
||||
// TODO: Re-investigate timeout handling
|
||||
}
|
||||
|
||||
public function peer():{host:KromHost, port:Int} {
|
||||
return {host: _host != null ? _host : new KromHost("0.0.0.0"), port: _port};
|
||||
}
|
||||
|
||||
public function host():{host:KromHost, port:Int} {
|
||||
return {host: _host != null ? _host : new KromHost("0.0.0.0"), port: _port};
|
||||
}
|
||||
|
||||
public function close():Void {
|
||||
if (_socketId >= 0) {
|
||||
krom_socket_close(_socketId);
|
||||
_socketId = -1;
|
||||
}
|
||||
_connections.remove(this);
|
||||
}
|
||||
|
||||
public function sendRaw(data:Bytes):Int {
|
||||
var arrayBuffer:JsBuffer = data.getData();
|
||||
var exactBuffer:JsBuffer = untyped arrayBuffer.slice(0, data.length);
|
||||
return krom_socket_send(_socketId, exactBuffer);
|
||||
}
|
||||
|
||||
// throws "ConnectionClosed" if peer disconnected
|
||||
public function recvRaw(maxLength:Int):Bytes {
|
||||
if (_socketId < 0) return null;
|
||||
var result:Dynamic = krom_socket_recv(_socketId, maxLength);
|
||||
// V8 returns ArrayBuffer data, null would-block, or -2 connection closed
|
||||
if (result == -2) {
|
||||
_socketId = -1;
|
||||
throw "ConnectionClosed";
|
||||
}
|
||||
if (result == null) return null;
|
||||
return Bytes.ofData(result);
|
||||
}
|
||||
|
||||
public function isConnected():Bool {
|
||||
if (_socketId < 0) return false;
|
||||
return krom_socket_is_connected(_socketId);
|
||||
}
|
||||
|
||||
public static function select(read:Array<KromSocket>, write:Array<KromSocket>, others:Array<KromSocket>, ?timeout:Float):{read:Array<KromSocket>, write:Array<KromSocket>, others:Array<KromSocket>} {
|
||||
var readable:Array<KromSocket> = [];
|
||||
|
||||
if (read != null) {
|
||||
for (sock in read) {
|
||||
if (sock._socketId >= 0) {
|
||||
readable.push(sock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (readable.length == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
read: readable,
|
||||
write: write,
|
||||
others: others
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class KromSocketInput {
|
||||
private var _socket:KromSocket;
|
||||
public var hasData:Bool = false;
|
||||
private var _buffer:Bytes = null;
|
||||
private var _bufferPos:Int = 0;
|
||||
|
||||
public function new(socket:KromSocket) {
|
||||
_socket = socket;
|
||||
}
|
||||
|
||||
public function readBytes(buf:Bytes, pos:Int, len:Int):Int {
|
||||
var data = _socket.recvRaw(len);
|
||||
if (data == null || data.length == 0) {
|
||||
hasData = false;
|
||||
throw Error.Blocked;
|
||||
}
|
||||
hasData = true;
|
||||
var toRead = data.length < len ? data.length : len;
|
||||
buf.blit(pos, data, 0, toRead);
|
||||
return toRead;
|
||||
}
|
||||
|
||||
public function read(nbytes:Int):Bytes {
|
||||
var buf = Bytes.alloc(nbytes);
|
||||
var read = readBytes(buf, 0, nbytes);
|
||||
if (read < nbytes) {
|
||||
return buf.sub(0, read);
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
|
||||
class KromSocketOutput {
|
||||
private var _socket:KromSocket;
|
||||
private var _buffer:BytesOutput;
|
||||
|
||||
public function new(socket:KromSocket) {
|
||||
_socket = socket;
|
||||
_buffer = new BytesOutput();
|
||||
}
|
||||
|
||||
public function write(data:Bytes):Void {
|
||||
_buffer.write(data);
|
||||
}
|
||||
|
||||
public function writeBytes(buf:Bytes, pos:Int, len:Int):Int {
|
||||
_buffer.writeBytes(buf, pos, len);
|
||||
return len;
|
||||
}
|
||||
|
||||
public function flush():Void {
|
||||
var data = _buffer.getBytes();
|
||||
if (data.length > 0) {
|
||||
_socket.sendRaw(data);
|
||||
_buffer = new BytesOutput();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class KromHost {
|
||||
public var host:String;
|
||||
|
||||
public function new(hostname:String) {
|
||||
host = hostname;
|
||||
}
|
||||
|
||||
public static function resolve(hostname:String):String {
|
||||
return hostname;
|
||||
}
|
||||
|
||||
public function toString():String {
|
||||
return host;
|
||||
}
|
||||
}
|
||||
@ -13,11 +13,15 @@ import iron.math.Vec2;
|
||||
import haxe.ds.Vector;
|
||||
import iron.object.Object;
|
||||
import iron.object.Animation;
|
||||
import iron.object.Animation.ActionSampler;
|
||||
#if lnx_skin
|
||||
import iron.object.BoneAnimation;
|
||||
#end
|
||||
import iron.object.ObjectAnimation;
|
||||
|
||||
class AnimationExtension {
|
||||
|
||||
#if lnx_skin
|
||||
public static function solveIKBlend(boneAnimation: BoneAnimation, actionMats: Array<Mat4>, effector: TObj, goal: Vec4, precision = 0.01, maxIterations = 100, chainLenght = 100, pole: Vec4 = null, rollAngle = 0.0, influence = 0.0, layerMask: Null<Int> = null, threshold: FastFloat = 0.1) {
|
||||
|
||||
var matsBlend = boneAnimation.initMatsEmpty();
|
||||
@ -31,7 +35,9 @@ class AnimationExtension {
|
||||
boneAnimation.solveIK(actionMats, effector, goal, precision, maxIterations, chainLenght, pole, rollAngle);
|
||||
boneAnimation.blendAction(matsBlend, actionMats, actionMats, influence, layerMask, threshold);
|
||||
}
|
||||
#end
|
||||
|
||||
#if lnx_skin
|
||||
public static function solveTwoBoneIKBlend(boneAnimation: BoneAnimation, actionMats: Array<Mat4>, effector: TObj, goal: Vec4, pole: Vec4 = null, rollAngle = 0.0, influence = 0.0, layerMask: Null<Int> = null, threshold: FastFloat = 0.1) {
|
||||
|
||||
var matsBlend = boneAnimation.initMatsEmpty();
|
||||
@ -45,6 +51,7 @@ class AnimationExtension {
|
||||
boneAnimation.solveTwoBoneIK(actionMats, effector, goal, pole, rollAngle);
|
||||
boneAnimation.blendAction(matsBlend, actionMats, actionMats, influence, layerMask, threshold);
|
||||
}
|
||||
#end
|
||||
|
||||
static inline function sortWeights(vecs: Array<Vec2>, sampleVec: Vec2): Map<Int, Vec2> {
|
||||
var weightIndex: Array<WeightIndex> = [];
|
||||
@ -113,7 +120,9 @@ class AnimationExtension {
|
||||
|
||||
class OneShotOperator {
|
||||
|
||||
#if lnx_skin
|
||||
var boneAnimation: BoneAnimation;
|
||||
#end
|
||||
var objectAnimation: ObjectAnimation;
|
||||
var isArmature: Bool;
|
||||
var oneShotAction: ActionSampler;
|
||||
@ -136,12 +145,15 @@ class OneShotOperator {
|
||||
|
||||
var animation = animation;
|
||||
this.oneShotAction = oneShotAction;
|
||||
#if lnx_skin
|
||||
if(Std.isOfType(animation, BoneAnimation)) {
|
||||
boneAnimation = cast animation;
|
||||
tempMatsBone = boneAnimation.initMatsEmpty();
|
||||
this.isArmature = true;
|
||||
}
|
||||
else {
|
||||
else
|
||||
#end
|
||||
{
|
||||
objectAnimation = cast animation;
|
||||
tempMatsObject = objectAnimation.initTransformMap();
|
||||
this.isArmature = false;
|
||||
@ -150,10 +162,13 @@ class OneShotOperator {
|
||||
}
|
||||
|
||||
function initOneShot() {
|
||||
#if lnx_skin
|
||||
if(isArmature) {
|
||||
totalFrames = boneAnimation.getTotalFrames(oneShotAction) - 1;
|
||||
}
|
||||
else {
|
||||
else
|
||||
#end
|
||||
{
|
||||
totalFrames = objectAnimation.getTotalFrames(oneShotAction) - 1;
|
||||
}
|
||||
blendFactor = 0.0;
|
||||
@ -257,7 +272,9 @@ class OneShotOperator {
|
||||
|
||||
class SwitchActionOperator {
|
||||
|
||||
#if lnx_skin
|
||||
var boneAnimation: BoneAnimation;
|
||||
#end
|
||||
var objectAnimation: ObjectAnimation;
|
||||
var isArmature: Bool;
|
||||
var boneLayer: Null<Int>;
|
||||
@ -268,12 +285,14 @@ class SwitchActionOperator {
|
||||
var tween: TAnim = null;
|
||||
|
||||
public function new(animation: Animation) {
|
||||
|
||||
#if lnx_skin
|
||||
if(Std.isOfType(animation, BoneAnimation)) {
|
||||
boneAnimation = cast animation;
|
||||
this.isArmature = true;
|
||||
}
|
||||
else {
|
||||
else
|
||||
#end
|
||||
{
|
||||
objectAnimation = cast animation;
|
||||
this.isArmature = false;
|
||||
}
|
||||
@ -338,6 +357,7 @@ class SwitchActionOperator {
|
||||
}
|
||||
}
|
||||
|
||||
#if lnx_skin
|
||||
class SimpleBiPedalIK {
|
||||
|
||||
var object: Object;
|
||||
@ -477,6 +497,7 @@ class SimpleBiPedalIK {
|
||||
}
|
||||
|
||||
}
|
||||
#end
|
||||
|
||||
@:enum abstract SelectAction(Int) from Int to Int {
|
||||
var action1 = 0;
|
||||
|
||||
56
leenkx/Sources/leenkx/renderpath/FSR1.hx
Normal file
56
leenkx/Sources/leenkx/renderpath/FSR1.hx
Normal file
@ -0,0 +1,56 @@
|
||||
package leenkx.renderpath;
|
||||
|
||||
import iron.RenderPath;
|
||||
|
||||
/**
|
||||
* AMD FidelityFX Super Resolution (FSR V1) - Spatial Upscaling
|
||||
* MIT License -
|
||||
* Quality sharpness values:
|
||||
* - Ultra_Quality: 0.0
|
||||
* - Quality: 0.25
|
||||
* - Balanced: 0.5
|
||||
* - Performance: 0.75
|
||||
*/
|
||||
class FSR1 {
|
||||
|
||||
static var path: RenderPath;
|
||||
|
||||
public static inline var ULTRA_QUALITY: Float = 0.0;
|
||||
public static inline var QUALITY: Float = 0.25;
|
||||
public static inline var BALANCED: Float = 0.5;
|
||||
public static inline var PERFORMANCE: Float = 0.75;
|
||||
|
||||
public static function init(_path: RenderPath) {
|
||||
path = _path;
|
||||
|
||||
#if rp_fsr1
|
||||
path.loadShader("shader_datas/fsr1_easu_pass/fsr1_easu_pass");
|
||||
path.loadShader("shader_datas/fsr1_rcas_pass/fsr1_rcas_pass");
|
||||
#end
|
||||
}
|
||||
|
||||
public static function getSharpnessFromPreset(preset: String): Float {
|
||||
return switch(preset) {
|
||||
case "Ultra_Quality": ULTRA_QUALITY;
|
||||
case "Quality": QUALITY;
|
||||
case "Balanced": BALANCED;
|
||||
case "Performance": PERFORMANCE;
|
||||
default: QUALITY;
|
||||
};
|
||||
}
|
||||
|
||||
public static function applyRCAS(inputTarget: String, outputTarget: String = null) {
|
||||
#if rp_fsr1
|
||||
if (path == null) return;
|
||||
|
||||
if (outputTarget != null) {
|
||||
path.setTarget(outputTarget);
|
||||
} else {
|
||||
path.setTarget("");
|
||||
}
|
||||
|
||||
path.bindTarget(inputTarget, "tex");
|
||||
path.drawShader("shader_datas/fsr1_rcas_pass/fsr1_rcas_pass");
|
||||
#end
|
||||
}
|
||||
}
|
||||
@ -14,6 +14,13 @@ class Inc {
|
||||
static var spotIndex = 0;
|
||||
static var lastFrame = -1;
|
||||
|
||||
#if lnx_shadowmap_atlas
|
||||
static var tilesToRemove: Array<ShadowMapTile> = [];
|
||||
#if lnx_shadowmap_atlas_lod
|
||||
static var tilesToChangeSize: Array<ShadowMapTile> = [];
|
||||
#end
|
||||
#end
|
||||
|
||||
#if ((rp_voxels != 'Off') && lnx_config)
|
||||
static var voxelsCreated = false;
|
||||
#end
|
||||
@ -149,12 +156,39 @@ class Inc {
|
||||
}
|
||||
|
||||
public static function bindShadowMapAtlas() {
|
||||
var hasAtlas = false;
|
||||
|
||||
for (atlas in ShadowMapAtlas.shadowMapAtlases) {
|
||||
path.bindTarget(atlas.target, atlas.target);
|
||||
hasAtlas = true;
|
||||
}
|
||||
|
||||
if (!hasAtlas) {
|
||||
#if lnx_shadowmap_atlas_single_map
|
||||
path.bindTarget("empty_shadowmap", "shadowMapAtlas");
|
||||
#else
|
||||
path.bindTarget("empty_shadowmap", "shadowMapAtlasSun");
|
||||
path.bindTarget("empty_shadowmap", "shadowMapAtlasPoint");
|
||||
path.bindTarget("empty_shadowmap", "shadowMapAtlasSpot");
|
||||
#end
|
||||
}
|
||||
|
||||
#if rp_shadowmap_transparent
|
||||
var hasAtlasT = false;
|
||||
|
||||
for (atlas in ShadowMapAtlas.shadowMapAtlasesTransparent) {
|
||||
path.bindTarget(atlas.target, atlas.target);
|
||||
hasAtlasT = true;
|
||||
}
|
||||
|
||||
if (!hasAtlasT) {
|
||||
#if lnx_shadowmap_atlas_single_map
|
||||
path.bindTarget("empty_shadowmap_transparent", "shadowMapAtlasTransparent");
|
||||
#else
|
||||
path.bindTarget("empty_shadowmap_transparent", "shadowMapAtlasSunTransparent");
|
||||
path.bindTarget("empty_shadowmap_transparent", "shadowMapAtlasPointTransparent");
|
||||
path.bindTarget("empty_shadowmap_transparent", "shadowMapAtlasSpotTransparent");
|
||||
#end
|
||||
}
|
||||
#end
|
||||
}
|
||||
@ -223,9 +257,9 @@ class Inc {
|
||||
#end
|
||||
|
||||
for (atlas in ShadowMapAtlas.shadowMapAtlases) {
|
||||
var tilesToRemove = [];
|
||||
tilesToRemove.resize(0);
|
||||
#if lnx_shadowmap_atlas_lod
|
||||
var tilesToChangeSize = [];
|
||||
tilesToChangeSize.resize(0);
|
||||
#end
|
||||
|
||||
var shadowmap = getShadowMapAtlas(atlas, false);
|
||||
@ -296,13 +330,14 @@ class Inc {
|
||||
path.currentFace = -1;
|
||||
}
|
||||
path.endStream();
|
||||
path.currentG = null;
|
||||
}
|
||||
|
||||
#if rp_shadowmap_transparent
|
||||
for (atlas in ShadowMapAtlas.shadowMapAtlasesTransparent) {
|
||||
var tilesToRemove = [];
|
||||
tilesToRemove.resize(0);
|
||||
#if lnx_shadowmap_atlas_lod
|
||||
var tilesToChangeSize = [];
|
||||
tilesToChangeSize.resize(0);
|
||||
#end
|
||||
|
||||
var shadowmap = getShadowMapAtlas(atlas, true);
|
||||
@ -374,6 +409,8 @@ class Inc {
|
||||
}
|
||||
path.endStream();
|
||||
|
||||
path.currentG = null;
|
||||
|
||||
#if lnx_shadowmap_atlas_lod
|
||||
for (tile in tilesToChangeSize) {
|
||||
tilesToRemove.push(tile);
|
||||
@ -382,9 +419,6 @@ class Inc {
|
||||
if (newTile != null)
|
||||
atlas.activeTiles.push(newTile);
|
||||
}
|
||||
// update point light data after changing size of tiles to avoid render issues
|
||||
updatePointLightAtlasData(false);
|
||||
// update point light data after changing size of tiles to avoid render issues
|
||||
updatePointLightAtlasData(true);
|
||||
#end
|
||||
|
||||
@ -554,8 +588,16 @@ class Inc {
|
||||
}
|
||||
if (superSample != config.rp_supersample) {
|
||||
superSample = config.rp_supersample;
|
||||
|
||||
var inVRPresent = false;
|
||||
#if (kha_webgl && lnx_vr)
|
||||
inVRPresent = kha.vr.VrInterface.instance != null &&
|
||||
kha.vr.VrInterface.instance.IsPresenting();
|
||||
#end
|
||||
|
||||
for (rt in path.renderTargets) {
|
||||
if (rt.raw.width == 0 && rt.raw.scale != null) {
|
||||
// VR present mode renderpath automatically overrides Inc.superSample to 4.0
|
||||
rt.raw.scale = getSuperSampling();
|
||||
}
|
||||
}
|
||||
@ -1372,6 +1414,10 @@ class ShadowMapAtlas {
|
||||
return;
|
||||
}
|
||||
|
||||
mainTile.forEachTileLinked(function(tile) {
|
||||
tile.isTransparent = transparent;
|
||||
});
|
||||
|
||||
atlas.activeTiles.push(mainTile);
|
||||
// notify the tile on light remove
|
||||
light.tileNotifyOnRemove = mainTile.notifyOnLightRemove;
|
||||
@ -1560,6 +1606,7 @@ class ShadowMapTile {
|
||||
public var size:Int;
|
||||
public var tiles:Array<ShadowMapTile> = [];
|
||||
public var linkedTile:ShadowMapTile = null;
|
||||
public var isTransparent:Bool = false; // track for tile
|
||||
|
||||
#if lnx_shadowmap_atlas_lod
|
||||
public var parentTile: ShadowMapTile = null;
|
||||
@ -1791,7 +1838,11 @@ class ShadowMapTile {
|
||||
public function freeTile(): Void {
|
||||
// prevent duplicates
|
||||
if (light != null && unlockLight) {
|
||||
light.lightInAtlas = false;
|
||||
if (isTransparent) {
|
||||
light.lightInAtlasTransparent = false;
|
||||
} else {
|
||||
light.lightInAtlas = false;
|
||||
}
|
||||
unlockLight = false;
|
||||
}
|
||||
|
||||
|
||||
@ -15,9 +15,9 @@ class RenderPathDeferred {
|
||||
static var bloomUpsampler: Upsampler;
|
||||
#end
|
||||
|
||||
#if (rp_ssgi == "SSGI")
|
||||
static var ssgitex = "singleb";
|
||||
static var ssgitexb = "singleb";
|
||||
#if rp_ssgi
|
||||
static var ssgitex = "ssgi_a";
|
||||
static var ssgitexb = "ssgi_b";
|
||||
#end
|
||||
|
||||
public static inline function setTargetMeshes() {
|
||||
@ -187,40 +187,32 @@ class RenderPathDeferred {
|
||||
path.loadShader("shader_datas/copy_pass/copy_pass");
|
||||
#end
|
||||
|
||||
#if ((rp_ssgi == "RTGI") || (rp_ssgi == "RTAO"))
|
||||
{
|
||||
path.loadShader("shader_datas/ssgi_pass/ssgi_pass");
|
||||
path.loadShader("shader_datas/blur_edge_pass/blur_edge_pass_x");
|
||||
path.loadShader("shader_datas/blur_edge_pass/blur_edge_pass_y");
|
||||
}
|
||||
#elseif (rp_ssgi == "SSAO")
|
||||
#if rp_ssao
|
||||
{
|
||||
path.loadShader("shader_datas/ssao_pass/ssao_pass");
|
||||
path.loadShader("shader_datas/blur_edge_pass/blur_edge_pass_x");
|
||||
path.loadShader("shader_datas/blur_edge_pass/blur_edge_pass_y");
|
||||
}
|
||||
#elseif (rp_ssgi == "SSGI")
|
||||
#end
|
||||
|
||||
#if rp_ssgi
|
||||
{
|
||||
path.loadShader("shader_datas/ssgi_pass/ssgi_pass");
|
||||
path.loadShader("shader_datas/blur_edge_pass/blur_edge_pass_x");
|
||||
path.loadShader("shader_datas/blur_edge_pass/blur_edge_pass_y");
|
||||
path.loadShader("shader_datas/ssgi_blur_pass/ssgi_blur_pass_x");
|
||||
path.loadShader("shader_datas/ssgi_blur_pass/ssgi_blur_pass_y");
|
||||
}
|
||||
#end
|
||||
|
||||
#if (rp_ssgi != "Off")
|
||||
#if rp_ssao
|
||||
{
|
||||
var t = new RenderTargetRaw();
|
||||
t.name = "singlea";
|
||||
t.width = 0;
|
||||
t.height = 0;
|
||||
t.displayp = Inc.getDisplayp();
|
||||
#if (rp_ssgi == "SSGI")
|
||||
t.format = "RGBA32";
|
||||
#else
|
||||
t.format = "R8";
|
||||
#end
|
||||
t.scale = Inc.getSuperSampling();
|
||||
#if rp_ssgi_half
|
||||
#if rp_ssao_half
|
||||
t.scale *= 0.5;
|
||||
#end
|
||||
path.createRenderTarget(t);
|
||||
@ -230,11 +222,35 @@ class RenderPathDeferred {
|
||||
t.width = 0;
|
||||
t.height = 0;
|
||||
t.displayp = Inc.getDisplayp();
|
||||
#if (rp_ssgi == "SSGI")
|
||||
t.format = "R8";
|
||||
t.scale = Inc.getSuperSampling();
|
||||
#if rp_ssao_half
|
||||
t.scale *= 0.5;
|
||||
#end
|
||||
path.createRenderTarget(t);
|
||||
}
|
||||
#end
|
||||
|
||||
#if rp_ssgi
|
||||
{
|
||||
var t = new RenderTargetRaw();
|
||||
t.name = "ssgi_a";
|
||||
t.width = 0;
|
||||
t.height = 0;
|
||||
t.displayp = Inc.getDisplayp();
|
||||
t.format = "RGBA32";
|
||||
#else
|
||||
t.format = "R8";
|
||||
t.scale = Inc.getSuperSampling();
|
||||
#if rp_ssgi_half
|
||||
t.scale *= 0.5;
|
||||
#end
|
||||
path.createRenderTarget(t);
|
||||
|
||||
var t = new RenderTargetRaw();
|
||||
t.name = "ssgi_b";
|
||||
t.width = 0;
|
||||
t.height = 0;
|
||||
t.displayp = Inc.getDisplayp();
|
||||
t.format = "RGBA32";
|
||||
t.scale = Inc.getSuperSampling();
|
||||
#if rp_ssgi_half
|
||||
t.scale *= 0.5;
|
||||
@ -252,9 +268,6 @@ class RenderPathDeferred {
|
||||
t.displayp = Inc.getDisplayp();
|
||||
t.format = "R8";
|
||||
t.scale = Inc.getSuperSampling();
|
||||
#if rp_ssgi_half // Do we keep this ?
|
||||
t.scale *= 0.5;
|
||||
#end
|
||||
path.createRenderTarget(t);
|
||||
|
||||
var t = new RenderTargetRaw();
|
||||
@ -264,37 +277,6 @@ class RenderPathDeferred {
|
||||
t.displayp = Inc.getDisplayp();
|
||||
t.format = "R8";
|
||||
t.scale = Inc.getSuperSampling();
|
||||
#if rp_ssgi_half
|
||||
t.scale *= 0.5;
|
||||
#end
|
||||
path.createRenderTarget(t);
|
||||
}
|
||||
#end
|
||||
|
||||
#if rp_volumetriclight
|
||||
{
|
||||
var t = new RenderTargetRaw();
|
||||
t.name = "volumetrica";
|
||||
t.width = 0;
|
||||
t.height = 0;
|
||||
t.displayp = Inc.getDisplayp();
|
||||
t.format = "R8";
|
||||
t.scale = Inc.getSuperSampling();
|
||||
#if rp_ssgi_half // Do we keep this ?
|
||||
t.scale *= 0.5;
|
||||
#end
|
||||
path.createRenderTarget(t);
|
||||
|
||||
var t = new RenderTargetRaw();
|
||||
t.name = "volumetricb";
|
||||
t.width = 0;
|
||||
t.height = 0;
|
||||
t.displayp = Inc.getDisplayp();
|
||||
t.format = "R8";
|
||||
t.scale = Inc.getSuperSampling();
|
||||
#if rp_ssgi_half
|
||||
t.scale *= 0.5;
|
||||
#end
|
||||
path.createRenderTarget(t);
|
||||
}
|
||||
#end
|
||||
@ -381,6 +363,14 @@ class RenderPathDeferred {
|
||||
}
|
||||
#end
|
||||
|
||||
#if rp_fsr1
|
||||
{
|
||||
path.loadShader("shader_datas/fsr1_easu_pass/fsr1_easu_pass");
|
||||
path.loadShader("shader_datas/fsr1_rcas_pass/fsr1_rcas_pass");
|
||||
path.loadShader("shader_datas/copy_pass/copy_pass");
|
||||
}
|
||||
#end
|
||||
|
||||
#if rp_autoexposure
|
||||
{
|
||||
var t = new RenderTargetRaw();
|
||||
@ -401,7 +391,7 @@ class RenderPathDeferred {
|
||||
}
|
||||
#end
|
||||
|
||||
#if (rp_ssr_half || rp_ssgi_half || rp_voxels != "Off") //we need half depth for resolve voxels shaders
|
||||
#if (rp_ssr_half || rp_ssao_half || rp_ssgi_half || rp_voxels != "Off") //we need half depth for resolve voxels shaders
|
||||
{
|
||||
path.loadShader("shader_datas/downsample_depth/downsample_depth");
|
||||
var t = new RenderTargetRaw();
|
||||
@ -423,6 +413,7 @@ class RenderPathDeferred {
|
||||
t.displayp = Inc.getDisplayp();
|
||||
t.format = Inc.getHdrFormat();
|
||||
t.scale = Inc.getSuperSampling();
|
||||
t.depth_buffer = "main";
|
||||
path.createRenderTarget(t);
|
||||
}
|
||||
#end
|
||||
@ -511,6 +502,31 @@ class RenderPathDeferred {
|
||||
}
|
||||
#end
|
||||
|
||||
// TODO: Re-investigate creating fallback empty shadowmaps for WebGL when no lights exist
|
||||
#if lnx_shadowmap_atlas
|
||||
{
|
||||
var t = new RenderTargetRaw();
|
||||
t.name = "empty_shadowmap";
|
||||
t.width = 1;
|
||||
t.height = 1;
|
||||
t.format = "DEPTH16";
|
||||
path.createRenderTarget(t);
|
||||
path.setTarget("empty_shadowmap");
|
||||
path.clearTarget(null, 1.0);
|
||||
|
||||
#if rp_shadowmap_transparent
|
||||
var t2 = new RenderTargetRaw();
|
||||
t2.name = "empty_shadowmap_transparent";
|
||||
t2.width = 1;
|
||||
t2.height = 1;
|
||||
t2.format = "RGBA64";
|
||||
path.createRenderTarget(t2);
|
||||
path.setTarget("empty_shadowmap_transparent");
|
||||
path.clearTarget(0xffffffff, null);
|
||||
#end
|
||||
}
|
||||
#end
|
||||
|
||||
#if rp_chromatic_aberration
|
||||
{
|
||||
path.loadShader("shader_datas/chromatic_aberration_pass/chromatic_aberration_pass");
|
||||
@ -536,7 +552,7 @@ class RenderPathDeferred {
|
||||
#if (rp_ssrefr || lnx_voxelgi_refract)
|
||||
{
|
||||
path.setTarget("gbuffer_refraction");
|
||||
path.clearTarget(0xffffff00);
|
||||
path.clearTarget(0xffff00ff);
|
||||
}
|
||||
#end
|
||||
|
||||
@ -585,7 +601,7 @@ class RenderPathDeferred {
|
||||
}
|
||||
#end
|
||||
|
||||
#if (rp_ssr_half || rp_ssgi_half || (rp_voxels != "Off"))
|
||||
#if (rp_ssr_half || rp_ssao_half || rp_ssgi_half || (rp_voxels != "Off"))
|
||||
path.setTarget("half");
|
||||
path.bindTarget("_main", "texdepth");
|
||||
path.drawShader("shader_datas/downsample_depth/downsample_depth");
|
||||
@ -600,9 +616,9 @@ class RenderPathDeferred {
|
||||
#end
|
||||
#end
|
||||
|
||||
#if (rp_ssgi == "SSAO")
|
||||
#if rp_ssao
|
||||
{
|
||||
if (leenkx.data.Config.raw.rp_ssgi != false) {
|
||||
if (leenkx.data.Config.raw.rp_ssao != false) {
|
||||
path.setTarget("singlea");
|
||||
path.bindTarget("_main", "gbufferD");
|
||||
path.bindTarget("gbuffer0", "gbuffer0");
|
||||
@ -619,10 +635,12 @@ class RenderPathDeferred {
|
||||
path.drawShader("shader_datas/blur_edge_pass/blur_edge_pass_y");
|
||||
}
|
||||
}
|
||||
#elseif (rp_ssgi == "SSGI")
|
||||
#end
|
||||
|
||||
#if rp_ssgi
|
||||
{
|
||||
if (leenkx.data.Config.raw.rp_ssgi != false) {
|
||||
path.setTarget("singlea");
|
||||
path.setTarget("ssgi_a");
|
||||
path.bindTarget("_main", "gbufferD");
|
||||
path.bindTarget("gbuffer0", "gbuffer0");
|
||||
path.bindTarget("gbuffer1", "gbuffer1");
|
||||
@ -631,29 +649,20 @@ class RenderPathDeferred {
|
||||
path.bindTarget("gbuffer_emission", "gbufferEmission");
|
||||
}
|
||||
#end
|
||||
#if rp_gbuffer2
|
||||
path.bindTarget("gbuffer2", "sveloc");
|
||||
#end
|
||||
#if rp_shadowmap
|
||||
{
|
||||
#if lnx_shadowmap_atlas
|
||||
Inc.bindShadowMapAtlas();
|
||||
#else
|
||||
Inc.bindShadowMap();
|
||||
#end
|
||||
}
|
||||
#end
|
||||
|
||||
path.drawShader("shader_datas/ssgi_pass/ssgi_pass");
|
||||
path.setTarget("singleb");
|
||||
path.bindTarget("singlea", "tex");
|
||||
path.bindTarget("gbuffer0", "gbuffer0");
|
||||
path.drawShader("shader_datas/blur_edge_pass/blur_edge_pass_x");
|
||||
|
||||
path.setTarget("singlea");
|
||||
path.bindTarget("singleb", "tex");
|
||||
path.setTarget("ssgi_b");
|
||||
path.bindTarget("ssgi_a", "tex");
|
||||
path.bindTarget("gbuffer0", "gbuffer0");
|
||||
path.drawShader("shader_datas/blur_edge_pass/blur_edge_pass_y");
|
||||
path.bindTarget("_main", "gbufferD");
|
||||
path.drawShader("shader_datas/ssgi_blur_pass/ssgi_blur_pass_x");
|
||||
|
||||
path.setTarget("ssgi_a");
|
||||
path.bindTarget("ssgi_b", "tex");
|
||||
path.bindTarget("gbuffer0", "gbuffer0");
|
||||
path.bindTarget("_main", "gbufferD");
|
||||
path.drawShader("shader_datas/ssgi_blur_pass/ssgi_blur_pass_y");
|
||||
}
|
||||
}
|
||||
#end
|
||||
@ -740,9 +749,9 @@ class RenderPathDeferred {
|
||||
}
|
||||
#end
|
||||
|
||||
#if (rp_ssgi != "Off")
|
||||
#if rp_ssao
|
||||
{
|
||||
if (leenkx.data.Config.raw.rp_ssgi != false) {
|
||||
if (leenkx.data.Config.raw.rp_ssao != false) {
|
||||
path.bindTarget("singlea", "ssaotex");
|
||||
}
|
||||
else {
|
||||
@ -751,6 +760,14 @@ class RenderPathDeferred {
|
||||
}
|
||||
#end
|
||||
|
||||
#if rp_ssgi
|
||||
{
|
||||
if (leenkx.data.Config.raw.rp_ssgi != false) {
|
||||
path.bindTarget("ssgi_a", "ssgitex");
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
||||
var voxelao_pass = false;
|
||||
#if (rp_voxels != "Off")
|
||||
if (leenkx.data.Config.raw.rp_gi != false)
|
||||
@ -848,24 +865,9 @@ class RenderPathDeferred {
|
||||
}
|
||||
#end
|
||||
|
||||
#if rp_volumetriclight
|
||||
#if (rp_translucency && !rp_ssrefr)
|
||||
{
|
||||
path.setTarget("volumetrica");
|
||||
path.bindTarget("_main", "gbufferD");
|
||||
#if lnx_shadowmap_atlas
|
||||
Inc.bindShadowMapAtlas();
|
||||
#else
|
||||
Inc.bindShadowMap();
|
||||
#end
|
||||
path.drawShader("shader_datas/volumetric_light/volumetric_light");
|
||||
|
||||
path.setTarget("volumetricb");
|
||||
path.bindTarget("volumetrica", "tex");
|
||||
path.drawShader("shader_datas/blur_bilat_pass/blur_bilat_pass_x");
|
||||
|
||||
path.setTarget("tex");
|
||||
path.bindTarget("volumetricb", "tex");
|
||||
path.drawShader("shader_datas/blur_bilat_blend_pass/blur_bilat_blend_pass_y");
|
||||
Inc.drawTranslucency("tex");
|
||||
}
|
||||
#end
|
||||
|
||||
@ -937,12 +939,6 @@ class RenderPathDeferred {
|
||||
}
|
||||
#end
|
||||
|
||||
#if (rp_translucency && !rp_ssrefr)
|
||||
{
|
||||
Inc.drawTranslucency("tex");
|
||||
}
|
||||
#end
|
||||
|
||||
#if rp_ssrefr
|
||||
{
|
||||
if (leenkx.data.Config.raw.rp_ssrefr != false)
|
||||
@ -982,19 +978,55 @@ class RenderPathDeferred {
|
||||
|
||||
path.drawMeshes("refraction");
|
||||
|
||||
path.setTarget("tex");
|
||||
path.bindTarget("tex", "tex");
|
||||
path.bindTarget("refr", "tex1");
|
||||
path.setTarget("buf");
|
||||
path.bindTarget("tex", "tex"); // scene with refractive objects
|
||||
path.bindTarget("refr", "tex1"); // background without refractive objects
|
||||
path.bindTarget("_main", "gbufferD");
|
||||
path.bindTarget("gbufferD1", "gbufferD1");
|
||||
path.bindTarget("gbuffer0", "gbuffer0");
|
||||
path.bindTarget("gbuffer_refraction", "gbuffer_refraction");
|
||||
|
||||
path.drawShader("shader_datas/ssrefr_pass/ssrefr_pass");
|
||||
|
||||
path.setTarget("tex");
|
||||
path.bindTarget("buf", "tex");
|
||||
path.drawShader("shader_datas/copy_pass/copy_pass");
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
||||
#if rp_chromatic_aberration
|
||||
{
|
||||
path.setTarget("buf");
|
||||
path.bindTarget("tex", "tex");
|
||||
path.drawShader("shader_datas/chromatic_aberration_pass/chromatic_aberration_pass");
|
||||
path.setTarget("tex");
|
||||
path.bindTarget("buf", "tex");
|
||||
path.drawShader("shader_datas/copy_pass/copy_pass");
|
||||
}
|
||||
#end
|
||||
|
||||
#if rp_volumetriclight
|
||||
{
|
||||
path.setTarget("volumetrica");
|
||||
path.bindTarget("_main", "gbufferD");
|
||||
#if lnx_shadowmap_atlas
|
||||
Inc.bindShadowMapAtlas();
|
||||
#else
|
||||
Inc.bindShadowMap();
|
||||
#end
|
||||
path.drawShader("shader_datas/volumetric_light/volumetric_light");
|
||||
|
||||
path.setTarget("volumetricb");
|
||||
path.bindTarget("volumetrica", "tex");
|
||||
path.drawShader("shader_datas/blur_bilat_pass/blur_bilat_pass_x");
|
||||
|
||||
path.setTarget("tex");
|
||||
path.bindTarget("volumetricb", "tex");
|
||||
path.drawShader("shader_datas/blur_bilat_blend_pass/blur_bilat_blend_pass_y");
|
||||
}
|
||||
#end
|
||||
|
||||
#if ((rp_motionblur == "Camera") || (rp_motionblur == "Object"))
|
||||
{
|
||||
if (leenkx.data.Config.raw.rp_motionblur != false) {
|
||||
@ -1029,22 +1061,6 @@ class RenderPathDeferred {
|
||||
}
|
||||
#end
|
||||
|
||||
#if rp_chromatic_aberration
|
||||
{
|
||||
path.setTarget("buf");
|
||||
path.bindTarget("tex", "tex");
|
||||
path.drawShader("shader_datas/chromatic_aberration_pass/chromatic_aberration_pass");
|
||||
path.setTarget("tex");
|
||||
path.bindTarget("buf", "tex");
|
||||
path.drawShader("shader_datas/copy_pass/copy_pass");
|
||||
}
|
||||
#end
|
||||
|
||||
// We are just about to enter compositing, add more custom passes here
|
||||
// #if rp_custom_pass
|
||||
// {
|
||||
// }
|
||||
// #end
|
||||
|
||||
// Begin compositor
|
||||
#if rp_autoexposure
|
||||
@ -1084,6 +1100,7 @@ class RenderPathDeferred {
|
||||
}
|
||||
#end
|
||||
path.setTarget(target);
|
||||
path.clearTarget(0x00000000);
|
||||
|
||||
path.bindTarget("tex", "tex");
|
||||
#if rp_compositordepth
|
||||
@ -1167,12 +1184,58 @@ class RenderPathDeferred {
|
||||
}
|
||||
#end
|
||||
|
||||
#if rp_fsr1
|
||||
{
|
||||
#if ((rp_antialiasing == "SMAA") || (rp_antialiasing == "TAA"))
|
||||
#if (rp_supersampling == 4)
|
||||
var fsrSource = "buf";
|
||||
var fsrDest = "buf";
|
||||
#else
|
||||
path.setTarget("bufb");
|
||||
path.bindTarget(framebuffer != "" ? framebuffer : "buf", "tex");
|
||||
path.drawShader("shader_datas/copy_pass/copy_pass");
|
||||
var fsrSource = "bufb";
|
||||
var fsrDest = "";
|
||||
#end
|
||||
#else
|
||||
#if (rp_supersampling == 4)
|
||||
var fsrSource = "buf";
|
||||
var fsrDest = "buf";
|
||||
#else
|
||||
path.setTarget("bufa");
|
||||
path.bindTarget(target != "" ? target : "buf", "tex");
|
||||
path.drawShader("shader_datas/copy_pass/copy_pass");
|
||||
var fsrSource = "bufa";
|
||||
var fsrDest = "";
|
||||
#end
|
||||
#end
|
||||
|
||||
path.setTarget(fsrDest);
|
||||
path.bindTarget(fsrSource, "tex");
|
||||
path.drawShader("shader_datas/fsr1_rcas_pass/fsr1_rcas_pass");
|
||||
}
|
||||
#end
|
||||
|
||||
#if (rp_supersampling == 4)
|
||||
{
|
||||
var finalTarget = "";
|
||||
path.setTarget(finalTarget);
|
||||
path.bindTarget(framebuffer, "tex");
|
||||
path.drawShader("shader_datas/supersample_resolve/supersample_resolve");
|
||||
path.drawShader("shader_datas/copy_pass/copy_pass");
|
||||
}
|
||||
#end
|
||||
|
||||
// VR: Composite final output to XR framebuffer
|
||||
#if (kha_webgl && lnx_vr)
|
||||
if (iron.RenderPath.isVRPresenting()) {
|
||||
#if ((rp_antialiasing == "SMAA") || (rp_antialiasing == "TAA"))
|
||||
var vrCompositeSource = framebuffer;
|
||||
#else
|
||||
var vrCompositeSource = "tex";
|
||||
#end
|
||||
if (vrCompositeSource == "") vrCompositeSource = "tex";
|
||||
|
||||
path.compositeToXR(vrCompositeSource);
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
@ -29,7 +29,12 @@ class RenderPathForward {
|
||||
}
|
||||
#else
|
||||
{
|
||||
path.setTarget("");
|
||||
var isVR = iron.RenderPath.isVRPresenting() || iron.RenderPath.isVRSimulateMode();
|
||||
if (isVR) {
|
||||
path.setTarget("lbuffer0");
|
||||
} else {
|
||||
path.setTarget("");
|
||||
}
|
||||
}
|
||||
#end
|
||||
}
|
||||
@ -251,25 +256,18 @@ class RenderPathForward {
|
||||
#end
|
||||
#end
|
||||
|
||||
#if (rp_volumetriclight || rp_ssgi != "Off")
|
||||
#if rp_volumetriclight
|
||||
{
|
||||
#if (rp_volumetriclight)
|
||||
path.loadShader("shader_datas/volumetric_light/volumetric_light");
|
||||
path.loadShader("shader_datas/blur_bilat_pass/blur_bilat_pass_x");
|
||||
path.loadShader("shader_datas/blur_bilat_blend_pass/blur_bilat_blend_pass_y");
|
||||
#end
|
||||
|
||||
|
||||
var t = new RenderTargetRaw();
|
||||
t.name = "singlea";
|
||||
t.width = 0;
|
||||
t.height = 0;
|
||||
t.displayp = Inc.getDisplayp();
|
||||
#if (rp_ssgi == "SSGI")
|
||||
t.format = "RGBA32";
|
||||
#else
|
||||
t.format = "R8";
|
||||
#end
|
||||
t.scale = Inc.getSuperSampling();
|
||||
path.createRenderTarget(t);
|
||||
|
||||
@ -278,11 +276,51 @@ class RenderPathForward {
|
||||
t.width = 0;
|
||||
t.height = 0;
|
||||
t.displayp = Inc.getDisplayp();
|
||||
#if (rp_ssgi == "SSGI")
|
||||
t.format = "RGBA32";
|
||||
#else
|
||||
t.format = "R8";
|
||||
#end
|
||||
t.scale = Inc.getSuperSampling();
|
||||
path.createRenderTarget(t);
|
||||
}
|
||||
#end
|
||||
|
||||
#if rp_ssao
|
||||
{
|
||||
var t = new RenderTargetRaw();
|
||||
t.name = "singlea";
|
||||
t.width = 0;
|
||||
t.height = 0;
|
||||
t.displayp = Inc.getDisplayp();
|
||||
t.format = "R8";
|
||||
t.scale = Inc.getSuperSampling();
|
||||
path.createRenderTarget(t);
|
||||
|
||||
var t = new RenderTargetRaw();
|
||||
t.name = "singleb";
|
||||
t.width = 0;
|
||||
t.height = 0;
|
||||
t.displayp = Inc.getDisplayp();
|
||||
t.format = "R8";
|
||||
t.scale = Inc.getSuperSampling();
|
||||
path.createRenderTarget(t);
|
||||
}
|
||||
#end
|
||||
|
||||
#if rp_ssgi
|
||||
{
|
||||
var t = new RenderTargetRaw();
|
||||
t.name = "ssgi_a";
|
||||
t.width = 0;
|
||||
t.height = 0;
|
||||
t.displayp = Inc.getDisplayp();
|
||||
t.format = "RGBA32";
|
||||
t.scale = Inc.getSuperSampling();
|
||||
path.createRenderTarget(t);
|
||||
|
||||
var t = new RenderTargetRaw();
|
||||
t.name = "ssgi_b";
|
||||
t.width = 0;
|
||||
t.height = 0;
|
||||
t.displayp = Inc.getDisplayp();
|
||||
t.format = "RGBA32";
|
||||
t.scale = Inc.getSuperSampling();
|
||||
path.createRenderTarget(t);
|
||||
}
|
||||
@ -308,7 +346,15 @@ class RenderPathForward {
|
||||
}
|
||||
#end
|
||||
|
||||
#if (rp_ssr_half || rp_ssgi_half || (rp_voxels != "Off"))
|
||||
#if rp_fsr1
|
||||
{
|
||||
path.loadShader("shader_datas/fsr1_easu_pass/fsr1_easu_pass");
|
||||
path.loadShader("shader_datas/fsr1_rcas_pass/fsr1_rcas_pass");
|
||||
path.loadShader("shader_datas/copy_pass/copy_pass");
|
||||
}
|
||||
#end
|
||||
|
||||
#if (rp_ssr_half || rp_ssao_half || rp_ssgi_half || (rp_voxels != "Off"))
|
||||
{
|
||||
path.loadShader("shader_datas/downsample_depth/downsample_depth");
|
||||
var t = new RenderTargetRaw();
|
||||
@ -422,14 +468,21 @@ class RenderPathForward {
|
||||
|
||||
#if (rp_ssrefr || lnx_voxelgi_refract)
|
||||
{
|
||||
path.setTarget("gbuffer_refraction"); // Only clear gbuffer0
|
||||
path.clearTarget(0xffffff00);
|
||||
path.setTarget("gbuffer_refraction");
|
||||
path.clearTarget(0xffff00ff);
|
||||
}
|
||||
#end
|
||||
|
||||
#if rp_depthprepass
|
||||
{
|
||||
path.drawMeshes("depth");
|
||||
#if rp_stereo
|
||||
var isVR = iron.RenderPath.isVRPresenting() || iron.RenderPath.isVRSimulateMode();
|
||||
if (!isVR) {
|
||||
#end
|
||||
path.drawMeshes("depth");
|
||||
#if rp_stereo
|
||||
}
|
||||
#end
|
||||
}
|
||||
#end
|
||||
|
||||
@ -461,16 +514,23 @@ class RenderPathForward {
|
||||
#if rp_stereo
|
||||
{
|
||||
path.drawStereo(drawMeshes);
|
||||
if (iron.RenderPath.isVRPresenting()) {
|
||||
#if (kha_webgl && lnx_vr)
|
||||
// split-screen lbuffer0 to XR framebuffer
|
||||
path.compositeToXR("lbuffer0");
|
||||
#end
|
||||
return;
|
||||
}
|
||||
}
|
||||
#else
|
||||
{
|
||||
RenderPathCreator.drawMeshes();
|
||||
drawMeshes();
|
||||
}
|
||||
#end
|
||||
|
||||
#if (rp_render_to_texture || rp_voxels != "Off")
|
||||
{
|
||||
#if (rp_ssr_half || rp_ssgi_half || rp_voxels != "Off")
|
||||
#if (rp_ssr_half || rp_ssao_half || rp_ssgi_half || rp_voxels != "Off")
|
||||
path.setTarget("half");
|
||||
path.bindTarget("_main", "texdepth");
|
||||
path.drawShader("shader_datas/downsample_depth/downsample_depth");
|
||||
@ -515,8 +575,7 @@ class RenderPathForward {
|
||||
|
||||
path.drawMeshes("refraction");
|
||||
|
||||
path.setTarget("lbuffer0");
|
||||
|
||||
path.setTarget("bufa");
|
||||
path.bindTarget("lbuffer0", "tex");
|
||||
path.bindTarget("refr", "tex1");
|
||||
path.bindTarget("_main", "gbufferD");
|
||||
@ -525,6 +584,9 @@ class RenderPathForward {
|
||||
path.bindTarget("gbuffer_refraction", "gbuffer_refraction");
|
||||
|
||||
path.drawShader("shader_datas/ssrefr_pass/ssrefr_pass");
|
||||
path.setTarget("lbuffer0");
|
||||
path.bindTarget("bufa", "tex");
|
||||
path.drawShader("shader_datas/copy_pass/copy_pass");
|
||||
}
|
||||
}
|
||||
#end
|
||||
@ -595,7 +657,7 @@ class RenderPathForward {
|
||||
|
||||
path.drawMeshes("refraction");
|
||||
|
||||
path.setTarget("lbuffer0");
|
||||
path.setTarget("bufa");
|
||||
path.bindTarget("lbuffer0", "tex");
|
||||
path.bindTarget("refr", "tex1");
|
||||
path.bindTarget("_main", "gbufferD");
|
||||
@ -604,6 +666,9 @@ class RenderPathForward {
|
||||
path.bindTarget("gbuffer_refraction", "gbuffer_refraction");
|
||||
|
||||
path.drawShader("shader_datas/ssrefr_pass/ssrefr_pass");
|
||||
path.setTarget("lbuffer0");
|
||||
path.bindTarget("bufa", "tex");
|
||||
path.drawShader("shader_datas/copy_pass/copy_pass");
|
||||
}
|
||||
}
|
||||
#end
|
||||
@ -614,6 +679,18 @@ class RenderPathForward {
|
||||
}
|
||||
#end
|
||||
|
||||
#if rp_chromatic_aberration
|
||||
{
|
||||
path.setTarget("bufa");
|
||||
path.bindTarget("lbuffer0", "tex");
|
||||
path.drawShader("shader_datas/chromatic_aberration_pass/chromatic_aberration_pass");
|
||||
|
||||
path.setTarget("lbuffer0");
|
||||
path.bindTarget("bufa", "tex");
|
||||
path.drawShader("shader_datas/copy_pass/copy_pass");
|
||||
}
|
||||
#end
|
||||
|
||||
#if rp_volumetriclight
|
||||
{
|
||||
path.setTarget("singlea");
|
||||
@ -654,18 +731,6 @@ class RenderPathForward {
|
||||
}
|
||||
#end
|
||||
|
||||
#if rp_chromatic_aberration
|
||||
{
|
||||
path.setTarget("bufa");
|
||||
path.bindTarget("lbuffer0", "tex");
|
||||
path.drawShader("shader_datas/chromatic_aberration_pass/chromatic_aberration_pass");
|
||||
|
||||
path.setTarget("lbuffer0");
|
||||
path.bindTarget("bufa", "tex");
|
||||
path.drawShader("shader_datas/copy_pass/copy_pass");
|
||||
}
|
||||
#end
|
||||
|
||||
#if (rp_supersampling == 4)
|
||||
var framebuffer = "buf";
|
||||
#else
|
||||
@ -685,6 +750,7 @@ class RenderPathForward {
|
||||
}
|
||||
#end
|
||||
path.setTarget(target);
|
||||
path.clearTarget(0x00000000);
|
||||
|
||||
#if rp_compositordepth
|
||||
{
|
||||
@ -732,6 +798,40 @@ class RenderPathForward {
|
||||
}
|
||||
#end
|
||||
|
||||
#if rp_fsr1
|
||||
{
|
||||
// FSR1 RCAS sharpening pass applied after AA, expects sRGB [0-1] input
|
||||
#if ((rp_antialiasing == "SMAA") || (rp_antialiasing == "TAA"))
|
||||
#if (rp_supersampling == 4)
|
||||
var fsrSource = "buf";
|
||||
var fsrDest = "buf";
|
||||
#else
|
||||
// SMAA outputs to framebuffer which needs an intermediate buffer
|
||||
path.setTarget("bufb");
|
||||
path.bindTarget(framebuffer != "" ? framebuffer : "buf", "tex");
|
||||
path.drawShader("shader_datas/copy_pass/copy_pass");
|
||||
var fsrSource = "bufb";
|
||||
var fsrDest = "";
|
||||
#end
|
||||
#else
|
||||
#if (rp_supersampling == 4)
|
||||
var fsrSource = "buf";
|
||||
var fsrDest = "buf";
|
||||
#else
|
||||
path.setTarget("bufa");
|
||||
path.bindTarget(target != "" ? target : "buf", "tex");
|
||||
path.drawShader("shader_datas/copy_pass/copy_pass");
|
||||
var fsrSource = "bufa";
|
||||
var fsrDest = "";
|
||||
#end
|
||||
#end
|
||||
|
||||
path.setTarget(fsrDest);
|
||||
path.bindTarget(fsrSource, "tex");
|
||||
path.drawShader("shader_datas/fsr1_rcas_pass/fsr1_rcas_pass");
|
||||
}
|
||||
#end
|
||||
|
||||
#if (rp_supersampling == 4)
|
||||
{
|
||||
var finalTarget = "";
|
||||
|
||||
@ -96,6 +96,25 @@ class Starter {
|
||||
}
|
||||
#end
|
||||
|
||||
#if (js && lnx_jolt)
|
||||
function loadLibJolt(name: String) {
|
||||
kha.Assets.loadBlobFromPath(name, function(b: kha.Blob) {
|
||||
js.Syntax.code("(1,eval)({0})", b.toString());
|
||||
#if kha_krom
|
||||
js.Syntax.code("Jolt({print:function(s){iron.log(s);},instantiateWasm:function(imports,successCallback) {
|
||||
var wasmbin = Krom.loadBlob('jolt.wasm.wasm');
|
||||
var module = new WebAssembly.Module(wasmbin);
|
||||
var inst = new WebAssembly.Instance(module,imports);
|
||||
successCallback(inst);
|
||||
return inst.exports;
|
||||
}}).then(function(m){ Jolt=m; tasks--; start();})");
|
||||
#else
|
||||
js.Syntax.code("Jolt({print:function(s){iron.log(s);},locateFile:function(f){return 'jolt.wasm.wasm';}}).then(function(m){ Jolt=m; tasks--; start();})");
|
||||
#end
|
||||
});
|
||||
}
|
||||
#end
|
||||
|
||||
#if (js && lnx_navigation)
|
||||
function loadLib(name: String) {
|
||||
kha.Assets.loadBlobFromPath(name, function(b: kha.Blob) {
|
||||
@ -126,6 +145,11 @@ class Starter {
|
||||
#end
|
||||
#end
|
||||
|
||||
#if (js && lnx_jolt)
|
||||
tasks++;
|
||||
loadLibJolt("jolt.wasm.js");
|
||||
#end
|
||||
|
||||
#if (js && lnx_navigation)
|
||||
tasks++;
|
||||
#if kha_krom
|
||||
|
||||
@ -7,13 +7,11 @@ class KinematicCharacterController extends iron.Trait { public function new() {
|
||||
#else
|
||||
|
||||
#if lnx_bullet
|
||||
|
||||
typedef KinematicCharacterController = leenkx.trait.physics.bullet.KinematicCharacterController;
|
||||
|
||||
#elseif lnx_jolt
|
||||
typedef KinematicCharacterController = leenkx.trait.physics.jolt.KinematicCharacterController;
|
||||
#else
|
||||
|
||||
typedef KinematicCharacterController = leenkx.trait.physics.oimo.KinematicCharacterController;
|
||||
|
||||
#end
|
||||
|
||||
#end
|
||||
|
||||
@ -21,6 +21,8 @@ class PhysicsCache {
|
||||
|
||||
#if lnx_bullet
|
||||
var rb = object.getTrait(leenkx.trait.physics.bullet.RigidBody);
|
||||
#elseif lnx_jolt
|
||||
var rb = object.getTrait(leenkx.trait.physics.jolt.RigidBody);
|
||||
#else
|
||||
var rb = object.getTrait(leenkx.trait.physics.oimo.RigidBody);
|
||||
#end
|
||||
@ -42,6 +44,9 @@ class PhysicsCache {
|
||||
#if lnx_bullet
|
||||
if (leenkx.trait.physics.bullet.PhysicsWorld.active == null) return null;
|
||||
return leenkx.trait.physics.bullet.PhysicsWorld.active.getContacts(rb);
|
||||
#elseif lnx_jolt
|
||||
if (leenkx.trait.physics.jolt.PhysicsWorld.active == null) return null;
|
||||
return leenkx.trait.physics.jolt.PhysicsWorld.active.getContacts(rb);
|
||||
#else
|
||||
if (leenkx.trait.physics.oimo.PhysicsWorld.active == null) return null;
|
||||
return leenkx.trait.physics.oimo.PhysicsWorld.active.getContacts(rb);
|
||||
@ -61,6 +66,9 @@ class PhysicsCache {
|
||||
#if lnx_bullet
|
||||
if (leenkx.trait.physics.bullet.PhysicsWorld.active == null) return null;
|
||||
var contacts = leenkx.trait.physics.bullet.PhysicsWorld.active.getContacts(rb);
|
||||
#elseif lnx_jolt
|
||||
if (leenkx.trait.physics.jolt.PhysicsWorld.active == null) return null;
|
||||
var contacts = leenkx.trait.physics.jolt.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);
|
||||
|
||||
@ -10,6 +10,9 @@ class PhysicsConstraint extends iron.Trait { public function new() { super(); }
|
||||
#if lnx_bullet
|
||||
typedef PhysicsConstraint = leenkx.trait.physics.bullet.PhysicsConstraint;
|
||||
typedef ConstraintAxis = leenkx.trait.physics.bullet.PhysicsConstraint.ConstraintAxis;
|
||||
#elseif lnx_jolt
|
||||
typedef PhysicsConstraint = leenkx.trait.physics.jolt.PhysicsConstraint;
|
||||
typedef ConstraintAxis = leenkx.trait.physics.jolt.PhysicsConstraint.ConstraintType;
|
||||
#else
|
||||
typedef PhysicsConstraint = leenkx.trait.physics.oimo.PhysicsConstraint;
|
||||
typedef ConstraintAxis = leenkx.trait.physics.oimo.PhysicsConstraint.ConstraintAxis;
|
||||
|
||||
@ -7,13 +7,11 @@ class PhysicsHook extends iron.Trait { public function new() { super(); } }
|
||||
#else
|
||||
|
||||
#if lnx_bullet
|
||||
|
||||
typedef PhysicsHook = leenkx.trait.physics.bullet.PhysicsHook;
|
||||
|
||||
#elseif lnx_jolt
|
||||
typedef PhysicsHook = leenkx.trait.physics.jolt.PhysicsHook;
|
||||
#else
|
||||
|
||||
typedef PhysicsHook = leenkx.trait.physics.oimo.PhysicsHook;
|
||||
|
||||
#end
|
||||
|
||||
#end
|
||||
|
||||
@ -10,6 +10,9 @@ class PhysicsWorld extends iron.Trait { public function new() { super(); } }
|
||||
#if lnx_bullet
|
||||
typedef PhysicsWorld = leenkx.trait.physics.bullet.PhysicsWorld;
|
||||
typedef Hit = leenkx.trait.physics.bullet.PhysicsWorld.Hit;
|
||||
#elseif lnx_jolt
|
||||
typedef PhysicsWorld = leenkx.trait.physics.jolt.PhysicsWorld;
|
||||
typedef Hit = leenkx.trait.physics.jolt.PhysicsWorld.Hit;
|
||||
#else
|
||||
typedef PhysicsWorld = leenkx.trait.physics.oimo.PhysicsWorld;
|
||||
typedef Hit = leenkx.trait.physics.oimo.PhysicsWorld.Hit;
|
||||
|
||||
@ -8,15 +8,14 @@ class RigidBody extends iron.Trait { public function new() { super(); } }
|
||||
#else
|
||||
|
||||
#if lnx_bullet
|
||||
|
||||
typedef RigidBody = leenkx.trait.physics.bullet.RigidBody;
|
||||
typedef Shape = leenkx.trait.physics.bullet.RigidBody.Shape;
|
||||
|
||||
#elseif lnx_jolt
|
||||
typedef RigidBody = leenkx.trait.physics.jolt.RigidBody;
|
||||
typedef Shape = leenkx.trait.physics.jolt.RigidBody.Shape;
|
||||
#else
|
||||
|
||||
typedef RigidBody = leenkx.trait.physics.oimo.RigidBody;
|
||||
typedef Shape = leenkx.trait.physics.oimo.RigidBody.Shape;
|
||||
|
||||
#end
|
||||
|
||||
#end
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
package leenkx.trait.physics;
|
||||
|
||||
import iron.Trait;
|
||||
|
||||
#if (!lnx_physics_soft)
|
||||
|
||||
class SoftBody extends Trait { public function new() { super(); } }
|
||||
@ -7,13 +9,11 @@ class SoftBody extends Trait { public function new() { super(); } }
|
||||
#else
|
||||
|
||||
#if lnx_bullet
|
||||
|
||||
typedef SoftBody = leenkx.trait.physics.bullet.SoftBody;
|
||||
|
||||
#elseif lnx_jolt
|
||||
typedef SoftBody = leenkx.trait.physics.jolt.SoftBody;
|
||||
#else
|
||||
|
||||
typedef SoftBody = leenkx.trait.physics.oimo.SoftBody;
|
||||
|
||||
#end
|
||||
|
||||
#end
|
||||
|
||||
376
leenkx/Sources/leenkx/trait/physics/jolt/DebugDrawHelper.hx
Normal file
376
leenkx/Sources/leenkx/trait/physics/jolt/DebugDrawHelper.hx
Normal file
@ -0,0 +1,376 @@
|
||||
package leenkx.trait.physics.jolt;
|
||||
|
||||
#if lnx_jolt
|
||||
|
||||
import kha.FastFloat;
|
||||
import kha.System;
|
||||
import iron.math.Vec4;
|
||||
|
||||
#if lnx_ui
|
||||
import leenkx.ui.Canvas;
|
||||
#end
|
||||
|
||||
using StringTools;
|
||||
|
||||
enum abstract DebugDrawMode(Int) from Int to Int {
|
||||
var NoDebug = 0;
|
||||
var DrawWireframe = 1;
|
||||
var DrawAabb = 2;
|
||||
var DrawContactPoints = 4;
|
||||
var DrawConstraints = 8;
|
||||
var DrawConstraintLimits = 16;
|
||||
var DrawRayCast = 32;
|
||||
var DrawAll = 63;
|
||||
|
||||
@:op(A | B) static function or(lhs:DebugDrawMode, rhs:DebugDrawMode):DebugDrawMode;
|
||||
@:op(A & B) static function and(lhs:DebugDrawMode, rhs:DebugDrawMode):DebugDrawMode;
|
||||
}
|
||||
|
||||
class DebugDrawHelper {
|
||||
static inline var contactPointSizePx = 4;
|
||||
static inline var contactPointNormalColor = 0xffffffff;
|
||||
|
||||
final rayCastColor:Vec4 = new Vec4(0.0, 1.0, 0.0);
|
||||
final rayCastHitColor:Vec4 = new Vec4(1.0, 0.0, 0.0);
|
||||
final rayCastHitPointColor:Vec4 = new Vec4(1.0, 1.0, 0.0);
|
||||
final wireframeColor:Vec4 = new Vec4(0.0, 1.0, 0.0);
|
||||
final aabbColor:Vec4 = new Vec4(1.0, 1.0, 0.0);
|
||||
final constraintColor:Vec4 = new Vec4(0.0, 0.5, 1.0);
|
||||
|
||||
final physicsWorld:PhysicsWorld;
|
||||
final lines:Array<LineData> = [];
|
||||
final texts:Array<TextData> = [];
|
||||
var font:kha.Font = null;
|
||||
|
||||
var rayCasts:Array<TRayCastData> = [];
|
||||
var debugDrawMode:DebugDrawMode = NoDebug;
|
||||
|
||||
public function new(physicsWorld:PhysicsWorld, debugDrawMode:DebugDrawMode) {
|
||||
this.physicsWorld = physicsWorld;
|
||||
this.debugDrawMode = debugDrawMode;
|
||||
|
||||
#if lnx_ui
|
||||
iron.data.Data.getFont(Canvas.defaultFontName, function(defaultFont:kha.Font) {
|
||||
font = defaultFont;
|
||||
});
|
||||
#end
|
||||
|
||||
iron.App.notifyOnRender2D(onRender);
|
||||
if (debugDrawMode & DrawRayCast != 0) {
|
||||
iron.App.notifyOnFixedUpdate(function() {
|
||||
rayCasts.resize(0);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function drawLine(fromX:Float, fromY:Float, fromZ:Float, toX:Float, toY:Float, toZ:Float, r:Float, g:Float, b:Float) {
|
||||
final fromScreenSpace = worldToScreenFast(new Vec4(fromX, fromY, fromZ, 1.0));
|
||||
final toScreenSpace = worldToScreenFast(new Vec4(toX, toY, toZ, 1.0));
|
||||
|
||||
if (fromScreenSpace.w == 1 || toScreenSpace.w == 1) {
|
||||
lines.push({
|
||||
fromX: fromScreenSpace.x,
|
||||
fromY: fromScreenSpace.y,
|
||||
toX: toScreenSpace.x,
|
||||
toY: toScreenSpace.y,
|
||||
color: kha.Color.fromFloats(r, g, b, 1.0)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function drawLineVec(from:Vec4, to:Vec4, color:Vec4) {
|
||||
drawLine(from.x, from.y, from.z, to.x, to.y, to.z, color.x, color.y, color.z);
|
||||
}
|
||||
|
||||
public function drawContactPoint(pointX:Float, pointY:Float, pointZ:Float, normalX:Float, normalY:Float, normalZ:Float, distance:Float, r:Float, g:Float, b:Float) {
|
||||
final contactPointScreenSpace = worldToScreenFast(new Vec4(pointX, pointY, pointZ, 1.0));
|
||||
final toScreenSpace = worldToScreenFast(new Vec4(pointX + normalX * distance, pointY + normalY * distance, pointZ + normalZ * distance, 1.0));
|
||||
|
||||
if (contactPointScreenSpace.w == 1) {
|
||||
final color = kha.Color.fromFloats(r, g, b, 1.0);
|
||||
|
||||
lines.push({
|
||||
fromX: contactPointScreenSpace.x - contactPointSizePx,
|
||||
fromY: contactPointScreenSpace.y - contactPointSizePx,
|
||||
toX: contactPointScreenSpace.x + contactPointSizePx,
|
||||
toY: contactPointScreenSpace.y + contactPointSizePx,
|
||||
color: color
|
||||
});
|
||||
|
||||
lines.push({
|
||||
fromX: contactPointScreenSpace.x - contactPointSizePx,
|
||||
fromY: contactPointScreenSpace.y + contactPointSizePx,
|
||||
toX: contactPointScreenSpace.x + contactPointSizePx,
|
||||
toY: contactPointScreenSpace.y - contactPointSizePx,
|
||||
color: color
|
||||
});
|
||||
|
||||
if (toScreenSpace.w == 1) {
|
||||
lines.push({
|
||||
fromX: contactPointScreenSpace.x,
|
||||
fromY: contactPointScreenSpace.y,
|
||||
toX: toScreenSpace.x,
|
||||
toY: toScreenSpace.y,
|
||||
color: contactPointNormalColor
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function rayCast(rayCastData:TRayCastData) {
|
||||
rayCasts.push(rayCastData);
|
||||
}
|
||||
|
||||
function drawRayCast(f:Vec4, t:Vec4, hit:Bool) {
|
||||
final from = worldToScreenFast(f.clone());
|
||||
final to = worldToScreenFast(t.clone());
|
||||
var c:kha.Color;
|
||||
|
||||
if (from.w == 1 && to.w == 1) {
|
||||
if (hit)
|
||||
c = kha.Color.fromFloats(rayCastHitColor.x, rayCastHitColor.y, rayCastHitColor.z);
|
||||
else
|
||||
c = kha.Color.fromFloats(rayCastColor.x, rayCastColor.y, rayCastColor.z);
|
||||
|
||||
lines.push({
|
||||
fromX: from.x,
|
||||
fromY: from.y,
|
||||
toX: to.x,
|
||||
toY: to.y,
|
||||
color: c
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function drawHitPoint(hp:Vec4) {
|
||||
final hitPoint = worldToScreenFast(hp.clone());
|
||||
final c = kha.Color.fromFloats(rayCastHitPointColor.x, rayCastHitPointColor.y, rayCastHitPointColor.z);
|
||||
|
||||
if (hitPoint.w == 1) {
|
||||
lines.push({
|
||||
fromX: hitPoint.x - contactPointSizePx,
|
||||
fromY: hitPoint.y - contactPointSizePx,
|
||||
toX: hitPoint.x + contactPointSizePx,
|
||||
toY: hitPoint.y + contactPointSizePx,
|
||||
color: c
|
||||
});
|
||||
|
||||
lines.push({
|
||||
fromX: hitPoint.x - contactPointSizePx,
|
||||
fromY: hitPoint.y + contactPointSizePx,
|
||||
toX: hitPoint.x + contactPointSizePx,
|
||||
toY: hitPoint.y - contactPointSizePx,
|
||||
color: c
|
||||
});
|
||||
|
||||
if (font != null) {
|
||||
texts.push({
|
||||
x: hitPoint.x,
|
||||
y: hitPoint.y,
|
||||
color: c,
|
||||
text: 'RAYCAST HIT'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function drawBox(center:Vec4, halfExtents:Vec4, color:Vec4) {
|
||||
var c = center;
|
||||
var h = halfExtents;
|
||||
|
||||
// Bottom face
|
||||
drawLine(c.x - h.x, c.y - h.y, c.z - h.z, c.x + h.x, c.y - h.y, c.z - h.z, color.x, color.y, color.z);
|
||||
drawLine(c.x + h.x, c.y - h.y, c.z - h.z, c.x + h.x, c.y + h.y, c.z - h.z, color.x, color.y, color.z);
|
||||
drawLine(c.x + h.x, c.y + h.y, c.z - h.z, c.x - h.x, c.y + h.y, c.z - h.z, color.x, color.y, color.z);
|
||||
drawLine(c.x - h.x, c.y + h.y, c.z - h.z, c.x - h.x, c.y - h.y, c.z - h.z, color.x, color.y, color.z);
|
||||
|
||||
// Top face
|
||||
drawLine(c.x - h.x, c.y - h.y, c.z + h.z, c.x + h.x, c.y - h.y, c.z + h.z, color.x, color.y, color.z);
|
||||
drawLine(c.x + h.x, c.y - h.y, c.z + h.z, c.x + h.x, c.y + h.y, c.z + h.z, color.x, color.y, color.z);
|
||||
drawLine(c.x + h.x, c.y + h.y, c.z + h.z, c.x - h.x, c.y + h.y, c.z + h.z, color.x, color.y, color.z);
|
||||
drawLine(c.x - h.x, c.y + h.y, c.z + h.z, c.x - h.x, c.y - h.y, c.z + h.z, color.x, color.y, color.z);
|
||||
|
||||
// Vertical edges
|
||||
drawLine(c.x - h.x, c.y - h.y, c.z - h.z, c.x - h.x, c.y - h.y, c.z + h.z, color.x, color.y, color.z);
|
||||
drawLine(c.x + h.x, c.y - h.y, c.z - h.z, c.x + h.x, c.y - h.y, c.z + h.z, color.x, color.y, color.z);
|
||||
drawLine(c.x + h.x, c.y + h.y, c.z - h.z, c.x + h.x, c.y + h.y, c.z + h.z, color.x, color.y, color.z);
|
||||
drawLine(c.x - h.x, c.y + h.y, c.z - h.z, c.x - h.x, c.y + h.y, c.z + h.z, color.x, color.y, color.z);
|
||||
}
|
||||
|
||||
public function drawSphere(center:Vec4, radius:Float, color:Vec4) {
|
||||
final segments = 16;
|
||||
final step = Math.PI * 2 / segments;
|
||||
|
||||
// XY circle
|
||||
for (i in 0...segments) {
|
||||
var angle1 = i * step;
|
||||
var angle2 = (i + 1) * step;
|
||||
drawLine(
|
||||
center.x + Math.cos(angle1) * radius, center.y + Math.sin(angle1) * radius, center.z,
|
||||
center.x + Math.cos(angle2) * radius, center.y + Math.sin(angle2) * radius, center.z,
|
||||
color.x, color.y, color.z
|
||||
);
|
||||
}
|
||||
|
||||
// XZ circle
|
||||
for (i in 0...segments) {
|
||||
var angle1 = i * step;
|
||||
var angle2 = (i + 1) * step;
|
||||
drawLine(
|
||||
center.x + Math.cos(angle1) * radius, center.y, center.z + Math.sin(angle1) * radius,
|
||||
center.x + Math.cos(angle2) * radius, center.y, center.z + Math.sin(angle2) * radius,
|
||||
color.x, color.y, color.z
|
||||
);
|
||||
}
|
||||
|
||||
// YZ circle
|
||||
for (i in 0...segments) {
|
||||
var angle1 = i * step;
|
||||
var angle2 = (i + 1) * step;
|
||||
drawLine(
|
||||
center.x, center.y + Math.cos(angle1) * radius, center.z + Math.sin(angle1) * radius,
|
||||
center.x, center.y + Math.cos(angle2) * radius, center.z + Math.sin(angle2) * radius,
|
||||
color.x, color.y, color.z
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function setDebugMode(debugDrawMode:DebugDrawMode) {
|
||||
this.debugDrawMode = debugDrawMode;
|
||||
}
|
||||
|
||||
public function getDebugMode():DebugDrawMode {
|
||||
return debugDrawMode;
|
||||
}
|
||||
|
||||
function drawBodyWireframe(body:RigidBody) {
|
||||
if (body == null || body.object == null)
|
||||
return;
|
||||
|
||||
var transform = body.object.transform;
|
||||
var pos = transform.world.getLoc();
|
||||
var dim = transform.dim;
|
||||
var halfExtents = new Vec4(dim.x * 0.5, dim.y * 0.5, dim.z * 0.5);
|
||||
|
||||
drawBox(pos, halfExtents, wireframeColor);
|
||||
}
|
||||
|
||||
function drawBodyAabb(body:RigidBody) {
|
||||
if (body == null || body.object == null)
|
||||
return;
|
||||
|
||||
var transform = body.object.transform;
|
||||
var pos = transform.world.getLoc();
|
||||
var dim = transform.dim;
|
||||
var halfExtents = new Vec4(dim.x * 0.5, dim.y * 0.5, dim.z * 0.5);
|
||||
|
||||
drawBox(pos, halfExtents, aabbColor);
|
||||
}
|
||||
|
||||
function drawConstraintDebug(constraint:PhysicsConstraint) {
|
||||
if (constraint == null || constraint.body1 == null || constraint.body2 == null)
|
||||
return;
|
||||
|
||||
var pos1 = constraint.body1.object.transform.world.getLoc();
|
||||
var pos2 = constraint.body2.object.transform.world.getLoc();
|
||||
|
||||
drawLineVec(pos1, pos2, constraintColor);
|
||||
}
|
||||
|
||||
function onRender(g:kha.graphics2.Graphics) {
|
||||
if (getDebugMode() == NoDebug) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Draw physics debug info
|
||||
if (debugDrawMode & DrawWireframe != 0 || debugDrawMode & DrawAabb != 0) {
|
||||
for (body in physicsWorld.rbMap) {
|
||||
if (debugDrawMode & DrawWireframe != 0) {
|
||||
drawBodyWireframe(body);
|
||||
}
|
||||
if (debugDrawMode & DrawAabb != 0) {
|
||||
drawBodyAabb(body);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (debugDrawMode & DrawConstraints != 0) {
|
||||
for (constraint in physicsWorld.constraints) {
|
||||
drawConstraintDebug(constraint);
|
||||
}
|
||||
}
|
||||
|
||||
g.opacity = 1.0;
|
||||
|
||||
for (line in lines) {
|
||||
g.color = line.color;
|
||||
g.drawLine(line.fromX, line.fromY, line.toX, line.toY, 1.0);
|
||||
}
|
||||
lines.resize(0);
|
||||
|
||||
if (font != null) {
|
||||
g.font = font;
|
||||
g.fontSize = 12;
|
||||
for (text in texts) {
|
||||
g.color = text.color;
|
||||
g.drawString(text.text, text.x, text.y);
|
||||
}
|
||||
texts.resize(0);
|
||||
}
|
||||
|
||||
if (debugDrawMode & DrawRayCast != 0) {
|
||||
for (rayCastData in rayCasts) {
|
||||
if (rayCastData.hasHit) {
|
||||
drawRayCast(rayCastData.from, rayCastData.hitPoint, true);
|
||||
drawHitPoint(rayCastData.hitPoint);
|
||||
} else {
|
||||
drawRayCast(rayCastData.from, rayCastData.to, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline function worldToScreenFast(loc:Vec4):Vec4 {
|
||||
final cam = iron.Scene.active.camera;
|
||||
loc.w = 1.0;
|
||||
loc.applyproj(cam.VP);
|
||||
|
||||
if (loc.z < -1 || loc.z > 1) {
|
||||
loc.w = 0.0;
|
||||
} else {
|
||||
loc.x = (loc.x + 1) * 0.5 * System.windowWidth();
|
||||
loc.y = (1 - loc.y) * 0.5 * System.windowHeight();
|
||||
loc.w = 1.0;
|
||||
}
|
||||
|
||||
return loc;
|
||||
}
|
||||
}
|
||||
|
||||
@:structInit
|
||||
class LineData {
|
||||
public var fromX:FastFloat;
|
||||
public var fromY:FastFloat;
|
||||
public var toX:FastFloat;
|
||||
public var toY:FastFloat;
|
||||
public var color:kha.Color;
|
||||
}
|
||||
|
||||
@:structInit
|
||||
class TextData {
|
||||
public var x:FastFloat;
|
||||
public var y:FastFloat;
|
||||
public var color:kha.Color;
|
||||
public var text:String;
|
||||
}
|
||||
|
||||
@:structInit
|
||||
typedef TRayCastData = {
|
||||
var from:Vec4;
|
||||
var to:Vec4;
|
||||
var hasHit:Bool;
|
||||
@:optional var hitPoint:Vec4;
|
||||
@:optional var hitNormal:Vec4;
|
||||
}
|
||||
|
||||
#end
|
||||
@ -0,0 +1,380 @@
|
||||
package leenkx.trait.physics.jolt;
|
||||
|
||||
#if lnx_jolt
|
||||
|
||||
import iron.Trait;
|
||||
import iron.math.Vec4;
|
||||
import iron.math.Quat;
|
||||
import iron.object.Transform;
|
||||
import iron.object.MeshObject;
|
||||
|
||||
class KinematicCharacterController extends Trait {
|
||||
|
||||
var shape:ControllerShape;
|
||||
public var physics:PhysicsWorld;
|
||||
public var transform:Transform = null;
|
||||
public var mass:Float;
|
||||
public var friction:Float;
|
||||
public var restitution:Float;
|
||||
public var collisionMargin:Float;
|
||||
public var animated:Bool;
|
||||
public var group = 1;
|
||||
var bodyScaleX:Float;
|
||||
var bodyScaleY:Float;
|
||||
var bodyScaleZ:Float;
|
||||
var currentScaleX:Float;
|
||||
var currentScaleY:Float;
|
||||
var currentScaleZ:Float;
|
||||
var jumpSpeed:Float;
|
||||
|
||||
public var body:jolt.Jt.Body;
|
||||
public var bodyId:jolt.Jt.BodyID;
|
||||
public var ready = false;
|
||||
static var nextId = 0;
|
||||
public var id = 0;
|
||||
public var onReady:Void->Void = null;
|
||||
|
||||
static var nullvec = true;
|
||||
static var vec1:jolt.Jt.Vec3;
|
||||
static var quat1:jolt.Jt.Quat;
|
||||
static var quat = new Quat();
|
||||
|
||||
var walkDirection:Vec4 = new Vec4();
|
||||
var gravityEnabled = true;
|
||||
var gravityFactor = 1.0;
|
||||
|
||||
public function new(mass = 1.0, shape = ControllerShape.Capsule, jumpSpeed = 8.0, friction = 0.5, restitution = 0.0,
|
||||
collisionMargin = 0.0, animated = false, group = 1) {
|
||||
super();
|
||||
|
||||
this.mass = mass;
|
||||
this.jumpSpeed = jumpSpeed;
|
||||
this.shape = shape;
|
||||
this.friction = friction;
|
||||
this.restitution = restitution;
|
||||
this.collisionMargin = collisionMargin;
|
||||
this.animated = animated;
|
||||
this.group = group;
|
||||
|
||||
notifyOnAdd(init);
|
||||
notifyOnLateUpdate(lateUpdate);
|
||||
notifyOnRemove(removeFromWorld);
|
||||
}
|
||||
|
||||
inline function withMargin(f:Float):Float {
|
||||
return f + f * collisionMargin;
|
||||
}
|
||||
|
||||
public function notifyOnReady(f:Void->Void) {
|
||||
onReady = f;
|
||||
if (ready)
|
||||
onReady();
|
||||
}
|
||||
|
||||
public function init() {
|
||||
if (ready)
|
||||
return;
|
||||
|
||||
transform = object.transform;
|
||||
physics = PhysicsWorld.active;
|
||||
|
||||
if (physics == null) {
|
||||
new PhysicsWorld();
|
||||
physics = PhysicsWorld.active;
|
||||
}
|
||||
|
||||
#if js
|
||||
// Check if Jolt is initialized - defer if not
|
||||
if (!physics.physicsReady) {
|
||||
haxe.Timer.delay(init, 16);
|
||||
return;
|
||||
}
|
||||
#end
|
||||
|
||||
ready = true;
|
||||
|
||||
if (nullvec) {
|
||||
nullvec = false;
|
||||
vec1 = new jolt.Jt.Vec3(0, 0, 0);
|
||||
quat1 = new jolt.Jt.Quat(0, 0, 0, 1);
|
||||
}
|
||||
|
||||
var joltShape:jolt.Jt.Shape = createShape();
|
||||
|
||||
var pos = transform.world.getLoc();
|
||||
var rot = new iron.math.Quat();
|
||||
rot.fromMat(transform.world);
|
||||
|
||||
// Jolt uses RVec3 for world positions
|
||||
var jPos = new jolt.Jt.RVec3(pos.x, pos.y, pos.z);
|
||||
var jRot = new jolt.Jt.Quat(rot.x, rot.y, rot.z, rot.w);
|
||||
var settings = new jolt.Jt.BodyCreationSettings(joltShape, jPos, jRot, 1, 1);
|
||||
|
||||
// Use kinematic body for character controller
|
||||
settings.mFriction = friction;
|
||||
settings.mRestitution = restitution;
|
||||
|
||||
body = physics.bodyInterface.CreateBody(settings);
|
||||
bodyId = body.GetID();
|
||||
|
||||
#if hl
|
||||
settings.delete();
|
||||
jPos.delete();
|
||||
jRot.delete();
|
||||
#end
|
||||
|
||||
physics.bodyInterface.AddBody(bodyId, 1);
|
||||
|
||||
bodyScaleX = currentScaleX = transform.scale.x;
|
||||
bodyScaleY = currentScaleY = transform.scale.y;
|
||||
bodyScaleZ = currentScaleZ = transform.scale.z;
|
||||
|
||||
id = nextId;
|
||||
nextId++;
|
||||
|
||||
if (onReady != null)
|
||||
onReady();
|
||||
}
|
||||
|
||||
function createShape():jolt.Jt.Shape {
|
||||
var t = transform;
|
||||
if (shape == ControllerShape.Box) {
|
||||
var halfExtent = new jolt.Jt.Vec3(withMargin(t.dim.x / 2), withMargin(t.dim.y / 2), withMargin(t.dim.z / 2));
|
||||
return new jolt.Jt.BoxShape(halfExtent);
|
||||
} else if (shape == ControllerShape.Sphere) {
|
||||
var width = Math.max(t.dim.x, Math.max(t.dim.y, t.dim.z));
|
||||
return new jolt.Jt.SphereShape(withMargin(width / 2));
|
||||
} else if (shape == ControllerShape.Cylinder) {
|
||||
var radius = Math.max(t.dim.x, t.dim.y) / 2;
|
||||
var halfHeight = t.dim.z / 2;
|
||||
return new jolt.Jt.CylinderShape(withMargin(halfHeight), withMargin(radius));
|
||||
} else if (shape == ControllerShape.Capsule) {
|
||||
var r = t.dim.x / 2;
|
||||
var halfHeight = (t.dim.z - r * 2) / 2;
|
||||
if (halfHeight < 0.01) halfHeight = 0.01;
|
||||
return new jolt.Jt.CapsuleShape(withMargin(halfHeight), withMargin(r));
|
||||
} else if (shape == ControllerShape.Cone) {
|
||||
var radius = Math.max(t.dim.x, t.dim.y) / 2;
|
||||
var halfHeight = t.dim.z / 2;
|
||||
return new jolt.Jt.CylinderShape(withMargin(halfHeight), withMargin(radius));
|
||||
}
|
||||
// Default capsule
|
||||
var r = t.dim.x / 2;
|
||||
var halfHeight = (t.dim.z - r * 2) / 2;
|
||||
if (halfHeight < 0.01) halfHeight = 0.01;
|
||||
return new jolt.Jt.CapsuleShape(withMargin(halfHeight), withMargin(r));
|
||||
}
|
||||
|
||||
function lateUpdate() {
|
||||
if (!ready)
|
||||
return;
|
||||
if (object.animation != null || animated) {
|
||||
syncTransform();
|
||||
} else {
|
||||
var p = physics.bodyInterface.GetPosition(bodyId);
|
||||
var q = physics.bodyInterface.GetRotation(bodyId);
|
||||
|
||||
#if js
|
||||
transform.loc.set(cast p.GetX(), cast p.GetY(), cast p.GetZ());
|
||||
#else
|
||||
transform.loc.set(p.GetX(), p.GetY(), p.GetZ());
|
||||
#end
|
||||
transform.rot.set(q.GetX(), q.GetY(), q.GetZ(), q.GetW());
|
||||
|
||||
#if hl
|
||||
p.delete();
|
||||
q.delete();
|
||||
#end
|
||||
|
||||
if (object.parent != null) {
|
||||
var ptransform = object.parent.transform;
|
||||
transform.loc.x -= ptransform.worldx();
|
||||
transform.loc.y -= ptransform.worldy();
|
||||
transform.loc.z -= ptransform.worldz();
|
||||
}
|
||||
transform.buildMatrix();
|
||||
}
|
||||
}
|
||||
|
||||
public function canJump():Bool {
|
||||
// Simple ground check - could be improved with raycast
|
||||
return onGround();
|
||||
}
|
||||
|
||||
public function onGround():Bool {
|
||||
// Perform downward raycast to check ground
|
||||
var pos = transform.world.getLoc();
|
||||
var from = new Vec4(pos.x, pos.y, pos.z);
|
||||
var to = new Vec4(pos.x, pos.y, pos.z - 0.2);
|
||||
var hit = physics.rayCast(from, to);
|
||||
return hit != null;
|
||||
}
|
||||
|
||||
public function setJumpSpeed(jumpSpeed:Float) {
|
||||
this.jumpSpeed = jumpSpeed;
|
||||
}
|
||||
|
||||
public function setFallSpeed(fallSpeed:Float) {
|
||||
// Jolt handles this through gravity
|
||||
}
|
||||
|
||||
public function setMaxSlope(slopeRadians:Float) {
|
||||
// Would need CharacterVirtual for proper slope handling
|
||||
}
|
||||
|
||||
public function getMaxSlope():Float {
|
||||
return Math.PI / 4; // 45 degrees default
|
||||
}
|
||||
|
||||
public function setMaxJumpHeight(maxJumpHeight:Float) {
|
||||
// Calculate jump speed from height: v = sqrt(2 * g * h)
|
||||
var g = physics.getGravity().length();
|
||||
jumpSpeed = Math.sqrt(2 * g * maxJumpHeight);
|
||||
}
|
||||
|
||||
public function setWalkDirection(dir:Vec4) {
|
||||
walkDirection.setFrom(dir);
|
||||
var vel = new jolt.Jt.Vec3(dir.x, dir.y, dir.z);
|
||||
physics.bodyInterface.SetLinearVelocity(bodyId, vel);
|
||||
#if hl vel.delete(); #end
|
||||
}
|
||||
|
||||
public function setUpInterpolate(value:Bool) {
|
||||
// Not directly applicable in Jolt kinematic body
|
||||
}
|
||||
|
||||
public function jump() {
|
||||
var currentVel = physics.bodyInterface.GetLinearVelocity(bodyId);
|
||||
var vel = new jolt.Jt.Vec3(currentVel.GetX(), currentVel.GetY(), jumpSpeed);
|
||||
physics.bodyInterface.SetLinearVelocity(bodyId, vel);
|
||||
#if hl currentVel.delete(); vel.delete(); #end
|
||||
}
|
||||
|
||||
public function removeFromWorld() {
|
||||
if (physics != null && ready) {
|
||||
physics.bodyInterface.RemoveBody(bodyId);
|
||||
physics.bodyInterface.DestroyBody(bodyId);
|
||||
}
|
||||
}
|
||||
|
||||
public function activate() {
|
||||
physics.bodyInterface.ActivateBody(bodyId);
|
||||
}
|
||||
|
||||
public function disableGravity() {
|
||||
gravityEnabled = false;
|
||||
physics.bodyInterface.SetGravityFactor(bodyId, 0.0);
|
||||
}
|
||||
|
||||
public function enableGravity() {
|
||||
gravityEnabled = true;
|
||||
physics.bodyInterface.SetGravityFactor(bodyId, gravityFactor);
|
||||
}
|
||||
|
||||
public function setGravity(f:Float) {
|
||||
gravityFactor = f / 9.81; // Normalize
|
||||
if (gravityEnabled) {
|
||||
physics.bodyInterface.SetGravityFactor(bodyId, gravityFactor);
|
||||
}
|
||||
}
|
||||
|
||||
public function setActivationState(newState:Int) {
|
||||
if (newState == ControllerActivationState.NoDeactivation) {
|
||||
// Keep active - Jolt handles this differently
|
||||
activate();
|
||||
}
|
||||
}
|
||||
|
||||
public function setFriction(f:Float) {
|
||||
physics.bodyInterface.SetFriction(bodyId, f);
|
||||
this.friction = f;
|
||||
}
|
||||
|
||||
public function syncTransform() {
|
||||
var t = transform;
|
||||
t.buildMatrix();
|
||||
var pos = t.world.getLoc();
|
||||
var rot = new iron.math.Quat();
|
||||
rot.fromMat(t.world);
|
||||
|
||||
// Jolt uses RVec3 for world positions
|
||||
var p = new jolt.Jt.RVec3(pos.x, pos.y, pos.z);
|
||||
var q = new jolt.Jt.Quat(rot.x, rot.y, rot.z, rot.w);
|
||||
physics.bodyInterface.SetPosition(bodyId, p, 0);
|
||||
physics.bodyInterface.SetRotation(bodyId, q, 0);
|
||||
#if hl p.delete(); q.delete(); #end
|
||||
|
||||
activate();
|
||||
}
|
||||
|
||||
public function getLinearVelocity():Vec4 {
|
||||
var vel = physics.bodyInterface.GetLinearVelocity(bodyId);
|
||||
var result = new Vec4(vel.GetX(), vel.GetY(), vel.GetZ());
|
||||
#if hl vel.delete(); #end
|
||||
return result;
|
||||
}
|
||||
|
||||
public function setLinearVelocity(velocity:Vec4) {
|
||||
var vel = new jolt.Jt.Vec3(velocity.x, velocity.y, velocity.z);
|
||||
physics.bodyInterface.SetLinearVelocity(bodyId, vel);
|
||||
#if hl vel.delete(); #end
|
||||
}
|
||||
|
||||
public function getPosition():Vec4 {
|
||||
var pos = physics.bodyInterface.GetPosition(bodyId);
|
||||
var result = new Vec4(pos.GetX(), pos.GetY(), pos.GetZ());
|
||||
#if hl pos.delete(); #end
|
||||
return result;
|
||||
}
|
||||
|
||||
public function setPosition(position:Vec4) {
|
||||
var p = new jolt.Jt.RVec3(position.x, position.y, position.z);
|
||||
physics.bodyInterface.SetPosition(bodyId, p, 0);
|
||||
#if hl p.delete(); #end
|
||||
}
|
||||
|
||||
public function warp(position:Vec4) {
|
||||
setPosition(position);
|
||||
var zeroVel = new jolt.Jt.Vec3(0, 0, 0);
|
||||
physics.bodyInterface.SetLinearVelocity(bodyId, zeroVel);
|
||||
#if hl zeroVel.delete(); #end
|
||||
}
|
||||
|
||||
public function move(direction:Vec4, speed:Float) {
|
||||
var moveVel = new Vec4(direction.x * speed, direction.y * speed, direction.z * speed);
|
||||
|
||||
// Preserve vertical velocity for jumping/falling
|
||||
var currentVel = physics.bodyInterface.GetLinearVelocity(bodyId);
|
||||
var vel = new jolt.Jt.Vec3(moveVel.x, moveVel.y, currentVel.GetZ());
|
||||
physics.bodyInterface.SetLinearVelocity(bodyId, vel);
|
||||
#if hl currentVel.delete(); vel.delete(); #end
|
||||
}
|
||||
|
||||
public function getGroundState():Int {
|
||||
if (onGround()) {
|
||||
return 0; // OnGround
|
||||
}
|
||||
return 3; // InAir
|
||||
}
|
||||
|
||||
public function isSupported():Bool {
|
||||
return onGround();
|
||||
}
|
||||
}
|
||||
|
||||
@:enum abstract ControllerShape(Int) from Int to Int {
|
||||
var Box = 0;
|
||||
var Sphere = 1;
|
||||
var ConvexHull = 2;
|
||||
var Cone = 3;
|
||||
var Cylinder = 4;
|
||||
var Capsule = 5;
|
||||
}
|
||||
|
||||
@:enum abstract ControllerActivationState(Int) from Int to Int {
|
||||
var Active = 1;
|
||||
var NoDeactivation = 4;
|
||||
var NoSimulation = 5;
|
||||
}
|
||||
|
||||
#end
|
||||
405
leenkx/Sources/leenkx/trait/physics/jolt/PhysicsConstraint.hx
Normal file
405
leenkx/Sources/leenkx/trait/physics/jolt/PhysicsConstraint.hx
Normal file
@ -0,0 +1,405 @@
|
||||
package leenkx.trait.physics.jolt;
|
||||
|
||||
#if lnx_jolt
|
||||
|
||||
import iron.Trait;
|
||||
import iron.math.Vec4;
|
||||
import iron.math.Quat;
|
||||
import iron.math.Mat4;
|
||||
import iron.object.Object;
|
||||
|
||||
@:enum abstract ConstraintType(Int) from Int to Int {
|
||||
var Fixed = 0;
|
||||
var Point = 1;
|
||||
var Hinge = 2;
|
||||
var Slider = 3;
|
||||
var Piston = 4;
|
||||
var Generic = 5;
|
||||
var GenericSpring = 6;
|
||||
var Distance = 7;
|
||||
}
|
||||
|
||||
class PhysicsConstraint extends Trait {
|
||||
public var id:Int;
|
||||
public var physics:PhysicsWorld;
|
||||
public var body1:RigidBody;
|
||||
public var body2:RigidBody;
|
||||
public var type:ConstraintType;
|
||||
public var con:jolt.Jt.Constraint;
|
||||
public var conReady:Bool = false;
|
||||
public var disableCollisions:Bool;
|
||||
|
||||
var body1Obj:Object;
|
||||
var body2Obj:Object;
|
||||
var limits:Array<Float>;
|
||||
var breakingThreshold:Float;
|
||||
|
||||
static var nextId = 0;
|
||||
|
||||
public function new(body1:Object, body2:Object, type:ConstraintType, disableCollisions:Bool = false, breakingThreshold:Float = 0.0,
|
||||
limits:Array<Float> = null) {
|
||||
super();
|
||||
|
||||
this.type = type;
|
||||
this.disableCollisions = disableCollisions;
|
||||
this.breakingThreshold = breakingThreshold;
|
||||
this.limits = limits;
|
||||
this.id = nextId++;
|
||||
this.body1Obj = body1;
|
||||
this.body2Obj = body2;
|
||||
|
||||
notifyOnInit(function() {
|
||||
this.body1 = body1.getTrait(RigidBody);
|
||||
this.body2 = body2.getTrait(RigidBody);
|
||||
tryInit();
|
||||
});
|
||||
|
||||
notifyOnRemove(removeFromWorld);
|
||||
}
|
||||
|
||||
function tryInit() {
|
||||
if (this.body1 != null && this.body1.ready && this.body2 != null && this.body2.ready) {
|
||||
init();
|
||||
} else if (this.body1 != null || this.body2 != null) {
|
||||
// Bodies exist but not ready yet, retry next frame
|
||||
iron.App.notifyOnUpdate(retryInit);
|
||||
}
|
||||
}
|
||||
|
||||
function retryInit() {
|
||||
iron.App.removeUpdate(retryInit);
|
||||
tryInit();
|
||||
}
|
||||
|
||||
function init() {
|
||||
physics = PhysicsWorld.active;
|
||||
|
||||
// Compute constraint frames in each body's local space (exactly matches Bullet approach)
|
||||
var t = object.transform; // pivot object
|
||||
var t1 = body1Obj.transform; // body1 object
|
||||
var t2 = body2Obj.transform; // body2 object
|
||||
|
||||
// Frame In A: pivot transform in body1's local space
|
||||
var frameT = t.world.clone();
|
||||
var frameInA = t1.world.clone();
|
||||
frameInA.getInverse(frameInA);
|
||||
frameT.multmat(frameInA);
|
||||
frameInA = frameT.clone();
|
||||
|
||||
// Frame In B: pivot transform in body2's local space
|
||||
frameT = t.world.clone();
|
||||
var frameInB = t2.world.clone();
|
||||
frameInB.getInverse(frameInB);
|
||||
frameT.multmat(frameInB);
|
||||
frameInB = frameT.clone();
|
||||
|
||||
// Decompose frames to get local positions and orientations
|
||||
var locA = new Vec4();
|
||||
var rotA = new Quat();
|
||||
var sclA = new Vec4();
|
||||
frameInA.decompose(locA, rotA, sclA);
|
||||
|
||||
var locB = new Vec4();
|
||||
var rotB = new Quat();
|
||||
var sclB = new Vec4();
|
||||
frameInB.decompose(locB, rotB, sclB);
|
||||
|
||||
// Extract local axes from each frame (normalized to remove scale)
|
||||
var rightA = frameInA.right().normalize();
|
||||
var upA = frameInA.up().normalize();
|
||||
var rightB = frameInB.right().normalize();
|
||||
var upB = frameInB.up().normalize();
|
||||
|
||||
// Create Jolt vectors for body1 local frame
|
||||
var jPt1 = new jolt.Jt.RVec3(locA.x, locA.y, locA.z);
|
||||
var jAxX1 = new jolt.Jt.Vec3(rightA.x, rightA.y, rightA.z);
|
||||
var jAxY1 = new jolt.Jt.Vec3(upA.x, upA.y, upA.z);
|
||||
|
||||
// Create Jolt vectors for body2 local frame
|
||||
var jPt2 = new jolt.Jt.RVec3(locB.x, locB.y, locB.z);
|
||||
var jAxX2 = new jolt.Jt.Vec3(rightB.x, rightB.y, rightB.z);
|
||||
var jAxY2 = new jolt.Jt.Vec3(upB.x, upB.y, upB.z);
|
||||
|
||||
switch (type) {
|
||||
case Fixed:
|
||||
var settings = new jolt.Jt.FixedConstraintSettings();
|
||||
settings.mSpace = 0; // LocalToBodyCOM
|
||||
settings.mAutoDetectPoint = false;
|
||||
settings.mPoint1 = jPt1;
|
||||
settings.mPoint2 = jPt2;
|
||||
settings.mAxisX1 = jAxX1;
|
||||
settings.mAxisY1 = jAxY1;
|
||||
settings.mAxisX2 = jAxX2;
|
||||
settings.mAxisY2 = jAxY2;
|
||||
con = settings.Create(body1.body, body2.body);
|
||||
#if hl settings.delete(); #end
|
||||
|
||||
case Point:
|
||||
var settings = new jolt.Jt.PointConstraintSettings();
|
||||
settings.mSpace = 0;
|
||||
settings.mPoint1 = jPt1;
|
||||
settings.mPoint2 = jPt2;
|
||||
con = settings.Create(body1.body, body2.body);
|
||||
#if hl settings.delete(); #end
|
||||
|
||||
case Hinge:
|
||||
var settings = new jolt.Jt.HingeConstraintSettings();
|
||||
settings.mSpace = 0;
|
||||
settings.mPoint1 = jPt1;
|
||||
settings.mPoint2 = jPt2;
|
||||
settings.mHingeAxis1 = jAxY1;
|
||||
settings.mHingeAxis2 = jAxY2;
|
||||
settings.mNormalAxis1 = jAxX1;
|
||||
settings.mNormalAxis2 = jAxX2;
|
||||
if (limits != null && limits.length >= 3 && limits[0] != 0) {
|
||||
settings.mLimitsMin = limits[1];
|
||||
settings.mLimitsMax = limits[2];
|
||||
}
|
||||
con = settings.Create(body1.body, body2.body);
|
||||
#if hl settings.delete(); #end
|
||||
|
||||
case Slider:
|
||||
var settings = new jolt.Jt.SliderConstraintSettings();
|
||||
settings.mSpace = 0;
|
||||
settings.mAutoDetectPoint = false;
|
||||
settings.mPoint1 = jPt1;
|
||||
settings.mPoint2 = jPt2;
|
||||
settings.mSliderAxis1 = jAxX1;
|
||||
settings.mSliderAxis2 = jAxX2;
|
||||
settings.mNormalAxis1 = jAxY1;
|
||||
settings.mNormalAxis2 = jAxY2;
|
||||
if (limits != null && limits.length >= 3 && limits[0] != 0) {
|
||||
settings.mLimitsMin = limits[1];
|
||||
settings.mLimitsMax = limits[2];
|
||||
}
|
||||
con = settings.Create(body1.body, body2.body);
|
||||
#if hl settings.delete(); #end
|
||||
|
||||
case Distance:
|
||||
var settings = new jolt.Jt.DistanceConstraintSettings();
|
||||
settings.mSpace = 0;
|
||||
settings.mPoint1 = jPt1;
|
||||
settings.mPoint2 = jPt2;
|
||||
if (limits != null && limits.length >= 2) {
|
||||
settings.mMinDistance = limits[0];
|
||||
settings.mMaxDistance = limits[1];
|
||||
}
|
||||
con = settings.Create(body1.body, body2.body);
|
||||
#if hl settings.delete(); #end
|
||||
|
||||
case Piston:
|
||||
var settings = new jolt.Jt.SliderConstraintSettings();
|
||||
settings.mSpace = 0;
|
||||
settings.mAutoDetectPoint = false;
|
||||
settings.mPoint1 = jPt1;
|
||||
settings.mPoint2 = jPt2;
|
||||
settings.mSliderAxis1 = jAxY1;
|
||||
settings.mSliderAxis2 = jAxY2;
|
||||
settings.mNormalAxis1 = jAxX1;
|
||||
settings.mNormalAxis2 = jAxX2;
|
||||
if (limits != null && limits.length >= 3 && limits[0] != 0) {
|
||||
settings.mLimitsMin = limits[1];
|
||||
settings.mLimitsMax = limits[2];
|
||||
}
|
||||
con = settings.Create(body1.body, body2.body);
|
||||
#if hl settings.delete(); #end
|
||||
|
||||
case Generic:
|
||||
var settings = new jolt.Jt.SixDOFConstraintSettings();
|
||||
settings.mSpace = 0;
|
||||
settings.mPosition1 = jPt1;
|
||||
settings.mPosition2 = jPt2;
|
||||
settings.mAxisX1 = jAxX1;
|
||||
settings.mAxisY1 = jAxY1;
|
||||
settings.mAxisX2 = jAxX2;
|
||||
settings.mAxisY2 = jAxY2;
|
||||
if (limits != null) {
|
||||
applySixDOFLimits(settings);
|
||||
} else {
|
||||
for (i in 0...6) settings.MakeFreeAxis(i);
|
||||
}
|
||||
con = settings.Create(body1.body, body2.body);
|
||||
#if hl settings.delete(); #end
|
||||
|
||||
case GenericSpring:
|
||||
var settings = new jolt.Jt.SixDOFConstraintSettings();
|
||||
settings.mSpace = 0;
|
||||
settings.mPosition1 = jPt1;
|
||||
settings.mPosition2 = jPt2;
|
||||
settings.mAxisX1 = jAxX1;
|
||||
settings.mAxisY1 = jAxY1;
|
||||
settings.mAxisX2 = jAxX2;
|
||||
settings.mAxisY2 = jAxY2;
|
||||
if (limits != null) {
|
||||
applySixDOFLimits(settings);
|
||||
} else {
|
||||
for (i in 0...6) settings.MakeFreeAxis(i);
|
||||
}
|
||||
con = settings.Create(body1.body, body2.body);
|
||||
#if hl settings.delete(); #end
|
||||
|
||||
default:
|
||||
var settings = new jolt.Jt.FixedConstraintSettings();
|
||||
settings.mSpace = 0;
|
||||
settings.mAutoDetectPoint = false;
|
||||
settings.mPoint1 = jPt1;
|
||||
settings.mPoint2 = jPt2;
|
||||
settings.mAxisX1 = jAxX1;
|
||||
settings.mAxisY1 = jAxY1;
|
||||
settings.mAxisX2 = jAxX2;
|
||||
settings.mAxisY2 = jAxY2;
|
||||
con = settings.Create(body1.body, body2.body);
|
||||
#if hl settings.delete(); #end
|
||||
}
|
||||
|
||||
// Clean up temporary Jolt objects
|
||||
#if hl
|
||||
jPt1.delete();
|
||||
jPt2.delete();
|
||||
jAxX1.delete();
|
||||
jAxY1.delete();
|
||||
jAxX2.delete();
|
||||
jAxY2.delete();
|
||||
#end
|
||||
|
||||
conReady = true;
|
||||
physics.addPhysicsConstraint(this);
|
||||
}
|
||||
|
||||
function applySixDOFLimits(settings:jolt.Jt.SixDOFConstraintSettings) {
|
||||
// Linear X (limits[0..2]): limits[0]=enabled, limits[1]=lower, limits[2]=upper
|
||||
if (limits.length > 2 && limits[0] != 0) {
|
||||
if (limits[1] > limits[2])
|
||||
settings.MakeFreeAxis(0);
|
||||
else
|
||||
settings.SetLimitedAxis(0, limits[1], limits[2]);
|
||||
} else {
|
||||
settings.MakeFreeAxis(0);
|
||||
}
|
||||
// Linear Y (limits[3..5])
|
||||
if (limits.length > 5 && limits[3] != 0) {
|
||||
if (limits[4] > limits[5])
|
||||
settings.MakeFreeAxis(1);
|
||||
else
|
||||
settings.SetLimitedAxis(1, limits[4], limits[5]);
|
||||
} else {
|
||||
settings.MakeFreeAxis(1);
|
||||
}
|
||||
// Linear Z (limits[6..8])
|
||||
if (limits.length > 8 && limits[6] != 0) {
|
||||
if (limits[7] > limits[8])
|
||||
settings.MakeFreeAxis(2);
|
||||
else
|
||||
settings.SetLimitedAxis(2, limits[7], limits[8]);
|
||||
} else {
|
||||
settings.MakeFreeAxis(2);
|
||||
}
|
||||
// Angular X (limits[9..11])
|
||||
if (limits.length > 11 && limits[9] != 0) {
|
||||
if (limits[10] > limits[11])
|
||||
settings.MakeFreeAxis(3);
|
||||
else
|
||||
settings.SetLimitedAxis(3, limits[10], limits[11]);
|
||||
} else {
|
||||
settings.MakeFreeAxis(3);
|
||||
}
|
||||
// Angular Y (limits[12..14])
|
||||
if (limits.length > 14 && limits[12] != 0) {
|
||||
if (limits[13] > limits[14])
|
||||
settings.MakeFreeAxis(4);
|
||||
else
|
||||
settings.SetLimitedAxis(4, limits[13], limits[14]);
|
||||
} else {
|
||||
settings.MakeFreeAxis(4);
|
||||
}
|
||||
// Angular Z (limits[15..17])
|
||||
if (limits.length > 17 && limits[15] != 0) {
|
||||
if (limits[16] > limits[17])
|
||||
settings.MakeFreeAxis(5);
|
||||
else
|
||||
settings.SetLimitedAxis(5, limits[16], limits[17]);
|
||||
} else {
|
||||
settings.MakeFreeAxis(5);
|
||||
}
|
||||
}
|
||||
|
||||
function removeFromWorld() {
|
||||
if (physics != null) {
|
||||
physics.removePhysicsConstraint(this);
|
||||
}
|
||||
}
|
||||
|
||||
public function delete() {
|
||||
conReady = false;
|
||||
}
|
||||
|
||||
public function setEnabled(enabled:Bool) {
|
||||
if (conReady) {
|
||||
con.SetEnabled(enabled);
|
||||
}
|
||||
}
|
||||
|
||||
public function isEnabled():Bool {
|
||||
return conReady ? con.GetEnabled() : false;
|
||||
}
|
||||
|
||||
// Bullet-compatible limit setting methods
|
||||
public function setHingeConstraintLimits(angLimit:Bool, lowerAngLimit:Float, upperAngLimit:Float) {
|
||||
if (limits == null) limits = [for (i in 0...36) 0.0];
|
||||
limits[0] = angLimit ? 1 : 0;
|
||||
limits[1] = lowerAngLimit * (Math.PI / 180);
|
||||
limits[2] = upperAngLimit * (Math.PI / 180);
|
||||
}
|
||||
|
||||
public function setSliderConstraintLimits(linLimit:Bool, lowerLinLimit:Float, upperLinLimit:Float) {
|
||||
if (limits == null) limits = [for (i in 0...36) 0.0];
|
||||
limits[0] = linLimit ? 1 : 0;
|
||||
limits[1] = lowerLinLimit;
|
||||
limits[2] = upperLinLimit;
|
||||
}
|
||||
|
||||
public function setPistonConstraintLimits(linLimit:Bool, lowerLinLimit:Float, upperLinLimit:Float, angLimit:Bool, lowerAngLimit:Float, upperAngLimit:Float) {
|
||||
if (limits == null) limits = [for (i in 0...36) 0.0];
|
||||
limits[0] = linLimit ? 1 : 0;
|
||||
limits[1] = lowerLinLimit;
|
||||
limits[2] = upperLinLimit;
|
||||
limits[3] = angLimit ? 1 : 0;
|
||||
limits[4] = lowerAngLimit * (Math.PI / 180);
|
||||
limits[5] = upperAngLimit * (Math.PI / 180);
|
||||
}
|
||||
|
||||
public function setGenericConstraintLimits(setLimit:Bool = false, lowerLimit:Float = 1.0, upperLimit:Float = -1.0, axis:ConstraintAxis = X, isAngular:Bool = false) {
|
||||
if (limits == null) limits = [for (i in 0...36) 0.0];
|
||||
var i = switch (axis) {
|
||||
case X: 0;
|
||||
case Y: 3;
|
||||
case Z: 6;
|
||||
};
|
||||
var j = isAngular ? 9 : 0;
|
||||
var radian = isAngular ? (Math.PI / 180) : 1;
|
||||
limits[i + j] = setLimit ? 1 : 0;
|
||||
limits[i + j + 1] = lowerLimit * radian;
|
||||
limits[i + j + 2] = upperLimit * radian;
|
||||
}
|
||||
|
||||
public function setSpringParams(setSpring:Bool = false, stiffness:Float = 10.0, damping:Float = 0.5, axis:ConstraintAxis = X, isAngular:Bool = false) {
|
||||
if (limits == null) limits = [for (i in 0...36) 0.0];
|
||||
var i = switch (axis) {
|
||||
case X: 18;
|
||||
case Y: 21;
|
||||
case Z: 24;
|
||||
};
|
||||
var j = isAngular ? 9 : 0;
|
||||
limits[i + j] = setSpring ? 1 : 0;
|
||||
limits[i + j + 1] = stiffness;
|
||||
limits[i + j + 2] = damping;
|
||||
}
|
||||
}
|
||||
|
||||
@:enum abstract ConstraintAxis(Int) from Int to Int {
|
||||
var X = 0;
|
||||
var Y = 1;
|
||||
var Z = 2;
|
||||
}
|
||||
|
||||
#end
|
||||
@ -0,0 +1,60 @@
|
||||
package leenkx.trait.physics.jolt;
|
||||
|
||||
import iron.Scene;
|
||||
import iron.object.Object;
|
||||
#if lnx_jolt
|
||||
|
||||
/**
|
||||
* A helper trait to add physics constraints when exporting via Blender.
|
||||
* This trait will be automatically removed once the constraint is added. Note that this trait
|
||||
* uses object names instead of object reference.
|
||||
**/
|
||||
class PhysicsConstraintExportHelper extends iron.Trait {
|
||||
|
||||
var body1: String;
|
||||
var body2: String;
|
||||
var type: Int;
|
||||
var disableCollisions: Bool;
|
||||
var breakingThreshold: Float;
|
||||
var limits: Array<Float>;
|
||||
var constraintAdded: Bool = false;
|
||||
var relativeConstraint: Bool = false;
|
||||
|
||||
public function new(body1: String, body2: String, type: Int, disableCollisions: Bool, breakingThreshold: Float, relatieConstraint: Bool = false, limits: Array<Float> = null) {
|
||||
super();
|
||||
|
||||
this.body1 = body1;
|
||||
this.body2 = body2;
|
||||
this.type = type;
|
||||
this.disableCollisions = disableCollisions;
|
||||
this.breakingThreshold = breakingThreshold;
|
||||
this.relativeConstraint = relatieConstraint;
|
||||
this.limits = limits;
|
||||
notifyOnInit(init);
|
||||
notifyOnUpdate(update);
|
||||
}
|
||||
|
||||
function init() {
|
||||
var target1;
|
||||
var target2;
|
||||
|
||||
if(relativeConstraint) {
|
||||
|
||||
target1 = object.parent.getChild(body1);
|
||||
target2 = object.parent.getChild(body2);
|
||||
}
|
||||
else {
|
||||
|
||||
target1 = Scene.active.getChild(body1);
|
||||
target2 = Scene.active.getChild(body2);
|
||||
}
|
||||
object.addTrait(new PhysicsConstraint(target1, target2, type, disableCollisions, breakingThreshold, limits));
|
||||
constraintAdded = true;
|
||||
}
|
||||
|
||||
function update() {
|
||||
if(constraintAdded) this.remove();
|
||||
}
|
||||
}
|
||||
|
||||
#end
|
||||
122
leenkx/Sources/leenkx/trait/physics/jolt/PhysicsHook.hx
Normal file
122
leenkx/Sources/leenkx/trait/physics/jolt/PhysicsHook.hx
Normal file
@ -0,0 +1,122 @@
|
||||
package leenkx.trait.physics.jolt;
|
||||
|
||||
#if lnx_jolt
|
||||
|
||||
import iron.math.Vec4;
|
||||
import iron.math.Mat4;
|
||||
import iron.math.Quat;
|
||||
import iron.Trait;
|
||||
import iron.object.Object;
|
||||
import iron.object.MeshObject;
|
||||
import iron.object.Transform;
|
||||
import iron.data.MeshData;
|
||||
import iron.data.SceneFormat;
|
||||
|
||||
class PhysicsHook extends Trait {
|
||||
|
||||
var target:Object;
|
||||
var targetName:String;
|
||||
var targetTransform:Transform;
|
||||
var verts:Array<Float>;
|
||||
|
||||
var hookBodyId:jolt.Jt.BodyID = null;
|
||||
var constraintId:Int = -1;
|
||||
|
||||
static var nullvec = true;
|
||||
static var vec1:jolt.Jt.Vec3;
|
||||
static var quat1:jolt.Jt.Quat;
|
||||
static var quat = new Quat();
|
||||
|
||||
public function new(targetName:String, verts:Array<Float>) {
|
||||
super();
|
||||
|
||||
this.targetName = targetName;
|
||||
this.verts = verts;
|
||||
|
||||
iron.Scene.active.notifyOnInit(function() {
|
||||
notifyOnInit(init);
|
||||
notifyOnUpdate(update);
|
||||
});
|
||||
}
|
||||
|
||||
function init() {
|
||||
if (nullvec) {
|
||||
nullvec = false;
|
||||
vec1 = new jolt.Jt.Vec3(0, 0, 0);
|
||||
quat1 = new jolt.Jt.Quat(0, 0, 0, 1);
|
||||
}
|
||||
|
||||
target = targetName != "" ? iron.Scene.active.getChild(targetName) : null;
|
||||
targetTransform = target != null ? target.transform : iron.Scene.global.transform;
|
||||
|
||||
var physics = PhysicsWorld.active;
|
||||
if (physics == null)
|
||||
return;
|
||||
|
||||
#if lnx_physics_soft
|
||||
var sb:SoftBody = object.getTrait(SoftBody);
|
||||
if (sb != null && sb.ready) {
|
||||
// For soft body hooks, pin vertices near the target
|
||||
var numVerts = Std.int(verts.length / 3);
|
||||
for (j in 0...numVerts) {
|
||||
var x = verts[j * 3] + sb.vertOffsetX + sb.object.transform.loc.x;
|
||||
var y = verts[j * 3 + 1] + sb.vertOffsetY + sb.object.transform.loc.y;
|
||||
var z = verts[j * 3 + 2] + sb.vertOffsetZ + sb.object.transform.loc.z;
|
||||
|
||||
// Find and pin matching vertices
|
||||
for (i in 0...@:privateAccess sb.particles.length) {
|
||||
var p = @:privateAccess sb.particles[i];
|
||||
if (Math.abs(p.position.x - x) < 0.01 && Math.abs(p.position.y - y) < 0.01 && Math.abs(p.position.z - z) < 0.01) {
|
||||
sb.pinVertex(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
#end
|
||||
|
||||
// Rigid body hook using fixed constraint
|
||||
var rb1:RigidBody = object.getTrait(RigidBody);
|
||||
if (rb1 != null && rb1.ready) {
|
||||
var settings = new jolt.Jt.FixedConstraintSettings();
|
||||
settings.mAutoDetectPoint = true;
|
||||
var constraint = settings.Create(rb1.body, rb1.body);
|
||||
physics.physicsSystem.AddConstraint(constraint);
|
||||
return;
|
||||
}
|
||||
|
||||
// Rigid body or soft body not initialized yet
|
||||
notifyOnInit(init);
|
||||
}
|
||||
|
||||
function update() {
|
||||
#if lnx_physics_soft
|
||||
// Soft body hook - update pinned vertex positions to follow target
|
||||
var sb:SoftBody = object.getTrait(SoftBody);
|
||||
if (sb != null && sb.ready) {
|
||||
var numVerts = Std.int(verts.length / 3);
|
||||
for (j in 0...numVerts) {
|
||||
var x = verts[j * 3] + sb.vertOffsetX + sb.object.transform.loc.x;
|
||||
var y = verts[j * 3 + 1] + sb.vertOffsetY + sb.object.transform.loc.y;
|
||||
var z = verts[j * 3 + 2] + sb.vertOffsetZ + sb.object.transform.loc.z;
|
||||
|
||||
// Update pinned vertex positions to target
|
||||
for (i in 0...@:privateAccess sb.particles.length) {
|
||||
var p = @:privateAccess sb.particles[i];
|
||||
if (p.pinned) {
|
||||
// Move pinned vertex with target
|
||||
var dx = targetTransform.worldx() - targetTransform.loc.x;
|
||||
var dy = targetTransform.worldy() - targetTransform.loc.y;
|
||||
var dz = targetTransform.worldz() - targetTransform.loc.z;
|
||||
p.position.x = x + dx;
|
||||
p.position.y = y + dy;
|
||||
p.position.z = z + dz;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#end
|
||||
}
|
||||
}
|
||||
|
||||
#end
|
||||
448
leenkx/Sources/leenkx/trait/physics/jolt/PhysicsWorld.hx
Normal file
448
leenkx/Sources/leenkx/trait/physics/jolt/PhysicsWorld.hx
Normal file
@ -0,0 +1,448 @@
|
||||
package leenkx.trait.physics.jolt;
|
||||
|
||||
#if lnx_jolt
|
||||
|
||||
import iron.Trait;
|
||||
import iron.system.Time;
|
||||
import iron.math.Vec4;
|
||||
import iron.math.RayCaster;
|
||||
import leenkx.trait.physics.PhysicsCache;
|
||||
|
||||
class Hit {
|
||||
public var rb:RigidBody;
|
||||
public var pos:Vec4;
|
||||
public var normal:Vec4;
|
||||
|
||||
public function new(rb:RigidBody, pos:Vec4, normal:Vec4) {
|
||||
this.rb = rb;
|
||||
this.pos = pos;
|
||||
this.normal = normal;
|
||||
}
|
||||
}
|
||||
|
||||
class ConvexHit {
|
||||
public var pos:Vec4;
|
||||
public var normal:Vec4;
|
||||
public var hitFraction:Float;
|
||||
|
||||
public function new(pos:Vec4, normal:Vec4, hitFraction:Float) {
|
||||
this.pos = pos;
|
||||
this.normal = normal;
|
||||
this.hitFraction = hitFraction;
|
||||
}
|
||||
}
|
||||
|
||||
class ContactPair {
|
||||
public var a:Int;
|
||||
public var b:Int;
|
||||
public var posA:Vec4;
|
||||
public var posB:Vec4;
|
||||
public var normOnB:Vec4;
|
||||
public var impulse:Float;
|
||||
public var distance:Float;
|
||||
|
||||
public function new(a:Int, b:Int) {
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
}
|
||||
}
|
||||
|
||||
class PhysicsWorld extends Trait {
|
||||
public static var active:PhysicsWorld = null;
|
||||
static var sceneRemoved = false;
|
||||
|
||||
public var physicsSystem:jolt.Jt.PhysicsSystem;
|
||||
public var bodyInterface:jolt.Jt.BodyInterface;
|
||||
public var physicsReady:Bool = false;
|
||||
var broadPhaseOptimized:Bool = false;
|
||||
|
||||
var contacts:Array<ContactPair>;
|
||||
var preUpdates:Array<Void->Void> = null;
|
||||
public var rbMap:Map<Int, RigidBody>;
|
||||
public var conMap:Map<Int, PhysicsConstraint>;
|
||||
public var constraints:Array<PhysicsConstraint> = [];
|
||||
public var timeScale = 1.0;
|
||||
var maxSteps = 1;
|
||||
public var solverIterations = 10;
|
||||
public var hitPointWorld = new Vec4();
|
||||
public var hitNormalWorld = new Vec4();
|
||||
|
||||
// Debug drawing
|
||||
var debugDrawHelper:DebugDrawHelper = null;
|
||||
var debugDrawMode:DebugDrawHelper.DebugDrawMode = DebugDrawHelper.DebugDrawMode.NoDebug;
|
||||
|
||||
// Jolt-specific helpers
|
||||
static var nullvec = true;
|
||||
static var vec1:jolt.Jt.Vec3 = null;
|
||||
static var vec2:jolt.Jt.Vec3 = null;
|
||||
static var quat1:jolt.Jt.Quat = null;
|
||||
|
||||
#if js
|
||||
var joltInterface:jolt.Jt.JoltInterface;
|
||||
static var joltReady = false;
|
||||
static var joltModule:Dynamic = null;
|
||||
static var pendingWorlds:Array<PhysicsWorld> = [];
|
||||
#end
|
||||
|
||||
#if lnx_debug
|
||||
public static var physTime = 0.0;
|
||||
#end
|
||||
|
||||
#if hl
|
||||
@:hlNative("jolt", "Init")
|
||||
static function hlJoltInit():Void {}
|
||||
|
||||
@:hlNative("jolt", "Shutdown")
|
||||
static function hlJoltShutdown():Void {}
|
||||
#end
|
||||
|
||||
public function new(timeScale = 1.0, maxSteps = 10, solverIterations = 10, fixedStep = 1 / 60) {
|
||||
super();
|
||||
|
||||
if (active != null && !sceneRemoved)
|
||||
return;
|
||||
sceneRemoved = false;
|
||||
|
||||
this.timeScale = timeScale;
|
||||
this.maxSteps = maxSteps;
|
||||
this.solverIterations = solverIterations;
|
||||
Time.initFixedStep(fixedStep);
|
||||
|
||||
contacts = [];
|
||||
rbMap = new Map();
|
||||
conMap = new Map();
|
||||
active = this;
|
||||
|
||||
#if js
|
||||
// Check if Jolt is initialized
|
||||
if (!joltReady) {
|
||||
pendingWorlds.push(this);
|
||||
initJolt();
|
||||
return;
|
||||
}
|
||||
#end
|
||||
|
||||
initPhysicsWorld();
|
||||
}
|
||||
|
||||
#if js
|
||||
static var joltRetryRegistered = false;
|
||||
static function initJolt() {
|
||||
// Check if Jolt global exists (loaded via asset)
|
||||
var jolt:Dynamic = untyped __js__("typeof Jolt !== 'undefined' ? Jolt : null");
|
||||
if (jolt != null) {
|
||||
joltModule = jolt;
|
||||
joltReady = true;
|
||||
joltRetryRegistered = false;
|
||||
// Initialize pending worlds
|
||||
for (world in pendingWorlds) {
|
||||
world.initPhysicsWorld();
|
||||
}
|
||||
pendingWorlds = [];
|
||||
} else if (!joltRetryRegistered) {
|
||||
// Jolt not loaded yet, retry on next frame (NOT notifyOnInit which fires same-frame)
|
||||
joltRetryRegistered = true;
|
||||
var retryFn:Void->Void = null;
|
||||
retryFn = function() {
|
||||
iron.App.removeUpdate(retryFn);
|
||||
joltRetryRegistered = false;
|
||||
initJolt();
|
||||
};
|
||||
iron.App.notifyOnUpdate(retryFn);
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
||||
function initPhysicsWorld() {
|
||||
#if hl
|
||||
// Must initialize Jolt allocator before ANY Jolt object creation
|
||||
hlJoltInit();
|
||||
#end
|
||||
|
||||
if (nullvec) {
|
||||
nullvec = false;
|
||||
vec1 = new jolt.Jt.Vec3(0, 0, 0);
|
||||
vec2 = new jolt.Jt.Vec3(0, 0, 0);
|
||||
quat1 = new jolt.Jt.Quat(0, 0, 0, 1);
|
||||
}
|
||||
|
||||
if (!physicsReady) {
|
||||
createPhysics();
|
||||
} else if (active != null && active != this) {
|
||||
physicsSystem = active.physicsSystem;
|
||||
bodyInterface = active.bodyInterface;
|
||||
physicsReady = true;
|
||||
}
|
||||
|
||||
_fixedUpdate = [fixedUpdate];
|
||||
@:privateAccess iron.App.traitFixedUpdates.insert(0, fixedUpdate);
|
||||
|
||||
iron.Scene.active.notifyOnRemove(function() {
|
||||
sceneRemoved = true;
|
||||
PhysicsCache.clearCache();
|
||||
});
|
||||
}
|
||||
|
||||
public function reset() {
|
||||
for (rb in active.rbMap)
|
||||
removeRigidBody(rb);
|
||||
}
|
||||
|
||||
function createPhysics() {
|
||||
#if hl
|
||||
// HashLink initialization - uses native jolt library
|
||||
physicsSystem = new jolt.Jt.PhysicsSystem();
|
||||
physicsSystem.Init(10240, 0, 65536, 10240);
|
||||
bodyInterface = physicsSystem.GetBodyInterface();
|
||||
#elseif js
|
||||
// JavaScript/WASM initialization via JoltSettings + JoltInterface
|
||||
var settings = new jolt.Jt.JoltSettings();
|
||||
settings.mMaxBodies = 10240;
|
||||
settings.mMaxBodyPairs = 65536;
|
||||
settings.mMaxContactConstraints = 10240;
|
||||
|
||||
// Create layer interfaces
|
||||
var broadPhaseLayer = new jolt.Jt.BroadPhaseLayerInterfaceTable(2, 2);
|
||||
broadPhaseLayer.MapObjectToBroadPhaseLayer(0, new jolt.Jt.BroadPhaseLayer(0));
|
||||
broadPhaseLayer.MapObjectToBroadPhaseLayer(1, new jolt.Jt.BroadPhaseLayer(1));
|
||||
|
||||
var objectLayerPair = new jolt.Jt.ObjectLayerPairFilterTable(2);
|
||||
objectLayerPair.EnableCollision(0, 0);
|
||||
objectLayerPair.EnableCollision(0, 1);
|
||||
objectLayerPair.EnableCollision(1, 1);
|
||||
|
||||
var objectVsBroadPhase = new jolt.Jt.ObjectVsBroadPhaseLayerFilterTable(broadPhaseLayer, 2, objectLayerPair, 2);
|
||||
|
||||
settings.mBroadPhaseLayerInterface = broadPhaseLayer;
|
||||
settings.mObjectVsBroadPhaseLayerFilter = objectVsBroadPhase;
|
||||
settings.mObjectLayerPairFilter = objectLayerPair;
|
||||
|
||||
joltInterface = new jolt.Jt.JoltInterface(settings);
|
||||
physicsSystem = joltInterface.GetPhysicsSystem();
|
||||
bodyInterface = physicsSystem.GetBodyInterface();
|
||||
#end
|
||||
|
||||
physicsReady = true;
|
||||
broadPhaseOptimized = false;
|
||||
|
||||
var g = iron.Scene.active.raw.gravity;
|
||||
var gravity = g == null ? new Vec4(0, 0, -9.81) : new Vec4(g[0], g[1], g[2]);
|
||||
setGravity(gravity);
|
||||
}
|
||||
|
||||
public function setGravity(v:Vec4) {
|
||||
vec1.Set(v.x, v.y, v.z);
|
||||
physicsSystem.SetGravity(vec1);
|
||||
}
|
||||
|
||||
public function getGravity():Vec4 {
|
||||
var g = physicsSystem.GetGravity();
|
||||
var result = new Vec4(g.GetX(), g.GetY(), g.GetZ());
|
||||
#if hl g.delete(); #end
|
||||
return result;
|
||||
}
|
||||
|
||||
public function addRigidBody(body:RigidBody, activate:Bool = true) {
|
||||
bodyInterface.AddBody(body.bodyId, activate ? 1 : 0);
|
||||
rbMap.set(body.id, body);
|
||||
}
|
||||
|
||||
public function addPhysicsConstraint(constraint:PhysicsConstraint) {
|
||||
if (constraint.conReady) {
|
||||
physicsSystem.AddConstraint(constraint.con);
|
||||
}
|
||||
conMap.set(constraint.id, constraint);
|
||||
constraints.push(constraint);
|
||||
}
|
||||
|
||||
public function removeRigidBody(body:RigidBody) {
|
||||
if (body.destroyed)
|
||||
return;
|
||||
body.destroyed = true;
|
||||
bodyInterface.RemoveBody(body.bodyId);
|
||||
bodyInterface.DestroyBody(body.bodyId);
|
||||
rbMap.remove(body.id);
|
||||
}
|
||||
|
||||
public function removePhysicsConstraint(constraint:PhysicsConstraint) {
|
||||
if (constraint.conReady) {
|
||||
physicsSystem.RemoveConstraint(constraint.con);
|
||||
}
|
||||
conMap.remove(constraint.id);
|
||||
constraints.remove(constraint);
|
||||
constraint.delete();
|
||||
}
|
||||
|
||||
public function getContacts(body:RigidBody):Array<RigidBody> {
|
||||
if (contacts.length == 0)
|
||||
return null;
|
||||
var res:Array<RigidBody> = [];
|
||||
for (c in contacts) {
|
||||
var rb:RigidBody = null;
|
||||
if (c.a == body.id)
|
||||
rb = rbMap.get(c.b);
|
||||
else if (c.b == body.id)
|
||||
rb = rbMap.get(c.a);
|
||||
if (rb != null && res.indexOf(rb) == -1)
|
||||
res.push(rb);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
public function getContactPairs(body:RigidBody):Array<ContactPair> {
|
||||
if (contacts.length == 0)
|
||||
return null;
|
||||
var res:Array<ContactPair> = [];
|
||||
for (c in contacts) {
|
||||
if (c.a == body.id || c.b == body.id)
|
||||
res.push(c);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
public function findBody(id:Int):RigidBody {
|
||||
return rbMap.get(id);
|
||||
}
|
||||
|
||||
function fixedUpdate() {
|
||||
var t = Time.fixedStep * timeScale * Time.scale;
|
||||
if (t == 0.0)
|
||||
return;
|
||||
|
||||
PhysicsCache.clearContactsCache();
|
||||
|
||||
#if lnx_debug
|
||||
var startTime = kha.Scheduler.realTime();
|
||||
#end
|
||||
|
||||
if (preUpdates != null)
|
||||
for (f in preUpdates)
|
||||
f();
|
||||
|
||||
if (!broadPhaseOptimized) {
|
||||
physicsSystem.OptimizeBroadPhase();
|
||||
broadPhaseOptimized = true;
|
||||
}
|
||||
|
||||
var currMaxSteps = t < (Time.fixedStep * (maxSteps / 10)) ? maxSteps : 1;
|
||||
|
||||
#if js
|
||||
joltInterface.Step(t, currMaxSteps);
|
||||
#elseif hl
|
||||
physicsSystem.Update(t, currMaxSteps);
|
||||
#end
|
||||
|
||||
for (rb in rbMap)
|
||||
@:privateAccess rb.physicsUpdate();
|
||||
|
||||
#if lnx_debug
|
||||
physTime = kha.Scheduler.realTime() - startTime;
|
||||
#end
|
||||
}
|
||||
|
||||
public function pickClosest(inputX:Float, inputY:Float, group:Int = 0x00000001, mask = 0xFFFFFFFF):RigidBody {
|
||||
var camera = iron.Scene.active.camera;
|
||||
var start = new Vec4();
|
||||
var end = new Vec4();
|
||||
RayCaster.getDirection(start, end, inputX, inputY, camera);
|
||||
var hit = rayCast(camera.transform.world.getLoc(), end, group, mask);
|
||||
return hit != null ? hit.rb : null;
|
||||
}
|
||||
|
||||
public function rayCast(from:Vec4, to:Vec4, group:Int = 0x00000001, mask = 0xFFFFFFFF):Hit {
|
||||
var dirX = to.x - from.x;
|
||||
var dirY = to.y - from.y;
|
||||
var dirZ = to.z - from.z;
|
||||
|
||||
var origin = new jolt.Jt.RVec3(from.x, from.y, from.z);
|
||||
var direction = new jolt.Jt.Vec3(dirX, dirY, dirZ);
|
||||
var ray = new jolt.Jt.RRayCast(origin, direction);
|
||||
var result = new jolt.Jt.RayCastResult();
|
||||
|
||||
var narrowPhase = physicsSystem.GetNarrowPhaseQuery();
|
||||
var didHit = narrowPhase.CastRay(ray, result);
|
||||
|
||||
if (didHit) {
|
||||
var bodyId = result.mBodyID;
|
||||
var fraction = result.mFraction;
|
||||
|
||||
hitPointWorld.set(from.x + dirX * fraction, from.y + dirY * fraction, from.z + dirZ * fraction);
|
||||
|
||||
// Find rigid body by ID
|
||||
for (rb in rbMap) {
|
||||
if (rb.bodyId.GetIndex() == bodyId.GetIndex()) {
|
||||
#if hl
|
||||
origin.delete();
|
||||
direction.delete();
|
||||
ray.delete();
|
||||
result.delete();
|
||||
#end
|
||||
return new Hit(rb, hitPointWorld.clone(), hitNormalWorld.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if hl
|
||||
origin.delete();
|
||||
direction.delete();
|
||||
ray.delete();
|
||||
result.delete();
|
||||
#end
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function notifyOnPreUpdate(f:Void->Void) {
|
||||
if (preUpdates == null)
|
||||
preUpdates = [];
|
||||
preUpdates.push(f);
|
||||
}
|
||||
|
||||
public function removePreUpdate(f:Void->Void) {
|
||||
preUpdates.remove(f);
|
||||
}
|
||||
|
||||
public var convexHitPointWorld = new Vec4();
|
||||
public var convexHitNormalWorld = new Vec4();
|
||||
|
||||
public function convexSweepTest(rb:RigidBody, from:Vec4, to:Vec4, rotation:iron.math.Quat, group:Int = 0x00000001, mask = 0xFFFFFFFF):ConvexHit {
|
||||
// Jolt shape cast implementation
|
||||
// Note: Full shape cast requires additional Jolt bindings
|
||||
// For now, use raycast as approximation
|
||||
var hit = rayCast(from, to, group, mask);
|
||||
if (hit != null) {
|
||||
var fraction = hit.pos.sub(from).length() / to.sub(from).length();
|
||||
convexHitPointWorld = hit.pos.clone();
|
||||
convexHitNormalWorld = hit.normal.clone();
|
||||
return new ConvexHit(convexHitPointWorld, convexHitNormalWorld, fraction);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function setDebugDrawMode(mode:DebugDrawHelper.DebugDrawMode) {
|
||||
debugDrawMode = mode;
|
||||
if (mode != DebugDrawHelper.DebugDrawMode.NoDebug && debugDrawHelper == null) {
|
||||
debugDrawHelper = new DebugDrawHelper(this, mode);
|
||||
} else if (debugDrawHelper != null) {
|
||||
debugDrawHelper.setDebugMode(mode);
|
||||
}
|
||||
}
|
||||
|
||||
public function getDebugDrawMode():DebugDrawHelper.DebugDrawMode {
|
||||
return debugDrawMode;
|
||||
}
|
||||
|
||||
public function debugDrawRayCast(from:Vec4, to:Vec4, hasHit:Bool, ?hitPoint:Vec4, ?hitNormal:Vec4) {
|
||||
if (debugDrawHelper != null && (debugDrawMode & DebugDrawHelper.DebugDrawMode.DrawRayCast) != 0) {
|
||||
debugDrawHelper.rayCast({
|
||||
from: from,
|
||||
to: to,
|
||||
hasHit: hasHit,
|
||||
hitPoint: hitPoint,
|
||||
hitNormal: hitNormal
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#end
|
||||
746
leenkx/Sources/leenkx/trait/physics/jolt/RigidBody.hx
Normal file
746
leenkx/Sources/leenkx/trait/physics/jolt/RigidBody.hx
Normal file
@ -0,0 +1,746 @@
|
||||
package leenkx.trait.physics.jolt;
|
||||
|
||||
#if lnx_jolt
|
||||
|
||||
import iron.Trait;
|
||||
import iron.math.Vec4;
|
||||
import iron.math.Quat;
|
||||
import iron.data.SceneFormat;
|
||||
import iron.object.Transform;
|
||||
import iron.object.MeshObject;
|
||||
|
||||
@:enum abstract Shape(Int) from Int to Int {
|
||||
var Box = 0;
|
||||
var Sphere = 1;
|
||||
var ConvexHull = 2;
|
||||
var Mesh = 3;
|
||||
var Cone = 4;
|
||||
var Cylinder = 5;
|
||||
var Capsule = 6;
|
||||
var Terrain = 7;
|
||||
}
|
||||
|
||||
class RigidBody extends Trait {
|
||||
public var physics:PhysicsWorld;
|
||||
public var transform:Transform = null;
|
||||
|
||||
public var body:jolt.Jt.Body;
|
||||
public var bodyId:jolt.Jt.BodyID;
|
||||
|
||||
public var id:Int;
|
||||
public var destroyed = false;
|
||||
public var ready = false;
|
||||
|
||||
public var shape:Shape;
|
||||
public var mass:Float;
|
||||
public var friction:Float;
|
||||
public var restitution:Float;
|
||||
public var group:Int;
|
||||
public var mask:Int;
|
||||
public var linearDamping:Float;
|
||||
public var angularDamping:Float;
|
||||
public var animated:Bool;
|
||||
public var staticObj:Bool;
|
||||
public var trigger:Bool;
|
||||
var lockTranslationX:Bool;
|
||||
var lockTranslationY:Bool;
|
||||
var lockTranslationZ:Bool;
|
||||
var lockRotationX:Bool;
|
||||
var lockRotationY:Bool;
|
||||
var lockRotationZ:Bool;
|
||||
var ccd:Bool;
|
||||
var useDeactivation:Bool;
|
||||
|
||||
static var nextId = 0;
|
||||
|
||||
// Native bindings for complex shape creation (HL only)
|
||||
#if hl
|
||||
@:hlNative("jolt", "ConvexHullShapeSettings_new")
|
||||
static function hlConvexHullShapeSettings_new():Dynamic { return null; }
|
||||
|
||||
@:hlNative("jolt", "ConvexHullShapeSettings_delete")
|
||||
static function hlConvexHullShapeSettings_delete(settings:Dynamic):Void {}
|
||||
|
||||
@:hlNative("jolt", "ConvexHullShapeSettings_AddPoint")
|
||||
static function hlConvexHullShapeSettings_AddPoint(settings:Dynamic, x:Float, y:Float, z:Float):Void {}
|
||||
|
||||
@:hlNative("jolt", "ConvexHullShapeSettings_Create")
|
||||
static function hlConvexHullShapeSettings_Create(settings:Dynamic):Dynamic { return null; }
|
||||
|
||||
@:hlNative("jolt", "MeshShapeSettings_new")
|
||||
static function hlMeshShapeSettings_new():Dynamic { return null; }
|
||||
|
||||
@:hlNative("jolt", "MeshShapeSettings_delete")
|
||||
static function hlMeshShapeSettings_delete(settings:Dynamic):Void {}
|
||||
|
||||
@:hlNative("jolt", "MeshShapeSettings_AddVertex")
|
||||
static function hlMeshShapeSettings_AddVertex(settings:Dynamic, x:Float, y:Float, z:Float):Void {}
|
||||
|
||||
@:hlNative("jolt", "MeshShapeSettings_AddTriangle")
|
||||
static function hlMeshShapeSettings_AddTriangle(settings:Dynamic, i0:Int, i1:Int, i2:Int):Void {}
|
||||
|
||||
@:hlNative("jolt", "MeshShapeSettings_Create")
|
||||
static function hlMeshShapeSettings_Create(settings:Dynamic):Dynamic { return null; }
|
||||
#end
|
||||
|
||||
public function new(shape:Shape = Box, mass:Float = 1.0, friction:Float = 0.5, restitution:Float = 0.0, group:Int = 1, mask:Int = 1,
|
||||
params:RigidBodyParams = null, flags:RigidBodyFlags = null) {
|
||||
super();
|
||||
|
||||
if (params == null) params = {
|
||||
linearDamping: 0.04,
|
||||
angularDamping: 0.1,
|
||||
angularFriction: 0.1,
|
||||
linearFactorsX: 1.0,
|
||||
linearFactorsY: 1.0,
|
||||
linearFactorsZ: 1.0,
|
||||
angularFactorsX: 1.0,
|
||||
angularFactorsY: 1.0,
|
||||
angularFactorsZ: 1.0,
|
||||
collisionMargin: 0.0,
|
||||
linearDeactivationThreshold: 0.0,
|
||||
angularDeactivationThrshold: 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 = {
|
||||
animated: false,
|
||||
trigger: false,
|
||||
ccd: false,
|
||||
interpolate: false,
|
||||
staticObj: false,
|
||||
useDeactivation: true
|
||||
};
|
||||
|
||||
this.shape = shape;
|
||||
this.mass = mass;
|
||||
this.friction = friction;
|
||||
this.restitution = restitution;
|
||||
this.group = group;
|
||||
this.mask = mask;
|
||||
this.linearDamping = params.linearDamping;
|
||||
this.angularDamping = params.angularDamping;
|
||||
this.animated = flags.animated;
|
||||
this.trigger = flags.trigger;
|
||||
this.ccd = flags.ccd;
|
||||
this.staticObj = flags.staticObj || mass == 0.0;
|
||||
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.useDeactivation = flags.useDeactivation;
|
||||
this.id = nextId++;
|
||||
|
||||
notifyOnAdd(init);
|
||||
notifyOnRemove(removeFromWorld);
|
||||
}
|
||||
|
||||
function init() {
|
||||
if (ready)
|
||||
return;
|
||||
|
||||
transform = object.transform;
|
||||
physics = PhysicsWorld.active;
|
||||
|
||||
if (physics == null) {
|
||||
new PhysicsWorld();
|
||||
physics = PhysicsWorld.active;
|
||||
}
|
||||
|
||||
#if js
|
||||
// Check if Jolt is initialized - defer if not
|
||||
if (!physics.physicsReady) {
|
||||
// Jolt not ready yet, retry after delay
|
||||
haxe.Timer.delay(init, 16);
|
||||
return;
|
||||
}
|
||||
#end
|
||||
|
||||
ready = true;
|
||||
|
||||
var t = transform;
|
||||
t.buildMatrix();
|
||||
var pos = t.world.getLoc();
|
||||
var rot = new Quat();
|
||||
rot.fromMat(t.world);
|
||||
|
||||
// Create shape based on type - use transform.dim like Bullet does
|
||||
var joltShape = createShape(t);
|
||||
|
||||
// Determine motion type (0=Static, 1=Kinematic, 2=Dynamic)
|
||||
var motionType:Int = staticObj ? 0 : (animated ? 1 : 2);
|
||||
|
||||
// Jolt uses RVec3 for world positions
|
||||
var jPos = new jolt.Jt.RVec3(pos.x, pos.y, pos.z);
|
||||
var jRot = new jolt.Jt.Quat(rot.x, rot.y, rot.z, rot.w);
|
||||
if (staticObj || animated) mass = 0;
|
||||
|
||||
var settings = new jolt.Jt.BodyCreationSettings(joltShape, jPos, jRot, motionType, staticObj ? 0 : 1);
|
||||
settings.mFriction = friction;
|
||||
settings.mRestitution = restitution;
|
||||
settings.mIsSensor = trigger;
|
||||
|
||||
settings.mLinearDamping = linearDamping;
|
||||
settings.mAngularDamping = angularDamping;
|
||||
|
||||
// Match Bullet's deactivation: useDeactivation=false → DISABLE_DEACTIVATION
|
||||
if (!useDeactivation) {
|
||||
settings.mAllowSleeping = false;
|
||||
}
|
||||
|
||||
// Set mass to match Bullet (CalculateInertia = 1: use provided mass, compute inertia from shape)
|
||||
// Use explicit MassProperties object to avoid chained property access issues in HL
|
||||
if (mass > 0) {
|
||||
settings.mOverrideMassProperties = 1;
|
||||
var mp = new jolt.Jt.MassProperties();
|
||||
mp.mMass = mass;
|
||||
settings.mMassPropertiesOverride = mp;
|
||||
#if hl
|
||||
mp.delete();
|
||||
#end
|
||||
}
|
||||
|
||||
// Set allowed DOFs (matching Bullet's linear/angular factors + lock properties)
|
||||
var dofs = 0x3F; // All DOFs by default (0x3F = TranslationX|Y|Z|RotationX|Y|Z)
|
||||
if (lockTranslationX) dofs &= ~0x01;
|
||||
if (lockTranslationY) dofs &= ~0x02;
|
||||
if (lockTranslationZ) dofs &= ~0x04;
|
||||
if (lockRotationX) dofs &= ~0x08;
|
||||
if (lockRotationY) dofs &= ~0x10;
|
||||
if (lockRotationZ) dofs &= ~0x20;
|
||||
if (dofs != 0x3F) settings.mAllowedDOFs = dofs;
|
||||
|
||||
// CCD for fast-moving objects
|
||||
if (ccd) settings.mMotionQuality = 1; // LinearCast
|
||||
|
||||
body = physics.bodyInterface.CreateBody(settings);
|
||||
bodyId = body.GetID();
|
||||
|
||||
#if hl
|
||||
settings.delete();
|
||||
jPos.delete();
|
||||
jRot.delete();
|
||||
#end
|
||||
|
||||
// Add to world (activate dynamic bodies, matching Bullet behavior)
|
||||
physics.addRigidBody(this, !staticObj);
|
||||
|
||||
// Initialize cached position from body creation position
|
||||
currentPosX = pos.x;
|
||||
currentPosY = pos.y;
|
||||
currentPosZ = pos.z;
|
||||
currentRotX = rot.x;
|
||||
currentRotY = rot.y;
|
||||
currentRotZ = rot.z;
|
||||
currentRotW = rot.w;
|
||||
|
||||
// Register visual update callback for non-animated bodies (matching Bullet)
|
||||
if (!animated) notifyOnUpdate(update);
|
||||
}
|
||||
|
||||
function createShape(t:Transform):jolt.Jt.Shape {
|
||||
// Use transform.dim (mesh bounding box) for shape dimensions, matching Bullet
|
||||
var dimX = t.dim.x;
|
||||
var dimY = t.dim.y;
|
||||
var dimZ = t.dim.z;
|
||||
|
||||
return switch (shape) {
|
||||
case Box:
|
||||
var halfExtent = new jolt.Jt.Vec3(dimX / 2, dimY / 2, dimZ / 2);
|
||||
cast new jolt.Jt.BoxShape(halfExtent);
|
||||
case Sphere:
|
||||
var radius = dimX / 2;
|
||||
cast new jolt.Jt.SphereShape(radius);
|
||||
case Capsule:
|
||||
var radius = dimX / 2;
|
||||
var halfHeight = dimZ / 2 - radius;
|
||||
if (halfHeight < 0) halfHeight = 0.01;
|
||||
cast new jolt.Jt.CapsuleShape(halfHeight, radius);
|
||||
case Cylinder:
|
||||
var radius = Math.max(dimX, dimY) / 2;
|
||||
var halfHeight = dimZ / 2;
|
||||
cast new jolt.Jt.CylinderShape(halfHeight, radius);
|
||||
case Cone:
|
||||
var radius = Math.max(dimX, dimY) / 2;
|
||||
var halfHeight = dimZ / 2;
|
||||
cast new jolt.Jt.CylinderShape(halfHeight, radius);
|
||||
case ConvexHull:
|
||||
#if hl
|
||||
createConvexHullShape(t.scale);
|
||||
#else
|
||||
createConvexHullShapeJS(t.scale, dimX, dimY, dimZ);
|
||||
#end
|
||||
case Mesh:
|
||||
// Jolt MeshShape only works for static bodies (unlike Bullet's GImpact)
|
||||
// Dynamic mesh bodies must use ConvexHullShape instead
|
||||
if (staticObj) {
|
||||
#if hl
|
||||
createMeshShape(t.scale);
|
||||
#else
|
||||
createMeshShapeJS(t.scale, dimX, dimY, dimZ);
|
||||
#end
|
||||
} else {
|
||||
#if hl
|
||||
createConvexHullShape(t.scale);
|
||||
#else
|
||||
createConvexHullShapeJS(t.scale, dimX, dimY, dimZ);
|
||||
#end
|
||||
}
|
||||
case Terrain:
|
||||
#if hl
|
||||
createTerrainShape(t.scale);
|
||||
#else
|
||||
createMeshShapeJS(t.scale, dimX, dimY, dimZ);
|
||||
#end
|
||||
default:
|
||||
var halfExtent = new jolt.Jt.Vec3(dimX / 2, dimY / 2, dimZ / 2);
|
||||
cast new jolt.Jt.BoxShape(halfExtent);
|
||||
};
|
||||
}
|
||||
|
||||
function createConvexHullShape(scale:Vec4):jolt.Jt.Shape {
|
||||
var mo = cast(object, MeshObject);
|
||||
if (mo == null || mo.data == null || mo.data.geom == null) {
|
||||
return cast new jolt.Jt.BoxShape(new jolt.Jt.Vec3(scale.x * 0.5, scale.y * 0.5, scale.z * 0.5));
|
||||
}
|
||||
|
||||
var positions = mo.data.geom.positions.values;
|
||||
var scalePos = mo.data.scalePos;
|
||||
|
||||
#if hl
|
||||
var settings = hlConvexHullShapeSettings_new();
|
||||
var numVerts = Std.int(positions.length / 4);
|
||||
for (i in 0...numVerts) {
|
||||
var x = (positions[i * 4] / 32767) * scalePos * scale.x;
|
||||
var y = (positions[i * 4 + 1] / 32767) * scalePos * scale.y;
|
||||
var z = (positions[i * 4 + 2] / 32767) * scalePos * scale.z;
|
||||
hlConvexHullShapeSettings_AddPoint(settings, x, y, z);
|
||||
}
|
||||
var shape = hlConvexHullShapeSettings_Create(settings);
|
||||
hlConvexHullShapeSettings_delete(settings);
|
||||
if (shape == null) {
|
||||
return cast new jolt.Jt.BoxShape(new jolt.Jt.Vec3(scale.x * 0.5, scale.y * 0.5, scale.z * 0.5));
|
||||
}
|
||||
return cast shape;
|
||||
#else
|
||||
return cast new jolt.Jt.BoxShape(new jolt.Jt.Vec3(scale.x * 0.5, scale.y * 0.5, scale.z * 0.5));
|
||||
#end
|
||||
}
|
||||
|
||||
function createMeshShape(scale:Vec4):jolt.Jt.Shape {
|
||||
var mo = cast(object, MeshObject);
|
||||
if (mo == null || mo.data == null || mo.data.geom == null) {
|
||||
return cast new jolt.Jt.BoxShape(new jolt.Jt.Vec3(scale.x * 0.5, scale.y * 0.5, scale.z * 0.5));
|
||||
}
|
||||
|
||||
var positions = mo.data.geom.positions.values;
|
||||
var indices = mo.data.geom.indices;
|
||||
var scalePos = mo.data.scalePos;
|
||||
|
||||
#if hl
|
||||
var settings = hlMeshShapeSettings_new();
|
||||
var numVerts = Std.int(positions.length / 4);
|
||||
|
||||
for (i in 0...numVerts) {
|
||||
var x = (positions[i * 4] / 32767) * scalePos * scale.x;
|
||||
var y = (positions[i * 4 + 1] / 32767) * scalePos * scale.y;
|
||||
var z = (positions[i * 4 + 2] / 32767) * scalePos * scale.z;
|
||||
hlMeshShapeSettings_AddVertex(settings, x, y, z);
|
||||
}
|
||||
|
||||
for (indexArray in indices) {
|
||||
var numTris = Std.int(indexArray.length / 3);
|
||||
for (i in 0...numTris) {
|
||||
hlMeshShapeSettings_AddTriangle(settings, indexArray[i * 3], indexArray[i * 3 + 1], indexArray[i * 3 + 2]);
|
||||
}
|
||||
}
|
||||
|
||||
var shape = hlMeshShapeSettings_Create(settings);
|
||||
hlMeshShapeSettings_delete(settings);
|
||||
if (shape == null) {
|
||||
return cast new jolt.Jt.BoxShape(new jolt.Jt.Vec3(scale.x * 0.5, scale.y * 0.5, scale.z * 0.5));
|
||||
}
|
||||
return cast shape;
|
||||
#else
|
||||
return cast new jolt.Jt.BoxShape(new jolt.Jt.Vec3(scale.x * 0.5, scale.y * 0.5, scale.z * 0.5));
|
||||
#end
|
||||
}
|
||||
|
||||
function createTerrainShape(scale:Vec4):jolt.Jt.Shape {
|
||||
// Terrain/HeightField shape - requires height data from object
|
||||
var mo = cast(object, MeshObject);
|
||||
if (mo == null) {
|
||||
return cast new jolt.Jt.BoxShape(new jolt.Jt.Vec3(scale.x * 0.5, scale.y * 0.5, scale.z * 0.5));
|
||||
}
|
||||
|
||||
#if js
|
||||
// For JS, use HeightFieldShapeSettings or fallback to mesh
|
||||
// Terrain meshes are typically treated as mesh shapes in Jolt
|
||||
return createMeshShape(scale);
|
||||
#elseif hl
|
||||
// For HashLink, terrain is also best represented as mesh shape
|
||||
// HeightFieldShape requires specific grid data which terrain meshes may not have
|
||||
return createMeshShape(scale);
|
||||
#else
|
||||
return cast new jolt.Jt.BoxShape(new jolt.Jt.Vec3(scale.x * 0.5, scale.y * 0.5, scale.z * 0.5));
|
||||
#end
|
||||
}
|
||||
|
||||
#if js
|
||||
function createConvexHullShapeJS(scale:Vec4, dimX:Float, dimY:Float, dimZ:Float):jolt.Jt.Shape {
|
||||
var mo = cast(object, MeshObject);
|
||||
if (mo == null || mo.data == null || mo.data.geom == null) {
|
||||
return cast new jolt.Jt.BoxShape(new jolt.Jt.Vec3(dimX / 2, dimY / 2, dimZ / 2));
|
||||
}
|
||||
|
||||
var positions = mo.data.geom.positions.values;
|
||||
var scalePos = mo.data.scalePos;
|
||||
var numVerts = Std.int(positions.length / 4);
|
||||
|
||||
var settings:Dynamic = untyped __js__("new Jolt.ConvexHullShapeSettings()");
|
||||
var points:Dynamic = untyped settings.mPoints;
|
||||
points.clear();
|
||||
for (i in 0...numVerts) {
|
||||
var x:Float = (positions[i * 4] / 32767) * scalePos * scale.x;
|
||||
var y:Float = (positions[i * 4 + 1] / 32767) * scalePos * scale.y;
|
||||
var z:Float = (positions[i * 4 + 2] / 32767) * scalePos * scale.z;
|
||||
var pt:Dynamic = untyped __js__("new Jolt.Vec3({0}, {1}, {2})", x, y, z);
|
||||
untyped points.push_back(pt);
|
||||
}
|
||||
|
||||
var result:Dynamic = untyped settings.Create();
|
||||
if (untyped result.HasError()) {
|
||||
return cast new jolt.Jt.BoxShape(new jolt.Jt.Vec3(dimX / 2, dimY / 2, dimZ / 2));
|
||||
}
|
||||
return cast untyped result.Get();
|
||||
}
|
||||
|
||||
function createMeshShapeJS(scale:Vec4, dimX:Float, dimY:Float, dimZ:Float):jolt.Jt.Shape {
|
||||
var mo = cast(object, MeshObject);
|
||||
if (mo == null || mo.data == null || mo.data.geom == null) {
|
||||
return cast new jolt.Jt.BoxShape(new jolt.Jt.Vec3(dimX / 2, dimY / 2, dimZ / 2));
|
||||
}
|
||||
|
||||
var positions = mo.data.geom.positions.values;
|
||||
var indices = mo.data.geom.indices;
|
||||
var scalePos = mo.data.scalePos;
|
||||
var numVerts = Std.int(positions.length / 4);
|
||||
|
||||
var settings:Dynamic = untyped __js__("new Jolt.MeshShapeSettings()");
|
||||
var verts:Dynamic = untyped settings.mTriangleVertices;
|
||||
var tris:Dynamic = untyped settings.mIndexedTriangles;
|
||||
verts.clear();
|
||||
tris.clear();
|
||||
|
||||
for (i in 0...numVerts) {
|
||||
var x:Float = (positions[i * 4] / 32767) * scalePos * scale.x;
|
||||
var y:Float = (positions[i * 4 + 1] / 32767) * scalePos * scale.y;
|
||||
var z:Float = (positions[i * 4 + 2] / 32767) * scalePos * scale.z;
|
||||
var v:Dynamic = untyped __js__("new Jolt.Float3({0}, {1}, {2})", x, y, z);
|
||||
untyped verts.push_back(v);
|
||||
}
|
||||
|
||||
for (indexArray in indices) {
|
||||
var numTris = Std.int(indexArray.length / 3);
|
||||
for (i in 0...numTris) {
|
||||
var tri:Dynamic = untyped __js__("new Jolt.IndexedTriangle()");
|
||||
untyped tri.set_mIdx(0, indexArray[i * 3]);
|
||||
untyped tri.set_mIdx(1, indexArray[i * 3 + 1]);
|
||||
untyped tri.set_mIdx(2, indexArray[i * 3 + 2]);
|
||||
untyped tri.set_mMaterialIndex(0);
|
||||
untyped tris.push_back(tri);
|
||||
}
|
||||
}
|
||||
|
||||
var result:Dynamic = untyped settings.Create();
|
||||
if (untyped result.HasError()) {
|
||||
return cast new jolt.Jt.BoxShape(new jolt.Jt.Vec3(dimX / 2, dimY / 2, dimZ / 2));
|
||||
}
|
||||
return cast untyped result.Get();
|
||||
}
|
||||
#end
|
||||
|
||||
// Cached physics state for visual interpolation (matching Bullet pattern)
|
||||
var currentPosX:Float = 0;
|
||||
var currentPosY:Float = 0;
|
||||
var currentPosZ:Float = 0;
|
||||
var currentRotX:Float = 0;
|
||||
var currentRotY:Float = 0;
|
||||
var currentRotZ:Float = 0;
|
||||
var currentRotW:Float = 1;
|
||||
|
||||
public function physicsUpdate() {
|
||||
if (!ready)
|
||||
return;
|
||||
|
||||
if (staticObj)
|
||||
return;
|
||||
|
||||
if (animated) {
|
||||
syncTransform();
|
||||
return;
|
||||
}
|
||||
|
||||
var active = physics.bodyInterface.IsActive(bodyId);
|
||||
if (!active)
|
||||
return;
|
||||
|
||||
// Read position and rotation from Jolt into cached state
|
||||
var p = physics.bodyInterface.GetPosition(bodyId);
|
||||
var q = physics.bodyInterface.GetRotation(bodyId);
|
||||
|
||||
#if js
|
||||
currentPosX = cast p.GetX();
|
||||
currentPosY = cast p.GetY();
|
||||
currentPosZ = cast p.GetZ();
|
||||
currentRotX = cast q.GetX();
|
||||
currentRotY = cast q.GetY();
|
||||
currentRotZ = cast q.GetZ();
|
||||
currentRotW = cast q.GetW();
|
||||
// JS: getter return values use internal WASM wrappers - do NOT destroy
|
||||
#else
|
||||
currentPosX = p.GetX();
|
||||
currentPosY = p.GetY();
|
||||
currentPosZ = p.GetZ();
|
||||
currentRotX = q.GetX();
|
||||
currentRotY = q.GetY();
|
||||
currentRotZ = q.GetZ();
|
||||
currentRotW = q.GetW();
|
||||
p.delete();
|
||||
q.delete();
|
||||
#end
|
||||
}
|
||||
|
||||
function update() {
|
||||
transform.loc.set(currentPosX, currentPosY, currentPosZ);
|
||||
transform.rot.set(currentRotX, currentRotY, currentRotZ, currentRotW);
|
||||
|
||||
if (object.parent != null) {
|
||||
var ptransform = object.parent.transform;
|
||||
transform.loc.x -= ptransform.worldx();
|
||||
transform.loc.y -= ptransform.worldy();
|
||||
transform.loc.z -= ptransform.worldz();
|
||||
}
|
||||
|
||||
transform.buildMatrix();
|
||||
}
|
||||
|
||||
function removeFromWorld() {
|
||||
if (physics != null) {
|
||||
physics.removeRigidBody(this);
|
||||
}
|
||||
}
|
||||
|
||||
public function delete() {
|
||||
// Cleanup handled by physics world
|
||||
}
|
||||
|
||||
// Physics methods
|
||||
public function applyForce(force:Vec4, ?loc:Vec4) {
|
||||
activate();
|
||||
if (loc == null) {
|
||||
var f = new jolt.Jt.Vec3(force.x, force.y, force.z);
|
||||
physics.bodyInterface.AddForce(bodyId, f);
|
||||
#if hl f.delete(); #end
|
||||
} else {
|
||||
var f = new jolt.Jt.Vec3(force.x, force.y, force.z);
|
||||
var l = new jolt.Jt.RVec3(loc.x, loc.y, loc.z);
|
||||
physics.bodyInterface.AddForceAtPosition(bodyId, f, l);
|
||||
#if hl f.delete(); l.delete(); #end
|
||||
}
|
||||
}
|
||||
|
||||
public function applyImpulse(impulse:Vec4, ?loc:Vec4) {
|
||||
activate();
|
||||
if (loc == null) {
|
||||
var i = new jolt.Jt.Vec3(impulse.x, impulse.y, impulse.z);
|
||||
physics.bodyInterface.AddImpulse(bodyId, i);
|
||||
#if hl i.delete(); #end
|
||||
} else {
|
||||
var i = new jolt.Jt.Vec3(impulse.x, impulse.y, impulse.z);
|
||||
var l = new jolt.Jt.RVec3(loc.x, loc.y, loc.z);
|
||||
physics.bodyInterface.AddImpulseAtPosition(bodyId, i, l);
|
||||
#if hl i.delete(); l.delete(); #end
|
||||
}
|
||||
}
|
||||
|
||||
public function applyTorque(torque:Vec4) {
|
||||
activate();
|
||||
var t = new jolt.Jt.Vec3(torque.x, torque.y, torque.z);
|
||||
physics.bodyInterface.AddTorque(bodyId, t);
|
||||
#if hl t.delete(); #end
|
||||
}
|
||||
|
||||
public function applyTorqueImpulse(impulse:Vec4) {
|
||||
activate();
|
||||
var i = new jolt.Jt.Vec3(impulse.x, impulse.y, impulse.z);
|
||||
physics.bodyInterface.AddAngularImpulse(bodyId, i);
|
||||
#if hl i.delete(); #end
|
||||
}
|
||||
|
||||
public function setLinearVelocity(v:Vec4) {
|
||||
var vel = new jolt.Jt.Vec3(v.x, v.y, v.z);
|
||||
physics.bodyInterface.SetLinearVelocity(bodyId, vel);
|
||||
#if hl vel.delete(); #end
|
||||
}
|
||||
|
||||
public function getLinearVelocity():Vec4 {
|
||||
var v = physics.bodyInterface.GetLinearVelocity(bodyId);
|
||||
var result = new Vec4(v.GetX(), v.GetY(), v.GetZ());
|
||||
#if hl v.delete(); #end
|
||||
return result;
|
||||
}
|
||||
|
||||
public function setAngularVelocity(v:Vec4) {
|
||||
var vel = new jolt.Jt.Vec3(v.x, v.y, v.z);
|
||||
physics.bodyInterface.SetAngularVelocity(bodyId, vel);
|
||||
#if hl vel.delete(); #end
|
||||
}
|
||||
|
||||
public function getAngularVelocity():Vec4 {
|
||||
var v = physics.bodyInterface.GetAngularVelocity(bodyId);
|
||||
var result = new Vec4(v.GetX(), v.GetY(), v.GetZ());
|
||||
#if hl v.delete(); #end
|
||||
return result;
|
||||
}
|
||||
|
||||
public function setFriction(f:Float) {
|
||||
friction = f;
|
||||
physics.bodyInterface.SetFriction(bodyId, f);
|
||||
}
|
||||
|
||||
public function setRestitution(r:Float) {
|
||||
restitution = r;
|
||||
physics.bodyInterface.SetRestitution(bodyId, r);
|
||||
}
|
||||
|
||||
public function setGravityFactor(f:Float) {
|
||||
physics.bodyInterface.SetGravityFactor(bodyId, f);
|
||||
}
|
||||
|
||||
public function activate() {
|
||||
physics.bodyInterface.ActivateBody(bodyId);
|
||||
}
|
||||
|
||||
public function disableSimulation() {
|
||||
physics.bodyInterface.DeactivateBody(bodyId);
|
||||
}
|
||||
|
||||
public function setPosition(pos:Vec4) {
|
||||
var p = new jolt.Jt.RVec3(pos.x, pos.y, pos.z);
|
||||
physics.bodyInterface.SetPosition(bodyId, p, 0);
|
||||
#if hl p.delete(); #end
|
||||
}
|
||||
|
||||
public function setRotation(rot:Quat) {
|
||||
var q = new jolt.Jt.Quat(rot.x, rot.y, rot.z, rot.w);
|
||||
physics.bodyInterface.SetRotation(bodyId, q, 0);
|
||||
#if hl q.delete(); #end
|
||||
}
|
||||
|
||||
public function syncTransform() {
|
||||
var t = transform;
|
||||
var pos = t.world.getLoc();
|
||||
var rot = new Quat();
|
||||
rot.fromMat(t.world);
|
||||
setPosition(pos);
|
||||
setRotation(rot);
|
||||
}
|
||||
|
||||
public function isActive():Bool {
|
||||
return physics.bodyInterface.IsActive(bodyId);
|
||||
}
|
||||
|
||||
public function disableGravity() {
|
||||
setGravityFactor(0.0);
|
||||
}
|
||||
|
||||
public function enableGravity() {
|
||||
setGravityFactor(1.0);
|
||||
}
|
||||
|
||||
public function getPointVelocity(x:Float, y:Float, z:Float):Vec4 {
|
||||
var linear = getLinearVelocity();
|
||||
var relativePoint = new Vec4(x, y, z).sub(transform.world.getLoc());
|
||||
var angular = getAngularVelocity().cross(relativePoint);
|
||||
return linear.add(angular);
|
||||
}
|
||||
|
||||
public function disableCollision() {
|
||||
// In Jolt, use SetIsSensor to disable contact response
|
||||
body.SetIsSensor(true);
|
||||
}
|
||||
|
||||
public function enableCollision() {
|
||||
body.SetIsSensor(false);
|
||||
}
|
||||
|
||||
public function notifyOnContact(f:RigidBody->Void) {
|
||||
if (onContact == null)
|
||||
onContact = [];
|
||||
onContact.push(f);
|
||||
}
|
||||
|
||||
public function removeContact(f:RigidBody->Void) {
|
||||
if (onContact != null)
|
||||
onContact.remove(f);
|
||||
}
|
||||
|
||||
public var onContact:Array<RigidBody->Void> = null;
|
||||
public var onReady:Void->Void = null;
|
||||
|
||||
public function notifyOnReady(f:Void->Void) {
|
||||
onReady = f;
|
||||
if (ready)
|
||||
onReady();
|
||||
}
|
||||
}
|
||||
|
||||
typedef RigidBodyParams = {
|
||||
var linearDamping:Float;
|
||||
var angularDamping:Float;
|
||||
var angularFriction:Float;
|
||||
var linearFactorsX:Float;
|
||||
var linearFactorsY:Float;
|
||||
var linearFactorsZ:Float;
|
||||
var angularFactorsX:Float;
|
||||
var angularFactorsY:Float;
|
||||
var angularFactorsZ:Float;
|
||||
var collisionMargin:Float;
|
||||
var linearDeactivationThreshold:Float;
|
||||
var angularDeactivationThrshold: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 = {
|
||||
var animated:Bool;
|
||||
var trigger:Bool;
|
||||
var ccd:Bool;
|
||||
var interpolate:Bool;
|
||||
var staticObj:Bool;
|
||||
var useDeactivation:Bool;
|
||||
}
|
||||
|
||||
#end
|
||||
528
leenkx/Sources/leenkx/trait/physics/jolt/SoftBody.hx
Normal file
528
leenkx/Sources/leenkx/trait/physics/jolt/SoftBody.hx
Normal file
@ -0,0 +1,528 @@
|
||||
package leenkx.trait.physics.jolt;
|
||||
|
||||
#if lnx_jolt
|
||||
|
||||
import iron.Trait;
|
||||
import iron.math.Vec4;
|
||||
import iron.object.MeshObject;
|
||||
import iron.data.MeshData;
|
||||
import iron.data.SceneFormat;
|
||||
import iron.system.Time;
|
||||
import kha.arrays.ByteArray;
|
||||
|
||||
@:enum abstract SoftShape(Int) from Int {
|
||||
var Cloth = 0;
|
||||
var Volume = 1;
|
||||
}
|
||||
|
||||
// Soft body particle for spring-mass simulation
|
||||
class SoftParticle {
|
||||
public var position:Vec4;
|
||||
public var velocity:Vec4;
|
||||
public var acceleration:Vec4;
|
||||
public var invMass:Float;
|
||||
public var pinned:Bool;
|
||||
|
||||
public function new(x:Float, y:Float, z:Float, mass:Float) {
|
||||
position = new Vec4(x, y, z);
|
||||
velocity = new Vec4(0, 0, 0);
|
||||
acceleration = new Vec4(0, 0, 0);
|
||||
invMass = mass > 0 ? 1.0 / mass : 0;
|
||||
pinned = false;
|
||||
}
|
||||
|
||||
public function applyForce(force:Vec4) {
|
||||
acceleration.x += force.x * invMass;
|
||||
acceleration.y += force.y * invMass;
|
||||
acceleration.z += force.z * invMass;
|
||||
}
|
||||
|
||||
public function integrate(dt:Float) {
|
||||
if (pinned || invMass == 0)
|
||||
return;
|
||||
|
||||
velocity.x += acceleration.x * dt;
|
||||
velocity.y += acceleration.y * dt;
|
||||
velocity.z += acceleration.z * dt;
|
||||
|
||||
// Damping
|
||||
velocity.x *= 0.99;
|
||||
velocity.y *= 0.99;
|
||||
velocity.z *= 0.99;
|
||||
|
||||
position.x += velocity.x * dt;
|
||||
position.y += velocity.y * dt;
|
||||
position.z += velocity.z * dt;
|
||||
|
||||
acceleration.set(0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Spring constraint between particles
|
||||
class SoftSpring {
|
||||
public var p1:Int;
|
||||
public var p2:Int;
|
||||
public var restLength:Float;
|
||||
public var stiffness:Float;
|
||||
|
||||
public function new(p1:Int, p2:Int, restLength:Float, stiffness:Float) {
|
||||
this.p1 = p1;
|
||||
this.p2 = p2;
|
||||
this.restLength = restLength;
|
||||
this.stiffness = stiffness;
|
||||
}
|
||||
}
|
||||
|
||||
class SoftBody extends Trait {
|
||||
|
||||
static var physics:PhysicsWorld = null;
|
||||
|
||||
public var ready = false;
|
||||
var shape:SoftShape;
|
||||
var bend:Float;
|
||||
var mass:Float;
|
||||
var margin:Float;
|
||||
|
||||
public var vertOffsetX = 0.0;
|
||||
public var vertOffsetY = 0.0;
|
||||
public var vertOffsetZ = 0.0;
|
||||
|
||||
// Spring-mass simulation
|
||||
var particles:Array<SoftParticle> = [];
|
||||
var springs:Array<SoftSpring> = [];
|
||||
var gravity:Vec4;
|
||||
var iterations = 3;
|
||||
|
||||
// Mesh data for vertex updates
|
||||
var meshObject:MeshObject;
|
||||
|
||||
// Vertex deduplication: maps unique vertex index → list of raw vertex indices
|
||||
// Meshes split vertices for normals/UVs, but soft body particles must be unique
|
||||
var vertexIndexMap:Map<Int, Array<Int>>;
|
||||
|
||||
public function new(shape = SoftShape.Cloth, bend = 0.5, mass = 1.0, margin = 0.04) {
|
||||
super();
|
||||
this.shape = shape;
|
||||
this.bend = bend;
|
||||
this.mass = mass;
|
||||
this.margin = margin;
|
||||
|
||||
notifyOnInit(init);
|
||||
}
|
||||
|
||||
function init() {
|
||||
var mo = cast(object, MeshObject);
|
||||
new MeshData(mo.data.raw, function(data) {
|
||||
mo.setData(data);
|
||||
initSoftBody();
|
||||
});
|
||||
}
|
||||
|
||||
function retryInit() {
|
||||
iron.App.removeUpdate(retryInit);
|
||||
initSoftBody();
|
||||
}
|
||||
|
||||
function initSoftBody() {
|
||||
if (ready)
|
||||
return;
|
||||
|
||||
if (PhysicsWorld.active == null || !PhysicsWorld.active.physicsReady) {
|
||||
iron.App.notifyOnUpdate(retryInit);
|
||||
return;
|
||||
}
|
||||
|
||||
ready = true;
|
||||
|
||||
if (physics == null)
|
||||
physics = PhysicsWorld.active;
|
||||
|
||||
meshObject = cast(object, MeshObject);
|
||||
meshObject.frustumCulling = false;
|
||||
var geom = meshObject.data.geom;
|
||||
var rawData = meshObject.data.raw;
|
||||
|
||||
// Get gravity from physics world
|
||||
var g = physics.getGravity();
|
||||
gravity = new Vec4(g.x, g.y, g.z);
|
||||
|
||||
// Parented soft body - clear parent location
|
||||
if (object.parent != null && object.parent.name != "") {
|
||||
object.transform.loc.x += object.parent.transform.worldx();
|
||||
object.transform.loc.y += object.parent.transform.worldy();
|
||||
object.transform.loc.z += object.parent.transform.worldz();
|
||||
object.transform.localOnly = true;
|
||||
object.transform.buildMatrix();
|
||||
}
|
||||
|
||||
// Build vertex deduplication map from vertex_map (matching Bullet SoftBody pattern)
|
||||
// vertex_map[rawVertexBufferIdx] = uniqueVertexIdx
|
||||
// vertex_map.length = number of raw vertices in vertex buffer (NOT same as geom.indices length)
|
||||
// vertexIndexMap: uniqueVertexIdx → [rawVertexBufferIdx, ...]
|
||||
vertexIndexMap = new Map();
|
||||
var hasVertexMap = false;
|
||||
for (ind in rawData.index_arrays) {
|
||||
if (ind.vertex_map != null) {
|
||||
hasVertexMap = true;
|
||||
for (rawIdx in 0...ind.vertex_map.length) {
|
||||
var uniqueIdx = ind.vertex_map[rawIdx];
|
||||
var mapping = vertexIndexMap.get(uniqueIdx);
|
||||
if (mapping == null) {
|
||||
vertexIndexMap.set(uniqueIdx, [rawIdx]);
|
||||
} else {
|
||||
if (!mapping.contains(rawIdx))
|
||||
mapping.push(rawIdx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var positions = geom.positions.values;
|
||||
var scalePos = meshObject.data.scalePos;
|
||||
|
||||
if (hasVertexMap) {
|
||||
// Create particles only for unique vertices
|
||||
var numUnique = 0;
|
||||
for (_ in vertexIndexMap.keys())
|
||||
numUnique++;
|
||||
|
||||
for (key in 0...numUnique) {
|
||||
var rawIndices = vertexIndexMap.get(key);
|
||||
if (rawIndices == null || rawIndices.length == 0)
|
||||
continue;
|
||||
var ri = rawIndices[0];
|
||||
var x = (positions[ri * 4] / 32767) * scalePos;
|
||||
var y = (positions[ri * 4 + 1] / 32767) * scalePos;
|
||||
var z = (positions[ri * 4 + 2] / 32767) * scalePos;
|
||||
|
||||
// Apply object rotation and scale
|
||||
var vt = new Vec4(x, y, z);
|
||||
vt.applyQuat(object.transform.rot);
|
||||
vt.x *= object.transform.scale.x;
|
||||
vt.y *= object.transform.scale.y;
|
||||
vt.z *= object.transform.scale.z;
|
||||
vt.addf(object.transform.worldx(), object.transform.worldy(), object.transform.worldz());
|
||||
|
||||
var particle = new SoftParticle(vt.x, vt.y, vt.z, mass / numUnique);
|
||||
particles.push(particle);
|
||||
}
|
||||
|
||||
// Create springs from triangle connectivity
|
||||
// geom.indices[ia] = triangle index list of raw vertex buffer indices
|
||||
// vertex_map[rawIdx] = unique vertex index for that raw vertex
|
||||
var createdSprings:Map<String, Bool> = new Map();
|
||||
var indexArrayIdx = 0;
|
||||
for (ind in rawData.index_arrays) {
|
||||
if (ind.vertex_map == null) {
|
||||
indexArrayIdx++;
|
||||
continue;
|
||||
}
|
||||
var indexArray = geom.indices[indexArrayIdx];
|
||||
var numTris = Std.int(indexArray.length / 3);
|
||||
for (i in 0...numTris) {
|
||||
var r0 = indexArray[i * 3];
|
||||
var r1 = indexArray[i * 3 + 1];
|
||||
var r2 = indexArray[i * 3 + 2];
|
||||
// Map raw vertex buffer indices → unique vertex indices
|
||||
var u0 = ind.vertex_map[r0];
|
||||
var u1 = ind.vertex_map[r1];
|
||||
var u2 = ind.vertex_map[r2];
|
||||
|
||||
createSpring(u0, u1, createdSprings);
|
||||
createSpring(u1, u2, createdSprings);
|
||||
createSpring(u2, u0, createdSprings);
|
||||
}
|
||||
indexArrayIdx++;
|
||||
}
|
||||
} else {
|
||||
// Fallback: no vertex_map, treat each vertex as unique
|
||||
var numVerts = Std.int(positions.length / 4);
|
||||
vertexIndexMap = new Map();
|
||||
for (i in 0...numVerts) {
|
||||
vertexIndexMap.set(i, [i]);
|
||||
|
||||
var x = (positions[i * 4] / 32767) * scalePos;
|
||||
var y = (positions[i * 4 + 1] / 32767) * scalePos;
|
||||
var z = (positions[i * 4 + 2] / 32767) * scalePos;
|
||||
|
||||
var vt = new Vec4(x, y, z);
|
||||
vt.applyQuat(object.transform.rot);
|
||||
vt.x *= object.transform.scale.x;
|
||||
vt.y *= object.transform.scale.y;
|
||||
vt.z *= object.transform.scale.z;
|
||||
vt.addf(object.transform.worldx(), object.transform.worldy(), object.transform.worldz());
|
||||
|
||||
var particle = new SoftParticle(vt.x, vt.y, vt.z, mass / numVerts);
|
||||
particles.push(particle);
|
||||
}
|
||||
|
||||
var createdSprings:Map<String, Bool> = new Map();
|
||||
for (indexArray in geom.indices) {
|
||||
var numTris = Std.int(indexArray.length / 3);
|
||||
for (i in 0...numTris) {
|
||||
createSpring(indexArray[i * 3], indexArray[i * 3 + 1], createdSprings);
|
||||
createSpring(indexArray[i * 3 + 1], indexArray[i * 3 + 2], createdSprings);
|
||||
createSpring(indexArray[i * 3 + 2], indexArray[i * 3], createdSprings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pin top edge vertices for cloth simulation
|
||||
if (shape == SoftShape.Cloth) {
|
||||
var minX = Math.POSITIVE_INFINITY, maxX = Math.NEGATIVE_INFINITY;
|
||||
var minY = Math.POSITIVE_INFINITY, maxY = Math.NEGATIVE_INFINITY;
|
||||
var minZ = Math.POSITIVE_INFINITY, maxZ = Math.NEGATIVE_INFINITY;
|
||||
for (p in particles) {
|
||||
if (p.position.x < minX) minX = p.position.x;
|
||||
if (p.position.x > maxX) maxX = p.position.x;
|
||||
if (p.position.y < minY) minY = p.position.y;
|
||||
if (p.position.y > maxY) maxY = p.position.y;
|
||||
if (p.position.z < minZ) minZ = p.position.z;
|
||||
if (p.position.z > maxZ) maxZ = p.position.z;
|
||||
}
|
||||
var extX = maxX - minX;
|
||||
var extY = maxY - minY;
|
||||
var extZ = maxZ - minZ;
|
||||
|
||||
if (extZ > 0.01) {
|
||||
var threshold = maxZ - extZ * 0.05;
|
||||
for (p in particles) {
|
||||
if (p.position.z >= threshold)
|
||||
p.pinned = true;
|
||||
}
|
||||
} else if (extY > 0.01) {
|
||||
var threshold = maxY - extY * 0.05;
|
||||
for (p in particles) {
|
||||
if (p.position.y >= threshold)
|
||||
p.pinned = true;
|
||||
}
|
||||
} else if (extX > 0.01) {
|
||||
var threshold = maxX - extX * 0.05;
|
||||
for (p in particles) {
|
||||
if (p.position.x >= threshold)
|
||||
p.pinned = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
notifyOnRemove(removeFromWorld);
|
||||
notifyOnUpdate(update);
|
||||
}
|
||||
|
||||
function createSpring(i0:Int, i1:Int, createdSprings:Map<String, Bool>) {
|
||||
if (i0 == i1)
|
||||
return;
|
||||
var key = i0 < i1 ? '${i0}_${i1}' : '${i1}_${i0}';
|
||||
if (createdSprings.exists(key))
|
||||
return;
|
||||
createdSprings.set(key, true);
|
||||
|
||||
if (i0 >= particles.length || i1 >= particles.length)
|
||||
return;
|
||||
|
||||
var p0 = particles[i0];
|
||||
var p1 = particles[i1];
|
||||
var dx = p1.position.x - p0.position.x;
|
||||
var dy = p1.position.y - p0.position.y;
|
||||
var dz = p1.position.z - p0.position.z;
|
||||
var restLength = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||
|
||||
if (restLength < 0.0001)
|
||||
return;
|
||||
|
||||
springs.push(new SoftSpring(i0, i1, restLength, 1.0 - bend));
|
||||
}
|
||||
|
||||
function update() {
|
||||
var dt = Time.delta;
|
||||
if (dt <= 0 || dt > 0.1)
|
||||
return;
|
||||
|
||||
// Apply gravity as acceleration (m/s^2, independent of mass)
|
||||
for (p in particles) {
|
||||
if (!p.pinned) {
|
||||
p.acceleration.x += gravity.x;
|
||||
p.acceleration.y += gravity.y;
|
||||
p.acceleration.z += gravity.z;
|
||||
}
|
||||
}
|
||||
|
||||
// Integrate particles
|
||||
for (p in particles) {
|
||||
p.integrate(dt);
|
||||
}
|
||||
|
||||
// Solve spring constraints
|
||||
for (iter in 0...iterations) {
|
||||
for (spring in springs) {
|
||||
solveSpring(spring);
|
||||
}
|
||||
}
|
||||
|
||||
// Ground collision
|
||||
for (p in particles) {
|
||||
if (p.position.z < 0) {
|
||||
p.position.z = 0;
|
||||
p.velocity.z = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Update mesh vertices
|
||||
updateMeshVertices();
|
||||
}
|
||||
|
||||
function solveSpring(spring:SoftSpring) {
|
||||
var p1 = particles[spring.p1];
|
||||
var p2 = particles[spring.p2];
|
||||
|
||||
var dx = p2.position.x - p1.position.x;
|
||||
var dy = p2.position.y - p1.position.y;
|
||||
var dz = p2.position.z - p1.position.z;
|
||||
var dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||
|
||||
if (dist < 0.0001)
|
||||
return;
|
||||
|
||||
var diff = (dist - spring.restLength) / dist;
|
||||
var stiffness = spring.stiffness;
|
||||
var correction = diff * stiffness * 0.5;
|
||||
|
||||
if (!p1.pinned && p1.invMass > 0) {
|
||||
p1.position.x += dx * correction;
|
||||
p1.position.y += dy * correction;
|
||||
p1.position.z += dz * correction;
|
||||
}
|
||||
|
||||
if (!p2.pinned && p2.invMass > 0) {
|
||||
p2.position.x -= dx * correction;
|
||||
p2.position.y -= dy * correction;
|
||||
p2.position.z -= dz * correction;
|
||||
}
|
||||
}
|
||||
|
||||
function updateMeshVertices() {
|
||||
var geom = meshObject.data.geom;
|
||||
var numNodes = particles.length;
|
||||
|
||||
// Compute mean position (center of mass) for object placement
|
||||
vertOffsetX = 0.0;
|
||||
vertOffsetY = 0.0;
|
||||
vertOffsetZ = 0.0;
|
||||
for (p in particles) {
|
||||
vertOffsetX += p.position.x;
|
||||
vertOffsetY += p.position.y;
|
||||
vertOffsetZ += p.position.z;
|
||||
}
|
||||
vertOffsetX /= numNodes;
|
||||
vertOffsetY /= numNodes;
|
||||
vertOffsetZ /= numNodes;
|
||||
|
||||
// Set object transform to center of mass
|
||||
meshObject.transform.scale.set(1, 1, 1);
|
||||
meshObject.transform.loc.set(vertOffsetX, vertOffsetY, vertOffsetZ);
|
||||
meshObject.transform.rot.set(0, 0, 0, 1);
|
||||
|
||||
// Compute scalePos to fit all vertices
|
||||
var scalePos = 1.0;
|
||||
for (p in particles) {
|
||||
var mx = Math.abs((p.position.x - vertOffsetX) * 2);
|
||||
var my = Math.abs((p.position.y - vertOffsetY) * 2);
|
||||
var mz = Math.abs((p.position.z - vertOffsetZ) * 2);
|
||||
if (mx > scalePos) scalePos = mx;
|
||||
if (my > scalePos) scalePos = my;
|
||||
if (mz > scalePos) scalePos = mz;
|
||||
}
|
||||
|
||||
meshObject.data.scalePos = scalePos;
|
||||
meshObject.transform.scaleWorld = scalePos;
|
||||
meshObject.transform.buildMatrix();
|
||||
|
||||
var invScalePos = 1.0 / scalePos;
|
||||
|
||||
// Lock vertex buffer(s) for GPU upload
|
||||
#if lnx_deinterleaved
|
||||
var v:ByteArray = geom.vertexBuffers[0].buffer.lock();
|
||||
#else
|
||||
var v:ByteArray = geom.vertexBuffer.lock();
|
||||
var vbPos = geom.vertexBufferMap.get("pos");
|
||||
var v2 = vbPos != null ? vbPos.lock() : null;
|
||||
var l = geom.structLength;
|
||||
#end
|
||||
|
||||
// Write each unique particle position to all its raw vertex copies
|
||||
for (uniqueIdx in 0...numNodes) {
|
||||
var p = particles[uniqueIdx];
|
||||
var indices = vertexIndexMap.get(uniqueIdx);
|
||||
if (indices == null)
|
||||
continue;
|
||||
|
||||
var mx = p.position.x - vertOffsetX;
|
||||
var my = p.position.y - vertOffsetY;
|
||||
var mz = p.position.z - vertOffsetZ;
|
||||
|
||||
var sx = Std.int(mx * 32767 * invScalePos);
|
||||
var sy = Std.int(my * 32767 * invScalePos);
|
||||
var sz = Std.int(mz * 32767 * invScalePos);
|
||||
|
||||
for (idx in indices) {
|
||||
#if lnx_deinterleaved
|
||||
v.setInt16(idx * 8, sx);
|
||||
v.setInt16(idx * 8 + 2, sy);
|
||||
v.setInt16(idx * 8 + 4, sz);
|
||||
#else
|
||||
var vertIndex = idx * l * 2;
|
||||
v.setInt16(vertIndex, sx);
|
||||
v.setInt16(vertIndex + 2, sy);
|
||||
v.setInt16(vertIndex + 4, sz);
|
||||
if (v2 != null) {
|
||||
v2.setInt16(idx * 8, sx);
|
||||
v2.setInt16(idx * 8 + 2, sy);
|
||||
v2.setInt16(idx * 8 + 4, sz);
|
||||
}
|
||||
#end
|
||||
}
|
||||
}
|
||||
|
||||
// Unlock triggers GPU upload
|
||||
#if lnx_deinterleaved
|
||||
geom.vertexBuffers[0].buffer.unlock();
|
||||
#else
|
||||
geom.vertexBuffer.unlock();
|
||||
if (vbPos != null) vbPos.unlock();
|
||||
#end
|
||||
}
|
||||
|
||||
public function pinVertex(index:Int) {
|
||||
if (index >= 0 && index < particles.length) {
|
||||
particles[index].pinned = true;
|
||||
}
|
||||
}
|
||||
|
||||
public function unpinVertex(index:Int) {
|
||||
if (index >= 0 && index < particles.length) {
|
||||
particles[index].pinned = false;
|
||||
}
|
||||
}
|
||||
|
||||
public function applyForceToVertex(index:Int, force:Vec4) {
|
||||
if (index >= 0 && index < particles.length) {
|
||||
particles[index].applyForce(force);
|
||||
}
|
||||
}
|
||||
|
||||
public function applyWindForce(direction:Vec4, strength:Float) {
|
||||
var wind = new Vec4(direction.x * strength, direction.y * strength, direction.z * strength);
|
||||
for (p in particles) {
|
||||
if (!p.pinned) {
|
||||
p.applyForce(wind);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function removeFromWorld() {
|
||||
particles = [];
|
||||
springs = [];
|
||||
}
|
||||
}
|
||||
|
||||
#end
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user