forked from LeenkxTeam/LNXSDK
Compare commits
63 Commits
2abb6efe4f
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| f45ad72056 | |||
| 29f0765864 | |||
| 93e91a68d5 | |||
| 4c1ec8078b | |||
| d4a70b7882 | |||
| 92d6637695 | |||
| 84bab9bbc5 | |||
| 74997b1e23 | |||
| 6454d741d4 | |||
| 7d63d60d99 | |||
| 9f37c2bd8b | |||
| 083e7dbe7d | |||
| 5789e629f9 | |||
| 6c13722fc6 | |||
| c3a27b2ee9 | |||
| cac6c7e7b8 | |||
| eb211da457 | |||
| 7caa6f8ff5 | |||
| 478a97d2ed | |||
| 6c537591e8 | |||
| ee04bb87dd | |||
| 612a3d0a09 | |||
| 3273054766 | |||
| beabf76562 | |||
| c1aa2837ca | |||
| c320c4bb19 | |||
| 4c8c26bb33 | |||
| a133c5ee94 | |||
| 4895d7cfa5 | |||
| 052ce82588 | |||
| 211cba61a6 | |||
| 4ab646015d | |||
| 9fee67d231 | |||
| 860b73a8f4 | |||
| da38631584 | |||
| 6839203dc5 | |||
| d9ac74101f | |||
| e20400654c | |||
| 058cbed20f | |||
| e0c10318e5 | |||
| 7d609b126d | |||
| 36ecaea919 | |||
| fac01a2849 | |||
| c71c1a98e4 | |||
| f6d5f142e9 | |||
| 70961b0336 | |||
| 9c50274f29 | |||
| a94b95afc8 | |||
| cee36635e0 | |||
| d0337f5dae | |||
| fca51c7e61 | |||
| 0bb4bceb57 | |||
| af9a259fbb | |||
| f1d8522f51 | |||
| 56d1b0e4a7 | |||
| 73e4a0ad30 | |||
| 67d5e6b2c7 | |||
| 635f8f6645 | |||
| b2444be7e2 | |||
| 7d00036973 | |||
| 46814444af | |||
| b4de377433 | |||
| d787f9a7fc |
@ -130,6 +130,11 @@ extern class Krom {
|
||||
static function readStorage(name: String): haxe.io.BytesData;
|
||||
|
||||
static function fileSaveBytes(path: String, bytes: haxe.io.BytesData): Void;
|
||||
static function fileWriteBytes(path: String, bytes: haxe.io.BytesData, ?offset: Float, ?byteLength: Int): Void;
|
||||
static function fileReadBytes(path: String, ?offset: Float, ?length: Int): haxe.io.BytesData;
|
||||
static function fileSize(path: String): Float;
|
||||
static function fileExists(path: String): Bool;
|
||||
static function deleteFile(path: String): Void;
|
||||
static function sysCommand(cmd: String, ?args: Array<String>): Int;
|
||||
static function savePath(): String;
|
||||
static function getArgCount(): Int;
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Krom/Krom.exe
BIN
Krom/Krom.exe
Binary file not shown.
Binary file not shown.
@ -1,7 +0,0 @@
|
||||
# The zlib/libpng License
|
||||
|
||||
This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software.
|
||||
Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
|
||||
The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
|
||||
Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
|
||||
This notice may not be removed or altered from any source distribution.
|
||||
@ -1,7 +0,0 @@
|
||||
* 2024-12-02: Submodules have been moved in to this repository (previously contained in https://github.com/leenkx3d/lnxsdk).
|
||||
* 2019-06-30: Return value of `PhysicsWorld.rayCast()` changed, see https://github.com/leenkx3d/leenkx/commit/dfb7609a28cebf3a520e6a25a8563d01c32f2b01.
|
||||
* 2019-04-06: Use voxelao instead of voxelgi, gi will be reworked into raytracing.
|
||||
* 2019-01-13: If you are using Leenkx Updater, get Leenkx 0.6beta from https://leenkx.itch.io/leenkx3d first (or clone the sdk from https://github.com/leenkx3d/lnxsdk).
|
||||
* 2018-08-28: `LampObject` and `LampData` is now `LightObject` and `LightData`
|
||||
* 2018-06-01: 'Not Equal' has been removed from the Gate logic node. Use 'Equal' and 'False' output socket instead.
|
||||
* 2017-11-20: Use `leenkx.trait.physics.*` instead of `leenkx.trait.internal.*` to access physics traits.
|
||||
@ -6,7 +6,7 @@ bl_info = {
|
||||
"location": "Properties -> Render -> Leenkx Player",
|
||||
"description": "Full Stack SDK",
|
||||
"author": "Leenkx.com",
|
||||
"version": (2026, 5, 0),
|
||||
"version": (2026, 10, 6),
|
||||
"blender": (5, 2, 0),
|
||||
"doc_url": "https://leenkx.com/",
|
||||
"tracker_url": "https://leenkx.com/support"
|
||||
@ -361,7 +361,7 @@ class LeenkxAddonPreferences(AddonPreferences):
|
||||
layout.label(text="Welcome to Leenkx!")
|
||||
|
||||
# Compare version Blender and Leenkx (major, minor)
|
||||
if bpy.app.version[:2] not in [(4, 5), (4, 4), (4, 2), (3, 6), (3, 3)]:
|
||||
if bpy.app.version[:2] not in [(5, 2), (4, 5), (4, 4), (4, 2), (3, 6), (3, 3)]:
|
||||
box = layout.box().column()
|
||||
box.label(text="Warning: For Leenkx to work correctly, use a Blender LTS version")
|
||||
|
||||
|
||||
@ -48,6 +48,10 @@ void main() {
|
||||
}
|
||||
|
||||
if (currentMipLevel == 0) {
|
||||
if (any(isnan(fragColor.rgb)) || any(isinf(fragColor.rgb))) {
|
||||
fragColor.rgb = vec3(0.0);
|
||||
}
|
||||
|
||||
// https://catlikecoding.com/unity/tutorials/advanced-rendering/bloom/#3.2
|
||||
// https://catlikecoding.com/unity/tutorials/advanced-rendering/bloom/#3.4
|
||||
|
||||
|
||||
@ -69,6 +69,7 @@ uniform vec4 PPComp15;
|
||||
uniform vec4 PPComp16;
|
||||
uniform vec4 PPComp18;
|
||||
uniform vec4 PPComp19;
|
||||
uniform vec4 PPComp20;
|
||||
#endif
|
||||
|
||||
// #ifdef _CPos
|
||||
@ -499,14 +500,15 @@ void main() {
|
||||
|
||||
#ifdef _CExposure
|
||||
#ifdef _CPostprocess
|
||||
fragColor.rgb+=fragColor.rgb*PPComp8.x;
|
||||
fragColor.rgb *= pow(2.0, PPComp8.x);
|
||||
#else
|
||||
fragColor.rgb+= fragColor.rgb*compoExposureStrength;
|
||||
fragColor.rgb *= pow(2.0, compoExposureStrength);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef _CPostprocess
|
||||
fragColor.rgb *= ComputeEV(0.0);
|
||||
fragColor.rgb *= pow(2.0, PPComp20.x); // exposure and gamma
|
||||
#endif
|
||||
|
||||
#ifdef _AutoExposure
|
||||
@ -582,14 +584,16 @@ fragColor.rgb = min(fragColor.rgb, 65504 * 0.5);
|
||||
fragColor.rgb = tonemapAgXFull(fragColor.rgb);
|
||||
} //else { fragColor.rgb = vec3(0,1,0); //ERROR}
|
||||
#endif
|
||||
|
||||
#ifdef _CGamma
|
||||
fragColor.rgb = pow(fragColor.rgb, vec3(1.0 / PPComp20.y));
|
||||
#endif
|
||||
#else
|
||||
#ifdef _CToneFilmic
|
||||
fragColor.rgb = tonemapFilmic(fragColor.rgb); // With gamma
|
||||
#endif
|
||||
#ifdef _CToneFilmic2
|
||||
fragColor.rgb = acesFilm(fragColor.rgb);
|
||||
fragColor.rgb = pow(fragColor.rgb, vec3(1.0 / 1.0));
|
||||
fragColor.rgb = pow(fragColor.rgb, vec3(1.0 / 2.2));
|
||||
#endif
|
||||
#ifdef _CToneReinhard
|
||||
fragColor.rgb = tonemapReinhard(fragColor.rgb);
|
||||
@ -615,6 +619,10 @@ fragColor.rgb = min(fragColor.rgb, 65504 * 0.5);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef _CGamma
|
||||
fragColor.rgb = pow(fragColor.rgb, vec3(1.0 / compoGammaStrength));
|
||||
#endif
|
||||
|
||||
#ifdef _CBW
|
||||
// fragColor.rgb = vec3(clamp(dot(fragColor.rgb, fragColor.rgb), 0.0, 1.0));
|
||||
fragColor.rgb = vec3((fragColor.r * 0.3 + fragColor.g * 0.59 + fragColor.b * 0.11) / 3.0) * 2.5;
|
||||
|
||||
@ -636,7 +636,7 @@ void main() {
|
||||
shadowMap
|
||||
#endif
|
||||
#endif
|
||||
, eye, p + n * shadowsBias * 2, shadowsBias
|
||||
, eye, p, n, shadowsBias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, false
|
||||
#endif
|
||||
@ -907,26 +907,10 @@ tileBounds = vec4(0.0, 0.0, 1.0, 1.0);
|
||||
fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittance(LWVPSpotArray[li], p, n, cLightDir, lightPlane.y, shadowMapAtlas, sssColorCL, sssRadiusCL, tileBoundsSpotArray[li]);
|
||||
#endif
|
||||
#else
|
||||
if (li == 0) fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittance(LWVPSpotArray[0], p, n, cLightDir, lightPlane.y, shadowMapSpot[0], sssColorCL, sssRadiusCL
|
||||
#ifdef _ShadowMapAtlas
|
||||
, vec4(0.0, 0.0, 1.0, 1.0)
|
||||
#endif
|
||||
);
|
||||
else if (li == 1) fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittance(LWVPSpotArray[1], p, n, cLightDir, lightPlane.y, shadowMapSpot[1], sssColorCL, sssRadiusCL
|
||||
#ifdef _ShadowMapAtlas
|
||||
, vec4(0.0, 0.0, 1.0, 1.0)
|
||||
#endif
|
||||
);
|
||||
else if (li == 2) fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittance(LWVPSpotArray[2], p, n, cLightDir, lightPlane.y, shadowMapSpot[2], sssColorCL, sssRadiusCL
|
||||
#ifdef _ShadowMapAtlas
|
||||
, vec4(0.0, 0.0, 1.0, 1.0)
|
||||
#endif
|
||||
);
|
||||
else if (li == 3) fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittance(LWVPSpotArray[3], p, n, cLightDir, lightPlane.y, shadowMapSpot[3], sssColorCL, sssRadiusCL
|
||||
#ifdef _ShadowMapAtlas
|
||||
, vec4(0.0, 0.0, 1.0, 1.0)
|
||||
#endif
|
||||
);
|
||||
if (li == 0) fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittance(LWVPSpotArray[0], p, n, cLightDir, lightPlane.y, shadowMapSpot[0], sssColorCL, sssRadiusCL);
|
||||
else if (li == 1) fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittance(LWVPSpotArray[1], p, n, cLightDir, lightPlane.y, shadowMapSpot[1], sssColorCL, sssRadiusCL);
|
||||
else if (li == 2) fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittance(LWVPSpotArray[2], p, n, cLightDir, lightPlane.y, shadowMapSpot[2], sssColorCL, sssRadiusCL);
|
||||
else if (li == 3) fragColor.rgb += cLightCol * sssStrengthCL * SSSSTransmittance(LWVPSpotArray[3], p, n, cLightDir, lightPlane.y, shadowMapSpot[3], sssColorCL, sssRadiusCL);
|
||||
#endif
|
||||
} else {
|
||||
#ifdef _ShadowMapAtlas
|
||||
|
||||
@ -259,6 +259,11 @@
|
||||
"link": "_biasLightWorldViewProjectionMatrixSpotArray",
|
||||
"ifdef": ["_Clusters", "_ShadowMap", "_Spot"]
|
||||
},
|
||||
{
|
||||
"name": "LWVPSpotArray",
|
||||
"link": "_biasLightViewProjectionMatrixSpotArray",
|
||||
"ifdef": ["_SinglePoint", "_ShadowMap", "_Spot"]
|
||||
},
|
||||
{
|
||||
"name": "pointLightDataArray",
|
||||
"link": "_pointLightsAtlasArray",
|
||||
|
||||
@ -56,9 +56,15 @@ uniform vec2 cameraPlane;
|
||||
#ifdef _SinglePoint
|
||||
#ifdef _Spot
|
||||
//!uniform sampler2DShadow shadowMapSpot[1];
|
||||
#ifdef _ShadowMapTransparent
|
||||
//!uniform sampler2D shadowMapSpotTransparent[1];
|
||||
#endif
|
||||
//!uniform mat4 LWVPSpotArray[1];
|
||||
#else
|
||||
//!uniform samplerCubeShadow shadowMapPoint[1];
|
||||
#ifdef _ShadowMapTransparent
|
||||
//!uniform samplerCube shadowMapPointTransparent[1];
|
||||
#endif
|
||||
//!uniform vec2 lightProj;
|
||||
#endif
|
||||
#endif
|
||||
@ -66,26 +72,41 @@ uniform vec2 cameraPlane;
|
||||
#ifdef _ShadowMapAtlas
|
||||
#ifdef _SingleAtlas
|
||||
uniform sampler2DShadow shadowMapAtlas;
|
||||
#ifdef _ShadowMapTransparent
|
||||
uniform sampler2D shadowMapAtlasTransparent;
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#ifdef _ShadowMapAtlas
|
||||
#ifndef _SingleAtlas
|
||||
//!uniform sampler2DShadow shadowMapAtlasPoint;
|
||||
#ifdef _ShadowMapTransparent
|
||||
//!uniform sampler2D shadowMapAtlasPointTransparent;
|
||||
#endif
|
||||
//!uniform vec4 pointLightDataArray[4];
|
||||
#endif
|
||||
//!uniform vec4 pointLightDataArray[maxLightsCluster * 6];
|
||||
#else
|
||||
//!uniform samplerCubeShadow shadowMapPoint[4];
|
||||
#ifdef _ShadowMapTransparent
|
||||
//!uniform samplerCube shadowMapPointTransparent[4];
|
||||
#endif
|
||||
#endif
|
||||
//!uniform vec2 lightProj;
|
||||
#ifdef _Spot
|
||||
#ifdef _ShadowMapAtlas
|
||||
#ifndef _SingleAtlas
|
||||
//!uniform sampler2DShadow shadowMapAtlasSpot;
|
||||
#ifdef _ShadowMapTransparent
|
||||
//!uniform sampler2D shadowMapAtlasSpotTransparent;
|
||||
#endif
|
||||
#endif
|
||||
#else
|
||||
//!uniform sampler2DShadow shadowMapSpot[4];
|
||||
//!uniform sampler2DShadow shadowMapSpot[maxLightsCluster];
|
||||
#ifdef _ShadowMapTransparent
|
||||
//!uniform sampler2D shadowMapSpotTransparent[maxLightsCluster];
|
||||
#endif
|
||||
//!uniform mat4 LWVPSpotArray[4];
|
||||
#endif
|
||||
//!uniform mat4 LWVPSpotArray[maxLightsCluster];
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
@ -97,10 +118,16 @@ uniform vec3 sunCol;
|
||||
#ifdef _ShadowMapAtlas
|
||||
#ifndef _SingleAtlas
|
||||
uniform sampler2DShadow shadowMapAtlasSun;
|
||||
#ifdef _ShadowMapTransparent
|
||||
uniform sampler2D shadowMapAtlasSunTransparent;
|
||||
#endif
|
||||
#endif
|
||||
//!uniform vec4 tileBoundsSunArray[maxLights * shadowmapCascades];
|
||||
#else
|
||||
uniform sampler2DShadow shadowMap;
|
||||
#ifdef _ShadowMapTransparent
|
||||
uniform sampler2D shadowMapTransparent;
|
||||
#endif
|
||||
#endif
|
||||
uniform float shadowsBias;
|
||||
#ifdef _CSM
|
||||
@ -167,10 +194,11 @@ void main() {
|
||||
|
||||
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);
|
||||
vec3 basecolor = min(g1.rgb, vec3(2.0));
|
||||
vec3 albedo = surfaceAlbedo(basecolor, metallic);
|
||||
vec3 f0 = surfaceF0(basecolor, metallic);
|
||||
#ifdef _ExtBRDF
|
||||
f0 = mix(f0, min(g1.rgb, vec3(2.0)), vec3(matp6.y, matp6.z, matp6.w));
|
||||
f0 = mix(f0, basecolor, vec3(matp6.y, matp6.z, matp6.w));
|
||||
#endif
|
||||
|
||||
float depth = textureLod(gbufferD, texCoord, 0.0).r * 2.0 - 1.0;
|
||||
@ -206,7 +234,7 @@ void main() {
|
||||
envl /= PI;
|
||||
#endif
|
||||
#else
|
||||
vec3 envl = vec3(1.0);
|
||||
vec3 envl = vec3(0.0);
|
||||
#endif
|
||||
|
||||
#ifdef _Rad
|
||||
@ -227,13 +255,23 @@ void main() {
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef _Brdf
|
||||
vec3 F = f0 * envBRDF.x + envBRDF.y;
|
||||
#else
|
||||
vec3 F = f0;
|
||||
#endif
|
||||
|
||||
envl.rgb *= diffuseIBL(albedo, roughness, f0, dotNV);
|
||||
|
||||
#ifdef _Brdf
|
||||
envl.rgb *= 1.0 - F;
|
||||
#endif
|
||||
|
||||
#ifdef _Rad // Indirect specular
|
||||
envl.rgb += prefilteredColor * (f0 * envBRDF.x + envBRDF.y) * 1.5 * occspec.y;
|
||||
envl.rgb += prefilteredColor * F;
|
||||
#else
|
||||
#ifdef _EnvCol
|
||||
envl.rgb += backgroundCol * surfaceF0(g1.rgb, metallic); // f0
|
||||
envl.rgb += backgroundCol * F;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@ -281,7 +319,12 @@ void main() {
|
||||
float transmittance = 1.0 - transF;
|
||||
#ifdef _Rad
|
||||
if (matp2.z > 0.0 && transmittance > 0.0) {
|
||||
vec3 refrDir = transmissionIBLDirection(n, v, matp3.x);
|
||||
vec3 refrDir;
|
||||
if (matp3.y > 0.5) {
|
||||
refrDir = reflect(-v, n);
|
||||
} else {
|
||||
refrDir = transmissionIBLDirection(n, v, matp3.x);
|
||||
}
|
||||
float transLod = getMipFromRoughness(matp2.w, envmapNumMipmaps);
|
||||
vec3 transColor = textureLod(senvmapRadiance,
|
||||
envMapEquirect(refrDir), transLod).rgb;
|
||||
@ -337,7 +380,7 @@ void main() {
|
||||
float sdotNH = max(0.0, dot(n, sh));
|
||||
float sdotVH = max(0.0, dot(v, sh));
|
||||
float sdotNL = max(0.0, dot(n, sunDir));
|
||||
float svisibility = 1.0;
|
||||
vec3 svisibility = vec3(1.0);
|
||||
#ifdef _Anisotropy
|
||||
vec3 sdirect;
|
||||
if (abs(matp0.x) > 0.001 && dot(wTangent, wTangent) > 0.001) {
|
||||
@ -385,29 +428,59 @@ void main() {
|
||||
#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
|
||||
shadowMap
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMap, shadowMapTransparent
|
||||
#else
|
||||
shadowMap
|
||||
#endif
|
||||
#endif
|
||||
, eye, p, n, shadowsBias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, false
|
||||
#endif
|
||||
, eye, p + n * shadowsBias * 10, shadowsBias
|
||||
);
|
||||
#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
|
||||
shadowMap
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMap, shadowMapTransparent
|
||||
#else
|
||||
shadowMap
|
||||
#endif
|
||||
#endif
|
||||
, lPos.xyz / lPos.w, shadowsBias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, false
|
||||
#endif
|
||||
);
|
||||
#endif
|
||||
#endif
|
||||
@ -415,11 +488,18 @@ void main() {
|
||||
fragColor.rgb += sdirect * svisibility * sunCol;
|
||||
#endif
|
||||
|
||||
#ifdef _ShadowMapAtlas
|
||||
tileBounds = vec4(0.0, 0.0, 1.0, 1.0);
|
||||
#endif
|
||||
|
||||
#ifdef _SinglePoint
|
||||
fragColor.rgb += sampleLight(
|
||||
p, n, v, dotNV, pointPos, pointCol, albedo, roughness, occspec.y, f0
|
||||
#ifdef _ShadowMap
|
||||
, 0, pointBias, true
|
||||
#ifdef _ShadowMapTransparent
|
||||
, false
|
||||
#endif
|
||||
#endif
|
||||
#ifdef _Spot
|
||||
, true, spotData.x, spotData.y, spotDir, spotData.zw, spotRight // TODO: Test!
|
||||
@ -472,12 +552,15 @@ void main() {
|
||||
#ifdef _ShadowMap
|
||||
// light index, shadow bias, cast_shadows
|
||||
, li, lightsArray[li * 3 + 2].x, lightsArray[li * 3 + 2].z != 0.0
|
||||
#ifdef _ShadowMapTransparent
|
||||
, false
|
||||
#endif
|
||||
#endif
|
||||
#ifdef _Spot
|
||||
, lightsArray[li * 3 + 2].y != 0.0
|
||||
, lightsArray[li * 3 + 2].y // spot size (cutoff)
|
||||
, lightsArraySpot[li].w // spot blend (exponent)
|
||||
, lightsArraySpot[li].xyz // spotDir
|
||||
, 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
|
||||
|
||||
@ -122,11 +122,6 @@
|
||||
"link": "_inverseViewProjectionMatrixRight",
|
||||
"ifdef": ["_VRStereo"]
|
||||
},
|
||||
{
|
||||
"name": "invVP",
|
||||
"link": "_viewProjectionMatrix",
|
||||
"ifdef": ["_SSRS"]
|
||||
},
|
||||
{
|
||||
"name": "smSizeUniform",
|
||||
"link": "_shadowMapSize",
|
||||
@ -137,14 +132,6 @@
|
||||
"link": "_lightPlane",
|
||||
"ifdef": ["_SSS"]
|
||||
},
|
||||
{
|
||||
"name": "VP",
|
||||
"link": "_viewProjectionMatrix",
|
||||
"ifdef": ["_SSRS"]
|
||||
},
|
||||
{
|
||||
"ifdef": ["_SMSizeUniform"]
|
||||
},
|
||||
{
|
||||
"name": "lightProj",
|
||||
"link": "_lightPlaneProj",
|
||||
@ -196,6 +183,11 @@
|
||||
"link": "_biasLightWorldViewProjectionMatrixSpotArray",
|
||||
"ifdef": ["_Clusters", "_ShadowMap", "_Spot"]
|
||||
},
|
||||
{
|
||||
"name": "LWVPSpotArray",
|
||||
"link": "_biasLightViewProjectionMatrixSpotArray",
|
||||
"ifdef": ["_SinglePoint", "_ShadowMap", "_Spot"]
|
||||
},
|
||||
{
|
||||
"name": "pointLightDataArray",
|
||||
"link": "_pointLightsAtlasArray",
|
||||
|
||||
@ -3,8 +3,8 @@
|
||||
{
|
||||
"name": "probe_cubemap",
|
||||
"depth_write": false,
|
||||
"compare_mode": "less",
|
||||
"cull_mode": "clockwise",
|
||||
"compare_mode": "greater",
|
||||
"cull_mode": "counter_clockwise",
|
||||
"blend_source": "blend_one",
|
||||
"blend_destination": "blend_one",
|
||||
"blend_operation": "add",
|
||||
|
||||
@ -3,8 +3,8 @@
|
||||
{
|
||||
"name": "probe_planar",
|
||||
"depth_write": false,
|
||||
"compare_mode": "less",
|
||||
"cull_mode": "clockwise",
|
||||
"compare_mode": "greater",
|
||||
"cull_mode": "counter_clockwise",
|
||||
"blend_source": "blend_one",
|
||||
"blend_destination": "blend_one",
|
||||
"blend_operation": "add",
|
||||
|
||||
@ -81,11 +81,11 @@ void main() {
|
||||
vec4 gr = textureLod(gbuffer_refraction, texCoord, 0.0);
|
||||
float ior = unpackIOR(gr.x);
|
||||
float transmittance = gr.y;
|
||||
float surfaceDepth = gr.z;
|
||||
float surfaceDepth = textureLod(gbufferD, texCoord, 0.0).r;
|
||||
float d = surfaceDepth * 2.0 - 1.0;
|
||||
|
||||
vec4 sceneSample = textureLod(tex, texCoord, 0.0);
|
||||
if (surfaceDepth == 0.0 || surfaceDepth == 1.0) {
|
||||
if (gr.z == 0.0 || surfaceDepth == 1.0) {
|
||||
fragColor = sceneSample;
|
||||
return;
|
||||
}
|
||||
|
||||
@ -595,3 +595,4 @@ vec3 sampleLightVoxels(const vec3 p, const vec3 n, const vec3 v, const float dot
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
@ -14,15 +14,24 @@
|
||||
#ifdef _SinglePoint
|
||||
#ifdef _Spot
|
||||
uniform sampler2DShadow shadowMapSpot[1];
|
||||
#ifdef _ShadowMapTransparent
|
||||
uniform sampler2D shadowMapSpotTransparent[1];
|
||||
#endif
|
||||
uniform mat4 LWVPSpotArray[1];
|
||||
#else
|
||||
uniform samplerCubeShadow shadowMapPoint[1];
|
||||
#ifdef _ShadowMapTransparent
|
||||
uniform samplerCube shadowMapPointTransparent[1];
|
||||
#endif
|
||||
uniform vec2 lightProj;
|
||||
#endif
|
||||
#endif
|
||||
#ifdef _Clusters
|
||||
#ifdef _SingleAtlas
|
||||
//!uniform sampler2DShadow shadowMapAtlas;
|
||||
#ifdef _ShadowMapTransparent
|
||||
//!uniform sampler2D shadowMapAtlasTransparent;
|
||||
#endif
|
||||
#endif
|
||||
#ifndef _SinglePoint
|
||||
uniform vec2 lightProj;
|
||||
@ -30,17 +39,29 @@
|
||||
#ifdef _ShadowMapAtlas
|
||||
#ifndef _SingleAtlas
|
||||
uniform sampler2DShadow shadowMapAtlasPoint;
|
||||
#ifdef _ShadowMapTransparent
|
||||
uniform sampler2D shadowMapAtlasPointTransparent;
|
||||
#endif
|
||||
#endif
|
||||
#else
|
||||
uniform samplerCubeShadow shadowMapPoint[4];
|
||||
#ifdef _ShadowMapTransparent
|
||||
uniform samplerCube shadowMapPointTransparent[4];
|
||||
#endif
|
||||
#endif
|
||||
#ifdef _Spot
|
||||
#ifdef _ShadowMapAtlas
|
||||
#ifndef _SingleAtlas
|
||||
uniform sampler2DShadow shadowMapAtlasSpot;
|
||||
#ifdef _ShadowMapTransparent
|
||||
uniform sampler2D shadowMapAtlasSpotTransparent;
|
||||
#endif
|
||||
#endif
|
||||
#else
|
||||
uniform sampler2DShadow shadowMapSpot[maxLightsCluster];
|
||||
#ifdef _ShadowMapTransparent
|
||||
uniform sampler2D shadowMapSpotTransparent[maxLightsCluster];
|
||||
#endif
|
||||
#endif
|
||||
uniform mat4 LWVPSpotArray[maxLightsCluster];
|
||||
#endif
|
||||
@ -51,6 +72,9 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co
|
||||
const vec3 albedo, const float rough, const float spec, const vec3 f0
|
||||
#ifdef _ShadowMap
|
||||
, int index, float bias, bool receiveShadow
|
||||
#ifdef _ShadowMapTransparent
|
||||
, bool transparent
|
||||
#endif
|
||||
#endif
|
||||
#ifdef _Spot
|
||||
, bool isSpot, float spotSize, float spotBlend, vec3 spotDir, vec2 scale, vec3 right
|
||||
@ -121,26 +145,77 @@ 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);
|
||||
direct *= shadowTest(shadowMapSpot[0], lPos.xyz / lPos.w, bias);
|
||||
vec4 lPos = LWVPSpotArray[0] * vec4(p + n * bias * 10, 1.0);
|
||||
direct *= shadowTest(shadowMapSpot[0],
|
||||
#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);
|
||||
#ifdef _ShadowMapAtlas
|
||||
tileBounds = tileBoundsSpotArray[index];
|
||||
direct *= shadowTest(
|
||||
#ifndef _SingleAtlas
|
||||
shadowMapAtlasSpot
|
||||
#else
|
||||
shadowMapAtlas
|
||||
tileBounds = tileBoundsSpotArray[index];
|
||||
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
|
||||
);
|
||||
#else
|
||||
if (index == 0) direct *= shadowTest(shadowMapSpot[0],
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapSpotTransparent[0],
|
||||
#endif
|
||||
, lPos.xyz / lPos.w, bias
|
||||
);
|
||||
#else
|
||||
if (index == 0) direct *= shadowTest(shadowMapSpot[0], lPos.xyz / lPos.w, bias);
|
||||
else if (index == 1) direct *= shadowTest(shadowMapSpot[1], lPos.xyz / lPos.w, bias);
|
||||
else if (index == 2) direct *= shadowTest(shadowMapSpot[2], lPos.xyz / lPos.w, bias);
|
||||
else if (index == 3) direct *= shadowTest(shadowMapSpot[3], lPos.xyz / lPos.w, bias);
|
||||
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
|
||||
);
|
||||
else if (index == 2) direct *= shadowTest(shadowMapSpot[2],
|
||||
#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
|
||||
);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
@ -153,25 +228,76 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co
|
||||
|
||||
if (receiveShadow) {
|
||||
#ifdef _SinglePoint
|
||||
#ifndef _Spot
|
||||
direct *= PCFCube(shadowMapPoint[0], ld, -l, bias, lightProj, n);
|
||||
#ifndef _Spot
|
||||
direct *= PCFCube(shadowMapPoint[0],
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapPointTransparent[0],
|
||||
#endif
|
||||
ld, -l, bias, lightProj, n
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#endif
|
||||
#endif
|
||||
#ifdef _Clusters
|
||||
#ifdef _ShadowMapAtlas
|
||||
direct *= PCFFakeCube(
|
||||
#ifndef _SingleAtlas
|
||||
shadowMapAtlasPoint
|
||||
#else
|
||||
shadowMapAtlas
|
||||
#endif
|
||||
, ld, -l, bias, lightProj, n, index
|
||||
);
|
||||
direct *= PCFFakeCube(
|
||||
#ifdef _ShadowMapTransparent
|
||||
#ifndef _SingleAtlas
|
||||
shadowMapAtlasPoint, shadowMapAtlasPointTransparent
|
||||
#else
|
||||
if (index == 0) direct *= PCFCube(shadowMapPoint[0], ld, -l, bias, lightProj, n);
|
||||
else if (index == 1) direct *= PCFCube(shadowMapPoint[1], ld, -l, bias, lightProj, n);
|
||||
else if (index == 2) direct *= PCFCube(shadowMapPoint[2], ld, -l, bias, lightProj, n);
|
||||
else if (index == 3) direct *= PCFCube(shadowMapPoint[3], ld, -l, bias, lightProj, n);
|
||||
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
|
||||
);
|
||||
else if (index == 1) direct *= PCFCube(shadowMapPoint[1],
|
||||
#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
|
||||
);
|
||||
else if (index == 3) direct *= PCFCube(shadowMapPoint[3],
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapPointTransparent[3],
|
||||
#endif
|
||||
ld, -l, bias, lightProj, n
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
@ -186,6 +312,9 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co
|
||||
const vec3 albedo, const float rough, const float spec, const vec3 f0
|
||||
#ifdef _ShadowMap
|
||||
, int index, float bias, bool receiveShadow
|
||||
#ifdef _ShadowMapTransparent
|
||||
, bool transparent
|
||||
#endif
|
||||
#endif
|
||||
#ifdef _Spot
|
||||
, bool isSpot, float spotSize, float spotBlend, vec3 spotDir, vec2 scale, vec3 right
|
||||
@ -194,6 +323,9 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co
|
||||
return sampleLight(p, n, v, dotNV, lp, lightCol, albedo, rough, spec, f0
|
||||
#ifdef _ShadowMap
|
||||
, index, bias, receiveShadow
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
#endif
|
||||
#ifdef _Spot
|
||||
, isSpot, spotSize, spotBlend, spotDir, scale, right
|
||||
|
||||
@ -24,7 +24,7 @@ uniform vec2 smSizeUniform;
|
||||
|
||||
#ifdef _ShadowMapAtlas
|
||||
uniform vec4 tileBoundsSunArray[maxLights * shadowmapCascades];
|
||||
#if defined(_Clusters) && defined(_Spot) && defined(_ShadowMap)
|
||||
#if defined(_Clusters) && (defined(_Spot) || defined(_LTC)) && defined(_ShadowMap)
|
||||
uniform vec4 tileBoundsSpotArray[maxLightsCluster];
|
||||
#endif
|
||||
vec4 tileBounds = vec4(0.0, 0.0, 1.0, 1.0);
|
||||
@ -180,7 +180,7 @@ vec3 PCFCube(samplerCubeShadow shadowMapCube,
|
||||
#endif
|
||||
) {
|
||||
const float s = shadowmapCubePcfSize; // TODO: incorrect...
|
||||
float compare = lpToDepth(lp, lightProj) - bias * 1.5;
|
||||
float compare = lpToDepth(lp, lightProj) - bias * 1.5 - 0.0001;
|
||||
ml = ml + n * bias * 20;
|
||||
#ifdef _InvY
|
||||
ml.y = -ml.y;
|
||||
@ -321,7 +321,7 @@ vec3 PCFFakeCube(sampler2DShadow shadowMap,
|
||||
, const bool transparent
|
||||
#endif
|
||||
) {
|
||||
const float compare = lpToDepth(lp, lightProj) - bias * 1.5;
|
||||
const float compare = lpToDepth(lp, lightProj) - bias * 1.5 - 0.0001;
|
||||
ml = ml + n * bias * 20;
|
||||
int faceIndex = 0;
|
||||
const int lightIndex = index * 6;
|
||||
@ -408,10 +408,16 @@ vec3 PCFFakeCube(sampler2DShadow shadowMap,
|
||||
#ifdef _ShadowMapTransparent
|
||||
if (transparent == false) {
|
||||
vec3 transResult = vec3(0.0);
|
||||
vec2 tileMin = pointLightTile.xy;
|
||||
vec2 tileMax = pointLightTile.xy + pointLightTile.z;
|
||||
#ifdef _FlipY
|
||||
tileMin.y = 1.0 - tileMax.y;
|
||||
tileMax.y = 1.0 - pointLightTile.y;
|
||||
#endif
|
||||
for (int x = -1; x <= 1; x++) {
|
||||
for (int y = -1; y <= 1; y++) {
|
||||
vec4 smt = texture(shadowMapTransparent,
|
||||
clamp(uvtiled + vec2(x, y) / smSize, 0.0, 1.0));
|
||||
clamp(uvtiled + vec2(x, y) / smSize, tileMin, tileMax));
|
||||
transResult += (smt.a < compare) ? smt.rgb : vec3(1.0);
|
||||
}
|
||||
}
|
||||
@ -437,15 +443,15 @@ vec3 shadowTest(sampler2DShadow shadowMap,
|
||||
#ifdef _ShadowMapAtlas
|
||||
// use tile PCF
|
||||
#ifdef _SMSizeUniform
|
||||
vec2 smSizeAtlas = smSizeUniform * (tileBounds.zw - tileBounds.xy);
|
||||
vec2 smSizeAtlas = smSizeUniform;
|
||||
#else
|
||||
vec2 smSizeAtlas = shadowmapSize * (tileBounds.zw - tileBounds.xy);
|
||||
vec2 smSizeAtlas = shadowmapSize;
|
||||
#endif
|
||||
return PCFTileAware(shadowMap,
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapTransparent,
|
||||
#endif
|
||||
lPos.xy, lPos.z - shadowsBias, smSizeAtlas,
|
||||
lPos.xy, lPos.z - shadowsBias - 0.0001, smSizeAtlas,
|
||||
tileBounds.xy, tileBounds.zw
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
@ -462,7 +468,7 @@ vec3 shadowTest(sampler2DShadow shadowMap,
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapTransparent,
|
||||
#endif
|
||||
lPos.xy, lPos.z - shadowsBias, smSize
|
||||
lPos.xy, lPos.z - shadowsBias - 0.0001, smSize
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
@ -499,11 +505,22 @@ mat4 getCascadeMat(const float d, out int casi, out int casIndex) {
|
||||
// ..
|
||||
}
|
||||
|
||||
float worldPerTexel(const mat4 LWVP, const vec2 smSize) {
|
||||
return max(1.0 / abs(LWVP[0][0]), 1.0 / abs(LWVP[1][1])) / smSize.x;
|
||||
}
|
||||
|
||||
float nlSlopeBias(const vec3 n, const mat4 LWVP, const float wpt) {
|
||||
vec3 ldir = normalize(vec3(LWVP[0][2], LWVP[1][2], LWVP[2][2]));
|
||||
float ndotl = max(abs(dot(n, ldir)), 0.05);
|
||||
float tanTheta = min(sqrt(1.0 - ndotl * ndotl) / ndotl, 1.5);
|
||||
return wpt * tanTheta * abs(LWVP[2][2]);
|
||||
}
|
||||
|
||||
vec3 shadowTestCascade(sampler2DShadow shadowMap,
|
||||
#ifdef _ShadowMapTransparent
|
||||
sampler2D shadowMapTransparent,
|
||||
#endif
|
||||
const vec3 eye, const vec3 p, const float shadowsBias
|
||||
const vec3 eye, const vec3 p, const vec3 n, const float shadowsBias
|
||||
#ifdef _ShadowMapTransparent
|
||||
, const bool transparent
|
||||
#endif
|
||||
@ -522,8 +539,10 @@ vec3 shadowTestCascade(sampler2DShadow shadowMap,
|
||||
int casi;
|
||||
int casIndex;
|
||||
mat4 LWVP = getCascadeMat(d, casi, casIndex);
|
||||
vec4 lPos = LWVP * vec4(p, 1.0);
|
||||
float wpt = worldPerTexel(LWVP, smSize);
|
||||
vec4 lPos = LWVP * vec4(p + n * 0.5 * wpt, 1.0);
|
||||
lPos.xyz /= lPos.w;
|
||||
float scaledBias = shadowsBias + nlSlopeBias(n, LWVP, wpt);
|
||||
|
||||
#ifdef _ShadowMapAtlas
|
||||
tileBounds = tileBoundsSunArray[casi];
|
||||
@ -536,7 +555,7 @@ vec3 shadowTestCascade(sampler2DShadow shadowMap,
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapTransparent,
|
||||
#endif
|
||||
lPos.xy, lPos.z - shadowsBias, smSize,
|
||||
lPos.xy, lPos.z - scaledBias, smSize,
|
||||
tileBounds.xy, tileBounds.zw
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
@ -547,7 +566,7 @@ vec3 shadowTestCascade(sampler2DShadow shadowMap,
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapTransparent,
|
||||
#endif
|
||||
lPos.xy, lPos.z - shadowsBias, smSize
|
||||
lPos.xy, lPos.z - scaledBias, smSize
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
@ -569,8 +588,10 @@ vec3 shadowTestCascade(sampler2DShadow shadowMap,
|
||||
casData[casIndex2 + 2],
|
||||
casData[casIndex2 + 3]);
|
||||
|
||||
vec4 lPos2 = LWVP2 * vec4(p, 1.0);
|
||||
float wpt2 = worldPerTexel(LWVP2, smSize);
|
||||
vec4 lPos2 = LWVP2 * vec4(p + n * 0.5 * wpt2, 1.0);
|
||||
lPos2.xyz /= lPos2.w;
|
||||
float scaledBias2 = shadowsBias + nlSlopeBias(n, LWVP2, wpt2);
|
||||
vec3 visibility2 = vec3(1.0);
|
||||
// use lPos2 coordinates for second cascade, not lPos
|
||||
#ifdef _ShadowMapAtlas
|
||||
@ -582,7 +603,7 @@ vec3 shadowTestCascade(sampler2DShadow shadowMap,
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapTransparent,
|
||||
#endif
|
||||
lPos2.xy, lPos2.z - shadowsBias, smSize,
|
||||
lPos2.xy, lPos2.z - scaledBias2, smSize,
|
||||
tileBounds.xy, tileBounds.zw
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
@ -593,7 +614,7 @@ vec3 shadowTestCascade(sampler2DShadow shadowMap,
|
||||
#ifdef _ShadowMapTransparent
|
||||
shadowMapTransparent,
|
||||
#endif
|
||||
lPos2.xy, lPos2.z - shadowsBias, smSize
|
||||
lPos2.xy, lPos2.z - scaledBias2, smSize
|
||||
#ifdef _ShadowMapTransparent
|
||||
, transparent
|
||||
#endif
|
||||
@ -612,4 +633,4 @@ vec3 shadowTestCascade(sampler2DShadow shadowMap,
|
||||
// if (ci == 12) albedo.rgb = vec3(1.0, 1.0, 0.0);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@ -96,41 +96,81 @@ vec3 sampleWaterNormals(vec2 hitXY, float speed, out vec2 tcnor0, out vec2 tcnor
|
||||
return normalize(n0 + n1 - 1.0);
|
||||
}
|
||||
|
||||
float waveHeight(vec2 xy, float speed) {
|
||||
return (sin(xy.x * 10.0 / waterDisplace + speed) * cos(xy.y * 10.0 / waterDisplace + speed) + sin(xy.x * 20.0 / waterDisplace + speed * 1.3) * cos(xy.y * 20.0 / waterDisplace + speed * 1.3) * 0.5) / 50.0 * waterDisplace;
|
||||
}
|
||||
|
||||
void main() {
|
||||
float gdepth = textureLod(gbufferD, texCoord, 0.0).r * 2.0 - 1.0;
|
||||
vec3 vray = normalize(viewRay);
|
||||
float speed = time * 2.0 * waterSpeed;
|
||||
bool isSky = (gdepth == 1.0);
|
||||
float farPlane = cameraProj.y / (1.0 - cameraProj.x);
|
||||
float cosAng = dot(eyeLook, vray);
|
||||
float domeR = farPlane * 0.94;
|
||||
float tDome = -3.5 * vray.z + sqrt(max(domeR * domeR - 12.25 * (1.0 - vray.z * vray.z), 0.0));
|
||||
float domeDepth01 = cameraProj.x + cameraProj.y / (tDome * cosAng);
|
||||
bool isSky = (gdepth * 0.5 + 0.5) >= domeDepth01;
|
||||
|
||||
// Ray-plane intersection with water surface (z = waterLevel)
|
||||
float denom = dot(vray, vec3(0.0, 0.0, 1.0));
|
||||
float denom = vray.z;
|
||||
float tWater = (waterLevel - eye.z) / denom;
|
||||
bool hasWaterHit = (abs(denom) > 0.0001) && (tWater > 0.0);
|
||||
|
||||
if (eye.z < waterLevel) {
|
||||
vec2 tc = texCoord;
|
||||
float fogFactor;
|
||||
|
||||
if (hasWaterHit && denom > 0.0) {
|
||||
// Looking up at water surface - apply normal distortion
|
||||
vec3 hit = eye + tWater * vray;
|
||||
vec2 tc0, tc1;
|
||||
vec3 n2 = sampleWaterNormals(hit.xy * waterFreq, speed, tc0, tc1);
|
||||
tc = texCoord + (n2.xy * n2.z) / 30.0 * waterRefract;
|
||||
fogFactor = clamp(tWater * waterDensity, 0.0, 0.95);
|
||||
} else {
|
||||
// Looking forward/down - distort via water surface above fragment
|
||||
vec3 p = getPos(eye, eyeLook, vray, gdepth, cameraProj);
|
||||
vec2 wxy = isSky ? (eye.xy + vray.xy * 50.0) : p.xy;
|
||||
vec2 tc0, tc1;
|
||||
vec3 n2 = sampleWaterNormals(wxy * waterFreq, speed, tc0, tc1);
|
||||
tc = texCoord + (n2.xy * n2.z) / 30.0 * waterRefract;
|
||||
float waterDist = isSky ? 50.0 : length(p - eye);
|
||||
fogFactor = clamp(waterDist * waterDensity, 0.0, 0.95);
|
||||
float depthThick = 1.0 + (waterLevel - eye.z);
|
||||
vec3 p = vec3(0.0);
|
||||
float geomDist = farPlane;
|
||||
if (!isSky) {
|
||||
p = getPos(eye, eyeLook, vray, gdepth, cameraProj);
|
||||
geomDist = length(p - eye);
|
||||
}
|
||||
bool upRay = hasWaterHit && denom > 0.0;
|
||||
float waterDist = upRay ? min(tWater, geomDist) : geomDist;
|
||||
|
||||
vec2 wxy;
|
||||
if (upRay && tWater < geomDist) {
|
||||
wxy = (eye + tWater * vray).xy;
|
||||
} else if (!isSky) {
|
||||
wxy = p.xy;
|
||||
} else {
|
||||
wxy = eye.xy + vray.xy * 50.0;
|
||||
}
|
||||
vec2 tc0, tc1;
|
||||
vec3 n2 = sampleWaterNormals(wxy * waterFreq, speed, tc0, tc1);
|
||||
tc = texCoord + (n2.xy * n2.z) / 30.0 * waterRefract;
|
||||
float ddepth2 = textureLod(gbufferD, tc, 0.0).r * 2.0 - 1.0;
|
||||
float dist2 = cameraProj.y / ((ddepth2 * 0.5 + 0.5) - cameraProj.x) / cosAng;
|
||||
if (dist2 > geomDist + 1.0) tc = texCoord;
|
||||
|
||||
vec3 refracted = textureLod(tex, tc, 0.0).rgb;
|
||||
fragColor.rgb = mix(refracted, waterColor, fogFactor);
|
||||
vec3 outCol = refracted;
|
||||
|
||||
if (upRay && tWater < geomDist) {
|
||||
outCol *= max(1.0 - length(n2.xy) * 1.5, 0.0);
|
||||
vec3 h2 = normalize(-vray + ld);
|
||||
outCol += pow(max(dot(n2, h2), 0.0), 80.0) * 0.6;
|
||||
}
|
||||
|
||||
if (!isSky) {
|
||||
float fdw = abs(p.z - (waterLevel + waveHeight(p.xy, speed)));
|
||||
float bandw = 0.1 + waterDist * 0.003;
|
||||
if (fdw < bandw) {
|
||||
float foamLod = clamp(waterDist / 15.0, 0.0, 5.0);
|
||||
vec3 fm0 = textureLod(sfoam, tc0 * 3.0 + vec2(speed / 30.0, speed / 50.0), foamLod).rgb;
|
||||
vec3 fm1 = textureLod(sfoam, tc1 * 4.0 + vec2(-speed / 35.0, speed / 45.0), foamLod).rgb;
|
||||
float fstr = clamp(1.0 - fm0.r * 0.5 - fm1.b * 0.5, 0.0, 1.0);
|
||||
float ffac = clamp(1.0 - fdw / bandw, 0.0, 1.0);
|
||||
float foamFade = clamp(1.0 - (waterLevel - eye.z) * 0.4, 0.0, 1.0);
|
||||
vec3 foamCol = mix(waterColor, vec3(1.0), 0.6);
|
||||
outCol = mix(outCol, foamCol, ffac * fstr * 0.5 * foamFade);
|
||||
}
|
||||
}
|
||||
|
||||
float fogFactor = 1.0 - exp(-waterDist * waterDensity * 0.006 * depthThick);
|
||||
outCol = mix(outCol, waterColor, clamp(fogFactor, 0.0, 1.0));
|
||||
|
||||
fragColor.rgb = outCol;
|
||||
fragColor.a = 1.0;
|
||||
return;
|
||||
}
|
||||
@ -144,20 +184,19 @@ void main() {
|
||||
if (isSky) tWater = min(tWater, 100.0); // Clamp to prevent aliasing at horizon
|
||||
vec3 p = isSky ? (eye + tWater * vray) : getPos(eye, eyeLook, vray, gdepth, cameraProj);
|
||||
|
||||
float horizonFactor = clamp(1.0 - tWater / 60.0, 0.0, 1.0);
|
||||
if (!isSky && p.z > waterLevel) {
|
||||
fragColor = vec4(0.0);
|
||||
if (!isSky && p.z > waterLevel - 0.01) {
|
||||
fragColor = textureLod(tex, texCoord, 0.0);
|
||||
return;
|
||||
}
|
||||
float horizonFactor = clamp(1.0 - tWater / 60.0, 0.0, 1.0);
|
||||
|
||||
// Displace surface
|
||||
p.z += (sin(p.x * 10.0 / waterDisplace + speed) * cos(p.y * 10.0 / waterDisplace + speed)
|
||||
+ sin(p.x * 20.0 / waterDisplace + speed * 1.3) * cos(p.y * 20.0 / waterDisplace + speed * 1.3) * 0.5)
|
||||
/ 50.0 * waterDisplace;
|
||||
float geomZ = p.z; // undisplaced depth for foam/fog tests
|
||||
p.z += waveHeight(p.xy, speed);
|
||||
|
||||
// Hit plane to determine uvs
|
||||
vec3 v = normalize(eye - p);
|
||||
float t = (waterLevel - eye.z) / dot(v, vec3(0.0, 0.0, 1.0));
|
||||
float t = (waterLevel - eye.z) / v.z;
|
||||
vec3 hit = eye + t * v;
|
||||
|
||||
// Sample normal maps
|
||||
@ -170,8 +209,8 @@ void main() {
|
||||
tc = texCoord + (n2.xy * n2.z) / 30.0 * waterRefract;
|
||||
} else {
|
||||
float ddepth = textureLod(gbufferD, texCoord + (n2.xy * n2.z) / 40.0, 0.0).r * 2.0 - 1.0;
|
||||
vec3 p2 = getPos(eye, eyeLook, vray, ddepth, cameraProj);
|
||||
tc = p2.z > waterLevel ? texCoord : texCoord + (n2.xy * n2.z) / 30.0 * waterRefract;
|
||||
float dist2 = cameraProj.y / ((ddepth * 0.5 + 0.5) - cameraProj.x) / cosAng;
|
||||
tc = (eye.z + vray.z * dist2) > waterLevel ? texCoord : texCoord + (n2.xy * n2.z) / 30.0 * waterRefract;
|
||||
}
|
||||
|
||||
// Light
|
||||
@ -221,22 +260,25 @@ void main() {
|
||||
#else
|
||||
fragColor.rgb = mix(refracted, reflectedEnv, waterReflect * fresnel);
|
||||
#endif
|
||||
// Water color tint - blend rather than multiply to preserve brightness
|
||||
float colorMix = isSky ? 0.7 : 0.5;
|
||||
fragColor.rgb = mix(fragColor.rgb, fragColor.rgb * waterColor, colorMix * horizonFactor);
|
||||
fragColor.rgb = mix(fragColor.rgb, waterColor, colorMix);
|
||||
// Blinn-Phong specular using half-vector, faded at horizon
|
||||
vec3 h = normalize(v + ld);
|
||||
float specAmount = pow(max(dot(n2, h), 0.0), 200.0) * (200.0 + 8.0) / (PI * 8.0);
|
||||
fragColor.rgb += specAmount * (isSky ? 0.3 : 1.0) * horizonFactor;
|
||||
// Depth fog - blend toward waterColor with depth, faded at horizon
|
||||
float depthFog = clamp(-(p.z - waterLevel) * waterDensity, 0.0, 0.9);
|
||||
fragColor.rgb = mix(fragColor.rgb, waterColor, depthFog * horizonFactor);
|
||||
float waterColumn = isSky ? 1.0 : max(length(p - eye) - tWater, 0.0);
|
||||
float depthFog = 1.0 - exp(-waterColumn * waterDensity * 0.006);
|
||||
vec3 surfaceCol = mix(waterColor, reflectedEnv, waterReflect * fresnel);
|
||||
fragColor.rgb = mix(fragColor.rgb, surfaceCol, min(depthFog, 0.9));
|
||||
// Alpha fades smoothly at horizon instead of hard cut
|
||||
fragColor.a = isSky ? horizonFactor : clamp(abs(p.z - waterLevel) * 5.0, 0.0, 1.0);
|
||||
|
||||
// Foam - based on actual geometry depth below water surface
|
||||
float fd = isSky ? 1.0 : abs(p.z - waterLevel);
|
||||
if (fd < 0.1) {
|
||||
// Skydome sits 3.5 below the camera (_skydomeMatrix), so its horizon
|
||||
// appears where the ray z reaches -3.5 / domeRadius
|
||||
float horizonFade = clamp((-3.5 / (farPlane * 0.95) - vray.z) * 20.0, 0.0, 1.0);
|
||||
fragColor.a = isSky ? horizonFade : clamp(abs(geomZ - waterLevel) * 5.0 + 0.1, 0.0, 1.0);
|
||||
float waveH = p.z - geomZ; // displacement already applied above
|
||||
float band = 0.1 + tWater * 0.003;
|
||||
float fd = isSky ? 1.0 : abs(geomZ - (waterLevel + waveH));
|
||||
if (fd < band) {
|
||||
// Based on foam by Owen Deery
|
||||
// http://fire-face.com/personal/water
|
||||
// Distance-based LOD blurs foam at range to reduce noise
|
||||
@ -245,8 +287,10 @@ void main() {
|
||||
vec2 foamUV1 = tcnor1 * 4.0 + vec2(-speed / 35.0, speed / 45.0);
|
||||
vec3 foamMask0 = textureLod(sfoam, foamUV0, foamLod).rgb;
|
||||
vec3 foamMask1 = textureLod(sfoam, foamUV1, foamLod).rgb;
|
||||
float foamStrength = clamp(1.0 - foamMask0.r * 0.5 - foamMask1.b * 0.5, 0.0, 1.0);
|
||||
float fac = (1.0 - (fd * (1.0 / 0.1))) * horizonFactor;
|
||||
fragColor.rgb = mix(fragColor.rgb, mix(fragColor.rgb, waterColor + 0.2, foamStrength), clamp(fac, 0.0, 1.0) * 0.5);
|
||||
float foamMask = clamp(1.0 - foamMask0.r * 0.5 - foamMask1.b * 0.5, 0.0, 1.0);
|
||||
float fac = clamp(1.0 - fd / band, 0.0, 1.0);
|
||||
vec3 foamCol = mix(waterColor, vec3(1.0), 0.6);
|
||||
fragColor.rgb = mix(fragColor.rgb, foamCol, fac * foamMask * 0.8);
|
||||
fragColor.a = max(fragColor.a, fac * foamMask);
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,7 +34,7 @@
|
||||
},
|
||||
{
|
||||
"name": "ld",
|
||||
"link": "_lightDirection"
|
||||
"link": "_sunDirection"
|
||||
},
|
||||
{
|
||||
"name": "invVP",
|
||||
|
||||
@ -18,6 +18,13 @@ import iron.object.LightObject;
|
||||
import iron.object.MeshObject;
|
||||
import iron.object.Uniforms;
|
||||
import iron.object.Clipmap;
|
||||
#if lnx_radix
|
||||
import haxe.io.FPHelper;
|
||||
import radix.Radix;
|
||||
#end
|
||||
#if lnx_occlusion_culling
|
||||
import iron.culling.CullSystem;
|
||||
#end
|
||||
#if lnx_vr
|
||||
import iron.math.Vec4;
|
||||
import iron.math.Mat4;
|
||||
@ -27,17 +34,19 @@ import iron.math.Quat;
|
||||
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;
|
||||
#end
|
||||
|
||||
static var vrSimulateMode: Bool = false;
|
||||
public static inline function isVRPresenting(): Bool {
|
||||
#if (kha_webgl && lnx_vr)
|
||||
return kha.vr.VrInterface.instance != null && kha.vr.VrInterface.instance.IsPresenting();
|
||||
@ -45,17 +54,11 @@ class RenderPath {
|
||||
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;
|
||||
public var frameScissorY = 0;
|
||||
@ -81,6 +84,18 @@ class RenderPath {
|
||||
var renderToXRFramebuffer = false;
|
||||
#end
|
||||
public var drawOrder = DrawOrder.Distance;
|
||||
#if lnx_frustum_culling
|
||||
public var frustumCulling = true;
|
||||
#else
|
||||
public var frustumCulling = false;
|
||||
#end
|
||||
|
||||
#if lnx_occlusion_culling
|
||||
public var occlusionCulling = true;
|
||||
#else
|
||||
public var occlusionCulling = false;
|
||||
#end
|
||||
|
||||
public var paused = false;
|
||||
public var ready(get, never): Bool;
|
||||
function get_ready(): Bool { return loading == 0; }
|
||||
@ -95,6 +110,8 @@ class RenderPath {
|
||||
var lastH = 0;
|
||||
var bindParams: Array<String>;
|
||||
var meshesSorted: Bool;
|
||||
var cullComputed: Bool;
|
||||
var cullFlagsReset: Bool;
|
||||
var scissorSet = false;
|
||||
var viewportScaled = false;
|
||||
var lastFrameTime = 0.0;
|
||||
@ -162,24 +179,24 @@ class RenderPath {
|
||||
|
||||
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;
|
||||
|
||||
@ -196,18 +213,18 @@ class RenderPath {
|
||||
#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;
|
||||
@ -216,13 +233,13 @@ class RenderPath {
|
||||
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) {
|
||||
@ -234,7 +251,7 @@ class RenderPath {
|
||||
}
|
||||
resize();
|
||||
}
|
||||
|
||||
|
||||
var xrVr: kha.js.vr.VrInterface = cast vr;
|
||||
if (xrVr.xrGLLayer != null) {
|
||||
appW = xrVr.xrGLLayer.framebufferWidth;
|
||||
@ -304,6 +321,8 @@ class RenderPath {
|
||||
currentD = 1;
|
||||
currentFace = -1;
|
||||
meshesSorted = false;
|
||||
cullComputed = false;
|
||||
cullFlagsReset = false;
|
||||
sun = null;
|
||||
|
||||
for (l in Scene.active.lights) {
|
||||
@ -384,7 +403,7 @@ class RenderPath {
|
||||
if (currentG != null) end();
|
||||
currentG = g;
|
||||
additionalTargets = additionalRenderTargets;
|
||||
|
||||
|
||||
// we still bind but skip begin() when explicitly rendering to XR framebuffer (renderToXRFramebuffer flag)
|
||||
#if lnx_vr
|
||||
if (!renderToXRFramebuffer) {
|
||||
@ -405,7 +424,7 @@ class RenderPath {
|
||||
currentG.disableScissor();
|
||||
scissorSet = false;
|
||||
}
|
||||
|
||||
|
||||
#if lnx_vr
|
||||
if (beginCalled) {
|
||||
currentG.end();
|
||||
@ -470,6 +489,60 @@ class RenderPath {
|
||||
rt.image.generateMipmaps(1000);
|
||||
}
|
||||
|
||||
#if lnx_radix
|
||||
static var sortDistanceKeys = new Array<Int>();
|
||||
static var sortDistanceMeshesBuf = new Array<MeshObject>();
|
||||
static var sortDistanceKeysBuf = new Array<Int>();
|
||||
|
||||
static var sortIndexKeys = new Array<Int>();
|
||||
static var sortIndexMeshesBuf = new Array<MeshObject>();
|
||||
static var sortIndexKeysBuf = new Array<Int>();
|
||||
|
||||
public static function sortMeshesDistance(meshes: Array<MeshObject>) {
|
||||
var n = meshes.length;
|
||||
var keys = sortDistanceKeys;
|
||||
var meshesBuf = sortDistanceMeshesBuf;
|
||||
var keysBuf = sortDistanceKeysBuf;
|
||||
if (keys.length < n) {
|
||||
keys.resize(n);
|
||||
meshesBuf.resize(n);
|
||||
keysBuf.resize(n);
|
||||
}
|
||||
for (i in 0...n) {
|
||||
var m = meshes[i];
|
||||
var key = FPHelper.floatToI32(m.cameraDistance) >>> 1;
|
||||
#if rp_depth_texture
|
||||
if (m.depthRead) key |= 0x80000000;
|
||||
#end
|
||||
keys[i] = key;
|
||||
}
|
||||
Radix.sortCopyKeyed(meshes, keys, meshesBuf, keysBuf);
|
||||
if (meshesBuf.length > n) meshesBuf.resize(n);
|
||||
}
|
||||
|
||||
public static function sortMeshesIndex(meshes: Array<MeshObject>) {
|
||||
var n = meshes.length;
|
||||
var keys = sortIndexKeys;
|
||||
var meshesBuf = sortIndexMeshesBuf;
|
||||
var keysBuf = sortIndexKeysBuf;
|
||||
if (keys.length < n) {
|
||||
keys.resize(n);
|
||||
meshesBuf.resize(n);
|
||||
keysBuf.resize(n);
|
||||
}
|
||||
for (i in 0...n) {
|
||||
var m = meshes[i];
|
||||
var si = m.data.sortingIndex;
|
||||
var key = si < -0x40000000 ? 0 : si > 0x3fffffff ? 0x7fffffff : si + 0x40000000;
|
||||
#if rp_depth_texture
|
||||
if (m.depthRead) key |= 0x80000000;
|
||||
#end
|
||||
keys[i] = key;
|
||||
}
|
||||
Radix.sortCopyKeyed(meshes, keys, meshesBuf, keysBuf);
|
||||
if (meshesBuf.length > n) meshesBuf.resize(n);
|
||||
}
|
||||
#else
|
||||
static inline function boolToInt(b: Bool): Int {
|
||||
return b ? 1 : 0;
|
||||
}
|
||||
@ -501,6 +574,7 @@ class RenderPath {
|
||||
return a.data.name > b.data.name ? 1 : -1;
|
||||
});
|
||||
}
|
||||
#end
|
||||
|
||||
public function drawMeshes(context: String) {
|
||||
var isShadows = context == "shadowmap" || context == "shadowmap_transparent";
|
||||
@ -519,6 +593,11 @@ class RenderPath {
|
||||
for (i in 0...LightObject.cascadeCount) {
|
||||
light.setCascade(Scene.active.camera, i);
|
||||
currentG.viewport(i * step, 0, step, step);
|
||||
#if lnx_occlusion_culling
|
||||
if (occlusionCulling && CullSystem.active != null) {
|
||||
CullSystem.active.cullShadow(light);
|
||||
}
|
||||
#end
|
||||
submitDraw(context);
|
||||
}
|
||||
drawn = true;
|
||||
@ -529,7 +608,14 @@ class RenderPath {
|
||||
if (context == "mesh") LightObject.updateClusters(Scene.active.camera);
|
||||
#end
|
||||
|
||||
if (!drawn) submitDraw(context);
|
||||
if (!drawn) {
|
||||
#if lnx_occlusion_culling
|
||||
if (isShadows && occlusionCulling && CullSystem.active != null) {
|
||||
CullSystem.active.cullShadow(light);
|
||||
}
|
||||
#end
|
||||
submitDraw(context);
|
||||
}
|
||||
|
||||
// Callbacks to specific context
|
||||
if (contextEvents != null) {
|
||||
@ -546,7 +632,34 @@ class RenderPath {
|
||||
var meshes = Scene.active.meshes;
|
||||
MeshObject.lastPipeline = null;
|
||||
|
||||
if (!meshesSorted && camera != null) { // Order max once per frame for now
|
||||
#if lnx_occlusion_culling
|
||||
if (!cullFlagsReset && !occlusionCulling) {
|
||||
cullFlagsReset = true;
|
||||
for (m in meshes) {
|
||||
m.occlusionVisible = true;
|
||||
m.shadowOcclusionVisible = true;
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
||||
if (!cullComputed && (context == "mesh" || context == "depth")) {
|
||||
#if lnx_occlusion_culling
|
||||
if (occlusionCulling) {
|
||||
if (CullSystem.active == null) CullSystem.init();
|
||||
if (CullSystem.active != null && camera != null) {
|
||||
if (CullSystem.active.proxiesDirty) {
|
||||
CullSystem.active.loadOccluderProxies();
|
||||
CullSystem.active.proxiesDirty = false;
|
||||
}
|
||||
CullSystem.active.cull(camera);
|
||||
}
|
||||
}
|
||||
#end
|
||||
cullComputed = true;
|
||||
}
|
||||
|
||||
var isShadowCtx = context == "shadowmap" || context == "shadowmap_transparent";
|
||||
if (!meshesSorted && camera != null && !isShadowCtx) {
|
||||
var camX = camera.transform.worldx();
|
||||
var camY = camera.transform.worldy();
|
||||
var camZ = camera.transform.worldz();
|
||||
@ -571,8 +684,20 @@ class RenderPath {
|
||||
|
||||
static inline function meshRenderLoop(g: Graphics, context: String, _bindParams: Array<String>, _meshes: Array<MeshObject>) {
|
||||
var isReadingDepth = false;
|
||||
#if lnx_occlusion_culling
|
||||
var cullSys = CullSystem.active;
|
||||
#end
|
||||
|
||||
for (m in _meshes) {
|
||||
#if lnx_occlusion_culling
|
||||
if (cullSys != null && !m.occlusionVisible && (context == "mesh" || context == "depth" || context == "translucent")) {
|
||||
continue;
|
||||
}
|
||||
if (cullSys != null && !m.shadowOcclusionVisible && (context == "shadowmap" || context == "shadowmap_transparent")) {
|
||||
continue;
|
||||
}
|
||||
#end
|
||||
|
||||
#if rp_depth_texture
|
||||
// First mesh that reads depth
|
||||
if (!isReadingDepth && m.depthRead) {
|
||||
@ -692,7 +817,7 @@ class RenderPath {
|
||||
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) {
|
||||
@ -703,21 +828,21 @@ class RenderPath {
|
||||
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 ||
|
||||
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(
|
||||
@ -736,7 +861,7 @@ class RenderPath {
|
||||
js.html.webgl.WebGL2RenderingContext.LINEAR
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
gl.bindFramebuffer(js.html.webgl.WebGL2RenderingContext.FRAMEBUFFER, null);
|
||||
renderToXRFramebuffer = false;
|
||||
#end
|
||||
@ -749,24 +874,24 @@ class RenderPath {
|
||||
if (currentG == null && frameG != null) {
|
||||
currentG = frameG;
|
||||
}
|
||||
|
||||
|
||||
var appw = iron.App.w();
|
||||
var apph = iron.App.h();
|
||||
var g = currentG;
|
||||
|
||||
|
||||
// get render target dimensions not App.w/h gbuffer is scaled in simulate mode with supersampling
|
||||
|
||||
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)
|
||||
@ -779,7 +904,7 @@ class RenderPath {
|
||||
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;
|
||||
@ -789,16 +914,16 @@ class RenderPath {
|
||||
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)
|
||||
@ -808,10 +933,10 @@ class RenderPath {
|
||||
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,
|
||||
@ -825,7 +950,7 @@ class RenderPath {
|
||||
}
|
||||
iron.system.VRController.updatePoses();
|
||||
#end
|
||||
|
||||
|
||||
Scene.active.camera.V.self = vr.GetViewMatrix(0);
|
||||
Scene.active.camera.P.self = vr.GetProjectionMatrix(0);
|
||||
Scene.active.camera.VP.setFrom(Scene.active.camera.P);
|
||||
@ -835,7 +960,7 @@ class RenderPath {
|
||||
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);
|
||||
@ -847,12 +972,12 @@ class RenderPath {
|
||||
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);
|
||||
@ -865,7 +990,7 @@ class RenderPath {
|
||||
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);
|
||||
@ -908,7 +1033,9 @@ class RenderPath {
|
||||
#end
|
||||
|
||||
Data.getShader(shaderPath[0], shaderPath[1], function(res: ShaderData) {
|
||||
cc.context = res.getContext(shaderPath[2]);
|
||||
if (res != null) {
|
||||
cc.context = res.getContext(shaderPath[2]);
|
||||
}
|
||||
loading--;
|
||||
});
|
||||
}
|
||||
|
||||
@ -1003,7 +1003,7 @@ class Scene {
|
||||
}
|
||||
|
||||
if (o.vertex_groups != null) {
|
||||
cast(object, MeshObject).vertexGroups = new Map();
|
||||
object.vertexGroups = new Map();
|
||||
for (p in o.vertex_groups) {
|
||||
var verts:Array<iron.math.Vec4> = [];
|
||||
|
||||
@ -1014,7 +1014,7 @@ class Scene {
|
||||
verts.push(new iron.math.Vec4(data[i * 3], data[i * 3 + 1], data[i * 3 + 2], 1.0));
|
||||
}
|
||||
}
|
||||
cast(object, MeshObject).vertexGroups.set(p.name, verts);
|
||||
object.vertexGroups.set(p.name, verts);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
20
leenkx/Sources/iron/culling/BVHNode.hx
Normal file
20
leenkx/Sources/iron/culling/BVHNode.hx
Normal file
@ -0,0 +1,20 @@
|
||||
package iron.culling;
|
||||
|
||||
class BVHNode {
|
||||
public var minX: Float = 0;
|
||||
public var minY: Float = 0;
|
||||
public var minZ: Float = 0;
|
||||
public var maxX: Float = 0;
|
||||
public var maxY: Float = 0;
|
||||
public var maxZ: Float = 0;
|
||||
public var centerX: Float = 0;
|
||||
public var centerY: Float = 0;
|
||||
public var centerZ: Float = 0;
|
||||
public var isLeaf: Bool = false;
|
||||
public var leftChild: Int = -1;
|
||||
public var rightChild: Int = -1;
|
||||
public var firstObject: Int = 0;
|
||||
public var objectCount: Int = 0;
|
||||
|
||||
public function new() {}
|
||||
}
|
||||
62
leenkx/Sources/iron/culling/ContributionCull.hx
Normal file
62
leenkx/Sources/iron/culling/ContributionCull.hx
Normal file
@ -0,0 +1,62 @@
|
||||
package iron.culling;
|
||||
|
||||
class ContributionCull {
|
||||
|
||||
public static var minPixelRadius: Float = 2.0;
|
||||
public static var enabled: Bool = false;
|
||||
|
||||
public static function passes(
|
||||
centerX: Float, centerY: Float, centerZ: Float,
|
||||
radius: Float,
|
||||
camX: Float, camY: Float, camZ: Float,
|
||||
focalLengthPx: Float
|
||||
): Bool {
|
||||
if (!enabled || minPixelRadius <= 0.0 || focalLengthPx <= 0.0) return true;
|
||||
|
||||
var dx = centerX - camX;
|
||||
var dy = centerY - camY;
|
||||
var dz = centerZ - camZ;
|
||||
var dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||
|
||||
if (dist <= radius) return true;
|
||||
|
||||
var projectedRadiusPx = focalLengthPx * radius / dist;
|
||||
return projectedRadiusPx >= minPixelRadius;
|
||||
}
|
||||
|
||||
public static function passesNode(
|
||||
node: BVHNode,
|
||||
camX: Float, camY: Float, camZ: Float,
|
||||
focalLengthPx: Float
|
||||
): Bool {
|
||||
if (!enabled || minPixelRadius <= 0.0 || focalLengthPx <= 0.0) return true;
|
||||
|
||||
var cx = (node.minX + node.maxX) * 0.5;
|
||||
var cy = (node.minY + node.maxY) * 0.5;
|
||||
var cz = (node.minZ + node.maxZ) * 0.5;
|
||||
var ex = (node.maxX - node.minX) * 0.5;
|
||||
var ey = (node.maxY - node.minY) * 0.5;
|
||||
var ez = (node.maxZ - node.minZ) * 0.5;
|
||||
var radius = Math.sqrt(ex * ex + ey * ey + ez * ez);
|
||||
|
||||
if (camX >= node.minX && camX <= node.maxX &&
|
||||
camY >= node.minY && camY <= node.maxY &&
|
||||
camZ >= node.minZ && camZ <= node.maxZ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var dx = cx - camX;
|
||||
var dy = cy - camY;
|
||||
var dz = cz - camZ;
|
||||
var dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||
|
||||
if (dist <= radius) return true;
|
||||
|
||||
var projectedRadiusPx = focalLengthPx * radius / dist;
|
||||
return projectedRadiusPx >= minPixelRadius;
|
||||
}
|
||||
|
||||
public static function computeFocalLengthPx(fov: Float, screenHeight: Int): Float {
|
||||
return screenHeight / (2.0 * Math.tan(fov * 0.5));
|
||||
}
|
||||
}
|
||||
373
leenkx/Sources/iron/culling/CullData.hx
Normal file
373
leenkx/Sources/iron/culling/CullData.hx
Normal file
@ -0,0 +1,373 @@
|
||||
package iron.culling;
|
||||
|
||||
import iron.object.MeshObject;
|
||||
import iron.Scene;
|
||||
import kha.arrays.Float32Array;
|
||||
|
||||
class CullData {
|
||||
|
||||
static inline var STRIDE = 16;
|
||||
static inline var BASIS_STRIDE = 12;
|
||||
|
||||
public var data: Float32Array;
|
||||
public var prevData: Float32Array;
|
||||
public var uidData: kha.arrays.Int32Array;
|
||||
public var prevUidData: kha.arrays.Int32Array;
|
||||
var basisData: Float32Array;
|
||||
var prevBasisData: Float32Array;
|
||||
public var count: Int = 0;
|
||||
public var prevCount: Int = 0;
|
||||
public var anyMoved: Bool = false;
|
||||
public var anyFlagsChanged: Bool = false;
|
||||
public var orderChanged: Bool = false;
|
||||
|
||||
public var meshes: Array<MeshObject> = [];
|
||||
public var slotsDirty: Bool = true;
|
||||
public var slotsRebuilt: Bool = false;
|
||||
|
||||
public var visibleList: Array<Int>;
|
||||
public var visibleCount: Int = 0;
|
||||
|
||||
var staticFlagsCache: haxe.ds.IntMap<Int> = new haxe.ds.IntMap<Int>();
|
||||
|
||||
var matTranspCache: haxe.ds.IntMap<Int> = new haxe.ds.IntMap<Int>();
|
||||
var matSnapshots: haxe.ds.IntMap<haxe.ds.Vector<iron.data.MaterialData>> = new haxe.ds.IntMap();
|
||||
|
||||
var prevUidMap: haxe.ds.IntMap<Int> = new haxe.ds.IntMap<Int>();
|
||||
|
||||
public function new() {
|
||||
visibleList = [];
|
||||
}
|
||||
|
||||
public inline function stride(): Int {
|
||||
return STRIDE;
|
||||
}
|
||||
|
||||
public function ensureCapacity(cap: Int) {
|
||||
if (data == null || data.length < cap * STRIDE) {
|
||||
var newCap = cap;
|
||||
if (data != null) newCap = Std.int(Math.max(cap, data.length / STRIDE * 2));
|
||||
var oldData = data;
|
||||
var oldPrev = prevData;
|
||||
var oldUid = uidData;
|
||||
var oldPrevUid = prevUidData;
|
||||
var oldBasis = basisData;
|
||||
var oldPrevBasis = prevBasisData;
|
||||
data = new Float32Array(newCap * STRIDE);
|
||||
prevData = new Float32Array(newCap * STRIDE);
|
||||
uidData = new kha.arrays.Int32Array(newCap);
|
||||
prevUidData = new kha.arrays.Int32Array(newCap);
|
||||
basisData = new Float32Array(newCap * BASIS_STRIDE);
|
||||
prevBasisData = new Float32Array(newCap * BASIS_STRIDE);
|
||||
if (oldData != null) {
|
||||
var copyLen = oldData.length;
|
||||
for (i in 0...copyLen) data[i] = oldData[i];
|
||||
}
|
||||
if (oldPrev != null) {
|
||||
var copyLen = oldPrev.length;
|
||||
for (i in 0...copyLen) prevData[i] = oldPrev[i];
|
||||
}
|
||||
if (oldUid != null) {
|
||||
var copyLen = oldUid.length;
|
||||
for (i in 0...copyLen) uidData[i] = oldUid[i];
|
||||
}
|
||||
if (oldPrevUid != null) {
|
||||
var copyLen = oldPrevUid.length;
|
||||
for (i in 0...copyLen) prevUidData[i] = oldPrevUid[i];
|
||||
}
|
||||
if (oldBasis != null) {
|
||||
var copyLen = oldBasis.length;
|
||||
for (i in 0...copyLen) basisData[i] = oldBasis[i];
|
||||
}
|
||||
if (oldPrevBasis != null) {
|
||||
var copyLen = oldPrevBasis.length;
|
||||
for (i in 0...copyLen) prevBasisData[i] = oldPrevBasis[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function syncFromScene() {
|
||||
var sceneMeshes = Scene.active.meshes;
|
||||
slotsRebuilt = false;
|
||||
if (slotsDirty || meshes.length != sceneMeshes.length) {
|
||||
meshes = sceneMeshes.copy();
|
||||
slotsDirty = false;
|
||||
slotsRebuilt = true;
|
||||
staticFlagsCache.clear();
|
||||
matTranspCache.clear();
|
||||
matSnapshots.clear();
|
||||
}
|
||||
count = meshes.length;
|
||||
ensureCapacity(count);
|
||||
|
||||
var tmpD = prevData; prevData = data; data = tmpD;
|
||||
var tmpU = prevUidData; prevUidData = uidData; uidData = tmpU;
|
||||
var tmpB = prevBasisData; prevBasisData = basisData; basisData = tmpB;
|
||||
|
||||
prevUidMap.clear();
|
||||
var prevUidMapBuilt = false;
|
||||
anyMoved = false;
|
||||
anyFlagsChanged = false;
|
||||
orderChanged = false;
|
||||
|
||||
for (i in 0...count) {
|
||||
var mesh = meshes[i];
|
||||
var t = mesh.transform;
|
||||
var uid = mesh.uid;
|
||||
var p = mesh.parent;
|
||||
while (p != null) {
|
||||
p.transform.update();
|
||||
p = p.parent;
|
||||
}
|
||||
t.update();
|
||||
|
||||
var off = i * STRIDE;
|
||||
var r = t.radius;
|
||||
var wx = t.worldx();
|
||||
var wy = t.worldy();
|
||||
var wz = t.worldz();
|
||||
|
||||
var radiusScale = mesh.data.isSkinned ? 2.0 : 1.0;
|
||||
#if lnx_gpu_particles
|
||||
if (mesh.particleSystems != null || mesh.particleOwner != null) radiusScale *= 1000;
|
||||
#end
|
||||
if (mesh.data.geom.instanced) radiusScale *= 100;
|
||||
r *= radiusScale;
|
||||
|
||||
data[off + 3] = r;
|
||||
data[off + 4] = wx;
|
||||
data[off + 5] = wy;
|
||||
data[off + 6] = wz;
|
||||
|
||||
var w = t.world;
|
||||
|
||||
var prevIdx: Null<Int> = null;
|
||||
if (prevUidData != null && i < prevCount && prevUidData[i] == uid) {
|
||||
prevIdx = i;
|
||||
} else {
|
||||
if (!prevUidMapBuilt) {
|
||||
prevUidMapBuilt = true;
|
||||
if (prevUidData != null) {
|
||||
for (pi in 0...prevCount) prevUidMap.set(prevUidData[pi], pi);
|
||||
}
|
||||
}
|
||||
prevIdx = prevUidMap.get(uid);
|
||||
}
|
||||
|
||||
if (prevIdx != null && prevIdx != i) orderChanged = true;
|
||||
var aabbCached = false;
|
||||
if (prevIdx != null) {
|
||||
var poff = prevIdx * STRIDE;
|
||||
var pb = prevIdx * BASIS_STRIDE;
|
||||
if (prevData[poff + 4] == wx && prevData[poff + 5] == wy && prevData[poff + 6] == wz &&
|
||||
prevBasisData[pb + 0] == w._00 && prevBasisData[pb + 1] == w._10 && prevBasisData[pb + 2] == w._20 &&
|
||||
prevBasisData[pb + 3] == w._01 && prevBasisData[pb + 4] == w._11 && prevBasisData[pb + 5] == w._21 &&
|
||||
prevBasisData[pb + 6] == w._02 && prevBasisData[pb + 7] == w._12 && prevBasisData[pb + 8] == w._22 &&
|
||||
prevBasisData[pb + 9] == t.dim.x && prevBasisData[pb + 10] == t.dim.y && prevBasisData[pb + 11] == t.dim.z) {
|
||||
data[off + 0] = prevData[poff + 0];
|
||||
data[off + 1] = prevData[poff + 1];
|
||||
data[off + 2] = prevData[poff + 2];
|
||||
data[off + 8] = prevData[poff + 8];
|
||||
data[off + 9] = prevData[poff + 9];
|
||||
data[off + 10] = prevData[poff + 10];
|
||||
aabbCached = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!aabbCached) {
|
||||
var r00 = w._00, r10 = w._10, r20 = w._20;
|
||||
var r01 = w._01, r11 = w._11, r21 = w._21;
|
||||
var r02 = w._02, r12 = w._12, r22 = w._22;
|
||||
var lenX = Math.sqrt(r00 * r00 + r01 * r01 + r02 * r02);
|
||||
var lenY = Math.sqrt(r10 * r10 + r11 * r11 + r12 * r12);
|
||||
var lenZ = Math.sqrt(r20 * r20 + r21 * r21 + r22 * r22);
|
||||
if (lenX > 1e-8) { r00 /= lenX; r01 /= lenX; r02 /= lenX; }
|
||||
if (lenY > 1e-8) { r10 /= lenY; r11 /= lenY; r12 /= lenY; }
|
||||
if (lenZ > 1e-8) { r20 /= lenZ; r21 /= lenZ; r22 /= lenZ; }
|
||||
|
||||
var hx = t.dim.x * 0.5 * radiusScale;
|
||||
var hy = t.dim.y * 0.5 * radiusScale;
|
||||
var hz = t.dim.z * 0.5 * radiusScale;
|
||||
|
||||
var aabbMinX = 1e20, aabbMinY = 1e20, aabbMinZ = 1e20;
|
||||
var aabbMaxX = -1e20, aabbMaxY = -1e20, aabbMaxZ = -1e20;
|
||||
for (c in 0...8) {
|
||||
var cx = (c & 1) != 0 ? hx : -hx;
|
||||
var cy = (c & 2) != 0 ? hy : -hy;
|
||||
var cz = (c & 4) != 0 ? hz : -hz;
|
||||
var px = cx * r00 + cy * r10 + cz * r20 + wx;
|
||||
var py = cx * r01 + cy * r11 + cz * r21 + wy;
|
||||
var pz = cx * r02 + cy * r12 + cz * r22 + wz;
|
||||
if (px < aabbMinX) aabbMinX = px;
|
||||
if (py < aabbMinY) aabbMinY = py;
|
||||
if (pz < aabbMinZ) aabbMinZ = pz;
|
||||
if (px > aabbMaxX) aabbMaxX = px;
|
||||
if (py > aabbMaxY) aabbMaxY = py;
|
||||
if (pz > aabbMaxZ) aabbMaxZ = pz;
|
||||
}
|
||||
|
||||
data[off + 0] = aabbMinX;
|
||||
data[off + 1] = aabbMinY;
|
||||
data[off + 2] = aabbMinZ;
|
||||
|
||||
data[off + 8] = aabbMaxX;
|
||||
data[off + 9] = aabbMaxY;
|
||||
data[off + 10] = aabbMaxZ;
|
||||
}
|
||||
|
||||
var flags: Int;
|
||||
var cachedFlags = staticFlagsCache.get(uid);
|
||||
if (cachedFlags != null) {
|
||||
flags = cachedFlags;
|
||||
} else {
|
||||
flags = 0;
|
||||
if (mesh.raw != null && mesh.raw.properties != null) {
|
||||
for (prop in mesh.raw.properties) {
|
||||
if (prop.name == "occluder" && prop.value == true) {
|
||||
flags |= 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((flags & 1) == 0 && mesh.data != null &&
|
||||
mesh.data.raw != null && mesh.data.raw.occluder_proxy != null) {
|
||||
flags |= 1;
|
||||
}
|
||||
if (mesh.raw != null && mesh.raw.visible_shadow != false) flags |= 4;
|
||||
staticFlagsCache.set(uid, flags);
|
||||
}
|
||||
var mats = mesh.materials;
|
||||
var snap = matSnapshots.get(uid);
|
||||
var matsChanged = snap == null || mats == null || snap.length != mats.length;
|
||||
if (!matsChanged) {
|
||||
for (mi in 0...mats.length) {
|
||||
if (snap[mi] != mats[mi]) { matsChanged = true; break; }
|
||||
}
|
||||
}
|
||||
if (matsChanged) {
|
||||
var tbit = 0;
|
||||
if (mats != null) {
|
||||
for (mi in 0...mats.length) {
|
||||
var mat = mats[mi];
|
||||
if (mat == null || mat.shader == null) continue;
|
||||
for (sc in mat.shader.contexts) {
|
||||
var sr = sc.raw;
|
||||
if (sr != null && sr.name != null && sr.name.substr(0, 4) == "mesh" &&
|
||||
(sr.depth_write != true || sr.blend_source != null)) {
|
||||
tbit = 32;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (tbit != 0) break;
|
||||
}
|
||||
}
|
||||
matTranspCache.set(uid, tbit);
|
||||
var nsnap = new haxe.ds.Vector<iron.data.MaterialData>(mats != null ? mats.length : 0);
|
||||
for (mi in 0...nsnap.length) nsnap[mi] = mats[mi];
|
||||
matSnapshots.set(uid, nsnap);
|
||||
}
|
||||
var tbitCached = matTranspCache.get(uid);
|
||||
if (tbitCached != null) flags |= tbitCached;
|
||||
if (mesh.visible) flags |= 8;
|
||||
if (radiusScale != 1.0) flags |= 16;
|
||||
if (prevIdx != null) {
|
||||
var poff = prevIdx * STRIDE;
|
||||
if (prevData[poff + 0] != data[off + 0] ||
|
||||
prevData[poff + 1] != data[off + 1] ||
|
||||
prevData[poff + 2] != data[off + 2] ||
|
||||
prevData[poff + 8] != data[off + 8] ||
|
||||
prevData[poff + 9] != data[off + 9] ||
|
||||
prevData[poff + 10] != data[off + 10]) {
|
||||
flags |= 2;
|
||||
anyMoved = true;
|
||||
}
|
||||
} else {
|
||||
flags |= 2;
|
||||
anyMoved = true;
|
||||
anyFlagsChanged = true;
|
||||
}
|
||||
if (prevIdx != null) {
|
||||
var poff = prevIdx * STRIDE;
|
||||
if (((flags ^ Std.int(prevData[poff + 7])) & ~2) != 0) anyFlagsChanged = true;
|
||||
}
|
||||
data[off + 7] = flags;
|
||||
|
||||
var boff = i * BASIS_STRIDE;
|
||||
basisData[boff + 0] = w._00;
|
||||
basisData[boff + 1] = w._10;
|
||||
basisData[boff + 2] = w._20;
|
||||
basisData[boff + 3] = w._01;
|
||||
basisData[boff + 4] = w._11;
|
||||
basisData[boff + 5] = w._21;
|
||||
basisData[boff + 6] = w._02;
|
||||
basisData[boff + 7] = w._12;
|
||||
basisData[boff + 8] = w._22;
|
||||
basisData[boff + 9] = t.dim.x;
|
||||
basisData[boff + 10] = t.dim.y;
|
||||
basisData[boff + 11] = t.dim.z;
|
||||
uidData[i] = uid;
|
||||
}
|
||||
|
||||
prevCount = count;
|
||||
}
|
||||
|
||||
public inline function getMinX(i: Int): Float {
|
||||
return data[i * STRIDE + 0];
|
||||
}
|
||||
public inline function getMinY(i: Int): Float {
|
||||
return data[i * STRIDE + 1];
|
||||
}
|
||||
public inline function getMinZ(i: Int): Float {
|
||||
return data[i * STRIDE + 2];
|
||||
}
|
||||
public inline function getRadius(i: Int): Float {
|
||||
return data[i * STRIDE + 3];
|
||||
}
|
||||
public inline function getCenterX(i: Int): Float {
|
||||
return data[i * STRIDE + 4];
|
||||
}
|
||||
public inline function getCenterY(i: Int): Float {
|
||||
return data[i * STRIDE + 5];
|
||||
}
|
||||
public inline function getCenterZ(i: Int): Float {
|
||||
return data[i * STRIDE + 6];
|
||||
}
|
||||
public inline function getFlags(i: Int): Int {
|
||||
return Std.int(data[i * STRIDE + 7]);
|
||||
}
|
||||
public inline function isOccluder(i: Int): Bool {
|
||||
return (getFlags(i) & 1) != 0;
|
||||
}
|
||||
public inline function isDynamic(i: Int): Bool {
|
||||
return (getFlags(i) & 2) != 0;
|
||||
}
|
||||
public inline function isShadowCaster(i: Int): Bool {
|
||||
return (getFlags(i) & 4) != 0;
|
||||
}
|
||||
public inline function isMeshVisible(i: Int): Bool {
|
||||
return (getFlags(i) & 8) != 0;
|
||||
}
|
||||
public inline function getObjectID(i: Int): Int {
|
||||
return uidData[i];
|
||||
}
|
||||
|
||||
public inline function getMaxX(i: Int): Float {
|
||||
return data[i * STRIDE + 8];
|
||||
}
|
||||
public inline function getMaxY(i: Int): Float {
|
||||
return data[i * STRIDE + 9];
|
||||
}
|
||||
public inline function getMaxZ(i: Int): Float {
|
||||
return data[i * STRIDE + 10];
|
||||
}
|
||||
|
||||
public function clearVisible() {
|
||||
visibleCount = 0;
|
||||
if (visibleList.length > 0) visibleList.resize(0);
|
||||
}
|
||||
|
||||
public function addVisible(objectIndex: Int) {
|
||||
visibleList.push(objectIndex);
|
||||
visibleCount++;
|
||||
}
|
||||
}
|
||||
773
leenkx/Sources/iron/culling/CullSystem.hx
Normal file
773
leenkx/Sources/iron/culling/CullSystem.hx
Normal file
@ -0,0 +1,773 @@
|
||||
package iron.culling;
|
||||
|
||||
import iron.object.CameraObject;
|
||||
import iron.object.FrustumPlane;
|
||||
import iron.Scene;
|
||||
import iron.RenderPath;
|
||||
import iron.math.Mat4;
|
||||
|
||||
class CullSystem {
|
||||
|
||||
public static var active: CullSystem = null;
|
||||
|
||||
public var tier: CullTier;
|
||||
public var cullData: CullData;
|
||||
public var rasterizer: SoftwareOcclusionRasterizer;
|
||||
public var shadowRasterizer: SoftwareOcclusionRasterizer;
|
||||
public var occluderProxies: Array<OccluderProxy>;
|
||||
public var bvh: ObjectBVH;
|
||||
public var temporal: TemporalState;
|
||||
|
||||
public var enabled: Bool = true;
|
||||
public var occlusionEnabled: Bool = true;
|
||||
public var bvhDirty: Bool = true;
|
||||
public var proxiesDirty: Bool = true;
|
||||
var dataSyncedFrame: Int = -1;
|
||||
|
||||
var focalLengthPx: Float = 500.0;
|
||||
var orthoPxPerUnit: Float = 0.0;
|
||||
var screenW: Int = 1920;
|
||||
var screenH: Int = 1080;
|
||||
|
||||
public static var maxOccluders: Int = 48;
|
||||
public static var maxOccluderTris: Int = 4000;
|
||||
public static var maxTrisPerProxy: Int = 768;
|
||||
public static var maxShadowOccluders: Int = 24;
|
||||
public static var maxShadowOccluderTris: Int = 1536;
|
||||
public static var boxOccluders: Bool = false;
|
||||
public static var shadowBoxOccluders: Bool = false;
|
||||
public static var shadowBoxShrink: Float = 0.7;
|
||||
public static var shadowCullingEnabled: Bool = true;
|
||||
public static var minOccluderPx: Float = 24.0;
|
||||
public static var boxShrink: Float = 0.5;
|
||||
public static var depthEps: Float = 1e-6;
|
||||
|
||||
public static var shadowCullInterval: Int = 2;
|
||||
|
||||
static inline var MAX_SHADOW_SLOTS = 8;
|
||||
var shadowPasses: haxe.ds.ObjectMap<iron.object.LightObject, Array<ShadowPassSlot>> = new haxe.ds.ObjectMap();
|
||||
var shadowCallIdx: Map<iron.object.LightObject, Int> = new Map();
|
||||
var shadowCallFrame: Map<iron.object.LightObject, Int> = new Map();
|
||||
var lastShadowSlot: ShadowPassSlot = null;
|
||||
var lastShadowFrame: Int = -1;
|
||||
var lastShadowCallIdx: Int = -1;
|
||||
#if lnx_csm
|
||||
var shadowVps: haxe.ds.ObjectMap<iron.object.LightObject, Mat4> = new haxe.ds.ObjectMap();
|
||||
#end
|
||||
var sceneRev: Int = 0;
|
||||
var sceneRevFrame: Int = -1;
|
||||
var frameCounter: Int = 0;
|
||||
var bvhSyncedFrame: Int = -1; // Frame when BVH was built/refit
|
||||
|
||||
var occlusionFrustumPlanes: Array<FrustumPlane> = null;
|
||||
var frustumPlanes: Array<FrustumPlane> = null;
|
||||
|
||||
var shadowFrustumPlanes: Array<FrustumPlane> = null;
|
||||
|
||||
var uidToProxy: haxe.ds.IntMap<OccluderProxy> = new haxe.ds.IntMap<OccluderProxy>();
|
||||
var uidScan: haxe.ds.IntMap<Bool> = new haxe.ds.IntMap<Bool>();
|
||||
|
||||
var prevVP: Mat4 = null;
|
||||
#if lnx_taa
|
||||
var unjitteredVP: Mat4 = null;
|
||||
#end
|
||||
public var shadowFlagsValid: Bool = false;
|
||||
|
||||
var inFrustumList: Array<Int> = [];
|
||||
var selIdx: Array<Int> = [];
|
||||
var selScore: Array<Float> = [];
|
||||
var selCount: Int = 0;
|
||||
|
||||
public function new() {
|
||||
cullData = new CullData();
|
||||
rasterizer = new SoftwareOcclusionRasterizer(128, 64);
|
||||
shadowRasterizer = new SoftwareOcclusionRasterizer(128, 128);
|
||||
occluderProxies = [];
|
||||
temporal = new TemporalState();
|
||||
tier = detectTier();
|
||||
}
|
||||
|
||||
static inline var MAT_EPS = 1e-7;
|
||||
|
||||
static inline function matEquals(a: Mat4, b: Mat4): Bool {
|
||||
return
|
||||
Math.abs(a._00 - b._00) < MAT_EPS && Math.abs(a._10 - b._10) < MAT_EPS &&
|
||||
Math.abs(a._20 - b._20) < MAT_EPS && Math.abs(a._30 - b._30) < MAT_EPS &&
|
||||
Math.abs(a._01 - b._01) < MAT_EPS && Math.abs(a._11 - b._11) < MAT_EPS &&
|
||||
Math.abs(a._21 - b._21) < MAT_EPS && Math.abs(a._31 - b._31) < MAT_EPS &&
|
||||
Math.abs(a._02 - b._02) < MAT_EPS && Math.abs(a._12 - b._12) < MAT_EPS &&
|
||||
Math.abs(a._22 - b._22) < MAT_EPS && Math.abs(a._32 - b._32) < MAT_EPS &&
|
||||
Math.abs(a._03 - b._03) < MAT_EPS && Math.abs(a._13 - b._13) < MAT_EPS &&
|
||||
Math.abs(a._23 - b._23) < MAT_EPS && Math.abs(a._33 - b._33) < MAT_EPS;
|
||||
}
|
||||
|
||||
public static function detectTier(): CullTier {
|
||||
#if (target.threaded)
|
||||
return CPUMultiThread;
|
||||
#else
|
||||
return CPUSingleThread;
|
||||
#end
|
||||
}
|
||||
|
||||
public function cull(camera: CameraObject) {
|
||||
frameCounter = RenderPath.active != null ? RenderPath.active.frame : frameCounter + 1;
|
||||
if (!enabled || Scene.active == null || Scene.active.meshes.length == 0) {
|
||||
if (Scene.active != null) {
|
||||
var meshes = Scene.active.meshes;
|
||||
for (i in 0...meshes.length) {
|
||||
meshes[i].occlusionVisible = true;
|
||||
meshes[i].shadowOcclusionVisible = true;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var rp = RenderPath.active;
|
||||
if (rp != null) {
|
||||
screenW = rp.currentW;
|
||||
screenH = rp.currentH;
|
||||
}
|
||||
|
||||
var targetW = Std.int(Math.max(128, Math.min(512, screenW >> 4)));
|
||||
var targetH = Std.int(Math.max(64, Math.min(256, screenH >> 4)));
|
||||
if (rasterizer.width < (targetW >> 1) || rasterizer.width > (targetW << 1) ||
|
||||
rasterizer.height < (targetH >> 1) || rasterizer.height > (targetH << 1)) {
|
||||
rasterizer = new SoftwareOcclusionRasterizer(targetW, targetH);
|
||||
}
|
||||
|
||||
if (camera.data.raw.ortho == null) {
|
||||
focalLengthPx = ContributionCull.computeFocalLengthPx(
|
||||
camera.data.raw.fov, screenH
|
||||
);
|
||||
orthoPxPerUnit = 0.0;
|
||||
} else {
|
||||
focalLengthPx = 0;
|
||||
var o = camera.data.raw.ortho;
|
||||
var halfH = (o[3] - o[2]) * 0.5;
|
||||
orthoPxPerUnit = halfH > 1e-9 ? screenH * 0.5 / halfH : 0.0;
|
||||
}
|
||||
rasterizer.nearClip = camera.data.raw.ortho == null ?
|
||||
camera.data.raw.near_plane : 0.0001;
|
||||
|
||||
if (dataSyncedFrame != frameCounter) {
|
||||
cullData.syncFromScene();
|
||||
dataSyncedFrame = frameCounter;
|
||||
}
|
||||
if (cullData.slotsRebuilt) temporal.clear();
|
||||
if (sceneRevFrame != frameCounter) {
|
||||
sceneRevFrame = frameCounter;
|
||||
if (cullData.anyMoved || cullData.anyFlagsChanged || cullData.orderChanged) sceneRev++;
|
||||
}
|
||||
var cd = cullData;
|
||||
var meshes = cd.meshes;
|
||||
|
||||
#if lnx_taa
|
||||
if (unjitteredVP == null) unjitteredVP = Mat4.identity();
|
||||
unjitteredVP.multmats(camera.noJitterP, camera.V);
|
||||
var cmpVP = unjitteredVP;
|
||||
#else
|
||||
var cmpVP = camera.VP;
|
||||
#end
|
||||
var vpSame = prevVP != null && matEquals(cmpVP, prevVP);
|
||||
if (vpSame && !cd.anyMoved && !cd.anyFlagsChanged && !cd.orderChanged && !bvhDirty) {
|
||||
return;
|
||||
}
|
||||
var needBvhRebuild = bvhDirty;
|
||||
bvhDirty = false;
|
||||
if (prevVP == null) prevVP = Mat4.identity();
|
||||
prevVP.setFrom(cmpVP);
|
||||
|
||||
if (occlusionFrustumPlanes == null) {
|
||||
occlusionFrustumPlanes = [];
|
||||
for (i in 0...6) occlusionFrustumPlanes.push(new FrustumPlane());
|
||||
}
|
||||
{
|
||||
var vp = camera.VP;
|
||||
var p = occlusionFrustumPlanes;
|
||||
p[0].setComponents(vp._03 + vp._00, vp._13 + vp._10, vp._23 + vp._20, vp._33 + vp._30);
|
||||
p[1].setComponents(vp._03 - vp._00, vp._13 - vp._10, vp._23 - vp._20, vp._33 - vp._30);
|
||||
p[2].setComponents(vp._03 - vp._01, vp._13 - vp._11, vp._23 - vp._21, vp._33 - vp._31);
|
||||
p[3].setComponents(vp._03 + vp._01, vp._13 + vp._11, vp._23 + vp._21, vp._33 + vp._31);
|
||||
p[4].setComponents(vp._02, vp._12, vp._22, vp._32);
|
||||
p[5].setComponents(vp._03 - vp._02, vp._13 - vp._12, vp._23 - vp._22, vp._33 - vp._32);
|
||||
}
|
||||
frustumPlanes = occlusionFrustumPlanes;
|
||||
|
||||
var camX = camera.transform.worldx();
|
||||
var camY = camera.transform.worldy();
|
||||
var camZ = camera.transform.worldz();
|
||||
|
||||
if (bvh == null) bvh = new ObjectBVH();
|
||||
if (bvhSyncedFrame != frameCounter) {
|
||||
if (needBvhRebuild || bvh.root < 0) {
|
||||
bvh.build(cd);
|
||||
} else if (cd.anyMoved || cd.orderChanged) {
|
||||
bvh.refit(cd);
|
||||
}
|
||||
bvhSyncedFrame = frameCounter;
|
||||
}
|
||||
inFrustumList.resize(0);
|
||||
var planes = frustumPlanes;
|
||||
var fpx = focalLengthPx;
|
||||
bvh.traverseFrontToBack(camX, camY, camZ,
|
||||
function(node: BVHNode): Bool {
|
||||
if (!ObjectBVH.aabbInFrustum(node, planes)) return true;
|
||||
if (!ContributionCull.passesNode(node, camX, camY, camZ, fpx)) return true;
|
||||
return false;
|
||||
},
|
||||
function(objIdx: Int, _: Int) {
|
||||
if ((cd.getFlags(objIdx) & 8) == 0) return;
|
||||
if (!ContributionCull.passes(
|
||||
cd.getCenterX(objIdx), cd.getCenterY(objIdx), cd.getCenterZ(objIdx),
|
||||
cd.getRadius(objIdx), camX, camY, camZ, fpx
|
||||
)) return;
|
||||
inFrustumList.push(objIdx);
|
||||
}
|
||||
);
|
||||
cullData.clearVisible();
|
||||
|
||||
if (occlusionEnabled) {
|
||||
selectOccluders(meshes, camX, camY, camZ);
|
||||
|
||||
for (i in 0...meshes.length) {
|
||||
meshes[i].occlusionVisible = true;
|
||||
}
|
||||
|
||||
rasterizer.clear();
|
||||
if (selCount > 0) rasterizeSelected(meshes, camera.VP);
|
||||
var trisDone = rasterizer.trianglesRasterized;
|
||||
if (trisDone > 0) rasterizer.buildHiZ();
|
||||
|
||||
if (trisDone > 0) {
|
||||
testVisibilityBVH(camera.VP, camX, camY, camZ, planes);
|
||||
} else {
|
||||
for (k in 0...inFrustumList.length) {
|
||||
var objIdx = inFrustumList[k];
|
||||
cd.addVisible(objIdx);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (i in 0...meshes.length) {
|
||||
meshes[i].occlusionVisible = true;
|
||||
}
|
||||
for (k in 0...inFrustumList.length) {
|
||||
var objIdx = inFrustumList[k];
|
||||
cd.addVisible(objIdx);
|
||||
}
|
||||
}
|
||||
|
||||
temporal.advanceFrame();
|
||||
}
|
||||
|
||||
public function cullShadow(light: iron.object.LightObject) {
|
||||
if (!occlusionEnabled || light == null || Scene.active == null) return;
|
||||
if (!shadowCullingEnabled) {
|
||||
var ms = Scene.active.meshes;
|
||||
for (m in ms) m.shadowOcclusionVisible = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var frame = RenderPath.active != null ? RenderPath.active.frame : frameCounter;
|
||||
var vp = light.VP;
|
||||
if (vp == null) return;
|
||||
|
||||
var cd = cullData;
|
||||
|
||||
if (dataSyncedFrame != frame) {
|
||||
cd.syncFromScene();
|
||||
dataSyncedFrame = frame;
|
||||
}
|
||||
if (sceneRevFrame != frame) {
|
||||
sceneRevFrame = frame;
|
||||
if (cd.anyMoved || cd.anyFlagsChanged || cd.orderChanged) sceneRev++;
|
||||
}
|
||||
var meshes = cd.meshes;
|
||||
|
||||
var slots = shadowPasses.get(light);
|
||||
if (slots == null) {
|
||||
slots = [];
|
||||
shadowPasses.set(light, slots);
|
||||
}
|
||||
var callIdx = 0;
|
||||
{
|
||||
var f = shadowCallFrame.get(light);
|
||||
callIdx = (f == frame) ? shadowCallIdx.get(light) + 1 : 0;
|
||||
shadowCallIdx.set(light, callIdx);
|
||||
shadowCallFrame.set(light, frame);
|
||||
}
|
||||
var slotIdx = callIdx < MAX_SHADOW_SLOTS ? callIdx : MAX_SHADOW_SLOTS - 1;
|
||||
while (slots.length <= slotIdx) slots.push(new ShadowPassSlot());
|
||||
var slot = slots[slotIdx];
|
||||
|
||||
if (slot != null && slot == lastShadowSlot && lastShadowFrame == frame &&
|
||||
lastShadowCallIdx == callIdx && slot.appliedRev == sceneRev) return;
|
||||
|
||||
for (i in 0...meshes.length) {
|
||||
meshes[i].shadowOcclusionVisible = true;
|
||||
}
|
||||
|
||||
#if lnx_csm
|
||||
var isCsmLight = light.data.raw.type == "sun" &&
|
||||
iron.object.LightObject.cascadeCount > 1;
|
||||
if (isCsmLight && callIdx == 0) {
|
||||
var vp0 = shadowVps.get(light);
|
||||
if (vp0 == null) { vp0 = Mat4.identity(); shadowVps.set(light, vp0); }
|
||||
vp0.setFrom(vp);
|
||||
}
|
||||
#end
|
||||
|
||||
if (shadowCullInterval > 1) {
|
||||
if ((frame + slotIdx) % shadowCullInterval != 0) {
|
||||
var applied = false;
|
||||
#if lnx_csm
|
||||
if (isCsmLight && callIdx > 0) {
|
||||
var head = slots[0];
|
||||
var vp0 = shadowVps.get(light);
|
||||
if (head.appliedRev == sceneRev && head.flagsLen > 0 &&
|
||||
head.vp != null && vp0 != null && matEquals(vp0, head.vp)) {
|
||||
var hn = head.flagsLen < meshes.length ? head.flagsLen : meshes.length;
|
||||
for (i in 0...hn) meshes[i].shadowOcclusionVisible = head.flags[i];
|
||||
}
|
||||
applied = true;
|
||||
}
|
||||
#end
|
||||
if (!applied && slot.appliedRev == sceneRev && slot.flagsLen > 0 &&
|
||||
slot.vp != null && matEquals(vp, slot.vp)) {
|
||||
var n = slot.flagsLen < meshes.length ? slot.flagsLen : meshes.length;
|
||||
for (i in 0...n) meshes[i].shadowOcclusionVisible = slot.flags[i];
|
||||
}
|
||||
lastShadowSlot = slot;
|
||||
lastShadowFrame = frame;
|
||||
lastShadowCallIdx = callIdx;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (bvh == null) bvh = new ObjectBVH();
|
||||
if (bvhSyncedFrame != frame) {
|
||||
if (bvhDirty || bvh.root < 0) {
|
||||
bvh.build(cd);
|
||||
} else if (cd.anyMoved || cd.orderChanged) {
|
||||
bvh.refit(cd);
|
||||
}
|
||||
bvhSyncedFrame = frame;
|
||||
}
|
||||
|
||||
if (shadowFrustumPlanes == null) {
|
||||
shadowFrustumPlanes = [];
|
||||
for (i in 0...6) shadowFrustumPlanes.push(new FrustumPlane());
|
||||
}
|
||||
{
|
||||
var p = shadowFrustumPlanes;
|
||||
p[0].setComponents(vp._03 + vp._00, vp._13 + vp._10, vp._23 + vp._20, vp._33 + vp._30);
|
||||
p[1].setComponents(vp._03 - vp._00, vp._13 - vp._10, vp._23 - vp._20, vp._33 - vp._30);
|
||||
p[2].setComponents(vp._03 - vp._01, vp._13 - vp._11, vp._23 - vp._21, vp._33 - vp._31);
|
||||
p[3].setComponents(vp._03 + vp._01, vp._13 + vp._11, vp._23 + vp._21, vp._33 + vp._31);
|
||||
p[4].setComponents(vp._02, vp._12, vp._22, vp._32);
|
||||
p[5].setComponents(vp._03 - vp._02, vp._13 - vp._12, vp._23 - vp._22, vp._33 - vp._32);
|
||||
}
|
||||
var lightPlanes = shadowFrustumPlanes;
|
||||
|
||||
var lx = light.transform.worldx();
|
||||
var ly = light.transform.worldy();
|
||||
var lz = light.transform.worldz();
|
||||
|
||||
var raster = slot.rasterizer;
|
||||
var vpSame = slot.vp != null && matEquals(vp, slot.vp);
|
||||
if (slot.sceneRev == sceneRev && vpSame && raster != null) {
|
||||
// skip
|
||||
} else {
|
||||
if (raster == null) {
|
||||
raster = new SoftwareOcclusionRasterizer(
|
||||
shadowRasterizer.width, shadowRasterizer.height);
|
||||
slot.rasterizer = raster;
|
||||
}
|
||||
if (slot.vp == null) slot.vp = Mat4.identity();
|
||||
slot.vp.setFrom(vp);
|
||||
|
||||
var perspective = light.data.raw.type != "sun";
|
||||
selCount = 0;
|
||||
bvh.traverseFrontToBack(lx, ly, lz,
|
||||
function(node: BVHNode): Bool {
|
||||
if (!ObjectBVH.aabbInFrustum(node, lightPlanes)) return true;
|
||||
return false;
|
||||
},
|
||||
function(i: Int, _: Int) {
|
||||
if ((cd.getFlags(i) & 8) == 0) return;
|
||||
if ((cd.getFlags(i) & 16) != 0) return;
|
||||
if ((cd.getFlags(i) & 4) == 0) return;
|
||||
if ((cd.getFlags(i) & 32) != 0) return;
|
||||
if (i >= meshes.length) return;
|
||||
var r = cd.getRadius(i);
|
||||
var score = r;
|
||||
if (perspective) {
|
||||
var dx = cd.getCenterX(i) - lx;
|
||||
var dy = cd.getCenterY(i) - ly;
|
||||
var dz = cd.getCenterZ(i) - lz;
|
||||
var dist2 = dx * dx + dy * dy + dz * dz;
|
||||
if (dist2 <= r * r) return;
|
||||
score = r / Math.sqrt(dist2);
|
||||
}
|
||||
insertTopK(i, score, maxShadowOccluders);
|
||||
}
|
||||
);
|
||||
|
||||
raster = slot.rasterizer;
|
||||
raster.clear();
|
||||
var triBudget = maxShadowOccluderTris;
|
||||
for (s in 0...selCount) {
|
||||
if (triBudget < 12) break;
|
||||
var i = selIdx[s];
|
||||
var proxy = uidToProxy.get(cd.getObjectID(i));
|
||||
if (proxy != null && proxy.triangleCount > 0 &&
|
||||
proxy.triangleCount <= maxTrisPerProxy && proxy.triangleCount <= triBudget) {
|
||||
proxy.rasterize(raster, meshes[i].transform.world, vp,
|
||||
raster.width, raster.height);
|
||||
triBudget -= proxy.triangleCount;
|
||||
} else if (shadowBoxOccluders) {
|
||||
rasterizeBoxOccluder(raster, i, vp, shadowBoxShrink);
|
||||
triBudget -= 12;
|
||||
}
|
||||
}
|
||||
if (raster.trianglesRasterized > 0) raster.buildHiZ();
|
||||
}
|
||||
|
||||
if (raster.trianglesRasterized > 0) {
|
||||
var sw = raster.width;
|
||||
var sh = raster.height;
|
||||
|
||||
bvh.traverseFrontToBack(lx, ly, lz,
|
||||
function(node: BVHNode): Bool {
|
||||
if (!ObjectBVH.aabbInFrustum(node, lightPlanes)) return true;
|
||||
var anyVisible = false;
|
||||
if (node.isLeaf) {
|
||||
for (j in 0...node.objectCount) {
|
||||
var idx = bvh.objectIndices[node.firstObject + j];
|
||||
if ((cd.getFlags(idx) & 8) != 0 && idx < meshes.length) {
|
||||
anyVisible = true; break;
|
||||
}
|
||||
}
|
||||
if (!anyVisible) return true;
|
||||
}
|
||||
if (raster.isOccluded(
|
||||
node.minX, node.minY, node.minZ,
|
||||
node.maxX, node.maxY, node.maxZ,
|
||||
vp, sw, sh
|
||||
)) {
|
||||
bvh.forEachLeafInSubtree(node, function(idx: Int) {
|
||||
if (idx < meshes.length && (cd.getFlags(idx) & 8) != 0) {
|
||||
meshes[idx].shadowOcclusionVisible = false;
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
function(objIdx: Int, _: Int) {
|
||||
if ((cd.getFlags(objIdx) & 8) == 0) return;
|
||||
if (objIdx >= meshes.length) return;
|
||||
meshes[objIdx].shadowOcclusionVisible = true;
|
||||
if (raster.isOccluded(
|
||||
cd.getMinX(objIdx), cd.getMinY(objIdx), cd.getMinZ(objIdx),
|
||||
cd.getMaxX(objIdx), cd.getMaxY(objIdx), cd.getMaxZ(objIdx),
|
||||
vp, sw, sh
|
||||
)) {
|
||||
meshes[objIdx].shadowOcclusionVisible = false;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#if lnx_csm
|
||||
if (isCsmLight) {
|
||||
for (c in 1...iron.object.LightObject.cascadeCount) {
|
||||
if (c >= slots.length) break;
|
||||
var s = slots[c];
|
||||
if (s == null || s == slot || s.vp == null || s.rasterizer == null) continue;
|
||||
for (i in 0...meshes.length) {
|
||||
if (meshes[i].shadowOcclusionVisible) continue;
|
||||
if (!s.rasterizer.isOccluded(
|
||||
cd.getMinX(i), cd.getMinY(i), cd.getMinZ(i),
|
||||
cd.getMaxX(i), cd.getMaxY(i), cd.getMaxZ(i),
|
||||
s.vp, s.rasterizer.width, s.rasterizer.height
|
||||
)) {
|
||||
meshes[i].shadowOcclusionVisible = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
||||
if (slot.flags.length < meshes.length) slot.flags.resize(meshes.length);
|
||||
slot.flagsLen = meshes.length;
|
||||
for (i in 0...meshes.length) slot.flags[i] = meshes[i].shadowOcclusionVisible;
|
||||
|
||||
#if lnx_csm
|
||||
if (isCsmLight && callIdx > 0) {
|
||||
var head = slots[0];
|
||||
var vp0 = shadowVps.get(light);
|
||||
if (head.appliedRev == sceneRev && head.flagsLen > 0 &&
|
||||
head.vp != null && vp0 != null && matEquals(vp0, head.vp)) {
|
||||
var hn = head.flagsLen < meshes.length ? head.flagsLen : meshes.length;
|
||||
for (i in 0...hn) meshes[i].shadowOcclusionVisible = head.flags[i];
|
||||
} else {
|
||||
for (i in 0...meshes.length) meshes[i].shadowOcclusionVisible = true;
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
||||
slot.sceneRev = sceneRev;
|
||||
slot.appliedRev = sceneRev;
|
||||
slot.lastUsed = frame;
|
||||
lastShadowSlot = slot;
|
||||
lastShadowFrame = frame;
|
||||
lastShadowCallIdx = callIdx;
|
||||
shadowFlagsValid = true;
|
||||
}
|
||||
|
||||
function selectOccluders(
|
||||
meshes: Array<iron.object.MeshObject>,
|
||||
camX: Float, camY: Float, camZ: Float
|
||||
) {
|
||||
var cd = cullData;
|
||||
selCount = 0;
|
||||
var minPx = minOccluderPx;
|
||||
var autoPx = autoOccluderMinScreenRatio * screenH;
|
||||
for (k in 0...inFrustumList.length) {
|
||||
var i = inFrustumList[k];
|
||||
if (i >= meshes.length) continue;
|
||||
if (!meshes[i].occlusionVisible) continue;
|
||||
var flags = cd.getFlags(i);
|
||||
if ((flags & 16) != 0) continue;
|
||||
if ((flags & 32) != 0) continue;
|
||||
var r = cd.getRadius(i);
|
||||
var px: Float;
|
||||
var camInside = false;
|
||||
if (focalLengthPx > 0) {
|
||||
var dx = cd.getCenterX(i) - camX;
|
||||
var dy = cd.getCenterY(i) - camY;
|
||||
var dz = cd.getCenterZ(i) - camZ;
|
||||
var dist2 = dx * dx + dy * dy + dz * dz;
|
||||
if (dist2 <= r * r) camInside = true;
|
||||
px = camInside ? 1e6 : focalLengthPx * r / Math.sqrt(dist2);
|
||||
} else {
|
||||
px = r * orthoPxPerUnit;
|
||||
}
|
||||
if (px < minPx) continue;
|
||||
if ((flags & 1) == 0 && px < autoPx) continue;
|
||||
if (camInside || !boxOccluders) {
|
||||
var p = uidToProxy.get(cd.getObjectID(i));
|
||||
if (p == null || p.triangleCount <= 0 || p.triangleCount > maxTrisPerProxy) continue;
|
||||
}
|
||||
insertTopK(i, px, maxOccluders);
|
||||
}
|
||||
}
|
||||
|
||||
function insertTopK(idx: Int, score: Float, k: Int) {
|
||||
if (selIdx.length < k + 1) {
|
||||
selIdx.resize(k + 1);
|
||||
selScore.resize(k + 1);
|
||||
}
|
||||
var pos = selCount;
|
||||
while (pos > 0 && selScore[pos - 1] < score) pos--;
|
||||
if (pos >= k) return;
|
||||
var last = selCount < k ? selCount : k - 1;
|
||||
var j = last;
|
||||
while (j > pos) {
|
||||
selIdx[j] = selIdx[j - 1];
|
||||
selScore[j] = selScore[j - 1];
|
||||
j--;
|
||||
}
|
||||
selIdx[pos] = idx;
|
||||
selScore[pos] = score;
|
||||
if (selCount < k) selCount++;
|
||||
}
|
||||
|
||||
function rasterizeSelected(meshes: Array<iron.object.MeshObject>, vp: Mat4) {
|
||||
var cd = cullData;
|
||||
var triBudget = maxOccluderTris;
|
||||
#if (target.threaded)
|
||||
rasterizer.beginCollect();
|
||||
#end
|
||||
for (s in 0...selCount) {
|
||||
if (triBudget < 12) break;
|
||||
var i = selIdx[s];
|
||||
var proxy = uidToProxy.get(cd.getObjectID(i));
|
||||
if (proxy != null && proxy.triangleCount > 0 &&
|
||||
proxy.triangleCount <= maxTrisPerProxy && proxy.triangleCount <= triBudget) {
|
||||
var mesh = i < meshes.length ? meshes[i] : null;
|
||||
if (mesh == null) continue;
|
||||
proxy.rasterize(rasterizer, mesh.transform.world, vp, screenW, screenH);
|
||||
triBudget -= proxy.triangleCount;
|
||||
} else {
|
||||
if (!boxOccluders) continue;
|
||||
rasterizeBoxOccluder(rasterizer, i, vp);
|
||||
triBudget -= 12;
|
||||
}
|
||||
}
|
||||
#if (target.threaded)
|
||||
rasterizer.flushCollected();
|
||||
#end
|
||||
}
|
||||
|
||||
function rasterizeBoxOccluder(raster: SoftwareOcclusionRasterizer, objectIndex: Int, vp: Mat4, shrinkOverride = -1.0) {
|
||||
var cd = cullData;
|
||||
var bx0 = cd.getMinX(objectIndex), by0 = cd.getMinY(objectIndex), bz0 = cd.getMinZ(objectIndex);
|
||||
var bx1 = cd.getMaxX(objectIndex), by1 = cd.getMaxY(objectIndex), bz1 = cd.getMaxZ(objectIndex);
|
||||
var cx = (bx0 + bx1) * 0.5;
|
||||
var cy = (by0 + by1) * 0.5;
|
||||
var cz = (bz0 + bz1) * 0.5;
|
||||
var boxShrink = shrinkOverride >= 0.0 ? shrinkOverride : CullSystem.boxShrink;
|
||||
var hx = (bx1 - bx0) * 0.5 * boxShrink;
|
||||
var hy = (by1 - by0) * 0.5 * boxShrink;
|
||||
var hz = (bz1 - bz0) * 0.5 * boxShrink;
|
||||
raster.rasterizeBox(
|
||||
cx - hx, cy - hy, cz - hz,
|
||||
cx + hx, cy + hy, cz + hz,
|
||||
vp, raster.width, raster.height
|
||||
);
|
||||
}
|
||||
|
||||
function testVisibilityBVH(
|
||||
vp: Mat4, camX: Float, camY: Float, camZ: Float,
|
||||
planes: Array<FrustumPlane>
|
||||
) {
|
||||
var cd = cullData;
|
||||
var raster = rasterizer;
|
||||
var sw = screenW;
|
||||
var sh = screenH;
|
||||
var meshes = cd.meshes;
|
||||
var bvh = this.bvh;
|
||||
var temp = temporal;
|
||||
var retestInterval = 4;
|
||||
|
||||
bvh.traverseFrontToBack(camX, camY, camZ,
|
||||
function(node: BVHNode): Bool {
|
||||
if (!ObjectBVH.aabbInFrustum(node, planes)) return true;
|
||||
var anyVisible = false;
|
||||
if (node.isLeaf) {
|
||||
for (j in 0...node.objectCount) {
|
||||
var idx = bvh.objectIndices[node.firstObject + j];
|
||||
if ((cd.getFlags(idx) & 8) != 0) { anyVisible = true; break; }
|
||||
}
|
||||
if (!anyVisible) return true;
|
||||
}
|
||||
if (raster.isOccluded(
|
||||
node.minX, node.minY, node.minZ,
|
||||
node.maxX, node.maxY, node.maxZ,
|
||||
vp, sw, sh
|
||||
)) {
|
||||
bvh.forEachLeafInSubtree(node, function(idx: Int) {
|
||||
if (idx >= meshes.length || (cd.getFlags(idx) & 8) == 0) return;
|
||||
meshes[idx].occlusionVisible = false;
|
||||
var uid = cd.getObjectID(idx);
|
||||
temp.setVisible(uid, false);
|
||||
temp.markTested(uid);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
function(objIdx: Int, _: Int) {
|
||||
if ((cd.getFlags(objIdx) & 8) == 0) return;
|
||||
if (objIdx >= meshes.length) return;
|
||||
var uid = cd.getObjectID(objIdx);
|
||||
if (temp.wasVisibleLastFrame(uid) && !temp.needsVisibleReTest(uid, retestInterval)) {
|
||||
meshes[objIdx].occlusionVisible = true;
|
||||
cd.addVisible(objIdx);
|
||||
return;
|
||||
}
|
||||
if (raster.isOccluded(
|
||||
cd.getMinX(objIdx), cd.getMinY(objIdx), cd.getMinZ(objIdx),
|
||||
cd.getMaxX(objIdx), cd.getMaxY(objIdx), cd.getMaxZ(objIdx),
|
||||
vp, sw, sh
|
||||
)) {
|
||||
meshes[objIdx].occlusionVisible = false;
|
||||
temp.setVisible(uid, false);
|
||||
temp.markTested(uid);
|
||||
} else {
|
||||
meshes[objIdx].occlusionVisible = true;
|
||||
cd.addVisible(objIdx);
|
||||
temp.setVisible(uid, true);
|
||||
temp.markTested(uid);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public static var autoOccluderMinScreenRatio: Float = 0.10;
|
||||
|
||||
public inline function isVisible(objectIndex: Int): Bool {
|
||||
var ms = cullData.meshes;
|
||||
return objectIndex >= 0 && objectIndex < ms.length && ms[objectIndex].occlusionVisible;
|
||||
}
|
||||
|
||||
public inline function markDirty() {
|
||||
bvhDirty = true;
|
||||
proxiesDirty = true;
|
||||
cullData.slotsDirty = true;
|
||||
shadowFlagsValid = false;
|
||||
}
|
||||
|
||||
public function removeLight(light: iron.object.LightObject) {
|
||||
shadowPasses.remove(light);
|
||||
shadowCallIdx.remove(light);
|
||||
shadowCallFrame.remove(light);
|
||||
#if lnx_csm
|
||||
shadowVps.remove(light);
|
||||
#end
|
||||
}
|
||||
|
||||
public static function init() {
|
||||
if (active == null) {
|
||||
active = new CullSystem();
|
||||
}
|
||||
}
|
||||
|
||||
public function loadOccluderProxies() {
|
||||
occluderProxies.resize(0);
|
||||
if (Scene.active == null) {
|
||||
uidToProxy.clear();
|
||||
return;
|
||||
}
|
||||
var meshes = Scene.active.meshes;
|
||||
uidScan.clear();
|
||||
for (i in 0...meshes.length) {
|
||||
var mesh = meshes[i];
|
||||
uidScan.set(mesh.uid, true);
|
||||
if (uidToProxy.exists(mesh.uid)) continue;
|
||||
if (mesh.data == null || mesh.data.raw == null) continue;
|
||||
var raw = mesh.data.raw;
|
||||
if (raw.occluder_proxy == null) continue;
|
||||
var proxyRaw = raw.occluder_proxy;
|
||||
if (proxyRaw.positions == null || proxyRaw.indices == null) continue;
|
||||
var proxy = new OccluderProxy();
|
||||
proxy.setData(proxyRaw.positions, proxyRaw.indices, mesh.uid);
|
||||
uidToProxy.set(mesh.uid, proxy);
|
||||
}
|
||||
var dead: Array<Int> = null;
|
||||
for (uid in uidToProxy.keys()) {
|
||||
if (!uidScan.exists(uid)) {
|
||||
if (dead == null) dead = [];
|
||||
dead.push(uid);
|
||||
}
|
||||
}
|
||||
if (dead != null) for (uid in dead) uidToProxy.remove(uid);
|
||||
for (proxy in uidToProxy.iterator()) occluderProxies.push(proxy);
|
||||
}
|
||||
|
||||
public static function shutdown() {
|
||||
active = null;
|
||||
}
|
||||
}
|
||||
|
||||
class ShadowPassSlot {
|
||||
public var vp: Mat4 = null;
|
||||
public var rasterizer: SoftwareOcclusionRasterizer = null;
|
||||
public var sceneRev: Int = -1;
|
||||
public var appliedRev: Int = -1;
|
||||
public var lastUsed: Int = -1;
|
||||
public var flags: Array<Bool> = [];
|
||||
public var flagsLen: Int = 0;
|
||||
public function new() {}
|
||||
}
|
||||
9
leenkx/Sources/iron/culling/CullTier.hx
Normal file
9
leenkx/Sources/iron/culling/CullTier.hx
Normal file
@ -0,0 +1,9 @@
|
||||
package iron.culling;
|
||||
|
||||
enum CullTier {
|
||||
CPUSingleThread;
|
||||
CPUMultiThread;
|
||||
GPUCompute;
|
||||
GPUDriven;
|
||||
MultiGPU;
|
||||
}
|
||||
171
leenkx/Sources/iron/culling/HiZPyramid.hx
Normal file
171
leenkx/Sources/iron/culling/HiZPyramid.hx
Normal file
@ -0,0 +1,171 @@
|
||||
package iron.culling;
|
||||
|
||||
class HiZPyramid {
|
||||
|
||||
public var width: Int;
|
||||
public var height: Int;
|
||||
public var mipLevels: Int;
|
||||
public var mipBuffers: Array<kha.arrays.Float32Array>;
|
||||
public var mipWidths: Array<Int>;
|
||||
public var mipHeights: Array<Int>;
|
||||
|
||||
public var depthEps: Float;
|
||||
|
||||
var mipShifts: Array<Int>;
|
||||
|
||||
public function new(width: Int, height: Int) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
mipBuffers = [];
|
||||
mipWidths = [];
|
||||
mipHeights = [];
|
||||
depthEps = CullSystem.depthEps;
|
||||
|
||||
var w = width;
|
||||
var h = height;
|
||||
mipLevels = 0;
|
||||
while (w > 1 || h > 1) {
|
||||
mipWidths.push(w);
|
||||
mipHeights.push(h);
|
||||
mipBuffers.push(new kha.arrays.Float32Array(w * h));
|
||||
mipLevels++;
|
||||
w = (w + 1) >> 1;
|
||||
h = (h + 1) >> 1;
|
||||
}
|
||||
mipWidths.push(1);
|
||||
mipHeights.push(1);
|
||||
mipBuffers.push(new kha.arrays.Float32Array(1));
|
||||
mipLevels++;
|
||||
|
||||
mipShifts = [0, 1, 2, 3, 4, 5, 6];
|
||||
while (mipShifts.length < mipLevels) mipShifts.push(mipShifts.length);
|
||||
}
|
||||
|
||||
public function clear(farDepth: Float = 1e20) {
|
||||
var buf = mipBuffers[0];
|
||||
for (i in 0...buf.length) buf[i] = farDepth;
|
||||
}
|
||||
|
||||
public inline function writeDepth(x: Int, y: Int, depth: Float) {
|
||||
var idx = y * width + x;
|
||||
if (x >= 0 && x < width && y >= 0 && y < height) {
|
||||
if (depth < mipBuffers[0][idx]) {
|
||||
mipBuffers[0][idx] = depth;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function buildPyramid() {
|
||||
for (mip in 1...mipLevels) {
|
||||
var prev = mipBuffers[mip - 1];
|
||||
var cur = mipBuffers[mip];
|
||||
var pw = mipWidths[mip - 1];
|
||||
var ph = mipHeights[mip - 1];
|
||||
var cw = mipWidths[mip];
|
||||
var ch = mipHeights[mip];
|
||||
|
||||
for (y in 0...ch) {
|
||||
for (x in 0...cw) {
|
||||
var sx = x * 2;
|
||||
var sy = y * 2;
|
||||
var d00 = prev[sy * pw + sx];
|
||||
var d10 = (sx + 1 < pw) ? prev[sy * pw + sx + 1] : d00;
|
||||
var d01 = (sy + 1 < ph) ? prev[(sy + 1) * pw + sx] : d00;
|
||||
var d11 = (sx + 1 < pw && sy + 1 < ph) ? prev[(sy + 1) * pw + sx + 1] : d00;
|
||||
var maxD = d00;
|
||||
if (d10 > maxD) maxD = d10;
|
||||
if (d01 > maxD) maxD = d01;
|
||||
if (d11 > maxD) maxD = d11;
|
||||
cur[y * cw + x] = maxD;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public inline function sample(x: Int, y: Int, mip: Int): Float {
|
||||
var mw = mipWidths[mip];
|
||||
var mh = mipHeights[mip];
|
||||
if (x < 0) x = 0;
|
||||
if (y < 0) y = 0;
|
||||
if (x >= mw) x = mw - 1;
|
||||
if (y >= mh) y = mh - 1;
|
||||
return mipBuffers[mip][y * mw + x];
|
||||
}
|
||||
|
||||
public static function mipForSize(rectW: Float, rectH: Float, maxMip: Int): Int {
|
||||
var maxDim = Math.max(rectW, rectH);
|
||||
if (maxDim <= 2.0) return 0;
|
||||
var mip = Std.int(Math.floor(Math.log(maxDim) / Math.log(2))) - 1;
|
||||
if (mip < 0) mip = 0;
|
||||
if (mip > maxMip) mip = maxMip;
|
||||
return mip;
|
||||
}
|
||||
|
||||
public function isOccluded(
|
||||
minX: Float, minY: Float, minZ: Float,
|
||||
maxX: Float, maxY: Float, maxZ: Float,
|
||||
vp: iron.math.Mat4,
|
||||
_screenW: Int, _screenH: Int
|
||||
): Bool {
|
||||
var minSx = 1e20, minSy = 1e20;
|
||||
var maxSx = -1e20, maxSy = -1e20;
|
||||
var minDepth = 1e20;
|
||||
|
||||
var m00 = vp._00, m10 = vp._10, m20 = vp._20, m30 = vp._30;
|
||||
var m01 = vp._01, m11 = vp._11, m21 = vp._21, m31 = vp._31;
|
||||
var m02 = vp._02, m12 = vp._12, m22 = vp._22, m32 = vp._32;
|
||||
var m03 = vp._03, m13 = vp._13, m23 = vp._23, m33 = vp._33;
|
||||
|
||||
for (c in 0...8) {
|
||||
var x = (c & 1) != 0 ? maxX : minX;
|
||||
var y = (c & 2) != 0 ? maxY : minY;
|
||||
var z = (c & 4) != 0 ? maxZ : minZ;
|
||||
var w = m03 * x + m13 * y + m23 * z + m33;
|
||||
if (w <= 0) return false;
|
||||
var invW = SimdOps.reciprocal(w);
|
||||
var ndcZ = (m02 * x + m12 * y + m22 * z + m32) * invW;
|
||||
if (ndcZ < -1.0) ndcZ = -1.0; else if (ndcZ > 1.0) ndcZ = 1.0;
|
||||
var sx = ((m00 * x + m10 * y + m20 * z + m30) * invW * 0.5 + 0.5) * width;
|
||||
var sy = ((m01 * x + m11 * y + m21 * z + m31) * invW * 0.5 + 0.5) * height;
|
||||
if (sx < minSx) minSx = sx;
|
||||
if (sy < minSy) minSy = sy;
|
||||
if (sx > maxSx) maxSx = sx;
|
||||
if (sy > maxSy) maxSy = sy;
|
||||
if (ndcZ < minDepth) minDepth = ndcZ;
|
||||
}
|
||||
|
||||
var ix0 = Std.int(Math.max(0, minSx));
|
||||
var iy0 = Std.int(Math.max(0, minSy));
|
||||
var ix1 = Std.int(Math.min(width - 1, maxSx));
|
||||
var iy1 = Std.int(Math.min(height - 1, maxSy));
|
||||
|
||||
if (ix0 > ix1 || iy0 > iy1) return false;
|
||||
|
||||
if (ix0 > 0) ix0--;
|
||||
if (iy0 > 0) iy0--;
|
||||
if (ix1 < width - 1) ix1++;
|
||||
if (iy1 < height - 1) iy1++;
|
||||
|
||||
var rectW = ix1 - ix0 + 1;
|
||||
var rectH = iy1 - iy0 + 1;
|
||||
var mip = mipForSize(rectW, rectH, mipLevels - 1);
|
||||
|
||||
var mw = mipWidths[mip];
|
||||
var mh = mipHeights[mip];
|
||||
var shift = mipShifts[mip];
|
||||
var tx0 = Std.int(Math.min(mw - 1, ix0 >> shift));
|
||||
var ty0 = Std.int(Math.min(mh - 1, iy0 >> shift));
|
||||
var tx1 = Std.int(Math.min(mw - 1, ix1 >> shift));
|
||||
var ty1 = Std.int(Math.min(mh - 1, iy1 >> shift));
|
||||
|
||||
var buf = mipBuffers[mip];
|
||||
var limit = minDepth - depthEps;
|
||||
for (ty in ty0...ty1 + 1) {
|
||||
var rowOff = ty * mw;
|
||||
for (tx in tx0...tx1 + 1) {
|
||||
if (buf[rowOff + tx] >= limit) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
60
leenkx/Sources/iron/culling/JobSystem.hx
Normal file
60
leenkx/Sources/iron/culling/JobSystem.hx
Normal file
@ -0,0 +1,60 @@
|
||||
package iron.culling;
|
||||
|
||||
#if (target.threaded)
|
||||
|
||||
class JobSystem {
|
||||
|
||||
public static var numWorkers(default, null): Int = 0;
|
||||
|
||||
static var jobQueue: sys.thread.Deque<Void->Void> = null;
|
||||
static var doneLock: sys.thread.Lock = null;
|
||||
static var jobError: Dynamic = null;
|
||||
|
||||
public static function init(workers: Int = 3) {
|
||||
if (jobQueue != null) return;
|
||||
numWorkers = workers < 1 ? 1 : workers;
|
||||
jobQueue = new sys.thread.Deque();
|
||||
doneLock = new sys.thread.Lock();
|
||||
for (_ in 0...numWorkers) {
|
||||
sys.thread.Thread.create(workerLoop);
|
||||
}
|
||||
}
|
||||
|
||||
static function workerLoop() {
|
||||
while (true) {
|
||||
var job = jobQueue.pop(true);
|
||||
if (job == null) return;
|
||||
try {
|
||||
job();
|
||||
} catch (e: Dynamic) {
|
||||
jobError = e;
|
||||
}
|
||||
doneLock.release();
|
||||
}
|
||||
}
|
||||
|
||||
public static function parallelFor(count: Int, fn: Int -> Int -> Void) {
|
||||
init();
|
||||
var chunks = numWorkers + 1;
|
||||
var chunkSize = Std.int((count + chunks - 1) / chunks);
|
||||
if (chunkSize < 1) chunkSize = 1;
|
||||
var jobs = 0;
|
||||
var start = chunkSize; // Chunk 0 runs on the calling thread
|
||||
while (start < count) {
|
||||
var s = start;
|
||||
var e = start + chunkSize > count ? count : start + chunkSize;
|
||||
jobQueue.add(function() fn(s, e));
|
||||
jobs++;
|
||||
start = e;
|
||||
}
|
||||
fn(0, chunkSize > count ? count : chunkSize);
|
||||
for (_ in 0...jobs) doneLock.wait();
|
||||
if (jobError != null) {
|
||||
var e = jobError;
|
||||
jobError = null;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#end
|
||||
376
leenkx/Sources/iron/culling/ObjectBVH.hx
Normal file
376
leenkx/Sources/iron/culling/ObjectBVH.hx
Normal file
@ -0,0 +1,376 @@
|
||||
package iron.culling;
|
||||
|
||||
class ObjectBVH {
|
||||
|
||||
static inline var LEAF_THRESHOLD = 4;
|
||||
static inline var NUM_BINS = 12;
|
||||
static inline var TRAVERSAL_COST = 1.0;
|
||||
static inline var INTERSECT_COST = 1.0;
|
||||
|
||||
public var nodes: Array<BVHNode>;
|
||||
public var objectIndices: Array<Int>;
|
||||
public var root: Int = -1;
|
||||
public var objectCount: Int = 0;
|
||||
|
||||
var stack: Array<Int> = [];
|
||||
|
||||
static var binCounts: Array<Int> = [for (i in 0...NUM_BINS) 0];
|
||||
static var binMinX: Array<Float> = [for (i in 0...NUM_BINS) 0];
|
||||
static var binMinY: Array<Float> = [for (i in 0...NUM_BINS) 0];
|
||||
static var binMinZ: Array<Float> = [for (i in 0...NUM_BINS) 0];
|
||||
static var binMaxX: Array<Float> = [for (i in 0...NUM_BINS) 0];
|
||||
static var binMaxY: Array<Float> = [for (i in 0...NUM_BINS) 0];
|
||||
static var binMaxZ: Array<Float> = [for (i in 0...NUM_BINS) 0];
|
||||
|
||||
public function new() {
|
||||
nodes = [];
|
||||
objectIndices = [];
|
||||
}
|
||||
|
||||
public function build(cullData: CullData) {
|
||||
objectCount = cullData.count;
|
||||
nodes = [];
|
||||
objectIndices = [];
|
||||
|
||||
if (objectCount == 0) {
|
||||
root = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
var indices = [for (i in 0...objectCount) i];
|
||||
root = buildRecursive(indices, cullData);
|
||||
}
|
||||
|
||||
static inline function surfaceArea(minX: Float, minY: Float, minZ: Float,
|
||||
maxX: Float, maxY: Float, maxZ: Float): Float {
|
||||
var wx = maxX - minX;
|
||||
var wy = maxY - minY;
|
||||
var wz = maxZ - minZ;
|
||||
return 2.0 * (wx * wy + wx * wz + wy * wz);
|
||||
}
|
||||
|
||||
function buildRecursive(indices: Array<Int>, cullData: CullData): Int {
|
||||
var nodeIdx = nodes.length;
|
||||
var node = new BVHNode();
|
||||
nodes.push(node);
|
||||
|
||||
var minX = 1e20, minY = 1e20, minZ = 1e20;
|
||||
var maxX = -1e20, maxY = -1e20, maxZ = -1e20;
|
||||
var centMinX = 1e20, centMinY = 1e20, centMinZ = 1e20;
|
||||
var centMaxX = -1e20, centMaxY = -1e20, centMaxZ = -1e20;
|
||||
var cx = 0.0, cy = 0.0, cz = 0.0;
|
||||
|
||||
for (idx in indices) {
|
||||
var x = cullData.getCenterX(idx);
|
||||
var y = cullData.getCenterY(idx);
|
||||
var z = cullData.getCenterZ(idx);
|
||||
if (cullData.getMinX(idx) < minX) minX = cullData.getMinX(idx);
|
||||
if (cullData.getMinY(idx) < minY) minY = cullData.getMinY(idx);
|
||||
if (cullData.getMinZ(idx) < minZ) minZ = cullData.getMinZ(idx);
|
||||
if (cullData.getMaxX(idx) > maxX) maxX = cullData.getMaxX(idx);
|
||||
if (cullData.getMaxY(idx) > maxY) maxY = cullData.getMaxY(idx);
|
||||
if (cullData.getMaxZ(idx) > maxZ) maxZ = cullData.getMaxZ(idx);
|
||||
if (x < centMinX) centMinX = x;
|
||||
if (y < centMinY) centMinY = y;
|
||||
if (z < centMinZ) centMinZ = z;
|
||||
if (x > centMaxX) centMaxX = x;
|
||||
if (y > centMaxY) centMaxY = y;
|
||||
if (z > centMaxZ) centMaxZ = z;
|
||||
cx += x;
|
||||
cy += y;
|
||||
cz += z;
|
||||
}
|
||||
|
||||
var n = indices.length;
|
||||
cx /= n;
|
||||
cy /= n;
|
||||
cz /= n;
|
||||
|
||||
node.minX = minX; node.minY = minY; node.minZ = minZ;
|
||||
node.maxX = maxX; node.maxY = maxY; node.maxZ = maxZ;
|
||||
node.centerX = cx;
|
||||
node.centerY = cy;
|
||||
node.centerZ = cz;
|
||||
|
||||
if (n <= LEAF_THRESHOLD) {
|
||||
node.isLeaf = true;
|
||||
node.firstObject = objectIndices.length;
|
||||
for (idx in indices) objectIndices.push(idx);
|
||||
node.objectCount = n;
|
||||
return nodeIdx;
|
||||
}
|
||||
|
||||
var parentSA = surfaceArea(minX, minY, minZ, maxX, maxY, maxZ);
|
||||
var leafCost = INTERSECT_COST * n;
|
||||
|
||||
var bestCost = 1e20;
|
||||
var bestAxis = -1;
|
||||
var bestSplitBin = -1;
|
||||
|
||||
var centBounds = [
|
||||
[centMinX, centMaxX],
|
||||
[centMinY, centMaxY],
|
||||
[centMinZ, centMaxZ]
|
||||
];
|
||||
|
||||
for (axis in 0...3) {
|
||||
var lo = centBounds[axis][0];
|
||||
var hi = centBounds[axis][1];
|
||||
var range = hi - lo;
|
||||
if (range < 1e-8) continue;
|
||||
|
||||
var invRange = NUM_BINS / range;
|
||||
|
||||
for (b in 0...NUM_BINS) {
|
||||
binCounts[b] = 0;
|
||||
binMinX[b] = 1e20; binMinY[b] = 1e20; binMinZ[b] = 1e20;
|
||||
binMaxX[b] = -1e20; binMaxY[b] = -1e20; binMaxZ[b] = -1e20;
|
||||
}
|
||||
|
||||
for (idx in indices) {
|
||||
var cv = axis == 0 ? cullData.getCenterX(idx) :
|
||||
axis == 1 ? cullData.getCenterY(idx) :
|
||||
cullData.getCenterZ(idx);
|
||||
var b = Std.int((cv - lo) * invRange);
|
||||
if (b < 0) b = 0;
|
||||
if (b >= NUM_BINS) b = NUM_BINS - 1;
|
||||
binCounts[b]++;
|
||||
if (cullData.getMinX(idx) < binMinX[b]) binMinX[b] = cullData.getMinX(idx);
|
||||
if (cullData.getMinY(idx) < binMinY[b]) binMinY[b] = cullData.getMinY(idx);
|
||||
if (cullData.getMinZ(idx) < binMinZ[b]) binMinZ[b] = cullData.getMinZ(idx);
|
||||
if (cullData.getMaxX(idx) > binMaxX[b]) binMaxX[b] = cullData.getMaxX(idx);
|
||||
if (cullData.getMaxY(idx) > binMaxY[b]) binMaxY[b] = cullData.getMaxY(idx);
|
||||
if (cullData.getMaxZ(idx) > binMaxZ[b]) binMaxZ[b] = cullData.getMaxZ(idx);
|
||||
}
|
||||
|
||||
var rightCount = [for (b in 0...NUM_BINS) 0];
|
||||
var rightMinX = [for (b in 0...NUM_BINS) 0.0];
|
||||
var rightMinY = [for (b in 0...NUM_BINS) 0.0];
|
||||
var rightMinZ = [for (b in 0...NUM_BINS) 0.0];
|
||||
var rightMaxX = [for (b in 0...NUM_BINS) 0.0];
|
||||
var rightMaxY = [for (b in 0...NUM_BINS) 0.0];
|
||||
var rightMaxZ = [for (b in 0...NUM_BINS) 0.0];
|
||||
|
||||
var cumCount = 0;
|
||||
var cumMinX = 1e20, cumMinY = 1e20, cumMinZ = 1e20;
|
||||
var cumMaxX = -1e20, cumMaxY = -1e20, cumMaxZ = -1e20;
|
||||
|
||||
for (b in 0...NUM_BINS) {
|
||||
var bi = NUM_BINS - 1 - b;
|
||||
cumCount += binCounts[bi];
|
||||
if (binMinX[bi] < cumMinX) cumMinX = binMinX[bi];
|
||||
if (binMinY[bi] < cumMinY) cumMinY = binMinY[bi];
|
||||
if (binMinZ[bi] < cumMinZ) cumMinZ = binMinZ[bi];
|
||||
if (binMaxX[bi] > cumMaxX) cumMaxX = binMaxX[bi];
|
||||
if (binMaxY[bi] > cumMaxY) cumMaxY = binMaxY[bi];
|
||||
if (binMaxZ[bi] > cumMaxZ) cumMaxZ = binMaxZ[bi];
|
||||
rightCount[bi] = cumCount;
|
||||
rightMinX[bi] = cumMinX; rightMinY[bi] = cumMinY; rightMinZ[bi] = cumMinZ;
|
||||
rightMaxX[bi] = cumMaxX; rightMaxY[bi] = cumMaxY; rightMaxZ[bi] = cumMaxZ;
|
||||
}
|
||||
|
||||
var leftCount = 0;
|
||||
var leftMinX = 1e20, leftMinY = 1e20, leftMinZ = 1e20;
|
||||
var leftMaxX = -1e20, leftMaxY = -1e20, leftMaxZ = -1e20;
|
||||
|
||||
for (b in 0...NUM_BINS - 1) {
|
||||
if (binCounts[b] == 0) continue;
|
||||
leftCount += binCounts[b];
|
||||
if (binMinX[b] < leftMinX) leftMinX = binMinX[b];
|
||||
if (binMinY[b] < leftMinY) leftMinY = binMinY[b];
|
||||
if (binMinZ[b] < leftMinZ) leftMinZ = binMinZ[b];
|
||||
if (binMaxX[b] > leftMaxX) leftMaxX = binMaxX[b];
|
||||
if (binMaxY[b] > leftMaxY) leftMaxY = binMaxY[b];
|
||||
if (binMaxZ[b] > leftMaxZ) leftMaxZ = binMaxZ[b];
|
||||
|
||||
var rc = rightCount[b + 1];
|
||||
if (rc == 0) continue;
|
||||
|
||||
var leftSA = surfaceArea(leftMinX, leftMinY, leftMinZ, leftMaxX, leftMaxY, leftMaxZ);
|
||||
var rightSA = surfaceArea(rightMinX[b + 1], rightMinY[b + 1], rightMinZ[b + 1],
|
||||
rightMaxX[b + 1], rightMaxY[b + 1], rightMaxZ[b + 1]);
|
||||
var cost = TRAVERSAL_COST + (leftSA * leftCount + rightSA * rc) / parentSA;
|
||||
|
||||
if (cost < bestCost) {
|
||||
bestCost = cost;
|
||||
bestAxis = axis;
|
||||
bestSplitBin = b;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bestAxis < 0 || bestCost >= leafCost) {
|
||||
node.isLeaf = true;
|
||||
node.firstObject = objectIndices.length;
|
||||
for (idx in indices) objectIndices.push(idx);
|
||||
node.objectCount = n;
|
||||
return nodeIdx;
|
||||
}
|
||||
|
||||
var lo = centBounds[bestAxis][0];
|
||||
var hi = centBounds[bestAxis][1];
|
||||
var invRange = NUM_BINS / (hi - lo);
|
||||
var splitBin = bestSplitBin;
|
||||
|
||||
var left = [];
|
||||
var right = [];
|
||||
for (idx in indices) {
|
||||
var cv = bestAxis == 0 ? cullData.getCenterX(idx) :
|
||||
bestAxis == 1 ? cullData.getCenterY(idx) :
|
||||
cullData.getCenterZ(idx);
|
||||
var b = Std.int((cv - lo) * invRange);
|
||||
if (b < 0) b = 0;
|
||||
if (b >= NUM_BINS) b = NUM_BINS - 1;
|
||||
if (b <= splitBin) left.push(idx);
|
||||
else right.push(idx);
|
||||
}
|
||||
|
||||
if (left.length == 0 || right.length == 0) {
|
||||
indices.sort(function(a, b): Int {
|
||||
var av = bestAxis == 0 ? cullData.getCenterX(a) :
|
||||
bestAxis == 1 ? cullData.getCenterY(a) :
|
||||
cullData.getCenterZ(a);
|
||||
var bv = bestAxis == 0 ? cullData.getCenterX(b) :
|
||||
bestAxis == 1 ? cullData.getCenterY(b) :
|
||||
cullData.getCenterZ(b);
|
||||
if (av < bv) return -1;
|
||||
if (av > bv) return 1;
|
||||
return 0;
|
||||
});
|
||||
var mid = n >> 1;
|
||||
left = indices.slice(0, mid);
|
||||
right = indices.slice(mid);
|
||||
}
|
||||
|
||||
node.isLeaf = false;
|
||||
node.leftChild = buildRecursive(left, cullData);
|
||||
node.rightChild = buildRecursive(right, cullData);
|
||||
|
||||
return nodeIdx;
|
||||
}
|
||||
|
||||
|
||||
public function refit(cullData: CullData) {
|
||||
var i = nodes.length - 1;
|
||||
while (i >= 0) {
|
||||
var node = nodes[i];
|
||||
if (node.isLeaf) {
|
||||
var minX = 1e20, minY = 1e20, minZ = 1e20;
|
||||
var maxX = -1e20, maxY = -1e20, maxZ = -1e20;
|
||||
for (j in 0...node.objectCount) {
|
||||
var idx = objectIndices[node.firstObject + j];
|
||||
if (cullData.getMinX(idx) < minX) minX = cullData.getMinX(idx);
|
||||
if (cullData.getMinY(idx) < minY) minY = cullData.getMinY(idx);
|
||||
if (cullData.getMinZ(idx) < minZ) minZ = cullData.getMinZ(idx);
|
||||
if (cullData.getMaxX(idx) > maxX) maxX = cullData.getMaxX(idx);
|
||||
if (cullData.getMaxY(idx) > maxY) maxY = cullData.getMaxY(idx);
|
||||
if (cullData.getMaxZ(idx) > maxZ) maxZ = cullData.getMaxZ(idx);
|
||||
}
|
||||
node.minX = minX; node.minY = minY; node.minZ = minZ;
|
||||
node.maxX = maxX; node.maxY = maxY; node.maxZ = maxZ;
|
||||
} else {
|
||||
var l = nodes[node.leftChild];
|
||||
var r = nodes[node.rightChild];
|
||||
node.minX = l.minX < r.minX ? l.minX : r.minX;
|
||||
node.minY = l.minY < r.minY ? l.minY : r.minY;
|
||||
node.minZ = l.minZ < r.minZ ? l.minZ : r.minZ;
|
||||
node.maxX = l.maxX > r.maxX ? l.maxX : r.maxX;
|
||||
node.maxY = l.maxY > r.maxY ? l.maxY : r.maxY;
|
||||
node.maxZ = l.maxZ > r.maxZ ? l.maxZ : r.maxZ;
|
||||
}
|
||||
node.centerX = (node.minX + node.maxX) * 0.5;
|
||||
node.centerY = (node.minY + node.maxY) * 0.5;
|
||||
node.centerZ = (node.minZ + node.maxZ) * 0.5;
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
public function forEachLeafInSubtree(node: BVHNode, cb: (Int) -> Void) {
|
||||
if (node.isLeaf) {
|
||||
for (i in 0...node.objectCount) {
|
||||
cb(objectIndices[node.firstObject + i]);
|
||||
}
|
||||
} else {
|
||||
forEachLeafInSubtree(nodes[node.leftChild], cb);
|
||||
forEachLeafInSubtree(nodes[node.rightChild], cb);
|
||||
}
|
||||
}
|
||||
|
||||
public function traverseFrontToBack(
|
||||
camX: Float, camY: Float, camZ: Float,
|
||||
shouldSkip: (BVHNode) -> Bool,
|
||||
onLeaf: (Int, Int) -> Void
|
||||
) {
|
||||
if (root < 0) return;
|
||||
|
||||
stack.resize(0);
|
||||
stack.push(root);
|
||||
|
||||
while (stack.length > 0) {
|
||||
var nodeIdx = stack.pop();
|
||||
var node = nodes[nodeIdx];
|
||||
|
||||
if (shouldSkip(node)) continue;
|
||||
|
||||
if (node.isLeaf) {
|
||||
var start = node.firstObject;
|
||||
for (i in 0...node.objectCount) {
|
||||
onLeaf(objectIndices[start + i], i);
|
||||
}
|
||||
} else {
|
||||
var leftNode = nodes[node.leftChild];
|
||||
var rightNode = nodes[node.rightChild];
|
||||
var leftDist = distSq(leftNode.centerX, leftNode.centerY, leftNode.centerZ, camX, camY, camZ);
|
||||
var rightDist = distSq(rightNode.centerX, rightNode.centerY, rightNode.centerZ, camX, camY, camZ);
|
||||
|
||||
if (leftDist > rightDist) {
|
||||
stack.push(node.leftChild);
|
||||
stack.push(node.rightChild);
|
||||
} else {
|
||||
stack.push(node.rightChild);
|
||||
stack.push(node.leftChild);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline function distSq(ax: Float, ay: Float, az: Float, bx: Float, by: Float, bz: Float): Float {
|
||||
var dx = ax - bx;
|
||||
var dy = ay - by;
|
||||
var dz = az - bz;
|
||||
return dx * dx + dy * dy + dz * dz;
|
||||
}
|
||||
|
||||
public static function aabbInFrustumRaw(
|
||||
minX: Float, minY: Float, minZ: Float,
|
||||
maxX: Float, maxY: Float, maxZ: Float,
|
||||
planes: Array<iron.object.FrustumPlane>
|
||||
): Bool {
|
||||
for (i in 0...planes.length) {
|
||||
var plane = planes[i];
|
||||
var px = plane.normal.x >= 0 ? maxX : minX;
|
||||
var py = plane.normal.y >= 0 ? maxY : minY;
|
||||
var pz = plane.normal.z >= 0 ? maxZ : minZ;
|
||||
if (SimdOps.dot3(px, py, pz, plane.normal.x, plane.normal.y, plane.normal.z) + plane.constant < 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function aabbInFrustum(
|
||||
node: BVHNode,
|
||||
planes: Array<iron.object.FrustumPlane>
|
||||
): Bool {
|
||||
for (i in 0...planes.length) {
|
||||
var plane = planes[i];
|
||||
var px = plane.normal.x >= 0 ? node.maxX : node.minX;
|
||||
var py = plane.normal.y >= 0 ? node.maxY : node.minY;
|
||||
var pz = plane.normal.z >= 0 ? node.maxZ : node.minZ;
|
||||
if (SimdOps.dot3(px, py, pz, plane.normal.x, plane.normal.y, plane.normal.z) + plane.constant < 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
71
leenkx/Sources/iron/culling/OccluderProxy.hx
Normal file
71
leenkx/Sources/iron/culling/OccluderProxy.hx
Normal file
@ -0,0 +1,71 @@
|
||||
package iron.culling;
|
||||
|
||||
class OccluderProxy {
|
||||
|
||||
public var positions: kha.arrays.Float32Array;
|
||||
public var indices: kha.arrays.Uint32Array;
|
||||
public var triangleCount: Int;
|
||||
public var meshUid: Int;
|
||||
|
||||
var worldPositions: kha.arrays.Float32Array;
|
||||
var mvp: iron.math.Mat4;
|
||||
|
||||
public function new() {
|
||||
indices = new kha.arrays.Uint32Array(0);
|
||||
triangleCount = 0;
|
||||
}
|
||||
|
||||
public function setData(pos: kha.arrays.Float32Array, idx: kha.arrays.Uint32Array, uid: Int) {
|
||||
#if js
|
||||
if (!Std.isOfType(pos, js.lib.DataView)) {
|
||||
var srcF: Array<Float> = cast pos;
|
||||
var dstF = new kha.arrays.Float32Array(srcF.length);
|
||||
for (i in 0...srcF.length) dstF[i] = srcF[i];
|
||||
pos = dstF;
|
||||
}
|
||||
if (!Std.isOfType(idx, js.lib.DataView)) {
|
||||
var srcI: Array<Int> = cast idx;
|
||||
var dstI = new kha.arrays.Uint32Array(srcI.length);
|
||||
for (i in 0...srcI.length) dstI[i] = srcI[i];
|
||||
idx = dstI;
|
||||
}
|
||||
#end
|
||||
positions = pos;
|
||||
indices = idx;
|
||||
triangleCount = Std.int(idx.length / 3);
|
||||
meshUid = uid;
|
||||
}
|
||||
|
||||
public function rasterize(
|
||||
rasterizer: SoftwareOcclusionRasterizer,
|
||||
world: iron.math.Mat4,
|
||||
vp: iron.math.Mat4,
|
||||
screenW: Int, screenH: Int
|
||||
) {
|
||||
if (positions == null || indices == null || triangleCount == 0) return;
|
||||
|
||||
var vertCount = Std.int(positions.length / 3);
|
||||
|
||||
var clipSize = vertCount * 4;
|
||||
if (worldPositions == null || worldPositions.length < clipSize) {
|
||||
worldPositions = new kha.arrays.Float32Array(clipSize);
|
||||
}
|
||||
|
||||
if (mvp == null) mvp = iron.math.Mat4.identity();
|
||||
mvp.multmats(vp, world);
|
||||
|
||||
for (i in 0...vertCount) {
|
||||
var x = positions[i * 3];
|
||||
var y = positions[i * 3 + 1];
|
||||
var z = positions[i * 3 + 2];
|
||||
var off = i * 4;
|
||||
worldPositions[off] = mvp._00 * x + mvp._10 * y + mvp._20 * z + mvp._30;
|
||||
worldPositions[off + 1] = mvp._01 * x + mvp._11 * y + mvp._21 * z + mvp._31;
|
||||
worldPositions[off + 2] = mvp._02 * x + mvp._12 * y + mvp._22 * z + mvp._32;
|
||||
worldPositions[off + 3] = mvp._03 * x + mvp._13 * y + mvp._23 * z + mvp._33;
|
||||
}
|
||||
|
||||
rasterizer.rasterizeProxyTransformed(worldPositions, indices, screenW, screenH);
|
||||
}
|
||||
|
||||
}
|
||||
83
leenkx/Sources/iron/culling/SimdOps.hx
Normal file
83
leenkx/Sources/iron/culling/SimdOps.hx
Normal file
@ -0,0 +1,83 @@
|
||||
package iron.culling;
|
||||
|
||||
#if cpp
|
||||
@:include("smmintrin.h")
|
||||
@:include("emmintrin.h")
|
||||
@:include("xmmintrin.h")
|
||||
#end
|
||||
class SimdOps {
|
||||
|
||||
public static inline function dot4(
|
||||
a0: Float, a1: Float, a2: Float, a3: Float,
|
||||
b0: Float, b1: Float, b2: Float, b3: Float
|
||||
): Float {
|
||||
#if cpp
|
||||
return untyped __cpp__("_mm_cvtss_f32(_mm_dp_ps(_mm_setr_ps({0}, {1}, {2}, {3}), _mm_setr_ps({4}, {5}, {6}, {7}), 0xF1))",
|
||||
a0, a1, a2, a3, b0, b1, b2, b3);
|
||||
#else
|
||||
return a0 * b0 + a1 * b1 + a2 * b2 + a3 * b3;
|
||||
#end
|
||||
}
|
||||
|
||||
public static inline function dot3(
|
||||
a0: Float, a1: Float, a2: Float,
|
||||
b0: Float, b1: Float, b2: Float
|
||||
): Float {
|
||||
#if cpp
|
||||
return untyped __cpp__("_mm_cvtss_f32(_mm_dp_ps(_mm_setr_ps({0}, {1}, {2}, 0.0f), _mm_setr_ps({3}, {4}, {5}, 0.0f), 0x71))",
|
||||
a0, a1, a2, b0, b1, b2);
|
||||
#else
|
||||
return a0 * b0 + a1 * b1 + a2 * b2;
|
||||
#end
|
||||
}
|
||||
|
||||
public static inline function reciprocal(x: Float): Float {
|
||||
#if cpp
|
||||
return untyped __cpp__("({ auto _r = _mm_rcp_ss(_mm_set_ss({0})); _mm_cvtss_f32(_mm_add_ss(_mm_mul_ss(_r, _mm_sub_ss(_mm_set_ss(2.0f), _mm_mul_ss(_r, _mm_set_ss({0})))), _r)); })", x);
|
||||
#else
|
||||
return 1.0 / x;
|
||||
#end
|
||||
}
|
||||
|
||||
public static inline function max4f(
|
||||
a: Float, b: Float, c: Float, d: Float
|
||||
): Float {
|
||||
#if cpp
|
||||
return untyped __cpp__("_mm_cvtss_f32(_mm_max_ss(_mm_max_ss(_mm_set_ss({0}), _mm_set_ss({1})), _mm_max_ss(_mm_set_ss({2}), _mm_set_ss({3}))))",
|
||||
a, b, c, d);
|
||||
#else
|
||||
var m = a;
|
||||
if (b > m) m = b;
|
||||
if (c > m) m = c;
|
||||
if (d > m) m = d;
|
||||
return m;
|
||||
#end
|
||||
}
|
||||
|
||||
public static inline function min4f(
|
||||
a: Float, b: Float, c: Float, d: Float
|
||||
): Float {
|
||||
#if cpp
|
||||
return untyped __cpp__("_mm_cvtss_f32(_mm_min_ss(_mm_min_ss(_mm_set_ss({0}), _mm_set_ss({1})), _mm_min_ss(_mm_set_ss({2}), _mm_set_ss({3}))))",
|
||||
a, b, c, d);
|
||||
#else
|
||||
var m = a;
|
||||
if (b < m) m = b;
|
||||
if (c < m) m = c;
|
||||
if (d < m) m = d;
|
||||
return m;
|
||||
#end
|
||||
}
|
||||
|
||||
public static inline function frustumSphereTest6(
|
||||
planeData: kha.arrays.Float32Array,
|
||||
cx: Float, cy: Float, cz: Float, radius: Float
|
||||
): Bool {
|
||||
for (i in 0...6) {
|
||||
var base = i * 4;
|
||||
var dist = dot3(cx, cy, cz, planeData[base], planeData[base + 1], planeData[base + 2]) + planeData[base + 3];
|
||||
if (dist + radius < 0) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
507
leenkx/Sources/iron/culling/SoftwareOcclusionRasterizer.hx
Normal file
507
leenkx/Sources/iron/culling/SoftwareOcclusionRasterizer.hx
Normal file
@ -0,0 +1,507 @@
|
||||
package iron.culling;
|
||||
|
||||
class SoftwareOcclusionRasterizer {
|
||||
|
||||
public var hiz: HiZPyramid;
|
||||
public var width: Int;
|
||||
public var height: Int;
|
||||
|
||||
public var nearClip: Float = 0.0001;
|
||||
|
||||
static inline var TILE_SIZE = 32;
|
||||
|
||||
public var trianglesRasterized: Int = 0;
|
||||
|
||||
public var collectMode = false;
|
||||
var screenTris: Array<Float> = [];
|
||||
var screenTriCount = 0;
|
||||
|
||||
static final BOX_FACES = [
|
||||
[0, 1, 3, 2], // -Z
|
||||
[4, 5, 7, 6], // +Z
|
||||
[0, 1, 5, 4], // -Y
|
||||
[2, 3, 7, 6], // +Y
|
||||
[0, 2, 6, 4], // -X
|
||||
[1, 3, 7, 5], // +X
|
||||
];
|
||||
|
||||
var inX: Array<Float> = [0,0,0];
|
||||
var inY: Array<Float> = [0,0,0];
|
||||
var inZ: Array<Float> = [0,0,0];
|
||||
var inW: Array<Float> = [0,0,0];
|
||||
var outX: Array<Float> = [0,0,0,0];
|
||||
var outY: Array<Float> = [0,0,0,0];
|
||||
var outZ: Array<Float> = [0,0,0,0];
|
||||
var outW: Array<Float> = [0,0,0,0];
|
||||
var projSx: Array<Float> = [0,0,0,0];
|
||||
var projSy: Array<Float> = [0,0,0,0];
|
||||
var projSz: Array<Float> = [0,0,0,0];
|
||||
|
||||
var boxClipX: Array<Float> = [0,0,0,0,0,0,0,0];
|
||||
var boxClipY: Array<Float> = [0,0,0,0,0,0,0,0];
|
||||
var boxClipZ: Array<Float> = [0,0,0,0,0,0,0,0];
|
||||
var boxClipW: Array<Float> = [0,0,0,0,0,0,0,0];
|
||||
|
||||
public function new(width: Int = 256, height: Int = 128) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
hiz = new HiZPyramid(width, height);
|
||||
}
|
||||
|
||||
public function clear() {
|
||||
hiz.clear(1e20);
|
||||
trianglesRasterized = 0;
|
||||
}
|
||||
|
||||
public function rasterizeTriangle(
|
||||
v0x: Float, v0y: Float, v0z: Float,
|
||||
v1x: Float, v1y: Float, v1z: Float,
|
||||
v2x: Float, v2y: Float, v2z: Float
|
||||
) {
|
||||
var e0a = v1y - v0y; var e0b = v0x - v1x;
|
||||
var e1a = v2y - v1y; var e1b = v1x - v2x;
|
||||
var e2a = v0y - v2y; var e2b = v2x - v0x;
|
||||
var e0c = -e0a * v0x - e0b * v0y;
|
||||
var e1c = -e1a * v1x - e1b * v1y;
|
||||
var e2c = -e2a * v2x - e2b * v2y;
|
||||
|
||||
var area = e0a * v2x + e0b * v2y + e0c;
|
||||
if (area < 0) {
|
||||
area = -area;
|
||||
e0a = -e0a; e0b = -e0b; e0c = -e0c;
|
||||
e1a = -e1a; e1b = -e1b; e1c = -e1c;
|
||||
e2a = -e2a; e2b = -e2b; e2c = -e2c;
|
||||
}
|
||||
if (area < 1e-8) return;
|
||||
|
||||
var minX = Std.int(Math.max(0, Math.floor(Math.min(v0x, Math.min(v1x, v2x)))));
|
||||
var maxX = Std.int(Math.min(width - 1, Math.ceil(Math.max(v0x, Math.max(v1x, v2x)))));
|
||||
var minY = Std.int(Math.max(0, Math.floor(Math.min(v0y, Math.min(v1y, v2y)))));
|
||||
var maxY = Std.int(Math.min(height - 1, Math.ceil(Math.max(v0y, Math.max(v1y, v2y)))));
|
||||
if (minX > maxX || minY > maxY) return;
|
||||
|
||||
var d = (v1x - v0x) * (v2y - v0y) - (v2x - v0x) * (v1y - v0y);
|
||||
var invD = 1.0 / d;
|
||||
var dzdx = ((v1z - v0z) * (v2y - v0y) - (v2z - v0z) * (v1y - v0y)) * invD;
|
||||
var dzdy = ((v2z - v0z) * (v1x - v0x) - (v1z - v0z) * (v2x - v0x)) * invD;
|
||||
|
||||
var zPush = (Math.abs(dzdx) + Math.abs(dzdy)) * 0.5;
|
||||
var zMaxTri = v0z;
|
||||
if (v1z > zMaxTri) zMaxTri = v1z;
|
||||
if (v2z > zMaxTri) zMaxTri = v2z;
|
||||
|
||||
var cx0 = minX + 0.5;
|
||||
var cy0 = minY + 0.5;
|
||||
var e0row = e0a * cx0 + e0b * cy0 + e0c;
|
||||
var e1row = e1a * cx0 + e1b * cy0 + e1c;
|
||||
var e2row = e2a * cx0 + e2b * cy0 + e2c;
|
||||
var zrow = v0z + dzdx * (cx0 - v0x) + dzdy * (cy0 - v0y);
|
||||
|
||||
var buf = hiz.mipBuffers[0];
|
||||
var w = width;
|
||||
for (py in minY...maxY + 1) {
|
||||
var e0 = e0row; var e1 = e1row; var e2 = e2row;
|
||||
var z = zrow;
|
||||
var idx = py * w + minX;
|
||||
for (px in minX...maxX + 1) {
|
||||
if (e0 >= 0 && e1 >= 0 && e2 >= 0) {
|
||||
var zPix = z + zPush;
|
||||
if (zPix > zMaxTri) zPix = zMaxTri;
|
||||
if (zPix < 0.0) zPix = 0.0;
|
||||
if (zPix > 1.0) zPix = 1.0;
|
||||
if (zPix < buf[idx]) buf[idx] = zPix;
|
||||
}
|
||||
e0 += e0a; e1 += e1a; e2 += e2a;
|
||||
z += dzdx;
|
||||
idx++;
|
||||
}
|
||||
e0row += e0b; e1row += e1b; e2row += e2b;
|
||||
zrow += dzdy;
|
||||
}
|
||||
}
|
||||
|
||||
function clipNearAndRasterize(
|
||||
cx0: Float, cy0: Float, cz0: Float, cw0: Float,
|
||||
cx1: Float, cy1: Float, cz1: Float, cw1: Float,
|
||||
cx2: Float, cy2: Float, cz2: Float, cw2: Float
|
||||
) {
|
||||
inX[0] = cx0; inY[0] = cy0; inZ[0] = cz0; inW[0] = cw0;
|
||||
inX[1] = cx1; inY[1] = cy1; inZ[1] = cz1; inW[1] = cw1;
|
||||
inX[2] = cx2; inY[2] = cy2; inZ[2] = cz2; inW[2] = cw2;
|
||||
|
||||
var outCount = 0;
|
||||
for (i in 0...3) {
|
||||
var aIdx = (i + 2) % 3;
|
||||
var bIdx = i;
|
||||
var aInside = inW[aIdx] >= nearClip;
|
||||
var bInside = inW[bIdx] >= nearClip;
|
||||
|
||||
if (aInside) {
|
||||
if (bInside) {
|
||||
outX[outCount] = inX[bIdx]; outY[outCount] = inY[bIdx];
|
||||
outZ[outCount] = inZ[bIdx]; outW[outCount] = inW[bIdx];
|
||||
outCount++;
|
||||
} else {
|
||||
var t = (inW[aIdx] - nearClip) / (inW[aIdx] - inW[bIdx]);
|
||||
outX[outCount] = inX[aIdx] + t * (inX[bIdx] - inX[aIdx]);
|
||||
outY[outCount] = inY[aIdx] + t * (inY[bIdx] - inY[aIdx]);
|
||||
outZ[outCount] = inZ[aIdx] + t * (inZ[bIdx] - inZ[aIdx]);
|
||||
outW[outCount] = inW[aIdx] + t * (inW[bIdx] - inW[aIdx]);
|
||||
outCount++;
|
||||
}
|
||||
} else {
|
||||
if (bInside) {
|
||||
var t = (inW[aIdx] - nearClip) / (inW[aIdx] - inW[bIdx]);
|
||||
outX[outCount] = inX[aIdx] + t * (inX[bIdx] - inX[aIdx]);
|
||||
outY[outCount] = inY[aIdx] + t * (inY[bIdx] - inY[aIdx]);
|
||||
outZ[outCount] = inZ[aIdx] + t * (inZ[bIdx] - inZ[aIdx]);
|
||||
outW[outCount] = inW[aIdx] + t * (inW[bIdx] - inW[aIdx]);
|
||||
outCount++;
|
||||
outX[outCount] = inX[bIdx]; outY[outCount] = inY[bIdx];
|
||||
outZ[outCount] = inZ[bIdx]; outW[outCount] = inW[bIdx];
|
||||
outCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (outCount < 3) return;
|
||||
|
||||
for (i in 0...outCount) {
|
||||
var invW = SimdOps.reciprocal(outW[i]);
|
||||
projSx[i] = (outX[i] * invW * 0.5 + 0.5) * width;
|
||||
projSy[i] = (outY[i] * invW * 0.5 + 0.5) * height;
|
||||
projSz[i] = outZ[i] * invW;
|
||||
}
|
||||
|
||||
var signedArea = (projSx[1] - projSx[0]) * (projSy[2] - projSy[0]) -
|
||||
(projSy[1] - projSy[0]) * (projSx[2] - projSx[0]);
|
||||
if (Math.abs(signedArea) < 1e-8) return;
|
||||
|
||||
trianglesRasterized++;
|
||||
|
||||
if (collectMode) {
|
||||
screenTris[screenTriCount++] = projSx[0]; screenTris[screenTriCount++] = projSy[0]; screenTris[screenTriCount++] = projSz[0];
|
||||
screenTris[screenTriCount++] = projSx[1]; screenTris[screenTriCount++] = projSy[1]; screenTris[screenTriCount++] = projSz[1];
|
||||
screenTris[screenTriCount++] = projSx[2]; screenTris[screenTriCount++] = projSy[2]; screenTris[screenTriCount++] = projSz[2];
|
||||
if (outCount == 4) {
|
||||
screenTris[screenTriCount++] = projSx[0]; screenTris[screenTriCount++] = projSy[0]; screenTris[screenTriCount++] = projSz[0];
|
||||
screenTris[screenTriCount++] = projSx[2]; screenTris[screenTriCount++] = projSy[2]; screenTris[screenTriCount++] = projSz[2];
|
||||
screenTris[screenTriCount++] = projSx[3]; screenTris[screenTriCount++] = projSy[3]; screenTris[screenTriCount++] = projSz[3];
|
||||
}
|
||||
} else {
|
||||
rasterizeTriangle(
|
||||
projSx[0], projSy[0], projSz[0],
|
||||
projSx[1], projSy[1], projSz[1],
|
||||
projSx[2], projSy[2], projSz[2]
|
||||
);
|
||||
if (outCount == 4) {
|
||||
rasterizeTriangle(
|
||||
projSx[0], projSy[0], projSz[0],
|
||||
projSx[2], projSy[2], projSz[2],
|
||||
projSx[3], projSy[3], projSz[3]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function rasterizeProxy(
|
||||
positions: kha.arrays.Float32Array,
|
||||
indices: kha.arrays.Uint32Array,
|
||||
vp: iron.math.Mat4,
|
||||
screenW: Int, screenH: Int
|
||||
) {
|
||||
var triCount = Std.int(indices.length / 3);
|
||||
|
||||
var m00 = vp._00, m10 = vp._10, m20 = vp._20, m30 = vp._30;
|
||||
var m01 = vp._01, m11 = vp._11, m21 = vp._21, m31 = vp._31;
|
||||
var m02 = vp._02, m12 = vp._12, m22 = vp._22, m32 = vp._32;
|
||||
var m03 = vp._03, m13 = vp._13, m23 = vp._23, m33 = vp._33;
|
||||
|
||||
for (t in 0...triCount) {
|
||||
var i0 = indices[t * 3] * 3;
|
||||
var i1 = indices[t * 3 + 1] * 3;
|
||||
var i2 = indices[t * 3 + 2] * 3;
|
||||
|
||||
var px0 = positions[i0], py0 = positions[i0 + 1], pz0 = positions[i0 + 2];
|
||||
var px1 = positions[i1], py1 = positions[i1 + 1], pz1 = positions[i1 + 2];
|
||||
var px2 = positions[i2], py2 = positions[i2 + 1], pz2 = positions[i2 + 2];
|
||||
|
||||
var cw0 = m03 * px0 + m13 * py0 + m23 * pz0 + m33;
|
||||
var cw1 = m03 * px1 + m13 * py1 + m23 * pz1 + m33;
|
||||
var cw2 = m03 * px2 + m13 * py2 + m23 * pz2 + m33;
|
||||
|
||||
if (cw0 < nearClip && cw1 < nearClip && cw2 < nearClip) continue;
|
||||
|
||||
var cx0 = m00 * px0 + m10 * py0 + m20 * pz0 + m30;
|
||||
var cy0 = m01 * px0 + m11 * py0 + m21 * pz0 + m31;
|
||||
var cz0 = m02 * px0 + m12 * py0 + m22 * pz0 + m32;
|
||||
|
||||
var cx1 = m00 * px1 + m10 * py1 + m20 * pz1 + m30;
|
||||
var cy1 = m01 * px1 + m11 * py1 + m21 * pz1 + m31;
|
||||
var cz1 = m02 * px1 + m12 * py1 + m22 * pz1 + m32;
|
||||
|
||||
var cx2 = m00 * px2 + m10 * py2 + m20 * pz2 + m30;
|
||||
var cy2 = m01 * px2 + m11 * py2 + m21 * pz2 + m31;
|
||||
var cz2 = m02 * px2 + m12 * py2 + m22 * pz2 + m32;
|
||||
|
||||
clipNearAndRasterize(
|
||||
cx0, cy0, cz0, cw0,
|
||||
cx1, cy1, cz1, cw1,
|
||||
cx2, cy2, cz2, cw2
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function rasterizeProxyTransformed(
|
||||
clipPositions: kha.arrays.Float32Array,
|
||||
indices: kha.arrays.Uint32Array,
|
||||
screenW: Int, screenH: Int
|
||||
) {
|
||||
var triCount = Std.int(indices.length / 3);
|
||||
|
||||
for (t in 0...triCount) {
|
||||
var i0 = indices[t * 3] * 4;
|
||||
var i1 = indices[t * 3 + 1] * 4;
|
||||
var i2 = indices[t * 3 + 2] * 4;
|
||||
|
||||
var cx0 = clipPositions[i0], cy0 = clipPositions[i0 + 1], cz0 = clipPositions[i0 + 2], cw0 = clipPositions[i0 + 3];
|
||||
var cx1 = clipPositions[i1], cy1 = clipPositions[i1 + 1], cz1 = clipPositions[i1 + 2], cw1 = clipPositions[i1 + 3];
|
||||
var cx2 = clipPositions[i2], cy2 = clipPositions[i2 + 1], cz2 = clipPositions[i2 + 2], cw2 = clipPositions[i2 + 3];
|
||||
|
||||
if (cw0 < nearClip && cw1 < nearClip && cw2 < nearClip) continue;
|
||||
|
||||
clipNearAndRasterize(
|
||||
cx0, cy0, cz0, cw0,
|
||||
cx1, cy1, cz1, cw1,
|
||||
cx2, cy2, cz2, cw2
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function rasterizeBox(
|
||||
minX: Float, minY: Float, minZ: Float,
|
||||
maxX: Float, maxY: Float, maxZ: Float,
|
||||
vp: iron.math.Mat4,
|
||||
screenW: Int, screenH: Int
|
||||
) {
|
||||
var m00 = vp._00, m10 = vp._10, m20 = vp._20, m30 = vp._30;
|
||||
var m01 = vp._01, m11 = vp._11, m21 = vp._21, m31 = vp._31;
|
||||
var m02 = vp._02, m12 = vp._12, m22 = vp._22, m32 = vp._32;
|
||||
var m03 = vp._03, m13 = vp._13, m23 = vp._23, m33 = vp._33;
|
||||
|
||||
for (c in 0...8) {
|
||||
var x = (c & 1) != 0 ? maxX : minX;
|
||||
var y = (c & 2) != 0 ? maxY : minY;
|
||||
var z = (c & 4) != 0 ? maxZ : minZ;
|
||||
boxClipX[c] = m00 * x + m10 * y + m20 * z + m30;
|
||||
boxClipY[c] = m01 * x + m11 * y + m21 * z + m31;
|
||||
boxClipZ[c] = m02 * x + m12 * y + m22 * z + m32;
|
||||
boxClipW[c] = m03 * x + m13 * y + m23 * z + m33;
|
||||
}
|
||||
|
||||
for (face in BOX_FACES) {
|
||||
clipNearAndRasterize(
|
||||
boxClipX[face[0]], boxClipY[face[0]], boxClipZ[face[0]], boxClipW[face[0]],
|
||||
boxClipX[face[1]], boxClipY[face[1]], boxClipZ[face[1]], boxClipW[face[1]],
|
||||
boxClipX[face[2]], boxClipY[face[2]], boxClipZ[face[2]], boxClipW[face[2]]
|
||||
);
|
||||
clipNearAndRasterize(
|
||||
boxClipX[face[0]], boxClipY[face[0]], boxClipZ[face[0]], boxClipW[face[0]],
|
||||
boxClipX[face[2]], boxClipY[face[2]], boxClipZ[face[2]], boxClipW[face[2]],
|
||||
boxClipX[face[3]], boxClipY[face[3]], boxClipZ[face[3]], boxClipW[face[3]]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function buildHiZ() {
|
||||
hiz.buildPyramid();
|
||||
}
|
||||
|
||||
public function beginCollect() {
|
||||
collectMode = true;
|
||||
screenTriCount = 0;
|
||||
}
|
||||
|
||||
var tileTriCounts: Array<Int> = [];
|
||||
var tileTriOffsets: Array<Int> = [];
|
||||
var tileTriLists: Array<Array<Int>> = [];
|
||||
|
||||
public function flushCollected() {
|
||||
collectMode = false;
|
||||
var nTris = Std.int(screenTriCount / 9);
|
||||
if (nTris == 0) return;
|
||||
|
||||
var tilesX = Std.int(Math.ceil(width / TILE_SIZE));
|
||||
var tilesY = Std.int(Math.ceil(height / TILE_SIZE));
|
||||
var totalTiles = tilesX * tilesY;
|
||||
|
||||
if (tileTriCounts.length < totalTiles) {
|
||||
tileTriCounts.resize(totalTiles);
|
||||
tileTriOffsets.resize(totalTiles);
|
||||
tileTriLists.resize(totalTiles);
|
||||
}
|
||||
for (t in 0...totalTiles) tileTriCounts[t] = 0;
|
||||
for (t in 0...nTris) {
|
||||
var off = t * 9;
|
||||
var tx0 = Std.int(Math.max(0, Math.floor(Math.min(
|
||||
Math.min(screenTris[off], screenTris[off + 3]), screenTris[off + 6]) / TILE_SIZE)));
|
||||
var tx1 = Std.int(Math.min(tilesX - 1, Math.ceil(Math.max(
|
||||
Math.max(screenTris[off], screenTris[off + 3]), screenTris[off + 6]) / TILE_SIZE)));
|
||||
var ty0 = Std.int(Math.max(0, Math.floor(Math.min(
|
||||
Math.min(screenTris[off + 1], screenTris[off + 4]), screenTris[off + 7]) / TILE_SIZE)));
|
||||
var ty1 = Std.int(Math.min(tilesY - 1, Math.ceil(Math.max(
|
||||
Math.max(screenTris[off + 1], screenTris[off + 4]), screenTris[off + 7]) / TILE_SIZE)));
|
||||
for (ty in ty0...ty1 + 1) {
|
||||
for (tx in tx0...tx1 + 1) {
|
||||
tileTriCounts[ty * tilesX + tx]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (t in 0...totalTiles) {
|
||||
var l = tileTriLists[t];
|
||||
if (l == null) { l = []; tileTriLists[t] = l; }
|
||||
if (l.length < tileTriCounts[t]) l.resize(tileTriCounts[t]);
|
||||
tileTriOffsets[t] = 0;
|
||||
}
|
||||
for (t in 0...nTris) {
|
||||
var off = t * 9;
|
||||
var tx0 = Std.int(Math.max(0, Math.floor(Math.min(
|
||||
Math.min(screenTris[off], screenTris[off + 3]), screenTris[off + 6]) / TILE_SIZE)));
|
||||
var tx1 = Std.int(Math.min(tilesX - 1, Math.ceil(Math.max(
|
||||
Math.max(screenTris[off], screenTris[off + 3]), screenTris[off + 6]) / TILE_SIZE)));
|
||||
var ty0 = Std.int(Math.max(0, Math.floor(Math.min(
|
||||
Math.min(screenTris[off + 1], screenTris[off + 4]), screenTris[off + 7]) / TILE_SIZE)));
|
||||
var ty1 = Std.int(Math.min(tilesY - 1, Math.ceil(Math.max(
|
||||
Math.max(screenTris[off + 1], screenTris[off + 4]), screenTris[off + 7]) / TILE_SIZE)));
|
||||
for (ty in ty0...ty1 + 1) {
|
||||
for (tx in tx0...tx1 + 1) {
|
||||
var ti = ty * tilesX + tx;
|
||||
tileTriLists[ti][tileTriOffsets[ti]++] = t;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var buf = hiz.mipBuffers[0];
|
||||
var w = width;
|
||||
var tris = screenTris;
|
||||
|
||||
#if (target.threaded)
|
||||
JobSystem.parallelFor(totalTiles, function(start, end) {
|
||||
for (ti in start...end) {
|
||||
var tx = ti % tilesX;
|
||||
var ty = Std.int(ti / tilesX);
|
||||
var px0 = tx * TILE_SIZE;
|
||||
var py0 = ty * TILE_SIZE;
|
||||
var px1 = Std.int(Math.min(w - 1, px0 + TILE_SIZE - 1));
|
||||
var py1 = Std.int(Math.min(height - 1, py0 + TILE_SIZE - 1));
|
||||
var list = tileTriLists[ti];
|
||||
var listLen = tileTriCounts[ti];
|
||||
for (li in 0...listLen) {
|
||||
var off = list[li] * 9;
|
||||
rasterizeTriangleBounded(
|
||||
tris[off], tris[off + 1], tris[off + 2],
|
||||
tris[off + 3], tris[off + 4], tris[off + 5],
|
||||
tris[off + 6], tris[off + 7], tris[off + 8],
|
||||
px0, py0, px1, py1, buf, w
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
#else
|
||||
for (ti in 0...totalTiles) {
|
||||
var tx = ti % tilesX;
|
||||
var ty = Std.int(ti / tilesX);
|
||||
var px0 = tx * TILE_SIZE;
|
||||
var py0 = ty * TILE_SIZE;
|
||||
var px1 = Std.int(Math.min(w - 1, px0 + TILE_SIZE - 1));
|
||||
var py1 = Std.int(Math.min(height - 1, py0 + TILE_SIZE - 1));
|
||||
var list = tileTriLists[ti];
|
||||
for (li in 0...list.length) {
|
||||
var off = list[li] * 9;
|
||||
rasterizeTriangleBounded(
|
||||
tris[off], tris[off + 1], tris[off + 2],
|
||||
tris[off + 3], tris[off + 4], tris[off + 5],
|
||||
tris[off + 6], tris[off + 7], tris[off + 8],
|
||||
px0, py0, px1, py1, buf, w
|
||||
);
|
||||
}
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
function rasterizeTriangleBounded(
|
||||
v0x: Float, v0y: Float, v0z: Float,
|
||||
v1x: Float, v1y: Float, v1z: Float,
|
||||
v2x: Float, v2y: Float, v2z: Float,
|
||||
tileX0: Int, tileY0: Int, tileX1: Int, tileY1: Int,
|
||||
buf: kha.arrays.Float32Array, w: Int
|
||||
) {
|
||||
var e0a = v1y - v0y; var e0b = v0x - v1x;
|
||||
var e1a = v2y - v1y; var e1b = v1x - v2x;
|
||||
var e2a = v0y - v2y; var e2b = v2x - v0x;
|
||||
var e0c = -e0a * v0x - e0b * v0y;
|
||||
var e1c = -e1a * v1x - e1b * v1y;
|
||||
var e2c = -e2a * v2x - e2b * v2y;
|
||||
|
||||
var area = e0a * v2x + e0b * v2y + e0c;
|
||||
if (area < 0) {
|
||||
area = -area;
|
||||
e0a = -e0a; e0b = -e0b; e0c = -e0c;
|
||||
e1a = -e1a; e1b = -e1b; e1c = -e1c;
|
||||
e2a = -e2a; e2b = -e2b; e2c = -e2c;
|
||||
}
|
||||
if (area < 1e-8) return;
|
||||
|
||||
var minX = Std.int(Math.max(tileX0, Math.floor(Math.min(v0x, Math.min(v1x, v2x)))));
|
||||
var maxX = Std.int(Math.min(tileX1, Math.ceil(Math.max(v0x, Math.max(v1x, v2x)))));
|
||||
var minY = Std.int(Math.max(tileY0, Math.floor(Math.min(v0y, Math.min(v1y, v2y)))));
|
||||
var maxY = Std.int(Math.min(tileY1, Math.ceil(Math.max(v0y, Math.max(v1y, v2y)))));
|
||||
if (minX > maxX || minY > maxY) return;
|
||||
|
||||
var d = (v1x - v0x) * (v2y - v0y) - (v2x - v0x) * (v1y - v0y);
|
||||
var invD = 1.0 / d;
|
||||
var dzdx = ((v1z - v0z) * (v2y - v0y) - (v2z - v0z) * (v1y - v0y)) * invD;
|
||||
var dzdy = ((v2z - v0z) * (v1x - v0x) - (v1z - v0z) * (v2x - v0x)) * invD;
|
||||
|
||||
var zPush = (Math.abs(dzdx) + Math.abs(dzdy)) * 0.5;
|
||||
var zMaxTri = v0z;
|
||||
if (v1z > zMaxTri) zMaxTri = v1z;
|
||||
if (v2z > zMaxTri) zMaxTri = v2z;
|
||||
|
||||
var cx0 = minX + 0.5;
|
||||
var cy0 = minY + 0.5;
|
||||
var e0row = e0a * cx0 + e0b * cy0 + e0c;
|
||||
var e1row = e1a * cx0 + e1b * cy0 + e1c;
|
||||
var e2row = e2a * cx0 + e2b * cy0 + e2c;
|
||||
var zrow = v0z + dzdx * (cx0 - v0x) + dzdy * (cy0 - v0y);
|
||||
|
||||
for (py in minY...maxY + 1) {
|
||||
var e0 = e0row; var e1 = e1row; var e2 = e2row;
|
||||
var z = zrow;
|
||||
var idx = py * w + minX;
|
||||
for (px in minX...maxX + 1) {
|
||||
if (e0 >= 0 && e1 >= 0 && e2 >= 0) {
|
||||
var zPix = z + zPush;
|
||||
if (zPix > zMaxTri) zPix = zMaxTri;
|
||||
if (zPix > 1.0) zPix = 1.0;
|
||||
if (zPix < buf[idx]) { buf[idx] = zPix; }
|
||||
}
|
||||
e0 += e0a; e1 += e1a; e2 += e2a;
|
||||
z += dzdx;
|
||||
idx++;
|
||||
}
|
||||
e0row += e0b; e1row += e1b; e2row += e2b;
|
||||
zrow += dzdy;
|
||||
}
|
||||
}
|
||||
|
||||
public function isOccluded(
|
||||
minX: Float, minY: Float, minZ: Float,
|
||||
maxX: Float, maxY: Float, maxZ: Float,
|
||||
vp: iron.math.Mat4,
|
||||
screenW: Int, screenH: Int
|
||||
): Bool {
|
||||
return hiz.isOccluded(minX, minY, minZ, maxX, maxY, maxZ, vp, screenW, screenH);
|
||||
}
|
||||
}
|
||||
79
leenkx/Sources/iron/culling/TemporalState.hx
Normal file
79
leenkx/Sources/iron/culling/TemporalState.hx
Normal file
@ -0,0 +1,79 @@
|
||||
package iron.culling;
|
||||
|
||||
class TemporalState {
|
||||
|
||||
public var visibleLastFrame: haxe.ds.IntMap<Bool>;
|
||||
public var visibleLastShadow: haxe.ds.IntMap<Bool>;
|
||||
public var frameLastTested: haxe.ds.IntMap<Int>;
|
||||
public var occlusionConfidence: haxe.ds.IntMap<Int>;
|
||||
public var capacity: Int = 0;
|
||||
public var frameCount: Int = 0;
|
||||
|
||||
public function new() {
|
||||
visibleLastFrame = new haxe.ds.IntMap<Bool>();
|
||||
visibleLastShadow = new haxe.ds.IntMap<Bool>();
|
||||
frameLastTested = new haxe.ds.IntMap<Int>();
|
||||
occlusionConfidence = new haxe.ds.IntMap<Int>();
|
||||
}
|
||||
|
||||
public function ensureCapacity(cap: Int) {
|
||||
if (capacity < cap) capacity = cap;
|
||||
}
|
||||
|
||||
public inline function wasVisibleLastFrame(i: Int): Bool {
|
||||
return visibleLastFrame.exists(i);
|
||||
}
|
||||
|
||||
public inline function wasVisibleShadowLastFrame(i: Int): Bool {
|
||||
return visibleLastShadow.exists(i);
|
||||
}
|
||||
|
||||
public inline function needsTest(i: Int, maxSkipFrames: Int): Bool {
|
||||
if (visibleLastFrame.exists(i)) return false; // assumed visible
|
||||
var lastTested = frameLastTested.get(i);
|
||||
if (lastTested == null) return true;
|
||||
return frameCount - lastTested >= maxSkipFrames;
|
||||
}
|
||||
|
||||
public inline function needsVisibleReTest(i: Int, retestInterval: Int): Bool {
|
||||
var lastTested = frameLastTested.get(i);
|
||||
if (lastTested == null) return true;
|
||||
return frameCount - lastTested >= retestInterval;
|
||||
}
|
||||
|
||||
public inline function setVisible(i: Int, visible: Bool) {
|
||||
if (visible) {
|
||||
visibleLastFrame.set(i, true);
|
||||
occlusionConfidence.remove(i);
|
||||
} else {
|
||||
visibleLastFrame.remove(i);
|
||||
var conf = occlusionConfidence.get(i);
|
||||
occlusionConfidence.set(i, conf == null ? 1 : Std.int(Math.min(255, conf + 1)));
|
||||
}
|
||||
}
|
||||
|
||||
public inline function markTested(i: Int) {
|
||||
frameLastTested.set(i, frameCount);
|
||||
}
|
||||
|
||||
public inline function setVisibleShadow(i: Int, visible: Bool) {
|
||||
if (visible) visibleLastShadow.set(i, true);
|
||||
else visibleLastShadow.remove(i);
|
||||
}
|
||||
|
||||
public inline function advanceFrame() {
|
||||
frameCount++;
|
||||
}
|
||||
|
||||
public function clear() {
|
||||
visibleLastFrame = new haxe.ds.IntMap<Bool>();
|
||||
visibleLastShadow = new haxe.ds.IntMap<Bool>();
|
||||
frameLastTested = new haxe.ds.IntMap<Int>();
|
||||
occlusionConfidence = new haxe.ds.IntMap<Int>();
|
||||
}
|
||||
|
||||
public inline function getConfidence(i: Int): Int {
|
||||
var c = occlusionConfidence.get(i);
|
||||
return c == null ? 0 : c;
|
||||
}
|
||||
}
|
||||
@ -14,6 +14,7 @@ import iron.math.Vec4;
|
||||
import iron.math.Mat4;
|
||||
import iron.data.SceneFormat;
|
||||
import iron.data.MeshData;
|
||||
import iron.data.ShaderData;
|
||||
|
||||
class Geometry {
|
||||
#if lnx_deinterleaved
|
||||
@ -57,6 +58,7 @@ class Geometry {
|
||||
// Skinned
|
||||
#if lnx_skin
|
||||
public var skeletonTransformsI: Array<Mat4> = null;
|
||||
public var skeletonBoneRefs: Array<String> = null;
|
||||
public var actions: Map<String, Array<iron.data.SceneFormat.TObj>> = new Map();
|
||||
public var mats: Map<String, Array<Mat4>> = new Map();
|
||||
#end
|
||||
@ -208,7 +210,7 @@ class Geometry {
|
||||
for (v in 0...vertexBuffers.length)
|
||||
if (vertexBuffers[v].name == e.name) {
|
||||
vbs.push(vertexBuffers[v].buffer);
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
if (e.name == "ipos" && instancedVB != null) {
|
||||
vbs.push(instancedVB);
|
||||
@ -264,7 +266,7 @@ class Geometry {
|
||||
for (i in 0...vaLength)
|
||||
vertexBuffers[i] = {
|
||||
name: vertexArrays[i].attrib,
|
||||
buffer: makeDeinterleavedVB(vertexArrays[i].values, vertexArrays[i].attrib, vertexArrays[i].size)
|
||||
buffer: makeDeinterleavedVB(vertexArrays[i])
|
||||
};
|
||||
#else
|
||||
|
||||
@ -295,14 +297,20 @@ class Geometry {
|
||||
}
|
||||
|
||||
#if lnx_deinterleaved
|
||||
function makeDeinterleavedVB(data: ByteArray, name: String, structLength: Int): VertexBuffer {
|
||||
function makeDeinterleavedVB(va: TVertexArray): VertexBuffer {
|
||||
var struct = new VertexStructure();
|
||||
struct.add(name, structLength == 2 ? VertexData.Short2Norm : VertexData.Short4Norm);
|
||||
struct.add(va.attrib, ShaderContext.parseData(va.data));
|
||||
|
||||
var vertexBuffer = new VertexBuffer(Std.int(data.byteLength / 2 / structLength), struct, usage);
|
||||
var vcount = verticesCount(va);
|
||||
var vertexBuffer = new VertexBuffer(vcount, struct, usage);
|
||||
|
||||
var stride = Std.int(struct.byteSize() / 2);
|
||||
var vertices = vertexBuffer.lock();
|
||||
for (i in 0...Std.int(vertices.byteLength / 2)) vertices.setInt16(i * 2, data.getInt16(i * 2));
|
||||
var di = -1;
|
||||
for (i in 0...vcount) {
|
||||
for (o in 0...va.size) vertices.setInt16(++di * 2, va.values[i * va.size + o]);
|
||||
for (o in va.size...stride) vertices.setInt16(++di * 2, 0);
|
||||
}
|
||||
vertexBuffer.unlock();
|
||||
|
||||
return vertexBuffer;
|
||||
@ -319,6 +327,33 @@ class Geometry {
|
||||
|
||||
// Skinned
|
||||
#if lnx_skin
|
||||
public function addArmature(armature: iron.data.Armature) {
|
||||
for (a in armature.actions) {
|
||||
addAction(a.bones, a.name);
|
||||
}
|
||||
}
|
||||
|
||||
public function addAction(bones: Array<TObj>, name: String) {
|
||||
if (bones == null || skeletonBoneRefs == null) return;
|
||||
if (actions.get(name) != null) return;
|
||||
var actionBones: Array<TObj> = [];
|
||||
|
||||
for (s in skeletonBoneRefs) {
|
||||
for (b in bones) {
|
||||
if (b.name == s) {
|
||||
actionBones.push(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
actions.set(name, actionBones);
|
||||
|
||||
var actionMats: Array<Mat4> = [];
|
||||
for (b in actionBones) {
|
||||
actionMats.push(Mat4.fromFloat32Array(b.transform.values));
|
||||
}
|
||||
mats.set(name, actionMats);
|
||||
}
|
||||
|
||||
public function initSkeletonTransforms(transformsI: Array<Float32Array>) {
|
||||
skeletonTransformsI = [];
|
||||
for (t in transformsI) {
|
||||
|
||||
@ -57,10 +57,8 @@ class MeshBatch {
|
||||
}
|
||||
|
||||
public function removeMesh(m: MeshObject) {
|
||||
var shader = m.materials[0].shader;
|
||||
var b = buckets.get(shader);
|
||||
if (b != null) b.removeMesh(m);
|
||||
else nonBatched.remove(m);
|
||||
var b = m.materials.length > 0 ? buckets.get(m.materials[0].shader) : null;
|
||||
if (b == null || !b.removeMesh(m)) nonBatched.remove(m);
|
||||
}
|
||||
|
||||
@:access(iron.RenderPath)
|
||||
@ -71,30 +69,52 @@ class MeshBatch {
|
||||
if (b.meshes.length > 0 && b.meshes[0].cullMaterial(context)) continue;
|
||||
|
||||
var scontext = b.shader.getContext(context);
|
||||
if (scontext == null) continue;
|
||||
g.setPipeline(scontext.pipeState);
|
||||
// #if lnx_deinterleaved // TODO
|
||||
// g.setVertexBuffers(b.vertexBuffers);
|
||||
// #else
|
||||
#if lnx_deinterleaved
|
||||
g.setVertexBuffers(b.getVertexBuffers(scontext.raw.vertex_elements));
|
||||
#else
|
||||
g.setVertexBuffer(b.getVertexBuffer(scontext.raw.vertex_elements));
|
||||
// #end
|
||||
#end
|
||||
g.setIndexBuffer(b.indexBuffer);
|
||||
|
||||
Uniforms.setContextConstants(g, scontext, bindParams);
|
||||
|
||||
RenderPath.sortMeshesDistance(b.meshes);
|
||||
var isShadowCtx = context == "shadowmap" || context == "shadowmap_transparent";
|
||||
if (!isShadowCtx) RenderPath.sortMeshesDistance(b.meshes);
|
||||
|
||||
for (m in b.meshes) {
|
||||
if (!m.visible) continue; // Skip render if object is hidden
|
||||
#if lnx_occlusion_culling
|
||||
if (!m.occlusionVisible && (context == "mesh" || context == "depth" || context == "translucent")) continue;
|
||||
if (!m.shadowOcclusionVisible && isShadowCtx) continue;
|
||||
#end
|
||||
if (m.cullMesh(context, Scene.active.camera, RenderPath.active.light)) continue;
|
||||
|
||||
// Get context
|
||||
var materialContexts: Array<MaterialContext> = [];
|
||||
var shaderContexts: Array<ShaderContext> = [];
|
||||
m.getContexts(context, m.materials, materialContexts, shaderContexts);
|
||||
var materialContexts: Array<MaterialContext> = null;
|
||||
var shaderContexts: Array<ShaderContext> = null;
|
||||
if (m.cachedMaterialContexts != null && MeshObject.matsEqual(m.cachedMats.get(context), m.materials)) {
|
||||
materialContexts = m.cachedMaterialContexts.get(context);
|
||||
shaderContexts = m.cachedShaderContexts.get(context);
|
||||
}
|
||||
if (materialContexts == null) {
|
||||
materialContexts = [];
|
||||
shaderContexts = [];
|
||||
m.getContexts(context, m.materials, materialContexts, shaderContexts);
|
||||
if (m.cachedMaterialContexts == null) {
|
||||
m.cachedMats = new Map();
|
||||
m.cachedMaterialContexts = new Map();
|
||||
m.cachedShaderContexts = new Map();
|
||||
}
|
||||
m.cachedMats.set(context, m.materials);
|
||||
m.cachedMaterialContexts.set(context, materialContexts);
|
||||
m.cachedShaderContexts.set(context, shaderContexts);
|
||||
}
|
||||
|
||||
Uniforms.posUnpack = m.data.scalePos;
|
||||
Uniforms.texUnpack = m.data.scaleTex;
|
||||
m.transform.update();
|
||||
if (!isShadowCtx) m.transform.update();
|
||||
|
||||
// Render mesh
|
||||
Uniforms.setObjectConstants(g, scontext, m);
|
||||
@ -126,8 +146,12 @@ class Bucket {
|
||||
|
||||
public var batched = false;
|
||||
public var shader: ShaderData;
|
||||
#if lnx_deinterleaved
|
||||
public var vertexBuffers: Array<InterleavedVertexBuffer> = [];
|
||||
#else
|
||||
var vertexBuffer: VertexBuffer;
|
||||
var vertexBufferMap: Map<String, VertexBuffer> = new Map();
|
||||
#end
|
||||
public var indexBuffer: IndexBuffer;
|
||||
public var meshes: Array<MeshObject> = [];
|
||||
|
||||
@ -137,8 +161,12 @@ class Bucket {
|
||||
|
||||
public function remove() {
|
||||
indexBuffer.delete();
|
||||
#if lnx_deinterleaved
|
||||
for (buf in vertexBuffers) buf.buffer.delete();
|
||||
#else
|
||||
// this.vertexBuffer is in the map, so it's also deleted here
|
||||
for (buf in vertexBufferMap) buf.delete();
|
||||
#end
|
||||
meshes = [];
|
||||
}
|
||||
|
||||
@ -146,10 +174,24 @@ class Bucket {
|
||||
meshes.push(m);
|
||||
}
|
||||
|
||||
public function removeMesh(m: MeshObject) {
|
||||
meshes.remove(m);
|
||||
public function removeMesh(m: MeshObject): Bool {
|
||||
return meshes.remove(m);
|
||||
}
|
||||
|
||||
#if lnx_deinterleaved
|
||||
public function getVertexBuffers(elems: Array<TVertexElement>): Array<VertexBuffer> {
|
||||
var vbs: Array<VertexBuffer> = [];
|
||||
for (e in elems) {
|
||||
for (v in vertexBuffers) {
|
||||
if (v.name == e.name) {
|
||||
vbs.push(v.buffer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return vbs;
|
||||
}
|
||||
#else
|
||||
function copyAttribute(attribSize: Int, count: Int, to: ByteArray, toStride: Int, toOffset: Int, from: ByteArray, fromStride: Int, fromOffset: Int) {
|
||||
for (i in 0...count) {
|
||||
for (j in 0...attribSize) {
|
||||
@ -195,6 +237,7 @@ class Bucket {
|
||||
}
|
||||
return vb;
|
||||
}
|
||||
#end
|
||||
|
||||
function vertexCount(g: Geometry, hasUVs: Bool): Int {
|
||||
var vcount = g.getVerticesLength();
|
||||
@ -239,6 +282,50 @@ class Bucket {
|
||||
|
||||
if (mdatas.length == 0) return;
|
||||
|
||||
#if lnx_deinterleaved
|
||||
var attrNames: Array<String> = [];
|
||||
var attrReps = new Map<String, TVertexArray>();
|
||||
for (md in mdatas) {
|
||||
for (va in md.geom.vertexArrays) {
|
||||
if (attrReps.get(va.attrib) == null) {
|
||||
attrReps.set(va.attrib, va);
|
||||
attrNames.push(va.attrib);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (name in attrNames) {
|
||||
var rep = attrReps.get(name);
|
||||
var attrStruct = new VertexStructure();
|
||||
attrStruct.add(name, ShaderContext.parseData(rep.data));
|
||||
var stride = Std.int(attrStruct.byteSize() / 2);
|
||||
var vtotal = 0;
|
||||
for (md in mdatas) vtotal += md.geom.getVerticesCount();
|
||||
var vb = new VertexBuffer(vtotal, attrStruct, Usage.StaticUsage);
|
||||
var vertices = vb.lock();
|
||||
var ai = -1;
|
||||
for (md in mdatas) {
|
||||
var va: TVertexArray = null;
|
||||
for (v in md.geom.vertexArrays) {
|
||||
if (v.attrib == name) {
|
||||
va = v;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (va != null && va.values != null) {
|
||||
var nverts = md.geom.getVerticesCount();
|
||||
for (i in 0...nverts) {
|
||||
for (o in 0...va.size) vertices.setInt16(++ai * 2, va.values[i * va.size + o]);
|
||||
for (o in va.size...stride) vertices.setInt16(++ai * 2, 0);
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (i in 0...md.geom.getVerticesCount() * stride) vertices.setInt16(++ai * 2, 0);
|
||||
}
|
||||
}
|
||||
vb.unlock();
|
||||
vertexBuffers.push({name: name, buffer: vb});
|
||||
}
|
||||
#else
|
||||
// Pick UVs if present
|
||||
var vs = mdatas[0].geom.struct;
|
||||
for (md in mdatas) if (md.geom.struct.size() > vs.size()) vs = md.geom.struct;
|
||||
@ -256,6 +343,7 @@ class Bucket {
|
||||
var s = "";
|
||||
for (e in vs.elements) s += e.name;
|
||||
vertexBufferMap.set(s, vertexBuffer);
|
||||
#end
|
||||
|
||||
indexBuffer = new IndexBuffer(icount, Usage.StaticUsage);
|
||||
var indices = indexBuffer.lock();
|
||||
|
||||
@ -108,6 +108,7 @@ class MeshData {
|
||||
// Skinned
|
||||
#if lnx_skin
|
||||
if (raw.skin != null) {
|
||||
dat.geom.skeletonBoneRefs = raw.skin.bone_ref_array;
|
||||
dat.geom.initSkeletonTransforms(raw.skin.transformsI);
|
||||
}
|
||||
#end
|
||||
|
||||
@ -60,6 +60,16 @@ typedef TMeshData = {
|
||||
@:optional public var scale_pos: Null<FastFloat>; // Unpack pos from (-1,1) coords
|
||||
@:optional public var scale_tex: Null<FastFloat>; // Unpack tex from (-1,1) coords
|
||||
@:optional public var morph_target: TMorphTarget;
|
||||
@:optional public var occluder_proxy: TOccluderProxy;
|
||||
}
|
||||
|
||||
#if js
|
||||
typedef TOccluderProxy = {
|
||||
#else
|
||||
@:structInit class TOccluderProxy {
|
||||
#end
|
||||
public var positions: Float32Array;
|
||||
public var indices: Uint32Array;
|
||||
}
|
||||
|
||||
#if js
|
||||
|
||||
@ -55,6 +55,7 @@ class ShaderData {
|
||||
if (raw == null) {
|
||||
trace('Shader data "$name" not found!');
|
||||
done(null);
|
||||
return;
|
||||
}
|
||||
new ShaderData(raw, done, overrideContext);
|
||||
});
|
||||
@ -101,8 +102,9 @@ class ShaderContext {
|
||||
constants = [];
|
||||
textureUnits = [];
|
||||
|
||||
var instStruct: VertexStructure = null;
|
||||
if (instancingType > 0) { // Instancing
|
||||
var instStruct = new VertexStructure();
|
||||
instStruct = new VertexStructure();
|
||||
instStruct.add("ipos", VertexData.Float3);
|
||||
if (instancingType == 2 || instancingType == 4) {
|
||||
instStruct.add("irot", VertexData.Float3);
|
||||
@ -116,11 +118,19 @@ class ShaderContext {
|
||||
this.instanceStride = Std.int(instStruct.byteSize() / 4);
|
||||
|
||||
instStruct.instanced = true;
|
||||
pipeState.inputLayout = [structure, instStruct];
|
||||
}
|
||||
else { // Regular
|
||||
pipeState.inputLayout = [structure];
|
||||
#if lnx_deinterleaved
|
||||
var layouts: Array<VertexStructure> = [];
|
||||
for (e in structure.elements) {
|
||||
var s = new VertexStructure();
|
||||
s.add(e.name, e.data);
|
||||
layouts.push(s);
|
||||
}
|
||||
#else
|
||||
var layouts = [structure];
|
||||
#end
|
||||
if (instStruct != null) layouts.push(instStruct);
|
||||
pipeState.inputLayout = layouts;
|
||||
|
||||
// Depth
|
||||
pipeState.depthWrite = raw.depth_write;
|
||||
|
||||
@ -49,7 +49,7 @@ class Reader {
|
||||
* @see https://msdn.microsoft.com/en-us/library/windows/desktop/dd318229(v=vs.85).aspx
|
||||
* @see https://en.wikipedia.org/wiki/BMP_file_format#Bitmap_file_header
|
||||
*/
|
||||
public function read() : format.bmp.Data {
|
||||
public function read() : iron.format.bmp.Data {
|
||||
// Read Header
|
||||
for (b in ["B".code, "M".code]) {
|
||||
if (input.readByte() != b) throw "Invalid header";
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
package format.gif;
|
||||
import format.gif.Data;
|
||||
package iron.format.gif;
|
||||
import iron.format.gif.Data;
|
||||
import haxe.ds.Vector;
|
||||
import haxe.io.Bytes;
|
||||
import haxe.io.Output;
|
||||
|
||||
@ -29,7 +29,7 @@
|
||||
*/
|
||||
package iron.format.jpg;
|
||||
|
||||
class Writer {
|
||||
class WriterOriginal {
|
||||
var ZigZag: Array<Int>;
|
||||
|
||||
// Static table initialization
|
||||
|
||||
@ -40,11 +40,6 @@ class Animation {
|
||||
public var paused = false;
|
||||
var frameTime: FastFloat = 1 / 60;
|
||||
|
||||
var blendTime: FastFloat = 0.0;
|
||||
var blendCurrent: FastFloat = 0.0;
|
||||
var blendFactor: FastFloat = 0.0;
|
||||
|
||||
var lastFrameIndex = -1;
|
||||
var markerEvents: Map<ActionSampler, Map<String, Array<Void->Void>>> = null;
|
||||
|
||||
public var activeActions: Map<String, ActionSampler> = null;
|
||||
@ -63,8 +58,6 @@ class Animation {
|
||||
|
||||
public function play(action = "", onComplete: Void->Void = null, blendTime = 0.0, speed = 1.0, loop = true) {
|
||||
if (blendTime > 0) {
|
||||
this.blendTime = blendTime;
|
||||
this.blendCurrent = 0.0;
|
||||
frameIndex = 0;
|
||||
time = 0.0;
|
||||
}
|
||||
@ -93,6 +86,7 @@ class Animation {
|
||||
}
|
||||
|
||||
public function update(delta: FastFloat) {
|
||||
if (paused) return;
|
||||
if(activeActions == null) return;
|
||||
|
||||
for(sampler in activeActions){
|
||||
@ -160,24 +154,25 @@ class Animation {
|
||||
while (checkFrameIndex(track.frames, time, frameIndex, speed)) frameIndex += sign;
|
||||
|
||||
// Marker events
|
||||
if (markerEvents != null && anim.marker_names != null && frameIndex != lastFrameIndex) {
|
||||
if(markerEvents.get(sampler) != null){
|
||||
if (markerEvents != null && anim.marker_names != null && frameIndex != sampler.offsetOld) {
|
||||
var markerListeners = markerEvents.get(sampler);
|
||||
if (markerListeners != null) {
|
||||
var fromFrame = sampler.offsetOld < 0 ?
|
||||
(speed > 0 ? -1 : track.frames[track.frames.length - 1] + 1) :
|
||||
track.frames[sampler.offsetOld];
|
||||
var toFrame = track.frames[frameIndex];
|
||||
for (i in 0...anim.marker_frames.length) {
|
||||
if (frameIndex == anim.marker_frames[i]) {
|
||||
var markerAct = markerEvents.get(sampler);
|
||||
var ar = markerAct.get(anim.marker_names[i]);
|
||||
if (ar != null) for (f in ar) f();
|
||||
} else {
|
||||
for (j in 0...(frameIndex - lastFrameIndex)) {
|
||||
if (lastFrameIndex + j + 1 == anim.marker_frames[i]) {
|
||||
var markerAct = markerEvents.get(sampler);
|
||||
var ar = markerAct.get(anim.marker_names[i]);
|
||||
if (ar != null) for (f in ar) f();
|
||||
}
|
||||
}
|
||||
var markerFrame = anim.marker_frames[i];
|
||||
var crossed =
|
||||
(speed > 0 && toFrame >= fromFrame && markerFrame > fromFrame && markerFrame <= toFrame) ||
|
||||
(speed < 0 && toFrame <= fromFrame && markerFrame <= fromFrame && markerFrame > toFrame) ||
|
||||
(speed > 0 && toFrame < fromFrame && (markerFrame > fromFrame || markerFrame <= toFrame)) ||
|
||||
(speed < 0 && toFrame > fromFrame && (markerFrame >= toFrame || markerFrame < fromFrame));
|
||||
if (crossed) {
|
||||
var listeners = markerListeners.get(anim.marker_names[i]);
|
||||
if (listeners != null) for (listener in listeners) listener();
|
||||
}
|
||||
}
|
||||
lastFrameIndex = frameIndex;
|
||||
}
|
||||
}
|
||||
|
||||
@ -191,8 +186,8 @@ class Animation {
|
||||
else {
|
||||
frameIndex -= sign;
|
||||
sampler.paused = true;
|
||||
if (sampler.onComplete != null) for(func in sampler.onComplete){ func();};
|
||||
}
|
||||
if (sampler.onComplete != null) for(func in sampler.onComplete){ func();};
|
||||
sampler.trackEnd = true;
|
||||
}
|
||||
|
||||
|
||||
@ -112,6 +112,10 @@ class BoneAnimation extends Animation {
|
||||
this.data = mo != null ? mo.data : null;
|
||||
this.isSkinned = data != null ? data.isSkinned : false;
|
||||
if (this.isSkinned) {
|
||||
if (armature != null && data.geom.skeletonBoneRefs != null
|
||||
&& data.geom.actions.keys().hasNext() == false) {
|
||||
data.geom.addArmature(armature);
|
||||
}
|
||||
var boneSize = 12; // Dual-quat skinning + scaling
|
||||
this.skinBuffer = new Float32Array(skinMaxBones * boneSize);
|
||||
for (i in 0...this.skinBuffer.length) this.skinBuffer[i] = 0;
|
||||
@ -205,18 +209,37 @@ 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());
|
||||
if (isSkinned) {
|
||||
skeletonBones = data.geom.actions.get(action);
|
||||
var geomMats = data.geom.mats.get(action);
|
||||
if (geomMats != null) {
|
||||
skeletonMats = [];
|
||||
for (i in 0...geomMats.length) {
|
||||
var m = Mat4.identity();
|
||||
m.setFrom(geomMats[i]);
|
||||
skeletonMats.push(m);
|
||||
}
|
||||
}
|
||||
else skeletonMats = null;
|
||||
}
|
||||
if (skeletonBones == null) {
|
||||
if (armature == null) return;
|
||||
armature.initMats();
|
||||
var a = armature.getAction(action);
|
||||
if (a == null) return;
|
||||
skeletonBones = a.bones;
|
||||
skeletonMats = a.mats;
|
||||
}
|
||||
if (skeletonMats.length == skeletonBones.length) skeletonMats.push(Mat4.identity()); // Root motion cache
|
||||
rootMotionCacheInit = true;
|
||||
setMats();
|
||||
}
|
||||
|
||||
function getAction(action: String): Array<TObj> {
|
||||
if (isSkinned) {
|
||||
var bones = data.geom.actions.get(action);
|
||||
if (bones != null) return bones;
|
||||
}
|
||||
if (armature == null) return null;
|
||||
armature.initMats();
|
||||
var a = armature.getAction(action);
|
||||
@ -251,15 +274,91 @@ class BoneAnimation extends Animation {
|
||||
updateAnimation = f;
|
||||
}
|
||||
|
||||
var blendSampler1: ActionSampler = null;
|
||||
var blendSampler2: ActionSampler = null;
|
||||
var blendMats: Array<Mat4> = null;
|
||||
|
||||
public function blend(action1: String, action2: String, factor: FastFloat) {
|
||||
var actionName1: String = getName(action1);
|
||||
var actionName2: String = getName(action2);
|
||||
if (blendSampler1 == null || blendSampler1.action != actionName1) {
|
||||
blendSampler1 = new ActionSampler(actionName1);
|
||||
registerAction("blendSampler1", blendSampler1);
|
||||
}
|
||||
if (blendSampler2 == null || blendSampler2.action != actionName2) {
|
||||
blendSampler2 = new ActionSampler(actionName2);
|
||||
registerAction("blendSampler2", blendSampler2);
|
||||
}
|
||||
blendSampler1.paused = false;
|
||||
blendSampler2.paused = false;
|
||||
if (transitionSampler != null) {
|
||||
transitionSampler.paused = true;
|
||||
transitionSampler = null;
|
||||
}
|
||||
if (currentPlaySampler != null) currentPlaySampler.paused = true;
|
||||
currentPlaySampler = blendSampler1;
|
||||
if (skeletonMats == null) setAction(actionName1);
|
||||
if (skeletonMats == null) return;
|
||||
if (blendMats == null) blendMats = initMatsEmpty();
|
||||
updateAnimation = function(mats) {
|
||||
sampleAction(blendSampler1, mats);
|
||||
sampleAction(blendSampler2, blendMats);
|
||||
blendAction(mats, blendMats, mats, factor);
|
||||
}
|
||||
}
|
||||
|
||||
var playSamplers: Map<String, ActionSampler> = [];
|
||||
var currentPlaySampler: ActionSampler = null;
|
||||
var transitionSampler: ActionSampler = null;
|
||||
var transitionMats: Array<Mat4> = null;
|
||||
var transitionTime: FastFloat = 0.0;
|
||||
var transitionDuration: FastFloat = 0.0;
|
||||
|
||||
override public function play(action = "", onComplete: Void->Void = null, blendTime = 0.2, speed = 1.0, loop = true) {
|
||||
var actionName: String = getName(action);
|
||||
if (actionName != "") {
|
||||
setAction(actionName);
|
||||
super.play(actionName, onComplete, blendTime, speed, loop);
|
||||
var tempAnimParam = new ActionSampler(actionName);
|
||||
registerAction("tempAction", tempAnimParam);
|
||||
var sampler = playSamplers.get(actionName);
|
||||
if (sampler == null) {
|
||||
if (activeActions != null) sampler = activeActions.get(actionName);
|
||||
if (sampler == null) sampler = new ActionSampler(actionName);
|
||||
playSamplers.set(actionName, sampler);
|
||||
}
|
||||
if (transitionSampler != null) {
|
||||
transitionSampler.paused = true;
|
||||
transitionSampler = null;
|
||||
}
|
||||
if (blendTime > 0 && currentPlaySampler != null && currentPlaySampler != sampler && skeletonMats != null) {
|
||||
transitionSampler = currentPlaySampler;
|
||||
if (transitionMats == null) transitionMats = initMatsEmpty();
|
||||
transitionTime = 0.0;
|
||||
transitionDuration = blendTime;
|
||||
if (blendSampler1 != null && blendSampler1 != transitionSampler) blendSampler1.paused = true;
|
||||
if (blendSampler2 != null && blendSampler2 != transitionSampler) blendSampler2.paused = true;
|
||||
}
|
||||
else if (currentPlaySampler != null) currentPlaySampler.paused = true;
|
||||
currentPlaySampler = sampler;
|
||||
sampler.speed = speed;
|
||||
sampler.loop = loop;
|
||||
sampler.onComplete = onComplete != null ? [onComplete] : null;
|
||||
sampler.restartAction();
|
||||
registerAction(actionName, sampler);
|
||||
updateAnimation = function(mats){
|
||||
sampleAction(tempAnimParam, mats);
|
||||
if (transitionSampler != null) {
|
||||
sampleAction(transitionSampler, transitionMats);
|
||||
sampleAction(sampler, mats);
|
||||
var f = transitionTime / transitionDuration;
|
||||
if (f > 1.0) f = 1.0;
|
||||
f = f * f * (3.0 - 2.0 * f); // Smoothstep
|
||||
blendAction(transitionMats, mats, mats, f, -1, 0.0);
|
||||
transitionTime += delta;
|
||||
if (transitionTime >= transitionDuration) {
|
||||
transitionSampler.paused = true;
|
||||
transitionSampler = null;
|
||||
}
|
||||
}
|
||||
else sampleAction(sampler, mats);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -279,6 +378,7 @@ class BoneAnimation extends Animation {
|
||||
#end
|
||||
|
||||
super.update(delta);
|
||||
if (paused) return;
|
||||
if(updateAnimation != null) {
|
||||
updateAnimation(skeletonMats);
|
||||
}
|
||||
@ -540,12 +640,14 @@ class BoneAnimation extends Animation {
|
||||
|
||||
|
||||
inline function interpolateSample(track: TTrack, m: Mat4, t: FastFloat, ti: Int, sign: Int) {
|
||||
var tj = ti + sign;
|
||||
if (tj < 0 || tj >= track.frames.length) tj = ti;
|
||||
var t1 = track.frames[ti] * frameTime;
|
||||
var t2 = track.frames[ti + sign] * frameTime;
|
||||
var s: FastFloat = (t - t1) / (t2 - t1); // Linear
|
||||
var t2 = track.frames[tj] * frameTime;
|
||||
var s: FastFloat = t2 != t1 ? (t - t1) / (t2 - t1) : 0.0; // Linear
|
||||
|
||||
m1.setF32(track.values, ti * 16); // Offset to 4x4 matrix array
|
||||
m2.setF32(track.values, (ti + sign) * 16);
|
||||
m2.setF32(track.values, tj * 16);
|
||||
|
||||
// Decompose
|
||||
m1.decompose(vpos, q1, vscl);
|
||||
@ -643,7 +745,7 @@ class BoneAnimation extends Animation {
|
||||
return actionMats != null ? actionMats[getBoneIndex(bone)] : null;
|
||||
}
|
||||
|
||||
function getWorldMat(actionMats: Array<Mat4>, bone: TObj): Mat4 {
|
||||
public function getWorldMat(actionMats: Array<Mat4>, bone: TObj): Mat4 {
|
||||
if (applyParent == null) {
|
||||
applyParent = [];
|
||||
for (m in actionMats) applyParent.push(true);
|
||||
@ -728,7 +830,7 @@ class BoneAnimation extends Animation {
|
||||
// Skip if root motion is disabled
|
||||
if(j < 0) continue;
|
||||
// Use Action 2
|
||||
if(skeletonBones[j].bone_layers[layerMask] || layerMask < 0){
|
||||
if(layerMask < 0 || skeletonBones[j].bone_layers[layerMask]){
|
||||
resultMat[i].setFrom(actionMats2[i]);
|
||||
}
|
||||
// Use Action 1 if not in layer
|
||||
@ -744,7 +846,7 @@ class BoneAnimation extends Animation {
|
||||
// Skip if root motion is disabled
|
||||
if(j < 0) continue;
|
||||
// Blend
|
||||
if(skeletonBones[j].bone_layers[j] || layerMask < 0) {
|
||||
if(layerMask < 0 || skeletonBones[j].bone_layers[layerMask]) {
|
||||
// Decompose
|
||||
m.setFrom(actionMats1[i]);
|
||||
m1.setFrom(actionMats2[i]);
|
||||
@ -786,8 +888,10 @@ class BoneAnimation extends Animation {
|
||||
}
|
||||
else{
|
||||
for(i in 0...baseActionMats.length){
|
||||
var j = i == skeletonBones.length ? rootMotionIndex : i;
|
||||
if(j < 0) continue;
|
||||
|
||||
if(skeletonBones[i].bone_layers[layerMask] || layerMask < 0) {
|
||||
if(layerMask < 0 || skeletonBones[j].bone_layers[layerMask]) {
|
||||
// Decompose
|
||||
m.setFrom(baseActionMats[i]);
|
||||
m1.setFrom(addActionMats[i]);
|
||||
|
||||
@ -247,25 +247,3 @@ class CameraObject extends Object {
|
||||
return new Vec4(-transform.world._20, -transform.world._21, -transform.world._22);
|
||||
}
|
||||
}
|
||||
|
||||
class FrustumPlane {
|
||||
public var normal = new Vec4(1.0, 0.0, 0.0);
|
||||
public var constant = 0.0;
|
||||
|
||||
public function new() {}
|
||||
|
||||
public function normalize() {
|
||||
var inverseNormalLength = 1.0 / normal.length();
|
||||
normal.mult(inverseNormalLength);
|
||||
constant *= inverseNormalLength;
|
||||
}
|
||||
|
||||
public function distanceToSphere(sphereCenter: Vec4, sphereRadius: Float): Float {
|
||||
return (normal.dot(sphereCenter) + constant) - sphereRadius;
|
||||
}
|
||||
|
||||
public inline function setComponents(x: Float, y: Float, z: Float, w: Float) {
|
||||
normal.set(x, y, z);
|
||||
constant = w;
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,7 +13,10 @@ class Constraint {
|
||||
}
|
||||
|
||||
public function apply(transform: Transform) {
|
||||
if (target == null && raw.target != null) target = Scene.active.getChild(raw.target).transform;
|
||||
if (target == null && raw.target != null) {
|
||||
var o = Scene.active.getChild(raw.target);
|
||||
if (o != null) target = o.transform;
|
||||
}
|
||||
|
||||
if (target == null && raw.type != "LIMIT_LOCATION" && raw.type != "LIMIT_ROTATION" && raw.type != "LIMIT_SCALE") return;
|
||||
|
||||
|
||||
25
leenkx/Sources/iron/object/FrustumPlane.hx
Normal file
25
leenkx/Sources/iron/object/FrustumPlane.hx
Normal file
@ -0,0 +1,25 @@
|
||||
package iron.object;
|
||||
|
||||
import iron.math.Vec4;
|
||||
|
||||
class FrustumPlane {
|
||||
public var normal = new Vec4(1.0, 0.0, 0.0);
|
||||
public var constant = 0.0;
|
||||
|
||||
public function new() {}
|
||||
|
||||
public function normalize() {
|
||||
var inverseNormalLength = 1.0 / normal.length();
|
||||
normal.mult(inverseNormalLength);
|
||||
constant *= inverseNormalLength;
|
||||
}
|
||||
|
||||
public function distanceToSphere(sphereCenter: Vec4, sphereRadius: Float): Float {
|
||||
return (normal.dot(sphereCenter) + constant) - sphereRadius;
|
||||
}
|
||||
|
||||
public inline function setComponents(x: Float, y: Float, z: Float, w: Float) {
|
||||
normal.set(x, y, z);
|
||||
constant = w;
|
||||
}
|
||||
}
|
||||
@ -34,7 +34,7 @@ class LightObject extends Object {
|
||||
// Cascades
|
||||
public static var cascadeCount = 1;
|
||||
public static var cascadeSplitFactor = 0.8;
|
||||
public static var cascadeBounds = 1.0;
|
||||
public static var cascadeBounds = 2.0; // occlusion culling shadows pop with 1.0
|
||||
#if lnx_csm
|
||||
var cascadeData: Float32Array = null;
|
||||
var cascadeVP: Array<Mat4>;
|
||||
@ -90,8 +90,16 @@ class LightObject extends Object {
|
||||
|
||||
#if lnx_spot
|
||||
if (data.raw.type == "spot"){
|
||||
this.size = data.raw.spot_size;
|
||||
this.blend = data.raw.spot_blend;
|
||||
this.size = 0.0;
|
||||
this.blend = 0.0;
|
||||
if (data.raw.spot_size != null) {
|
||||
var s: kha.FastFloat = data.raw.spot_size;
|
||||
this.size = s;
|
||||
}
|
||||
if (data.raw.spot_blend != null) {
|
||||
var b: kha.FastFloat = data.raw.spot_blend;
|
||||
this.blend = b;
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
||||
@ -132,6 +140,9 @@ class LightObject extends Object {
|
||||
}
|
||||
#end
|
||||
#end
|
||||
#if lnx_occlusion_culling
|
||||
if (iron.culling.CullSystem.active != null) iron.culling.CullSystem.active.removeLight(this);
|
||||
#end
|
||||
super.remove();
|
||||
}
|
||||
|
||||
@ -270,12 +281,13 @@ class LightObject extends Object {
|
||||
var worldPerTexelX = (maxx - minx) / smsize;
|
||||
var worldPerTexelY = (maxy - miny) / smsize;
|
||||
var worldPerTexelZ = (maxz - minz) / smsize;
|
||||
var ex = maxx - minx, ey = maxy - miny, ez = maxz - minz;
|
||||
minx = Math.floor(minx / worldPerTexelX) * worldPerTexelX;
|
||||
miny = Math.floor(miny / worldPerTexelY) * worldPerTexelY;
|
||||
minz = Math.floor(minz / worldPerTexelZ) * worldPerTexelZ;
|
||||
maxx = Math.floor(maxx / worldPerTexelX) * worldPerTexelX;
|
||||
maxy = Math.floor(maxy / worldPerTexelY) * worldPerTexelY;
|
||||
maxz = Math.floor(maxz / worldPerTexelZ) * worldPerTexelZ;
|
||||
maxx = minx + ex;
|
||||
maxy = miny + ey;
|
||||
maxz = minz + ez;
|
||||
|
||||
var hx = (maxx - minx) / 2;
|
||||
var hy = (maxy - miny) / 2;
|
||||
@ -662,7 +674,7 @@ class LightObject extends Object {
|
||||
break;
|
||||
}
|
||||
if (discardLightCulled(light)) continue;
|
||||
if (light.data.raw.type == type) {
|
||||
if (light.data.raw.type == type || (type == "spot" && light.data.raw.type == "area")) {
|
||||
m.setFrom(light.VP);
|
||||
m.multmat(Uniforms.biasMat);
|
||||
#if lnx_shadowmap_atlas
|
||||
@ -699,8 +711,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;
|
||||
}
|
||||
@ -779,7 +791,7 @@ class LightObject extends Object {
|
||||
public static var spotTileBoundsArray: Float32Array = null;
|
||||
|
||||
public static function updateSpotTileBoundsArray(): Float32Array {
|
||||
var maxLights = getMaxLights();
|
||||
var maxLights = maxLightsCluster;
|
||||
if (spotTileBoundsArray == null) {
|
||||
spotTileBoundsArray = new Float32Array(maxLights * 4);
|
||||
}
|
||||
@ -787,19 +799,20 @@ class LightObject extends Object {
|
||||
var i = 0;
|
||||
for (light in Scene.active.lights) {
|
||||
if (i >= maxLights) break;
|
||||
if (!light.visible || light.data.raw.type != "spot") continue;
|
||||
if (!light.data.raw.cast_shadow || light.data.raw.strength == 0.0) continue;
|
||||
var idx = i * 4;
|
||||
spotTileBoundsArray[idx ] = light.tileOffsetX[0];
|
||||
spotTileBoundsArray[idx + 2] = light.tileOffsetX[0] + light.tileScale[0];
|
||||
#if (!kha_opengl)
|
||||
// flip bounds
|
||||
spotTileBoundsArray[idx + 1] = 1.0 - (light.tileOffsetY[0] + light.tileScale[0]);
|
||||
spotTileBoundsArray[idx + 3] = 1.0 - light.tileOffsetY[0];
|
||||
#else
|
||||
spotTileBoundsArray[idx + 1] = light.tileOffsetY[0];
|
||||
spotTileBoundsArray[idx + 3] = light.tileOffsetY[0] + light.tileScale[0];
|
||||
#end
|
||||
if (discardLightCulled(light)) continue;
|
||||
if ((light.data.raw.type == "spot" || light.data.raw.type == "area") && light.data.raw.cast_shadow) {
|
||||
var idx = i * 4;
|
||||
spotTileBoundsArray[idx ] = light.tileOffsetX[0];
|
||||
spotTileBoundsArray[idx + 2] = light.tileOffsetX[0] + light.tileScale[0];
|
||||
#if (!kha_opengl)
|
||||
// flip bounds
|
||||
spotTileBoundsArray[idx + 1] = 1.0 - (light.tileOffsetY[0] + light.tileScale[0]);
|
||||
spotTileBoundsArray[idx + 3] = 1.0 - light.tileOffsetY[0];
|
||||
#else
|
||||
spotTileBoundsArray[idx + 1] = light.tileOffsetY[0];
|
||||
spotTileBoundsArray[idx + 3] = light.tileOffsetY[0] + light.tileScale[0];
|
||||
#end
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return spotTileBoundsArray;
|
||||
|
||||
@ -30,13 +30,24 @@ class MeshObject extends Object {
|
||||
public var frustumCulling = true;
|
||||
public var activeTilesheet: Tilesheet = null;
|
||||
public var tilesheets: Array<Tilesheet> = null;
|
||||
#if lnx_occlusion_culling
|
||||
public var occlusionVisible = true;
|
||||
public var shadowOcclusionVisible = true;
|
||||
#end
|
||||
var shadowCameraCulled: Bool = false;
|
||||
var shadowCameraCullFrame: Int = -1;
|
||||
var shadowCameraCullCamera: CameraObject = null;
|
||||
var shadowCameraCullLight: LightObject = null;
|
||||
public var skip_context: String = null; // Do not draw this context
|
||||
public var force_context: String = null; // Draw only this context
|
||||
static var lastPipeline: PipelineState = null;
|
||||
|
||||
var cachedMats: Map<String, Vector<MaterialData>> = null;
|
||||
var cachedMaterialContexts: Map<String, Array<MaterialContext>> = null;
|
||||
var cachedShaderContexts: Map<String, Array<ShaderContext>> = null;
|
||||
#if lnx_morph_target
|
||||
public var morphTarget: MorphTarget = null;
|
||||
#end
|
||||
public var vertexGroups: Map<String, Array<Vec4>> = null;
|
||||
|
||||
#if lnx_veloc
|
||||
public var prevMatrix = Mat4.identity();
|
||||
@ -48,6 +59,9 @@ class MeshObject extends Object {
|
||||
this.materials = materials;
|
||||
setData(data);
|
||||
Scene.active.meshes.push(this);
|
||||
#if lnx_occlusion_culling
|
||||
if (iron.culling.CullSystem.active != null) iron.culling.CullSystem.active.markDirty();
|
||||
#end
|
||||
}
|
||||
|
||||
public function setData(data: MeshData) {
|
||||
@ -96,6 +110,9 @@ class MeshObject extends Object {
|
||||
if (activeTilesheet != null) activeTilesheet.remove();
|
||||
if (tilesheets != null) { for (ts in tilesheets) { ts.remove(); } tilesheets = null; }
|
||||
if (Scene.active != null) Scene.active.meshes.remove(this);
|
||||
#if lnx_occlusion_culling
|
||||
if (iron.culling.CullSystem.active != null) iron.culling.CullSystem.active.markDirty();
|
||||
#end
|
||||
#if (rp_renderer == "Deferred")
|
||||
if (Scene.active != null) Scene.active.markMaterialParamsDirty();
|
||||
#end
|
||||
@ -196,7 +213,7 @@ class MeshObject extends Object {
|
||||
var isShadow = context == "shadowmap";
|
||||
if (camera == null) return false;
|
||||
|
||||
if (camera.data.raw.frustum_culling && frustumCulling) {
|
||||
if (camera.data.raw.frustum_culling && frustumCulling && RenderPath.active != null && RenderPath.active.frustumCulling) {
|
||||
// Scale radius for skinned mesh and particle system
|
||||
// TODO: define skin & particle bounds
|
||||
var radiusScale = data.isSkinned ? 2.0 : 1.0;
|
||||
@ -211,7 +228,8 @@ class MeshObject extends Object {
|
||||
var frustumPlanes = isShadow ? light.frustumPlanes : camera.frustumPlanes;
|
||||
|
||||
if (isShadow && light.data.raw.type != "sun") { // Non-sun light bounds intersect camera frustum
|
||||
light.transform.radius = light.data.raw.far_plane;
|
||||
var farPlane: kha.FastFloat = light.data.raw.far_plane;
|
||||
light.transform.radius = farPlane * 1.25;
|
||||
if (!CameraObject.sphereInFrustum(camera.frustumPlanes, light.transform)) {
|
||||
return setCulled(isShadow, true);
|
||||
}
|
||||
@ -220,10 +238,40 @@ class MeshObject extends Object {
|
||||
if (!CameraObject.sphereInFrustum(frustumPlanes, transform, radiusScale)) {
|
||||
return setCulled(isShadow, true);
|
||||
}
|
||||
}
|
||||
|
||||
//culled = false;
|
||||
//return culled;
|
||||
#if rp_shadowmap
|
||||
if (isShadow && light.data.raw.type == "sun") {
|
||||
var frame = RenderPath.active.frame;
|
||||
if (shadowCameraCullFrame == frame && shadowCameraCullCamera == camera &&
|
||||
shadowCameraCullLight == light) {
|
||||
if (shadowCameraCulled) return setCulled(isShadow, true);
|
||||
} else {
|
||||
shadowCameraCullFrame = frame;
|
||||
shadowCameraCullCamera = camera;
|
||||
shadowCameraCullLight = light;
|
||||
shadowCameraCulled = false;
|
||||
var ldx = -light.V._02;
|
||||
var ldy = -light.V._12;
|
||||
var ldz = -light.V._22;
|
||||
var radius = transform.radius * radiusScale;
|
||||
var cx = transform.worldx();
|
||||
var cy = transform.worldy();
|
||||
var cz = transform.worldz();
|
||||
var range = camera.data.raw.far_plane * (LightObject.cascadeBounds * 4.0);
|
||||
var margin = radius * 0.1;
|
||||
for (plane in camera.frustumPlanes) {
|
||||
var reach = plane.normal.x * cx + plane.normal.y * cy + plane.normal.z * cz + plane.constant + radius + margin;
|
||||
var dot = plane.normal.x * ldx + plane.normal.y * ldy + plane.normal.z * ldz;
|
||||
if (dot > 0) reach += dot * range;
|
||||
if (reach < 0) {
|
||||
shadowCameraCulled = true;
|
||||
return setCulled(isShadow, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#end
|
||||
}
|
||||
return setCulled(isShadow, false);
|
||||
}
|
||||
|
||||
@ -253,6 +301,12 @@ class MeshObject extends Object {
|
||||
}
|
||||
}
|
||||
|
||||
static function matsEqual(a: Vector<MaterialData>, b: Vector<MaterialData>): Bool {
|
||||
if (a == null || b == null || a.length != b.length) return false;
|
||||
for (i in 0...a.length) if (a[i] != b[i]) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public function render(g: Graphics, context: String, bindParams: Array<String>) {
|
||||
if (data == null || !data.geom.ready) return; // Data not yet streamed
|
||||
if (!visible) return; // Skip render if object is hidden
|
||||
@ -293,6 +347,7 @@ class MeshObject extends Object {
|
||||
// Get lod
|
||||
var mats = materials;
|
||||
var lod = this;
|
||||
var isShadowCtx = context == "shadowmap" || context == "shadowmap_transparent";
|
||||
if (raw != null && raw.lods != null && raw.lods.length > 0) {
|
||||
computeScreenSize(Scene.active.camera);
|
||||
initLods();
|
||||
@ -317,13 +372,29 @@ class MeshObject extends Object {
|
||||
if (isLodMaterial() && !validContext(mats, context)) return;
|
||||
|
||||
// Get context
|
||||
var materialContexts: Array<MaterialContext> = [];
|
||||
var shaderContexts: Array<ShaderContext> = [];
|
||||
getContexts(context, mats, materialContexts, shaderContexts);
|
||||
var materialContexts: Array<MaterialContext> = null;
|
||||
var shaderContexts: Array<ShaderContext> = null;
|
||||
if (cachedMaterialContexts != null && matsEqual(cachedMats.get(context), mats)) {
|
||||
materialContexts = cachedMaterialContexts.get(context);
|
||||
shaderContexts = cachedShaderContexts.get(context);
|
||||
}
|
||||
if (materialContexts == null) {
|
||||
materialContexts = [];
|
||||
shaderContexts = [];
|
||||
getContexts(context, mats, materialContexts, shaderContexts);
|
||||
if (cachedMaterialContexts == null) {
|
||||
cachedMats = new Map();
|
||||
cachedMaterialContexts = new Map();
|
||||
cachedShaderContexts = new Map();
|
||||
}
|
||||
cachedMats.set(context, mats);
|
||||
cachedMaterialContexts.set(context, materialContexts);
|
||||
cachedShaderContexts.set(context, shaderContexts);
|
||||
}
|
||||
|
||||
Uniforms.posUnpack = data.scalePos;
|
||||
Uniforms.texUnpack = data.scaleTex;
|
||||
transform.update();
|
||||
if (!isShadowCtx) transform.update();
|
||||
|
||||
// Render mesh
|
||||
var ldata = lod.data;
|
||||
|
||||
@ -15,6 +15,7 @@ class Object {
|
||||
public var transform: Transform;
|
||||
public var constraints: Array<Constraint> = null;
|
||||
public var traits: Array<Trait> = [];
|
||||
public var vertexGroups: Map<String, Array<iron.math.Vec4>> = null;
|
||||
|
||||
public var parent: Object = null;
|
||||
public var children: Array<Object> = [];
|
||||
|
||||
@ -17,6 +17,7 @@ class ObjectAnimation extends Animation {
|
||||
public var oactions: Array<TSceneFormat>;
|
||||
var oaction: TObj;
|
||||
var s0: FastFloat = 0.0;
|
||||
var delta: FastFloat = 0;
|
||||
var bezierFrameIndex = -1;
|
||||
|
||||
var updateAnimation: Map<String, FastFloat> -> Void;
|
||||
@ -26,7 +27,11 @@ class ObjectAnimation extends Animation {
|
||||
public var transformMap: Map<String, FastFloat>;
|
||||
|
||||
var defaultSampler: ActionSampler = null;
|
||||
static inline var DEFAULT_SAMPLER_ID = "__object_default_action__";
|
||||
var playSamplers: Map<String, ActionSampler> = [];
|
||||
var transitionSampler: ActionSampler = null;
|
||||
var transitionMap: Map<String, FastFloat> = null;
|
||||
var transitionTime: FastFloat = 0.0;
|
||||
var transitionDuration: FastFloat = 0.0;
|
||||
|
||||
public static var trackNames: Array<String> = [ "xloc", "yloc", "zloc",
|
||||
"xrot", "yrot", "zrot",
|
||||
@ -67,27 +72,59 @@ class ObjectAnimation extends Animation {
|
||||
oaction = getAction(this.action);
|
||||
if (oaction != null) {
|
||||
isSampled = oaction.sampled != null && oaction.sampled;
|
||||
if (defaultSampler != null) {
|
||||
deRegisterAction(DEFAULT_SAMPLER_ID);
|
||||
var sampler = playSamplers.get(this.action);
|
||||
if (sampler == null) {
|
||||
if (activeActions != null) sampler = activeActions.get(this.action);
|
||||
if (sampler == null) sampler = new ActionSampler(this.action);
|
||||
playSamplers.set(this.action, sampler);
|
||||
}
|
||||
var callbacks = onComplete != null ? [onComplete] : null;
|
||||
defaultSampler = new ActionSampler(this.action, speed, loop, false, callbacks);
|
||||
registerAction(DEFAULT_SAMPLER_ID, defaultSampler);
|
||||
if (paused) defaultSampler.paused = true;
|
||||
if (transitionSampler != null) {
|
||||
transitionSampler.paused = true;
|
||||
transitionSampler = null;
|
||||
}
|
||||
if (blendTime > 0 && defaultSampler != null && defaultSampler != sampler) {
|
||||
transitionSampler = defaultSampler;
|
||||
transitionMap = initTransformMap();
|
||||
transitionTime = 0.0;
|
||||
transitionDuration = blendTime;
|
||||
}
|
||||
else if (defaultSampler != null) defaultSampler.paused = true;
|
||||
defaultSampler = sampler;
|
||||
sampler.speed = speed;
|
||||
sampler.loop = loop;
|
||||
sampler.onComplete = onComplete != null ? [onComplete] : null;
|
||||
sampler.restartAction();
|
||||
registerAction(this.action, sampler);
|
||||
updateAnimation = function(map: Map<String, FastFloat>) {
|
||||
sampleAction(defaultSampler, map);
|
||||
if (transitionSampler != null) {
|
||||
sampleAction(transitionSampler, transitionMap);
|
||||
sampleAction(sampler, map);
|
||||
var f = transitionTime / transitionDuration;
|
||||
if (f > 1.0) f = 1.0;
|
||||
f = f * f * (3.0 - 2.0 * f); // Smoothstep
|
||||
blendActionObject(transitionMap, map, map, f);
|
||||
transitionTime += delta;
|
||||
if (transitionTime >= transitionDuration) {
|
||||
transitionSampler.paused = true;
|
||||
transitionSampler = null;
|
||||
}
|
||||
}
|
||||
else sampleAction(sampler, map);
|
||||
};
|
||||
}
|
||||
else {
|
||||
if (defaultSampler != null) {
|
||||
deRegisterAction(DEFAULT_SAMPLER_ID);
|
||||
defaultSampler = null;
|
||||
if (transitionSampler != null) {
|
||||
transitionSampler.paused = true;
|
||||
transitionSampler = null;
|
||||
}
|
||||
if (defaultSampler != null) defaultSampler.paused = true;
|
||||
defaultSampler = null;
|
||||
updateAnimation = null;
|
||||
}
|
||||
}
|
||||
|
||||
override public function update(delta: FastFloat) {
|
||||
this.delta = delta;
|
||||
if (!object.visible || object.culled) return;
|
||||
|
||||
#if lnx_debug
|
||||
@ -98,7 +135,6 @@ class ObjectAnimation extends Animation {
|
||||
transformMap = initTransformMap();
|
||||
|
||||
super.update(delta);
|
||||
if (defaultSampler != null) defaultSampler.paused = paused;
|
||||
if (paused) return;
|
||||
if (updateAnimation == null) return;
|
||||
if (!isSkinned) updateObjectAnimation();
|
||||
@ -160,11 +196,13 @@ class ObjectAnimation extends Animation {
|
||||
|
||||
var ti = sampler.offset;
|
||||
//ti = ti < 0 ? 1 : ti;
|
||||
var tj = ti + sign;
|
||||
if (tj < 0 || tj >= track.frames.length) tj = ti;
|
||||
|
||||
var t1 = track.frames[ti] * frameTime;
|
||||
var t2 = track.frames[ti + sign] * frameTime;
|
||||
var t2 = track.frames[tj] * frameTime;
|
||||
var v1 = track.values[ti];
|
||||
var v2 = track.values[ti + sign];
|
||||
var v2 = track.values[tj];
|
||||
|
||||
var value = interpolateLinear(t, t1, t2, v1, v2);
|
||||
|
||||
@ -202,6 +240,7 @@ class ObjectAnimation extends Animation {
|
||||
}
|
||||
|
||||
inline function interpolateLinear(t: FastFloat, t1: FastFloat, t2: FastFloat, v1: FastFloat, v2: FastFloat): Null<FastFloat> {
|
||||
if (t2 == t1) return v1;
|
||||
var s = (t - t1) / (t2 - t1);
|
||||
return (1.0 - s) * v1 + s * v2;
|
||||
}
|
||||
|
||||
@ -131,13 +131,20 @@ class Transform {
|
||||
dscale = new Vec4().set(1, 1, 1);
|
||||
}
|
||||
|
||||
static var _cdLoc = new Vec4();
|
||||
static var _cdRot = new Quat();
|
||||
static var _cdScale = new Vec4();
|
||||
|
||||
function composeDelta() {
|
||||
// Delta transform
|
||||
dloc.addvecs(loc, dloc);
|
||||
dscale.addvecs(dscale, scale);
|
||||
drot.fromEuler(_deulerX, _deulerY, _deulerZ);
|
||||
drot.multquats(rot, drot);
|
||||
local.compose(dloc, drot, dscale);
|
||||
// Delta transform: additive location, multiplicative rotation and scale
|
||||
_cdLoc.addvecs(loc, dloc);
|
||||
_cdScale.x = scale.x * dscale.x;
|
||||
_cdScale.y = scale.y * dscale.y;
|
||||
_cdScale.z = scale.z * dscale.z;
|
||||
_cdRot.fromEuler(_deulerX, _deulerY, _deulerZ);
|
||||
_cdRot.multquats(_cdRot, drot);
|
||||
_cdRot.multquats(rot, _cdRot);
|
||||
local.compose(_cdLoc, _cdRot, _cdScale);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -928,6 +928,14 @@ class Uniforms {
|
||||
case "_tileBoundsSunArray": {
|
||||
fa = LightObject.updateSunTileBoundsArray();
|
||||
}
|
||||
case "_tileBoundsSpotArray": {
|
||||
fa = LightObject.updateSpotTileBoundsArray();
|
||||
}
|
||||
#end
|
||||
#if (!lnx_clusters && lnx_spot)
|
||||
case "_biasLightWorldViewProjectionMatrixSpotArray": {
|
||||
fa = updateSpotMatrixFloats();
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
@ -1387,6 +1395,11 @@ class Uniforms {
|
||||
fa = LightObject.updateLWVPMatrixArray(object, "spot");
|
||||
}
|
||||
#end // lnx_clusters
|
||||
#if (!lnx_clusters && lnx_spot)
|
||||
case "_biasLightWorldViewProjectionMatrixSpotArray": {
|
||||
fa = updateSpotMatrixFloats();
|
||||
}
|
||||
#end
|
||||
#if lnx_morph_target
|
||||
case "_morphWeights": {
|
||||
fa = cast(object, MeshObject).morphTarget.morphWeights;
|
||||
@ -1473,6 +1486,32 @@ class Uniforms {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static var spotMatrixFloats: Float32Array = null;
|
||||
static function updateSpotMatrixFloats(): Float32Array {
|
||||
var light = getSpot(0);
|
||||
if (light == null) return null;
|
||||
if (spotMatrixFloats == null) spotMatrixFloats = new Float32Array(16);
|
||||
helpMat.setFrom(light.VP);
|
||||
helpMat.multmat(biasMat);
|
||||
spotMatrixFloats[0 ] = helpMat._00;
|
||||
spotMatrixFloats[1 ] = helpMat._01;
|
||||
spotMatrixFloats[2 ] = helpMat._02;
|
||||
spotMatrixFloats[3 ] = helpMat._03;
|
||||
spotMatrixFloats[4 ] = helpMat._10;
|
||||
spotMatrixFloats[5 ] = helpMat._11;
|
||||
spotMatrixFloats[6 ] = helpMat._12;
|
||||
spotMatrixFloats[7 ] = helpMat._13;
|
||||
spotMatrixFloats[8 ] = helpMat._20;
|
||||
spotMatrixFloats[9 ] = helpMat._21;
|
||||
spotMatrixFloats[10] = helpMat._22;
|
||||
spotMatrixFloats[11] = helpMat._23;
|
||||
spotMatrixFloats[12] = helpMat._30;
|
||||
spotMatrixFloats[13] = helpMat._31;
|
||||
spotMatrixFloats[14] = helpMat._32;
|
||||
spotMatrixFloats[15] = helpMat._33;
|
||||
return spotMatrixFloats;
|
||||
}
|
||||
#end
|
||||
|
||||
static function currentMat(object: Object): MaterialData {
|
||||
|
||||
@ -166,6 +166,7 @@ class LnxPack {
|
||||
case "anim": TAnimation;
|
||||
case "tracks": TTrack;
|
||||
case "morph_target": TMorphTarget;
|
||||
case "occluder_proxy": TOccluderProxy;
|
||||
case "vertex_groups": TVertex_groups;
|
||||
case "tilesheet": TTilesheetData;
|
||||
case "events": TTilesheetEvent;
|
||||
|
||||
@ -38,14 +38,14 @@ class Time {
|
||||
static var normalModeLogged: Bool = false;
|
||||
#end
|
||||
|
||||
static var lastTime = 0.0;
|
||||
static var lastTime = -1.0;
|
||||
static var _delta = 0.0;
|
||||
public static var delta(get, never): Float;
|
||||
static function get_delta(): Float {
|
||||
return _delta;
|
||||
}
|
||||
|
||||
static var lastRenderTime = 0.0;
|
||||
static var lastRenderTime = -1.0;
|
||||
static var _renderDelta = 0.0;
|
||||
public static var renderDelta(get, never): Float;
|
||||
static function get_renderDelta(): Float {
|
||||
@ -79,12 +79,14 @@ class Time {
|
||||
}
|
||||
#end
|
||||
|
||||
_delta = realTime() - lastTime;
|
||||
lastTime = realTime();
|
||||
var now = realTime();
|
||||
_delta = lastTime < 0.0 ? 0.0 : now - lastTime;
|
||||
lastTime = now;
|
||||
}
|
||||
|
||||
public static function render() {
|
||||
_renderDelta = realTime() - lastRenderTime;
|
||||
lastRenderTime = realTime();
|
||||
var now = realTime();
|
||||
_renderDelta = lastRenderTime < 0.0 ? 0.0 : now - lastRenderTime;
|
||||
lastRenderTime = now;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,79 +0,0 @@
|
||||
package leenkx.logicnode;
|
||||
import leenkx.system.Event;
|
||||
#if js
|
||||
import leenkx.network.Leenkx;
|
||||
#end
|
||||
import iron.object.Object;
|
||||
|
||||
class AddTorrentNode extends LogicNode {
|
||||
public var title: String;
|
||||
public var values: Array<Dynamic>;
|
||||
public var net_Url: String;
|
||||
public var data: Dynamic;
|
||||
public var runner: Bool = false;
|
||||
|
||||
|
||||
public function new(tree:LogicTree) {
|
||||
super(tree);
|
||||
}
|
||||
|
||||
public function promiseResult(connection,torrentid,net_Url){
|
||||
#if js
|
||||
if (runner == false){
|
||||
values = [];
|
||||
var script = 'var torrentId = "' + torrentid + '";
|
||||
lx_' + net_Url +'.wt.add(torrentId, function (torrent) {
|
||||
leenkx.network.Leenkx.torrent.set("' + net_Url +'", torrent.files);
|
||||
torrent.on("done", function () {
|
||||
leenkx.network.Leenkx.connections.h["' + net_Url +'"].ontorrentdone();
|
||||
})
|
||||
});
|
||||
';
|
||||
js.Syntax.code('(1, eval)({0})', script.toString());
|
||||
runner = true;
|
||||
promiseResult(connection,torrentid,net_Url);
|
||||
return;
|
||||
}else{
|
||||
try{
|
||||
values = leenkx.network.Leenkx.torrent.get(net_Url);
|
||||
if(values.length > 0){
|
||||
runOutput(0);
|
||||
runner = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch(error){
|
||||
haxe.Timer.delay(function () {
|
||||
promiseResult(connection,torrentid,net_Url);
|
||||
return;
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
|
||||
override function run(from:Int) {
|
||||
var connection = inputs[1].get();
|
||||
if (connection == null) return;
|
||||
var torrentid = inputs[2].get();
|
||||
if (torrentid == null) return;
|
||||
#if js
|
||||
net_Url = connection._url;
|
||||
promiseResult(connection,torrentid,net_Url);
|
||||
#end
|
||||
}
|
||||
|
||||
override function get(from: Int): Dynamic {
|
||||
#if js
|
||||
return switch (from) {
|
||||
case 1: Leenkx.id.get(net_Url);
|
||||
case 2: leenkx.network.Leenkx.torrent.get(net_Url);
|
||||
default: throw "Unreachable";
|
||||
}
|
||||
#else
|
||||
return null;
|
||||
#end
|
||||
}
|
||||
|
||||
}
|
||||
@ -35,13 +35,13 @@ class AnimActionNode extends LogicNode {
|
||||
if(object.animation == null) {
|
||||
#if lnx_skin
|
||||
animationBone = object.getBoneAnimation(object.uid);
|
||||
animationBone.registerAction(property0, sampler);
|
||||
animationBone.registerAction(sampler.action, sampler);
|
||||
func = sampleBonaAction;
|
||||
#end
|
||||
}
|
||||
else{
|
||||
animationObject = cast(object.animation, ObjectAnimation);
|
||||
animationObject.registerAction(property0, sampler);
|
||||
animationObject.registerAction(sampler.action, sampler);
|
||||
func = sampleObjectAction;
|
||||
}
|
||||
|
||||
|
||||
@ -61,14 +61,16 @@ class BlendActionNode extends LogicNode {
|
||||
return;
|
||||
}
|
||||
if(factor > 0.95) {
|
||||
|
||||
inputs[2].get()(animMats);
|
||||
var action2 = inputs[2].get();
|
||||
if (action2 != null) action2(animMats); else inputs[1].get()(animMats);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
inputs[1].get()(animMats);
|
||||
inputs[2].get()(tempMats);
|
||||
var action2 = inputs[2].get();
|
||||
if (action2 == null) return;
|
||||
action2(tempMats);
|
||||
animationBone.blendAction(animMats, tempMats, animMats, factor, boneLayer);
|
||||
|
||||
}
|
||||
|
||||
@ -1,131 +1,170 @@
|
||||
package leenkx.logicnode;
|
||||
import iron.object.Object;
|
||||
#if js
|
||||
import leenkx.network.Types;
|
||||
import leenkx.network.Util;
|
||||
#if lnx_torrent
|
||||
import leenkx.network.Leenkx;
|
||||
import leenkx.network.torrent.LeenkxClient;
|
||||
import leenkx.network.torrent.TorrentClient;
|
||||
import leenkx.network.torrent.peer.RtcPeerPool;
|
||||
#end
|
||||
import leenkx.system.Event;
|
||||
|
||||
|
||||
class CreateLeenkxNode extends LogicNode {
|
||||
public var property0: String;
|
||||
public var property1: Bool;
|
||||
public var property2: String;
|
||||
public var property3: String;
|
||||
public var net_Url: String;
|
||||
public var data: Dynamic;
|
||||
public var client: Dynamic;
|
||||
public var value: String;
|
||||
public var listener: TEvent = null;
|
||||
|
||||
public function new(tree:LogicTree) {
|
||||
super(tree);
|
||||
super(tree);
|
||||
}
|
||||
|
||||
override function run(from:Int) {
|
||||
#if js
|
||||
net_Url = inputs[1].get();
|
||||
if (net_Url == null) return;
|
||||
if(Leenkx.connections[net_Url] == null){
|
||||
var object = tree.object;
|
||||
value = Leenkx.onLoadEvent;
|
||||
listener = Event.add(value, onEvent, tree.object.uid);
|
||||
client = new leenkx.network.Leenkx(net_Url, object);
|
||||
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
function onEvent() {
|
||||
#if js
|
||||
var lnxjs:Dynamic = js.Lib.global;
|
||||
var lnxCxNew = lnxjs.lnxNew;
|
||||
|
||||
if (lnxCxNew == null) {
|
||||
trace("ERROR: lnxNew not available");
|
||||
return;
|
||||
#if lnx_torrent
|
||||
net_Url = inputs[1].get();
|
||||
if (net_Url == null) return;
|
||||
if (Leenkx.connections[net_Url] == null) {
|
||||
value = Leenkx.onLoadEvent;
|
||||
listener = Event.add(value, onEvent, tree.object.uid);
|
||||
new leenkx.network.Leenkx(net_Url, tree.object);
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
function onEvent() {
|
||||
if (listener != null) {
|
||||
Event.removeListener(listener);
|
||||
listener = null;
|
||||
}
|
||||
#if lnx_torrent
|
||||
var sock = Leenkx.connections[net_Url];
|
||||
if (sock == null) return;
|
||||
if (sock.client != null) {
|
||||
runOutput(0);
|
||||
return;
|
||||
}
|
||||
var opts:leenkx.network.torrent.Types.LeenkxOptions = {deferJoin: true};
|
||||
var nextInput = 2;
|
||||
if (property0 == "datachannel") {
|
||||
opts.transport = "datachannel";
|
||||
var imode = property2 != null ? property2 : "inherit";
|
||||
if (imode != "inherit") {
|
||||
if (inputs.length > nextInput) {
|
||||
var ice = TorrentClient.trackerUrls(inputs[nextInput].get());
|
||||
if (ice != null) {
|
||||
opts.iceServers = imode == "override" ? ice : RtcPeerPool.defaultIceServers.concat(ice);
|
||||
}
|
||||
}
|
||||
nextInput++;
|
||||
}
|
||||
}
|
||||
var tmode = property3 != null ? property3 : "inherit";
|
||||
var trackers:Array<String> = null;
|
||||
if (tmode != "inherit" && inputs.length > nextInput) {
|
||||
trackers = TorrentClient.trackerUrls(inputs[nextInput].get());
|
||||
}
|
||||
opts.announce = tmode == "override" && trackers != null ? trackers
|
||||
: trackers != null ? TorrentClient.defaultTrackers.concat(trackers)
|
||||
: TorrentClient.defaultTrackers;
|
||||
var lnxCn = new LeenkxClient(net_Url, opts);
|
||||
lnxCn.isRelay = property1 == true;
|
||||
|
||||
lnxCn.onOpen = function() {
|
||||
Leenkx.data.set(net_Url, lnxCn.lastPeerIndex);
|
||||
Leenkx.id.set(net_Url, lnxCn.lastPeerId);
|
||||
sock.onopen();
|
||||
};
|
||||
lnxCn.onConnections = function(c) {
|
||||
Leenkx.data.set(net_Url, c + 1);
|
||||
sock.onconnections();
|
||||
};
|
||||
lnxCn.onMessage = function(address, message, nonce) {
|
||||
Leenkx.data.set(net_Url, message);
|
||||
Leenkx.id.set(net_Url, address);
|
||||
sock.onmessage();
|
||||
};
|
||||
lnxCn.onSeen = function(address) {
|
||||
Leenkx.id.set(net_Url, address);
|
||||
sock.onseen();
|
||||
};
|
||||
lnxCn.onLeft = function(address) {
|
||||
Leenkx.id.set(net_Url, address);
|
||||
sock.onleft();
|
||||
sock.onclose();
|
||||
};
|
||||
lnxCn.onServer = function(address) {
|
||||
Leenkx.id.set(net_Url, address);
|
||||
sock.onserver();
|
||||
};
|
||||
lnxCn.onPing = function(address) {
|
||||
Leenkx.id.set(net_Url, address);
|
||||
sock.onping();
|
||||
};
|
||||
lnxCn.onTimeout = function(address) {
|
||||
Leenkx.id.set(net_Url, address);
|
||||
sock.ontimeout();
|
||||
sock.onclose();
|
||||
};
|
||||
lnxCn.onWireLeft = function(wirecount, wire) {
|
||||
Leenkx.data.set(net_Url, wirecount);
|
||||
if (wire != null && wire.peerId != null) {
|
||||
Leenkx.id.set(net_Url, wire.peerId);
|
||||
}
|
||||
sock.onwireleft();
|
||||
};
|
||||
lnxCn.onWireSeen = function(wirecount, wire) {
|
||||
Leenkx.data.set(net_Url, wirecount);
|
||||
if (wire != null && wire.peerId != null) {
|
||||
Leenkx.id.set(net_Url, wire.peerId);
|
||||
}
|
||||
sock.onwireseen();
|
||||
};
|
||||
lnxCn.onTorrent = function(identifier, torrent) {
|
||||
Leenkx.data.set(net_Url, torrent);
|
||||
Leenkx.id.set(net_Url, identifier);
|
||||
sock.ontorrent();
|
||||
};
|
||||
lnxCn.onTracker = function(identifier, tracker) {
|
||||
Leenkx.id.set(net_Url, identifier);
|
||||
sock.ontracker();
|
||||
};
|
||||
lnxCn.onAnnounce = function(identifier) {
|
||||
Leenkx.id.set(net_Url, identifier);
|
||||
sock.onannounce();
|
||||
};
|
||||
lnxCn.onError = function(msg) {
|
||||
Leenkx.data.set(net_Url, msg);
|
||||
sock.onerror();
|
||||
};
|
||||
lnxCn.onTorrentAdded = function(t) {
|
||||
Leenkx.torrent.set(net_Url, t);
|
||||
Leenkx.file.set(net_Url, t.files);
|
||||
Leenkx.data.set(net_Url, t);
|
||||
sock.ontorrentadded();
|
||||
};
|
||||
lnxCn.onTorrentDone = function(t) {
|
||||
Leenkx.data.set(net_Url, t);
|
||||
sock.ontorrentdone();
|
||||
};
|
||||
|
||||
sock.client = lnxCn;
|
||||
|
||||
lnxCn.connect();
|
||||
#if (js && !kha_krom)
|
||||
js.Browser.window.addEventListener("beforeunload", function(e) {
|
||||
lnxCn.destroy();
|
||||
});
|
||||
#end
|
||||
#end
|
||||
runOutput(0);
|
||||
}
|
||||
|
||||
override function get(from: Int): Dynamic {
|
||||
#if js
|
||||
#if lnx_torrent
|
||||
return switch (from) {
|
||||
case 1: Leenkx.connections[net_Url];
|
||||
case 2: Leenkx.id.get(net_Url);
|
||||
|
||||
@ -1,43 +1,100 @@
|
||||
package leenkx.logicnode;
|
||||
import leenkx.system.Event;
|
||||
#if lnx_torrent
|
||||
import leenkx.network.Leenkx;
|
||||
import iron.object.Object;
|
||||
import leenkx.network.torrent.Magnet;
|
||||
import leenkx.network.torrent.TorrentClient;
|
||||
#end
|
||||
|
||||
|
||||
class CreateTorrentNode extends LogicNode {
|
||||
public var torrent: Dynamic;
|
||||
public var net_Url: String;
|
||||
public var meta:Dynamic;
|
||||
public var metaBytes:Dynamic;
|
||||
public var magnetUri:Dynamic;
|
||||
public var infoHashHex:String;
|
||||
public var property0:String;
|
||||
public var property1:String;
|
||||
|
||||
public function new(tree:LogicTree) {
|
||||
super(tree);
|
||||
}
|
||||
}
|
||||
|
||||
override function run(from:Int) {
|
||||
#if lnx_torrent
|
||||
meta = null;
|
||||
metaBytes = null;
|
||||
magnetUri = null;
|
||||
infoHashHex = null;
|
||||
|
||||
var connection = inputs[1].get();
|
||||
if (connection == null) return;
|
||||
var data = inputs[2].get();
|
||||
if (data == null) return;
|
||||
net_Url = connection._url;
|
||||
var data = inputs[1].get();
|
||||
if (data == null) return;
|
||||
var mode = property1 != null ? property1 : "file";
|
||||
var opts:Dynamic = {};
|
||||
var mv = property0 != null ? Std.parseInt(property0) : null;
|
||||
if (mv != null) Reflect.setField(opts, "metaVersion", mv);
|
||||
field(2, opts, "name");
|
||||
field(3, opts, "comment");
|
||||
field(4, opts, "createdBy");
|
||||
field(5, opts, "source");
|
||||
field(6, opts, "announce");
|
||||
field(7, opts, "private");
|
||||
field(8, opts, "pieceLength");
|
||||
field(9, opts, "urlList");
|
||||
|
||||
var script = 'var file = ' + data + ';
|
||||
lx_' + net_Url +'.wt.add(file, function (torrent) {
|
||||
leenkx.network.Leenkx.file.set("' + net_Url +'", torrent.files);
|
||||
torrent.on("done", function () {
|
||||
leenkx.network.Leenkx.connections.h["' + net_Url +'"].ontorrentdone();
|
||||
});
|
||||
});
|
||||
';
|
||||
if (mode == "folder") {
|
||||
#if sys
|
||||
data = TorrentClient.readFolder(Std.string(data));
|
||||
var first:Dynamic = data[0];
|
||||
if (first != null && Reflect.field(opts, "name") == null) {
|
||||
var p = Std.string(first.path);
|
||||
var i = p.indexOf("/");
|
||||
Reflect.setField(opts, "name", p.substring(0, i >= 0 ? i : p.length));
|
||||
}
|
||||
#else
|
||||
trace("Folder mode requires a sys target";
|
||||
#end
|
||||
}
|
||||
|
||||
js.Syntax.code('(1, eval)({0})', script.toString());
|
||||
var m;
|
||||
try {
|
||||
m = TorrentClient.buildMeta(data, opts);
|
||||
} catch(e:Dynamic) {
|
||||
trace(e);
|
||||
return;
|
||||
}
|
||||
meta = m;
|
||||
metaBytes = m.toTorrentBytes();
|
||||
infoHashHex = m.infoHashHex != null ? m.infoHashHex : m.infoHashV2Hex;
|
||||
magnetUri = Magnet.build(m.infoHash, m.name, m.announce, m.urlList, m.infoHashV2);
|
||||
#end
|
||||
runOutput(0);
|
||||
}
|
||||
|
||||
#if lnx_torrent
|
||||
function field(i:Int, opts:Dynamic, name:String):Void {
|
||||
if (i >= inputs.length) return;
|
||||
var v:Dynamic = inputs[i].get();
|
||||
if (v == null || Std.string(v) == "") {
|
||||
return;
|
||||
}
|
||||
if (name == "announce") {
|
||||
v = TorrentClient.trackerUrls(v);
|
||||
if (v == null) return;
|
||||
}
|
||||
Reflect.setField(opts, name, v);
|
||||
}
|
||||
#end
|
||||
|
||||
override function get(from: Int): Dynamic {
|
||||
return switch (from) {
|
||||
case 1: Leenkx.file.get(net_Url);
|
||||
default: throw "Unreachable";
|
||||
}
|
||||
}
|
||||
|
||||
override function get(from:Int):Dynamic {
|
||||
#if lnx_torrent
|
||||
return switch (from) {
|
||||
case 1: meta;
|
||||
case 2: metaBytes;
|
||||
case 3: infoHashHex;
|
||||
case 4: magnetUri;
|
||||
default: throw "Unreachable";
|
||||
}
|
||||
#else
|
||||
return null;
|
||||
#end
|
||||
}
|
||||
}
|
||||
|
||||
88
leenkx/Sources/leenkx/logicnode/CreateTrackerNode.hx
Normal file
88
leenkx/Sources/leenkx/logicnode/CreateTrackerNode.hx
Normal file
@ -0,0 +1,88 @@
|
||||
package leenkx.logicnode;
|
||||
#if (lnx_torrent && (sys || kha_krom))
|
||||
import leenkx.network.torrent.tracker.HttpTracker.HttpTrackerServer;
|
||||
import leenkx.network.torrent.tracker.UdpTracker.UdpTrackerServer;
|
||||
import leenkx.network.torrent.transport.WsTrackerClient.WsTrackerServer;
|
||||
#end
|
||||
#if (lnx_torrent && sys)
|
||||
import leenkx.network.torrent.transport.WsTrackerClient.WssTrackerServer;
|
||||
#end
|
||||
|
||||
class CreateTrackerNode extends LogicNode {
|
||||
public var property0:String;
|
||||
public var tracker:Dynamic;
|
||||
public var url:String;
|
||||
|
||||
public function new(tree:LogicTree) {
|
||||
super(tree);
|
||||
}
|
||||
|
||||
override function run(from:Int) {
|
||||
tracker = null;
|
||||
url = null;
|
||||
startServer();
|
||||
runOutput(0);
|
||||
}
|
||||
|
||||
function startServer():Void {
|
||||
#if (lnx_torrent && (sys || kha_krom))
|
||||
var hv = inputs[1].get();
|
||||
var host = hv != null ? Std.string(hv) : "127.0.0.1";
|
||||
if (host == "") host = "127.0.0.1";
|
||||
var pv = inputs[2].get();
|
||||
var port = pv != null ? Std.int(Std.parseFloat(Std.string(pv))) : 0;
|
||||
if (port <= 0) port = 6969;
|
||||
var scheme = property0 != null ? property0 : "udp";
|
||||
switch (scheme) {
|
||||
case "http":
|
||||
var s = new HttpTrackerServer();
|
||||
s.onError = function(e) trace(e);
|
||||
s.start(port);
|
||||
tracker = s;
|
||||
url = "http://" + host + ":" + port + "/announce";
|
||||
case "ws":
|
||||
var s = new WsTrackerServer(host, port);
|
||||
s.onError = function(e) trace(e);
|
||||
s.start();
|
||||
tracker = s;
|
||||
url = "ws://" + host + ":" + port;
|
||||
case "wss":
|
||||
#if sys
|
||||
var cert = inputs.length > 3 ? Std.string(inputs[3].get()) : null;
|
||||
var key = inputs.length > 4 ? Std.string(inputs[4].get()) : null;
|
||||
if (cert == null || cert == "" || key == null || key == "") {
|
||||
trace("wss server needs Cert File and Key File");
|
||||
return;
|
||||
}
|
||||
var s = new WssTrackerServer(host, port, cert, key);
|
||||
s.onError = function(e) trace(e);
|
||||
s.start();
|
||||
tracker = s;
|
||||
url = "wss://" + host + ":" + port;
|
||||
#else
|
||||
trace("wss server requires a sys target");
|
||||
#end
|
||||
default:
|
||||
var s = new UdpTrackerServer();
|
||||
s.onError = function(e) trace(e);
|
||||
s.start(port);
|
||||
tracker = s;
|
||||
url = "udp://" + host + ":" + port;
|
||||
}
|
||||
#else
|
||||
trace("server mode needs a sys or krom target");
|
||||
#end
|
||||
}
|
||||
|
||||
override function get(from:Int):Dynamic {
|
||||
#if lnx_torrent
|
||||
return switch (from) {
|
||||
case 1: tracker;
|
||||
case 2: url;
|
||||
default: throw "Unreachable";
|
||||
}
|
||||
#else
|
||||
return null;
|
||||
#end
|
||||
}
|
||||
}
|
||||
101
leenkx/Sources/leenkx/logicnode/DownloadTorrentNode.hx
Normal file
101
leenkx/Sources/leenkx/logicnode/DownloadTorrentNode.hx
Normal file
@ -0,0 +1,101 @@
|
||||
package leenkx.logicnode;
|
||||
import leenkx.system.Event;
|
||||
#if lnx_torrent
|
||||
import leenkx.network.Leenkx;
|
||||
import leenkx.network.torrent.TorrentClient;
|
||||
#end
|
||||
|
||||
class DownloadTorrentNode extends LogicNode {
|
||||
public var net_Url:String;
|
||||
public var torrent:Dynamic;
|
||||
public var property0:String;
|
||||
public var property1:String;
|
||||
var listener:TEvent = null;
|
||||
|
||||
public function new(tree:LogicTree) {
|
||||
super(tree);
|
||||
}
|
||||
|
||||
public function promiseResult(connection, torrentid, net_Url, ?opts:Dynamic) {
|
||||
#if lnx_torrent
|
||||
var socket = Leenkx.connections[net_Url];
|
||||
if (socket == null) {
|
||||
if (listener == null) {
|
||||
var self = this;
|
||||
listener = Event.add(Leenkx.onLoadEvent, function() {
|
||||
if (Leenkx.connections[net_Url] == null) return;
|
||||
if (self.listener != null) {
|
||||
Event.removeListener(self.listener);
|
||||
self.listener = null;
|
||||
}
|
||||
self.promiseResult(connection, torrentid, net_Url, opts);
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
var client = socket.client;
|
||||
if (client == null) return;
|
||||
var self = this;
|
||||
client.add(torrentid, opts, function(torrent) {
|
||||
self.torrent = torrent;
|
||||
var onReady = function() {
|
||||
self.runOutput(0);
|
||||
};
|
||||
var onDone = function() {
|
||||
self.runOutput(4);
|
||||
};
|
||||
if (torrent.ready) onReady();
|
||||
else torrent.on("ready", onReady);
|
||||
if (torrent.done) onDone();
|
||||
else torrent.on("done", onDone);
|
||||
});
|
||||
#end
|
||||
}
|
||||
|
||||
function onRemove() {
|
||||
if (listener != null) {
|
||||
Event.removeListener(listener);
|
||||
listener = null;
|
||||
}
|
||||
}
|
||||
|
||||
override function run(from:Int) {
|
||||
var connection = inputs[1].get();
|
||||
if (connection == null) return;
|
||||
var v = inputs[2].get();
|
||||
if (v == null) return;
|
||||
#if lnx_torrent
|
||||
net_Url = connection._url;
|
||||
var torrentId:Dynamic = switch (property0 != null ? property0 : "magnet") {
|
||||
case "file": v;
|
||||
case "id": TorrentClient.torrentIdFor(v);
|
||||
default: Std.string(v);
|
||||
};
|
||||
var opts:Dynamic = null;
|
||||
var tmode = property1 != null ? property1 : "inherit";
|
||||
if (tmode != "inherit" && inputs.length > 3) {
|
||||
var announce = TorrentClient.trackerUrls(inputs[3].get());
|
||||
opts = {};
|
||||
if (announce != null) opts.announce = announce;
|
||||
if (tmode == "override") opts.announceOverride = true;
|
||||
}
|
||||
promiseResult(connection, torrentId, net_Url, opts);
|
||||
#end
|
||||
}
|
||||
|
||||
override function get(from:Int):Dynamic {
|
||||
#if lnx_torrent
|
||||
var t:leenkx.network.torrent.Torrent = torrent;
|
||||
return switch (from) {
|
||||
case 1: torrent;
|
||||
case 2: t != null && t.meta != null ? t.meta.name : null;
|
||||
case 3: t != null ? t.files : null;
|
||||
case 5: t != null ? t.infoHashHex : null;
|
||||
case 6: t != null ? t.magnetURI : null;
|
||||
default: throw "Unreachable";
|
||||
}
|
||||
#else
|
||||
return null;
|
||||
#end
|
||||
}
|
||||
}
|
||||
83
leenkx/Sources/leenkx/logicnode/GetTorrentFileNode.hx
Normal file
83
leenkx/Sources/leenkx/logicnode/GetTorrentFileNode.hx
Normal file
@ -0,0 +1,83 @@
|
||||
package leenkx.logicnode;
|
||||
#if lnx_torrent
|
||||
import leenkx.network.Leenkx;
|
||||
import leenkx.network.torrent.Torrent;
|
||||
import leenkx.network.torrent.TorrentFile;
|
||||
#end
|
||||
|
||||
class GetTorrentFileNode extends LogicNode {
|
||||
public var property0:String;
|
||||
public var file:Dynamic;
|
||||
var net_Url:String;
|
||||
|
||||
public function new(tree:LogicTree) {
|
||||
super(tree);
|
||||
}
|
||||
|
||||
override function run(from:Int) {
|
||||
#if lnx_torrent
|
||||
file = null;
|
||||
var id = inputs[2].get();
|
||||
var match = inputs[3].get();
|
||||
if (id != null && match != null) {
|
||||
var connection = inputs[1].get();
|
||||
if (connection != null) net_Url = connection._url;
|
||||
var socket = net_Url != null ? Leenkx.connections[net_Url] : null;
|
||||
var client = socket != null ? socket.client : null;
|
||||
var t = client != null ? client.get(id) : (Std.isOfType(id, Torrent) ? id : null);
|
||||
if (t != null) file = findFile(t, match);
|
||||
}
|
||||
#end
|
||||
runOutput(0);
|
||||
}
|
||||
|
||||
#if lnx_torrent
|
||||
function findFile(t:Torrent, match:Dynamic):TorrentFile {
|
||||
var files = t.files;
|
||||
switch (property0) {
|
||||
case "index":
|
||||
var i = Std.parseInt(Std.string(match));
|
||||
if (i != null && i >= 0 && i < files.length) {
|
||||
return files[i];
|
||||
}
|
||||
case "name":
|
||||
var s = Std.string(match);
|
||||
for (f in files) {
|
||||
if (f.name == s) return f;
|
||||
}
|
||||
case "path":
|
||||
var s = Std.string(match);
|
||||
for (f in files) {
|
||||
if (f.path == s) return f;
|
||||
}
|
||||
default:
|
||||
var s = Std.string(match);
|
||||
if (!StringTools.startsWith(s, ".")) s = "." + s;
|
||||
for (f in files) {
|
||||
if (StringTools.endsWith(f.name, s)) return f;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
#end
|
||||
|
||||
override function get(from:Int):Dynamic {
|
||||
#if lnx_torrent
|
||||
var f:TorrentFile = file;
|
||||
return switch (from) {
|
||||
case 1: file;
|
||||
case 2: f != null ? f.name : null;
|
||||
case 3: f != null ? f.path : null;
|
||||
case 4: f != null ? f.length : 0;
|
||||
case 5: f != null ? f.type : null;
|
||||
case 6: f != null ? f.getBytes() : null;
|
||||
case 7: f != null;
|
||||
case 8: f != null ? f.downloaded : 0.0;
|
||||
case 9: f != null ? f.progress : 0.0;
|
||||
default: throw "Unreachable";
|
||||
}
|
||||
#else
|
||||
return null;
|
||||
#end
|
||||
}
|
||||
}
|
||||
@ -1,8 +1,8 @@
|
||||
package leenkx.logicnode;
|
||||
import leenkx.system.Event;
|
||||
import leenkx.network.Connect;
|
||||
import iron.object.Object;
|
||||
|
||||
#if lnx_torrent
|
||||
import leenkx.network.Leenkx;
|
||||
import leenkx.network.torrent.LeenkxClient;
|
||||
#end
|
||||
|
||||
class LeenkxCloseConnectionNode extends LogicNode {
|
||||
public var property0: String;
|
||||
@ -12,41 +12,28 @@ class LeenkxCloseConnectionNode extends LogicNode {
|
||||
}
|
||||
|
||||
override function run(from:Int) {
|
||||
#if lnx_torrent
|
||||
var connection = inputs[1].get();
|
||||
if (connection == null) return;
|
||||
if (connection == null) return;
|
||||
var socket = Leenkx.connections[connection._url];
|
||||
if (socket == null) return;
|
||||
|
||||
if(property0 == "client") {
|
||||
var script = '
|
||||
for (p in leenkx.network.Leenkx.connections.h["' + connection._url + '"].client.torrent._peers){
|
||||
try{
|
||||
leenkx.network.Leenkx.id.set("' + connection._url + '",p);
|
||||
leenkx.network.Leenkx.connections.h["' + connection._url + '"].client.torrent._peers[p].conn._pc.close();
|
||||
leenkx.network.Leenkx.connections.h["' + connection._url + '"].client.torrent._peers[p].conn.destroy();
|
||||
leenkx.network.Leenkx.connections.h["' + connection._url + '"].onclose();
|
||||
}catch(error){
|
||||
console.log("Error: " + error);
|
||||
}
|
||||
}
|
||||
';
|
||||
js.Syntax.code('(1, eval)({0})', script.toString());
|
||||
} else {
|
||||
var script = '
|
||||
try{
|
||||
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);
|
||||
}
|
||||
';
|
||||
js.Syntax.code('(1, eval)({0})', script.toString());
|
||||
var lnxCn:LeenkxClient = socket.client;
|
||||
if (property0 == "peer") {
|
||||
var id:String = inputs.length > 2 ? inputs[2].get() : null;
|
||||
if (id != null && lnxCn != null) {
|
||||
lnxCn.peers.remove(id);
|
||||
}
|
||||
runOutput(0);
|
||||
return;
|
||||
}
|
||||
if (lnxCn != null) {
|
||||
lnxCn.destroy();
|
||||
socket.client = null;
|
||||
}
|
||||
Leenkx.connections.remove(connection._url);
|
||||
socket.onclose();
|
||||
#end
|
||||
runOutput(0);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
package leenkx.logicnode;
|
||||
import leenkx.system.Event;
|
||||
#if lnx_torrent
|
||||
import leenkx.network.Leenkx;
|
||||
import iron.object.Object;
|
||||
#end
|
||||
|
||||
class LeenkxEventNode extends LogicNode {
|
||||
public var property0: String;
|
||||
public var property1: String;
|
||||
|
||||
public var value: String;
|
||||
@ -14,90 +14,63 @@ class LeenkxEventNode extends LogicNode {
|
||||
public function new(tree:LogicTree) {
|
||||
super(tree);
|
||||
tree.notifyOnInit(init);
|
||||
tree.notifyOnRemove(onRemove);
|
||||
}
|
||||
|
||||
function init() {
|
||||
if (property0 == "client") {
|
||||
net_Url = inputs[0].get();
|
||||
switch (property1) {
|
||||
case "onopen": value = Leenkx.onOpenEvent;listener = Event.add(value, onEvent, tree.object.uid);
|
||||
case "onmessage": value = Leenkx.onMessageEvent;listener = Event.add(value, onEvent, tree.object.uid);
|
||||
case "onerror": value = Leenkx.onErrorEvent;listener = Event.add(value, onEvent, tree.object.uid);
|
||||
case "onclose": value = Leenkx.onCloseEvent;listener = Event.add(value, onEvent, tree.object.uid);
|
||||
case "onseen": value = Leenkx.onSeenEvent;listener = Event.add(value, onEvent, tree.object.uid);
|
||||
case "onserver": value = Leenkx.onServerEvent;listener = Event.add(value, onEvent, tree.object.uid);
|
||||
case "onconnections": value = Leenkx.onConnectionsEvent;listener = Event.add(value, onEvent, tree.object.uid);
|
||||
case "onping": value = Leenkx.onPingEvent;listener = Event.add(value, onEvent, tree.object.uid);
|
||||
case "onleft": value = Leenkx.onLeftEvent;listener = Event.add(value, onEvent, tree.object.uid);
|
||||
case "ontimeout": value = Leenkx.onTimeOutEvent;listener = Event.add(value, onEvent, tree.object.uid);
|
||||
case "onrpc": value = Leenkx.onRpcEvent;listener = Event.add(value, onEvent, tree.object.uid);
|
||||
case "onrpcresponse": value = Leenkx.onRpcResponseEvent;listener = Event.add(value, onEvent, tree.object.uid);
|
||||
case "onwireleft": value = Leenkx.onWireLeftEvent;listener = Event.add(value, onEvent, tree.object.uid);
|
||||
case "onwireseen": value = Leenkx.onWireSeenEvent;listener = Event.add(value, onEvent, tree.object.uid);
|
||||
case "ontorrent": value = Leenkx.onTorrentEvent;listener = Event.add(value, onEvent, tree.object.uid);
|
||||
case "ontracker": value = Leenkx.onTrackerEvent;listener = Event.add(value, onEvent, tree.object.uid);
|
||||
case "onannounce": value = Leenkx.onAnnounceEvent;listener = Event.add(value, onEvent, tree.object.uid);
|
||||
default: throw "Failed to set client event type.";
|
||||
}
|
||||
} else if (property0 == "host") {
|
||||
#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);
|
||||
switch (property1) {
|
||||
case "onopen": value = Leenkx.onOpenEvent;listener = Event.add(value, onEvent, tree.object.uid);
|
||||
case "onmessage": value = Leenkx.onMessageEvent;listener = Event.add(value, onEvent, tree.object.uid);
|
||||
case "onerror": value = Leenkx.onErrorEvent;listener = Event.add(value, onEvent, tree.object.uid);
|
||||
case "onclose": value = Leenkx.onCloseEvent;listener = Event.add(value, onEvent, tree.object.uid);
|
||||
default: throw "Failed to set host event type.";
|
||||
}
|
||||
#end
|
||||
} else if (property0 == "securehost"){
|
||||
#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);
|
||||
switch (property1) {
|
||||
case "onopen": value = Leenkx.onOpenEvent;listener = Event.add(value, onEvent, tree.object.uid);
|
||||
case "onmessage": value = Leenkx.onMessageEvent;listener = Event.add(value, onEvent, tree.object.uid);
|
||||
case "onerror": value = Leenkx.onErrorEvent;listener = Event.add(value, onEvent, tree.object.uid);
|
||||
case "onclose": value = Leenkx.onCloseEvent;listener = Event.add(value, onEvent, tree.object.uid);
|
||||
default: throw "Failed to set host event type.";
|
||||
}
|
||||
#end
|
||||
#if lnx_torrent
|
||||
var u:Dynamic = inputs[0].get();
|
||||
net_Url = Std.isOfType(u, String) ? u : (u != null ? u._url : null);
|
||||
switch (property1) {
|
||||
case "onopen": value = Leenkx.onOpenEvent;
|
||||
case "onmessage": value = Leenkx.onMessageEvent;
|
||||
case "onerror": value = Leenkx.onErrorEvent;
|
||||
case "onclose": value = Leenkx.onCloseEvent;
|
||||
case "onseen": value = Leenkx.onSeenEvent;
|
||||
case "onserver": value = Leenkx.onServerEvent;
|
||||
case "onconnections": value = Leenkx.onConnectionsEvent;
|
||||
case "onping": value = Leenkx.onPingEvent;
|
||||
case "onleft": value = Leenkx.onLeftEvent;
|
||||
case "ontimeout": value = Leenkx.onTimeOutEvent;
|
||||
case "onwireleft": value = Leenkx.onWireLeftEvent;
|
||||
case "onwireseen": value = Leenkx.onWireSeenEvent;
|
||||
case "ontorrent": value = Leenkx.onTorrentEvent;
|
||||
case "ontorrentadded": value = Leenkx.onTorrentAddedEvent;
|
||||
case "ontracker": value = Leenkx.onTrackerEvent;
|
||||
case "onannounce": value = Leenkx.onAnnounceEvent;
|
||||
case "ontorrentdone": value = Leenkx.onTorrentDoneEvent;
|
||||
case "onload": value = Leenkx.onLoadEvent;
|
||||
default:
|
||||
trace("Failed to set client event type");
|
||||
}
|
||||
|
||||
|
||||
listener = Event.add(value, onEvent, tree.object.uid);
|
||||
#end
|
||||
}
|
||||
|
||||
function onEvent() {
|
||||
runOutput(0);
|
||||
}
|
||||
|
||||
override function get(from: Int): Dynamic {
|
||||
if (property0 == "host") {
|
||||
return switch (from) {
|
||||
case 1: Leenkx.id.get(net_Url);
|
||||
case 2: Leenkx.data.get(net_Url);
|
||||
default: throw "Unreachable";
|
||||
}
|
||||
}
|
||||
else if (property0 == "securehost") {
|
||||
return switch (from) {
|
||||
case 1: Leenkx.id.get(net_Url);
|
||||
case 2: Leenkx.data.get(net_Url);
|
||||
default: throw "Unreachable";
|
||||
}
|
||||
}
|
||||
else {
|
||||
return switch (from) {
|
||||
case 1: Leenkx.id.get(net_Url);
|
||||
case 2: Leenkx.data.get(net_Url);
|
||||
default: throw "Unreachable";
|
||||
}
|
||||
function onRemove() {
|
||||
if (listener != null) {
|
||||
Event.removeListener(listener);
|
||||
listener = null;
|
||||
}
|
||||
}
|
||||
|
||||
override function get(from: Int): Dynamic {
|
||||
#if lnx_torrent
|
||||
return switch (from) {
|
||||
case 1: Leenkx.id.get(net_Url);
|
||||
case 2: Leenkx.data.get(net_Url);
|
||||
default: throw "Unreachable";
|
||||
}
|
||||
#else
|
||||
return null;
|
||||
#end
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
package leenkx.logicnode;
|
||||
import leenkx.network.Connect;
|
||||
import leenkx.network.Buffer;
|
||||
import haxe.io.Bytes;
|
||||
import iron.object.Object;
|
||||
import iron.math.Vec4;
|
||||
import iron.math.Quat;
|
||||
import iron.math.Mat4;
|
||||
@ -22,140 +20,107 @@ class LeenkxMessageParserNode extends LogicNode {
|
||||
if (data == null) return;
|
||||
|
||||
api = inputs[1].get();
|
||||
if (api == null) api = "";
|
||||
|
||||
var buffer:Buffer;
|
||||
if (Std.isOfType(data, Buffer)) {
|
||||
buffer = cast data;
|
||||
} else {
|
||||
var rawBytes:Bytes;
|
||||
try {
|
||||
rawBytes = haxe.crypto.Base64.decode(Std.string(data));
|
||||
} catch(e) {
|
||||
trace("Base64 decode failed: " + e);
|
||||
return;
|
||||
}
|
||||
buffer = new Buffer();
|
||||
buffer.writeBytes(rawBytes);
|
||||
}
|
||||
|
||||
/**
|
||||
var buffer = cast(data, Buffer);
|
||||
api = inputs[1].get();
|
||||
if(buffer.endsWith(api) == false){
|
||||
return;
|
||||
}
|
||||
**/
|
||||
var buffer = new Buffer();
|
||||
if(!StringTools.startsWith(data,api)){
|
||||
return;
|
||||
}
|
||||
switch (property0) {
|
||||
case "string":
|
||||
parsed = data.substr(api.length, data.length);
|
||||
runOutput(0);
|
||||
case "vector":
|
||||
var vec = data.substr(api.length, data.length);
|
||||
vec = StringTools.replace(vec, "(", "");
|
||||
vec = StringTools.replace(vec, ")", "");
|
||||
vec = StringTools.replace(vec, " ", "");
|
||||
vec = StringTools.trim(vec);
|
||||
var array = vec.split(",").map(Std.parseFloat);
|
||||
parsed = new Vec4( array[0], array[1], array[2], array[3] );
|
||||
runOutput(0);
|
||||
case "float":
|
||||
var flt:String = data.substr(api.length, data.length);
|
||||
var float = StringTools.trim(flt);
|
||||
parsed = Std.parseFloat(float);
|
||||
runOutput(0);
|
||||
case "integer":
|
||||
var int:String = data.substr(api.length, data.length);
|
||||
var integer = StringTools.trim(int);
|
||||
parsed = Std.parseInt(integer);
|
||||
runOutput(0);
|
||||
case "boolean":
|
||||
var boolean:String = data.substr(api.length, data.length);
|
||||
if(boolean == "true"){
|
||||
parsed = true;
|
||||
} else {
|
||||
parsed = false;
|
||||
}
|
||||
runOutput(0);
|
||||
case "transform":
|
||||
var tsfm = data.substr(api.length, data.length);
|
||||
tsfm = StringTools.replace(tsfm, "[", "");
|
||||
tsfm = StringTools.replace(tsfm, "]", "");
|
||||
tsfm = StringTools.replace(tsfm, " ", "");
|
||||
tsfm = StringTools.trim(tsfm);
|
||||
var array = tsfm.split(",").map(Std.parseFloat);
|
||||
parsed = new Mat4(
|
||||
array[0], array[1], array[2], array[3],
|
||||
array[4], array[5], array[6], array[7],
|
||||
array[8], array[9], array[10], array[11],
|
||||
array[12], array[13], array[14], array[15]
|
||||
);
|
||||
runOutput(0);
|
||||
case "rotation":
|
||||
var bytes = new haxe.io.BytesInput(buffer.readUntil(api));
|
||||
var rot = new Quat();
|
||||
for (f in Reflect.fields(rot)) {
|
||||
Reflect.setField(rot, f, bytes.readFloat());
|
||||
}
|
||||
parsed = rot;
|
||||
runOutput(0);
|
||||
default: throw "Failed to parse data.";
|
||||
}
|
||||
return;
|
||||
|
||||
|
||||
|
||||
switch (property0) {
|
||||
case "string":
|
||||
case "string":
|
||||
var bytes = buffer.readAllAvailableBytes();
|
||||
parsed = bytes.toString().substr(0, bytes.length - api.length);
|
||||
trace(parsed);
|
||||
runOutput(0);
|
||||
case "vector":
|
||||
var bytes = new haxe.io.BytesInput(buffer.readUntil(api));
|
||||
case "vector":
|
||||
var readBytes = buffer.readUntil(api);
|
||||
if (readBytes == null) return;
|
||||
var bytes = new haxe.io.BytesInput(readBytes);
|
||||
var vec = new Vec4();
|
||||
for (f in Reflect.fields(vec)) {
|
||||
Reflect.setField(vec, f, bytes.readFloat());
|
||||
}
|
||||
vec.x = bytes.readFloat();
|
||||
vec.y = bytes.readFloat();
|
||||
vec.z = bytes.readFloat();
|
||||
vec.w = bytes.readFloat();
|
||||
parsed = vec;
|
||||
runOutput(0);
|
||||
case "float":
|
||||
var bytes = new haxe.io.BytesInput(buffer.readUntil(api));
|
||||
case "float":
|
||||
var readBytes = buffer.readUntil(api);
|
||||
if (readBytes == null) return;
|
||||
var bytes = new haxe.io.BytesInput(readBytes);
|
||||
var float: Float = bytes.readFloat();
|
||||
parsed = float;
|
||||
runOutput(0);
|
||||
case "integer":
|
||||
var bytes = new haxe.io.BytesInput(buffer.readUntil(api));
|
||||
case "integer":
|
||||
var readBytes = buffer.readUntil(api);
|
||||
if (readBytes == null) return;
|
||||
var bytes = new haxe.io.BytesInput(readBytes);
|
||||
var integer: Int = bytes.readInt32();
|
||||
parsed = integer;
|
||||
runOutput(0);
|
||||
case "boolean":
|
||||
case "boolean":
|
||||
var bytes = buffer.readAllAvailableBytes();
|
||||
var boolean = bytes.toString().substr(0, bytes.length - api.length);
|
||||
if(boolean == "true"){
|
||||
if (boolean == "true") {
|
||||
parsed = true;
|
||||
} else {
|
||||
parsed = false;
|
||||
}
|
||||
runOutput(0);
|
||||
case "transform":
|
||||
var bytes = new haxe.io.BytesInput(buffer.readUntil(api));
|
||||
var readBytes = buffer.readUntil(api);
|
||||
if (readBytes == null) return;
|
||||
var bytes = new haxe.io.BytesInput(readBytes);
|
||||
var loc: Vec4 = new Vec4();
|
||||
var rot: Quat = new Quat();
|
||||
var scl: Vec4 = new Vec4();
|
||||
for (f in Reflect.fields(loc)) {
|
||||
Reflect.setField(loc, f, bytes.readFloat());
|
||||
try {
|
||||
loc.x = bytes.readFloat();
|
||||
loc.y = bytes.readFloat();
|
||||
loc.z = bytes.readFloat();
|
||||
loc.w = bytes.readFloat();
|
||||
rot.x = bytes.readFloat();
|
||||
rot.y = bytes.readFloat();
|
||||
rot.z = bytes.readFloat();
|
||||
rot.w = bytes.readFloat();
|
||||
scl.x = bytes.readFloat();
|
||||
scl.y = bytes.readFloat();
|
||||
scl.z = bytes.readFloat();
|
||||
scl.w = bytes.readFloat();
|
||||
} catch(e: Dynamic) {
|
||||
trace("readFloat error: " + e);
|
||||
return;
|
||||
}
|
||||
for (f in Reflect.fields(rot)) {
|
||||
Reflect.setField(rot, f, bytes.readFloat());
|
||||
}
|
||||
for (f in Reflect.fields(scl)) {
|
||||
Reflect.setField(scl, f, bytes.readFloat());
|
||||
}
|
||||
var transform = Mat4.identity();
|
||||
var transform = Mat4.identity();
|
||||
parsed = transform.compose(loc, rot, scl);
|
||||
runOutput(0);
|
||||
case "rotation":
|
||||
var bytes = new haxe.io.BytesInput(buffer.readUntil(api));
|
||||
case "rotation":
|
||||
var readBytes = buffer.readUntil(api);
|
||||
if (readBytes == null) return;
|
||||
var bytes = new haxe.io.BytesInput(readBytes);
|
||||
var rot = new Quat();
|
||||
for (f in Reflect.fields(rot)) {
|
||||
Reflect.setField(rot, f, bytes.readFloat());
|
||||
}
|
||||
rot.x = bytes.readFloat();
|
||||
rot.y = bytes.readFloat();
|
||||
rot.z = bytes.readFloat();
|
||||
rot.w = bytes.readFloat();
|
||||
parsed = rot;
|
||||
runOutput(0);
|
||||
default: throw "Failed to parse data.";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
override function get(from: Int): Dynamic {
|
||||
return switch (from) {
|
||||
|
||||
@ -1,401 +1,124 @@
|
||||
package leenkx.logicnode;
|
||||
import leenkx.network.Connect;
|
||||
import leenkx.network.Leenkx;
|
||||
import leenkx.network.Buffer;
|
||||
import haxe.io.Bytes;
|
||||
import iron.object.Object;
|
||||
#if lnx_torrent
|
||||
import leenkx.network.Leenkx;
|
||||
import leenkx.network.torrent.LeenkxClient;
|
||||
#end
|
||||
import iron.math.Vec4;
|
||||
import iron.math.Quat;
|
||||
import iron.math.Mat4;
|
||||
|
||||
|
||||
class LeenkxSendMessageNode extends LogicNode {
|
||||
public var property0: String;
|
||||
public var property1: String;
|
||||
public var id: String;
|
||||
public var all: Bool;
|
||||
public var property2: Bool;
|
||||
|
||||
public function new(tree:LogicTree) {
|
||||
super(tree);
|
||||
}
|
||||
|
||||
override function run(from:Int) {
|
||||
#if js
|
||||
var connection = inputs[1].get();
|
||||
if (connection == null) return;
|
||||
var api: String = inputs[2].get();
|
||||
if (connection == null) return;
|
||||
var api: String = inputs[2].get();
|
||||
var message: Dynamic = inputs[3].get();
|
||||
if (message == null) return;
|
||||
var broadcast: Bool = inputs.length > 4 && inputs[4].get() == true;
|
||||
var targetAddress: String = inputs.length > 5 ? inputs[5].get() : null;
|
||||
if (!broadcast && (targetAddress == null || targetAddress == "")) {
|
||||
return;
|
||||
}
|
||||
|
||||
#if lnx_torrent
|
||||
var socket = Leenkx.connections[connection._url];
|
||||
if (socket == null) return;
|
||||
var lnxCn:LeenkxClient = socket.client;
|
||||
if (lnxCn == null) return;
|
||||
|
||||
var buffer = new Buffer();
|
||||
|
||||
switch (property1) {
|
||||
case "string":
|
||||
buffer.writeBytes(Bytes.ofString(message));
|
||||
case "vector":
|
||||
var bytesOut = new haxe.io.BytesOutput();
|
||||
var f:Null<Float>;
|
||||
if ((f = message.x) != null) bytesOut.writeFloat(f);
|
||||
if ((f = message.y) != null) bytesOut.writeFloat(f);
|
||||
if ((f = message.z) != null) bytesOut.writeFloat(f);
|
||||
if ((f = message.w) != null) bytesOut.writeFloat(f);
|
||||
buffer.writeBytes(bytesOut.getBytes());
|
||||
case "float":
|
||||
var bytesOut = new haxe.io.BytesOutput();
|
||||
bytesOut.writeFloat(message);
|
||||
buffer.writeBytes(bytesOut.getBytes());
|
||||
case "integer":
|
||||
var bytesOut = new haxe.io.BytesOutput();
|
||||
bytesOut.writeInt32(message);
|
||||
buffer.writeBytes(bytesOut.getBytes());
|
||||
case "boolean":
|
||||
if (message == true) {
|
||||
buffer.writeBytes(Bytes.ofString("true"));
|
||||
} else {
|
||||
buffer.writeBytes(Bytes.ofString("false"));
|
||||
}
|
||||
case "transform":
|
||||
var bytesOut = new haxe.io.BytesOutput();
|
||||
var transform:Mat4 = message;
|
||||
var loc = new Vec4();
|
||||
var rot = new Quat();
|
||||
var scale = new Vec4();
|
||||
transform.decompose(loc, rot, scale);
|
||||
bytesOut.writeFloat(loc.x);
|
||||
bytesOut.writeFloat(loc.y);
|
||||
bytesOut.writeFloat(loc.z);
|
||||
bytesOut.writeFloat(loc.w);
|
||||
bytesOut.writeFloat(rot.x);
|
||||
bytesOut.writeFloat(rot.y);
|
||||
bytesOut.writeFloat(rot.z);
|
||||
bytesOut.writeFloat(rot.w);
|
||||
bytesOut.writeFloat(scale.x);
|
||||
bytesOut.writeFloat(scale.y);
|
||||
bytesOut.writeFloat(scale.z);
|
||||
bytesOut.writeFloat(scale.w);
|
||||
buffer.writeBytes(bytesOut.getBytes());
|
||||
case "rotation":
|
||||
var bytesOut = new haxe.io.BytesOutput();
|
||||
var f:Null<Float>;
|
||||
if ((f = message.x) != null) bytesOut.writeFloat(f);
|
||||
if ((f = message.y) != null) bytesOut.writeFloat(f);
|
||||
if ((f = message.z) != null) bytesOut.writeFloat(f);
|
||||
if ((f = message.w) != null) bytesOut.writeFloat(f);
|
||||
buffer.writeBytes(bytesOut.getBytes());
|
||||
default: trace("Failed to send data.");
|
||||
}
|
||||
|
||||
if (api != null && api != "") {
|
||||
buffer.writeBytes(Bytes.ofString(api));
|
||||
}
|
||||
|
||||
var rawBytes = buffer.readAllAvailableBytes();
|
||||
|
||||
try {
|
||||
if(inputs[5].get() == true){
|
||||
var script = 'for (p in lnx_' + connection._url +'.torrent._peers){
|
||||
try{
|
||||
lnx_' + connection._url +'.torrent._peers[p].conn.send(`'+ api + message + '`);
|
||||
}catch(error){
|
||||
console.log("Error: " + error);
|
||||
}
|
||||
}';
|
||||
js.Syntax.code('(1, eval)({0})', script.toString());
|
||||
runOutput(0);
|
||||
if (property2) {
|
||||
var payload = haxe.crypto.Base64.encode(rawBytes);
|
||||
if (broadcast) {
|
||||
lnxCn.send(null, payload);
|
||||
} else {
|
||||
lnxCn.send(targetAddress, payload);
|
||||
}
|
||||
} else {
|
||||
var script = 'lnx_' + connection._url +'.send(`' + inputs[4].get() + '`, `'+ api + message + '` );';
|
||||
js.Syntax.code('(1, eval)({0})', script.toString());
|
||||
runOutput(0);
|
||||
lnxCn.sendRawMessage(rawBytes, broadcast ? null : targetAddress);
|
||||
}
|
||||
|
||||
} catch(error) {
|
||||
trace("Error: " + error);
|
||||
}
|
||||
return;
|
||||
runOutput(0);
|
||||
|
||||
switch (property1) {
|
||||
case "string":
|
||||
if(property0 == "client"){
|
||||
var buffer = new Buffer();
|
||||
buffer.writeBytes(Bytes.ofString(message));
|
||||
if(api != "" || api != null){
|
||||
buffer.writeBytes(Bytes.ofString(api));
|
||||
|
||||
try {
|
||||
connection.buffer = buffer;
|
||||
if(inputs[5].get() == true){
|
||||
var script = 'for (p in lnx_' + connection._url +'.torrent._peers){
|
||||
try{
|
||||
lnx_' + connection._url +'.torrent._peers[p].conn.send(leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );
|
||||
}catch(error){
|
||||
console.log("Error: " + error);
|
||||
}
|
||||
}';
|
||||
js.Syntax.code('(1, eval)({0})', script.toString());
|
||||
runOutput(0);
|
||||
} else {
|
||||
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);
|
||||
}
|
||||
} catch(error) {
|
||||
trace("Error: " + error);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
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"){
|
||||
var bytesOut = new haxe.io.BytesOutput();
|
||||
var i = Reflect.fields(message);
|
||||
for (b in i) {
|
||||
bytesOut.writeFloat(Reflect.field(message, b));
|
||||
}
|
||||
var buffer = new Buffer();
|
||||
buffer.writeBytes(bytesOut.getBytes());
|
||||
if(api != "" || api != null){
|
||||
buffer.writeBytes(Bytes.ofString(api));
|
||||
}
|
||||
#else
|
||||
runOutput(0);
|
||||
|
||||
try {
|
||||
connection.buffer = buffer;
|
||||
if(inputs[5].get() == true){
|
||||
var script = 'for (p in lnx_' + connection._url +'.torrent._peers){
|
||||
try{
|
||||
lnx_' + connection._url +'.torrent._peers[p].conn.send("' + api + message + '");
|
||||
}catch(error){
|
||||
console.log("Error: " + error);
|
||||
}
|
||||
}';
|
||||
js.Syntax.code('(1, eval)({0})', script.toString());
|
||||
runOutput(0);
|
||||
} else {
|
||||
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);
|
||||
}
|
||||
} catch(error) {
|
||||
trace("Error: " + error);
|
||||
}
|
||||
} else {
|
||||
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 "float":
|
||||
if(property0 == "client"){
|
||||
var bytesOut = new haxe.io.BytesOutput();
|
||||
bytesOut.writeFloat(message);
|
||||
var buffer = new Buffer();
|
||||
buffer.writeBytes(bytesOut.getBytes());
|
||||
if(api != "" || api != null){
|
||||
buffer.writeBytes(Bytes.ofString(api));
|
||||
}
|
||||
try {
|
||||
connection.buffer = buffer;
|
||||
if(inputs[5].get() == true){
|
||||
var script = 'for (p in lnx_' + connection._url +'.torrent._peers){
|
||||
try{
|
||||
lnx_' + connection._url +'.torrent._peers[p].conn.send(leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );
|
||||
}catch(error){
|
||||
console.log("Error: " + error);
|
||||
}
|
||||
}';
|
||||
js.Syntax.code('(1, eval)({0})', script.toString());
|
||||
runOutput(0);
|
||||
} else {
|
||||
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);
|
||||
}
|
||||
} catch(error) {
|
||||
trace("Error: " + error);
|
||||
}
|
||||
} else {
|
||||
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 "integer":
|
||||
if(property0 == "client"){
|
||||
var bytesOut = new haxe.io.BytesOutput();
|
||||
bytesOut.writeInt32(message);
|
||||
var buffer = new Buffer();
|
||||
buffer.writeBytes(bytesOut.getBytes());
|
||||
if(api != "" || api != null){
|
||||
buffer.writeBytes(Bytes.ofString(api));
|
||||
}
|
||||
try {
|
||||
connection.buffer = buffer;
|
||||
if(inputs[5].get() == true){
|
||||
var script = 'for (p in lnx_' + connection._url +'.torrent._peers){
|
||||
try{
|
||||
lnx_' + connection._url +'.torrent._peers[p].conn.send(leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );
|
||||
}catch(error){
|
||||
console.log("Error: " + error);
|
||||
}
|
||||
}';
|
||||
js.Syntax.code('(1, eval)({0})', script.toString());
|
||||
runOutput(0);
|
||||
} else {
|
||||
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);
|
||||
}
|
||||
} catch(error) {
|
||||
trace("Error: " + error);
|
||||
}
|
||||
} else {
|
||||
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 "boolean":
|
||||
if(property0 == "client"){
|
||||
var buffer = new Buffer();
|
||||
if(message == true){
|
||||
buffer.writeBytes(Bytes.ofString("true"));
|
||||
} else {
|
||||
buffer.writeBytes(Bytes.ofString("false"));
|
||||
}
|
||||
if(api != "" || api != null){
|
||||
buffer.writeBytes(Bytes.ofString(api));
|
||||
}
|
||||
try {
|
||||
connection.buffer = buffer;
|
||||
if(inputs[5].get() == true){
|
||||
var script = 'for (p in lnx_' + connection._url +'.torrent._peers){
|
||||
try{
|
||||
lnx_' + connection._url +'.torrent._peers[p].conn.send(leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );
|
||||
}catch(error){
|
||||
console.log("Error: " + error);
|
||||
}
|
||||
}';
|
||||
js.Syntax.code('(1, eval)({0})', script.toString());
|
||||
runOutput(0);
|
||||
} else {
|
||||
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);
|
||||
}
|
||||
} catch(error) {
|
||||
trace("Error: " + error);
|
||||
}
|
||||
} else {
|
||||
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 "transform":
|
||||
if(property0 == "client"){
|
||||
var bytesOut = new haxe.io.BytesOutput();
|
||||
var transform:Mat4 = message;
|
||||
var loc = new Vec4();
|
||||
var rot = new Quat();
|
||||
var scale = new Vec4();
|
||||
transform.decompose(loc, rot, scale);
|
||||
var l = Reflect.fields(loc);
|
||||
for (b in l) {
|
||||
bytesOut.writeFloat(Reflect.field(loc, b));
|
||||
}
|
||||
var r = Reflect.fields(rot);
|
||||
for (b in r) {
|
||||
bytesOut.writeFloat(Reflect.field(rot, b));
|
||||
}
|
||||
var s = Reflect.fields(scale);
|
||||
for (b in s) {
|
||||
bytesOut.writeFloat(Reflect.field(scale, b));
|
||||
}
|
||||
var buffer = new Buffer();
|
||||
buffer.writeBytes(bytesOut.getBytes());
|
||||
if(api != "" || api != null){
|
||||
buffer.writeBytes(Bytes.ofString(api));
|
||||
}
|
||||
try {
|
||||
|
||||
connection.buffer = buffer;
|
||||
|
||||
if(inputs[5].get() == true){
|
||||
var script = 'for (p in lnx_' + connection._url +'.torrent._peers){
|
||||
try{
|
||||
//console.log("Mine: " + lx_8001.torrent.discovery.peerId + " || Incomming: " + p);
|
||||
lnx_' + connection._url +'.torrent._peers[p].conn.send(leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );
|
||||
}catch(error){
|
||||
console.log("Error: " + error);
|
||||
}
|
||||
}';
|
||||
js.Syntax.code('(1, eval)({0})', script.toString());
|
||||
runOutput(0);
|
||||
} else {
|
||||
//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);
|
||||
}
|
||||
|
||||
} catch(error) {
|
||||
trace("Error: " + error);
|
||||
}
|
||||
}
|
||||
case "rotation":
|
||||
if(property0 == "client"){
|
||||
var bytesOut = new haxe.io.BytesOutput();
|
||||
var i = Reflect.fields(message);
|
||||
for (b in i) {
|
||||
bytesOut.writeFloat(Reflect.field(message, b));
|
||||
}
|
||||
var buffer = new Buffer();
|
||||
buffer.writeBytes(bytesOut.getBytes());
|
||||
if(api != "" || api != null){
|
||||
buffer.writeBytes(Bytes.ofString(api));
|
||||
}
|
||||
try {
|
||||
connection.buffer = buffer;
|
||||
if(inputs[5].get() == true){
|
||||
var script = 'for (p in lnx_' + connection._url +'.torrent._peers){
|
||||
try{
|
||||
lnx_' + connection._url +'.torrent._peers[p].conn.send(leenkx.network.Leenkx.connections.h["'+ connection._url +'"].buffer );
|
||||
}catch(error){
|
||||
console.log("Error: " + error);
|
||||
}
|
||||
}';
|
||||
js.Syntax.code('(1, eval)({0})', script.toString());
|
||||
runOutput(0);
|
||||
} else {
|
||||
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);
|
||||
}
|
||||
} catch(error) {
|
||||
trace("Error: " + error);
|
||||
}
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*******_________________________________________
|
||||
|
||||
try {
|
||||
|
||||
if(inputs[5].get() == true){
|
||||
var script = 'for (p in lnx_' + connection._url +'.torrent._peers){
|
||||
try{
|
||||
lnx_' + connection._url +'.torrent._peers[p].conn.send("' + api + message + '");
|
||||
}catch(error){
|
||||
console.log("Error: " + error);
|
||||
}
|
||||
}';
|
||||
js.Syntax.code('(1, eval)({0})', script.toString());
|
||||
runOutput(0);
|
||||
} else {
|
||||
var script = 'for (p in lnx_' + connection._url +'.torrent._peers){
|
||||
try{
|
||||
|
||||
lx_' + connection._url +'.torrent._peers[' + inputs[4].get() + '].conn.send("' + api + message + '");
|
||||
}catch(error){
|
||||
console.log("Error: " + error);
|
||||
}
|
||||
}';
|
||||
js.Syntax.code('(1, eval)({0})', script.toString());
|
||||
runOutput(0);
|
||||
}
|
||||
} catch(error) {
|
||||
trace("Error: " + error);
|
||||
}
|
||||
return;
|
||||
|
||||
_________________________________________*****/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -23,6 +23,7 @@ class NetworkMessageParserNode extends LogicNode {
|
||||
|
||||
var buffer = cast(data, Buffer);
|
||||
api = inputs[1].get();
|
||||
if (api == null) api = "";
|
||||
if(buffer.endsWith(api) == false){
|
||||
return;
|
||||
}
|
||||
|
||||
@ -7,6 +7,9 @@ class OnActionMarkerNode extends LogicNode {
|
||||
public var property0: String;
|
||||
public var property1: String;
|
||||
|
||||
var registeredSamplers: Array<iron.object.Animation.ActionSampler> = [];
|
||||
var registeredCallbacks: Map<iron.object.Animation.ActionSampler, Void->Void> = new Map();
|
||||
|
||||
public function new(tree: LogicTree) {
|
||||
super(tree);
|
||||
|
||||
@ -21,9 +24,33 @@ class OnActionMarkerNode extends LogicNode {
|
||||
assert(Error, object != null, "Object input cannot be null");
|
||||
var animation = object.animation;
|
||||
if (animation == null) animation = object.getBoneAnimation(object.uid);
|
||||
var action = animation.activeActions.get(actionID);
|
||||
if(action == null) return;
|
||||
animation.notifyOnMarker(action, marker, function() { runOutput(0); });
|
||||
tree.removeUpdate(init);
|
||||
if (animation == null) return;
|
||||
var active = animation.activeActions;
|
||||
if (active == null) return;
|
||||
|
||||
var i = registeredSamplers.length;
|
||||
while (i-- > 0) {
|
||||
var alive = false;
|
||||
for (sampler in active) if (sampler == registeredSamplers[i]) {
|
||||
alive = true;
|
||||
break;
|
||||
}
|
||||
if (!alive) {
|
||||
var s = registeredSamplers[i];
|
||||
animation.removeMarker(s, marker, registeredCallbacks.get(s));
|
||||
registeredCallbacks.remove(s);
|
||||
registeredSamplers.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
for (key in active.keys()) {
|
||||
var sampler = active.get(key);
|
||||
if (actionID != "" && key != actionID && sampler.action != actionID) continue;
|
||||
if (registeredSamplers.indexOf(sampler) != -1) continue;
|
||||
registeredSamplers.push(sampler);
|
||||
var cb = function() { runOutput(0); };
|
||||
registeredCallbacks.set(sampler, cb);
|
||||
animation.notifyOnMarker(sampler, marker, cb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
65
leenkx/Sources/leenkx/logicnode/OnTorrentEventNode.hx
Normal file
65
leenkx/Sources/leenkx/logicnode/OnTorrentEventNode.hx
Normal file
@ -0,0 +1,65 @@
|
||||
package leenkx.logicnode;
|
||||
#if lnx_torrent
|
||||
import leenkx.network.Leenkx;
|
||||
import leenkx.network.torrent.Torrent;
|
||||
#end
|
||||
|
||||
class OnTorrentEventNode extends LogicNode {
|
||||
public var property0:String;
|
||||
public var eventValue:Dynamic;
|
||||
var net_Url:String;
|
||||
var torrent:Dynamic;
|
||||
var registeredTorrents:Map<String, Bool> = new Map();
|
||||
|
||||
public function new(tree:LogicTree) {
|
||||
super(tree);
|
||||
}
|
||||
|
||||
override function run(from:Int) {
|
||||
#if lnx_torrent
|
||||
var connection = inputs[1].get();
|
||||
if (connection == null) return;
|
||||
var id = inputs[2].get();
|
||||
if (id == null) return;
|
||||
net_Url = connection._url;
|
||||
|
||||
var socket = Leenkx.connections[connection._url];
|
||||
var client = socket != null ? socket.client : null;
|
||||
var t = client != null ? client.get(id) : (Std.isOfType(id, Torrent) ? id : null);
|
||||
if (t == null) return;
|
||||
|
||||
var event = property0 != null ? property0 : "done";
|
||||
var self = this;
|
||||
var fire = function(v:Dynamic) {
|
||||
self.eventValue = v;
|
||||
self.runOutput(0);
|
||||
};
|
||||
var key = t.infoHashHex != null ? t.infoHashHex : "v2:" + t.infoHashV2Hex;
|
||||
var alreadyRegistered = registeredTorrents.exists(key);
|
||||
if (!alreadyRegistered) registeredTorrents.set(key, true);
|
||||
switch (event) {
|
||||
case "ready":
|
||||
if (t.ready) fire(null);
|
||||
else if (!alreadyRegistered) t.on("ready", function() fire(null));
|
||||
case "piece" | "wire" | "metadata" | "error":
|
||||
if (!alreadyRegistered) t.on(event, function(v) fire(v));
|
||||
default:
|
||||
if (t.done) fire(null);
|
||||
else if (!alreadyRegistered) t.on("done", function() fire(null));
|
||||
}
|
||||
torrent = t;
|
||||
#end
|
||||
}
|
||||
|
||||
override function get(from:Int):Dynamic {
|
||||
#if lnx_torrent
|
||||
return switch (from) {
|
||||
case 1: torrent;
|
||||
case 2: eventValue;
|
||||
default: throw "Unreachable";
|
||||
}
|
||||
#else
|
||||
return null;
|
||||
#end
|
||||
}
|
||||
}
|
||||
73
leenkx/Sources/leenkx/logicnode/ParseTorrentNode.hx
Normal file
73
leenkx/Sources/leenkx/logicnode/ParseTorrentNode.hx
Normal file
@ -0,0 +1,73 @@
|
||||
package leenkx.logicnode;
|
||||
#if lnx_torrent
|
||||
import haxe.io.Bytes;
|
||||
import leenkx.network.torrent.Magnet;
|
||||
import leenkx.network.torrent.MetaInfo;
|
||||
#end
|
||||
|
||||
class ParseTorrentNode extends LogicNode {
|
||||
var meta:Dynamic;
|
||||
var magnet:Dynamic;
|
||||
var idHex:String;
|
||||
public var property0:String;
|
||||
|
||||
public function new(tree:LogicTree) {
|
||||
super(tree);
|
||||
}
|
||||
|
||||
override function run(from:Int) {
|
||||
#if lnx_torrent
|
||||
meta = null;
|
||||
magnet = null;
|
||||
idHex = null;
|
||||
var src = inputs[1].get();
|
||||
if (src == null) return;
|
||||
|
||||
switch (property0 != null ? property0 : "magnet") {
|
||||
case "file":
|
||||
if (Std.isOfType(src, MetaInfo)) {
|
||||
meta = src;
|
||||
} else {
|
||||
var b = Std.isOfType(src, Bytes) ? src : Bytes.ofString(Std.string(src));
|
||||
try {
|
||||
meta = MetaInfo.fromTorrentBytes(b);
|
||||
} catch(e:Dynamic) {
|
||||
trace(e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
case "id":
|
||||
var s = Std.string(src);
|
||||
idHex = s.length == 64 ? "v2:" + s : s;
|
||||
default:
|
||||
var m = Magnet.parse(Std.string(src));
|
||||
if (m == null) {
|
||||
return;
|
||||
}
|
||||
magnet = m;
|
||||
idHex = m.infoHashHex != null ? m.infoHashHex : m.infoHashV2Hex;
|
||||
}
|
||||
#end
|
||||
runOutput(0);
|
||||
}
|
||||
|
||||
override function get(from:Int):Dynamic {
|
||||
#if lnx_torrent
|
||||
var m:MetaInfo = meta;
|
||||
var mg:Magnet = magnet;
|
||||
return switch (from) {
|
||||
case 1: m;
|
||||
case 2: m != null ? (m.infoHashHex != null ? m.infoHashHex : m.infoHashV2Hex) : idHex;
|
||||
case 3: m != null ? m.name : (mg != null ? mg.name : null);
|
||||
case 4: m != null ? m.announce : (mg != null ? mg.trackers : null);
|
||||
case 5: m != null ? m.urlList : (mg != null ? mg.webSeeds : null);
|
||||
case 6: m != null ? m.files : null;
|
||||
case 7: m != null ? m.totalLength : 0;
|
||||
case 8: m != null ? m.toTorrentBytes() : null;
|
||||
default: throw "Unreachable";
|
||||
}
|
||||
#else
|
||||
return null;
|
||||
#end
|
||||
}
|
||||
}
|
||||
@ -27,11 +27,11 @@ class PlayAnimationTreeNode extends LogicNode {
|
||||
}
|
||||
|
||||
function playAnim() {
|
||||
if (init = false) return;
|
||||
if (init == false) return;
|
||||
|
||||
init = false;
|
||||
tree.removeUpdate(playAnim);
|
||||
|
||||
|
||||
var animation = object.animation;
|
||||
if(animation == null) {
|
||||
#if lnx_skin
|
||||
|
||||
@ -16,8 +16,10 @@ class RestartActionNode extends LogicNode {
|
||||
var animation = object.animation;
|
||||
|
||||
if (animation == null) animation = object.getBoneAnimation(object.uid);
|
||||
if(animation.activeActions == null) return;
|
||||
animation.activeActions.get(action).restartAction();
|
||||
if (animation == null || animation.activeActions == null) return;
|
||||
var sampler = animation.activeActions.get(action);
|
||||
if (sampler == null) return;
|
||||
sampler.restartAction();
|
||||
|
||||
runOutput(0);
|
||||
}
|
||||
|
||||
@ -1,52 +1,124 @@
|
||||
package leenkx.logicnode;
|
||||
import leenkx.system.Event;
|
||||
import iron.object.Object;
|
||||
|
||||
#if lnx_torrent
|
||||
import haxe.io.Bytes;
|
||||
import leenkx.network.Leenkx;
|
||||
import leenkx.network.torrent.MetaInfo;
|
||||
import leenkx.network.torrent.Torrent;
|
||||
#end
|
||||
|
||||
|
||||
class SeedTorrentNode extends LogicNode {
|
||||
public var torrent: Dynamic;
|
||||
public var net_Url: Dynamic;
|
||||
|
||||
public var torrent:Dynamic;
|
||||
public var net_Url:String;
|
||||
|
||||
public function new(tree:LogicTree) {
|
||||
super(tree);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override function run(from:Int) {
|
||||
#if js
|
||||
#if lnx_torrent
|
||||
var connection = inputs[1].get();
|
||||
if (connection == null) return;
|
||||
trace("got connection");
|
||||
var data = inputs[3].get();
|
||||
if (data == null) return;
|
||||
trace("got data");
|
||||
var opts = inputs[2].get();
|
||||
if (connection == null) return;
|
||||
var src = inputs[2].get();
|
||||
net_Url = connection._url;
|
||||
|
||||
var cx = "lx_" + net_Url;
|
||||
var lx:Dynamic = untyped js.Browser.window[cx];
|
||||
var args = opts.toString();
|
||||
trace(args);
|
||||
var func = Reflect.field(lx.wt, "seed");
|
||||
if (func != null) {
|
||||
torrent = func(data, function (torrent) {
|
||||
trace('Client is seeding ' + torrent.magnetURI);
|
||||
});
|
||||
//trace(torrent);
|
||||
var socket = Leenkx.connections[connection._url];
|
||||
if (socket == null || socket.client == null) return;
|
||||
var client = socket.client;
|
||||
var self = this;
|
||||
|
||||
var onSeeded = function(t:Torrent) {
|
||||
if (t == null) return;
|
||||
self.torrent = t;
|
||||
var onDone = function() {
|
||||
self.runOutput(2);
|
||||
};
|
||||
if (t.done) onDone();
|
||||
else t.on("done", onDone);
|
||||
};
|
||||
|
||||
if (Std.isOfType(src, Torrent)) {
|
||||
var t:Torrent = src;
|
||||
t.start();
|
||||
torrent = t;
|
||||
runOutput(0);
|
||||
return;
|
||||
}
|
||||
|
||||
var meta:MetaInfo = null;
|
||||
if (Std.isOfType(src, MetaInfo)) {
|
||||
meta = cast src;
|
||||
} else if (Std.isOfType(src, Bytes)) {
|
||||
try {
|
||||
meta = MetaInfo.fromTorrentBytes(cast src);
|
||||
} catch(e:Dynamic) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (meta == null) {
|
||||
return;
|
||||
}
|
||||
var files = filesFor(meta, inputs.length > 3 ? inputs[3].get() : null);
|
||||
if (files == null) {
|
||||
return;
|
||||
}
|
||||
client.seedMeta(meta, files, null, onSeeded);
|
||||
runOutput(0);
|
||||
#end
|
||||
}
|
||||
|
||||
override function get(from: Int): Dynamic {
|
||||
return switch (from) {
|
||||
case 1: torrent;
|
||||
default: throw "Unreachable";
|
||||
}
|
||||
}
|
||||
#if lnx_torrent
|
||||
function filesFor(meta:MetaInfo, data:Dynamic):Array<{path:String, data:Bytes}> {
|
||||
if (data == null) return null;
|
||||
var entries = meta.files;
|
||||
if (Std.isOfType(data, Bytes)) {
|
||||
var blob:Bytes = data;
|
||||
if (blob.length != meta.totalLength) return null;
|
||||
var files:Array<{path:String, data:Bytes}> = [];
|
||||
var cursor = 0;
|
||||
for (f in entries) {
|
||||
files.push({path: f.path, data: blob.sub(cursor, f.length)});
|
||||
cursor += f.length;
|
||||
}
|
||||
return files;
|
||||
}
|
||||
if (Std.isOfType(data, Array)) {
|
||||
var items:Array<Dynamic> = data;
|
||||
var files:Array<{path:String, data:Bytes}> = [];
|
||||
for (it in items) {
|
||||
var p = Reflect.field(it, "path");
|
||||
var d:Bytes = Reflect.field(it, "data");
|
||||
if (d == null) d = Reflect.field(it, "content");
|
||||
if (p == null || d == null) continue;
|
||||
var sp = Std.string(p);
|
||||
var matched = false;
|
||||
for (f in entries) {
|
||||
if (f.path == sp || StringTools.endsWith(f.path, "/" + sp)) {
|
||||
files.push({path: f.path, data: d});
|
||||
matched = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!matched) files.push({path: sp, data: d});
|
||||
}
|
||||
if (files.length == entries.length) return files;
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
#end
|
||||
|
||||
override function get(from:Int):Dynamic {
|
||||
#if lnx_torrent
|
||||
var t:Torrent = torrent;
|
||||
return switch (from) {
|
||||
case 1: torrent;
|
||||
case 3: t != null ? t.infoHashHex : null;
|
||||
case 4: t != null ? t.magnetURI : null;
|
||||
default: throw "Unreachable";
|
||||
}
|
||||
#else
|
||||
return null;
|
||||
#end
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -19,8 +19,10 @@ class SetActionPausedNode extends LogicNode {
|
||||
var animation = object.animation;
|
||||
|
||||
if (animation == null) animation = object.getBoneAnimation(object.uid);
|
||||
if(animation.activeActions == null) return;
|
||||
animation.activeActions.get(action).paused = paused;
|
||||
if (animation == null || animation.activeActions == null) return;
|
||||
var sampler = animation.activeActions.get(action);
|
||||
if (sampler == null) return;
|
||||
sampler.paused = paused;
|
||||
|
||||
runOutput(0);
|
||||
}
|
||||
|
||||
90
leenkx/Sources/leenkx/logicnode/SetTrackersNode.hx
Normal file
90
leenkx/Sources/leenkx/logicnode/SetTrackersNode.hx
Normal file
@ -0,0 +1,90 @@
|
||||
package leenkx.logicnode;
|
||||
#if lnx_torrent
|
||||
import leenkx.network.Leenkx;
|
||||
import leenkx.network.torrent.Torrent;
|
||||
import leenkx.network.torrent.TorrentClient;
|
||||
#end
|
||||
|
||||
class SetTrackersNode extends LogicNode {
|
||||
public var property0:String;
|
||||
public var target:Dynamic;
|
||||
|
||||
public function new(tree:LogicTree) {
|
||||
super(tree);
|
||||
}
|
||||
|
||||
override function run(from:Int) {
|
||||
#if lnx_torrent
|
||||
var p = property0 != null ? property0 : "add";
|
||||
switch (p) {
|
||||
case "add" | "override":
|
||||
setTorrentTrackers(p == "override");
|
||||
default:
|
||||
setTrackerProperty(p);
|
||||
}
|
||||
#end
|
||||
runOutput(0);
|
||||
}
|
||||
|
||||
#if lnx_torrent
|
||||
function setTorrentTrackers(trackerOverride:Bool):Void {
|
||||
var id = inputs[2].get();
|
||||
var urls = TorrentClient.trackerUrls(inputs[3].get());
|
||||
if (urls == null) return;
|
||||
var t:Torrent = Std.isOfType(id, Torrent) ? id : null;
|
||||
if (t == null && id != null) {
|
||||
var connection = inputs[1].get();
|
||||
var socket = connection != null ? Leenkx.connections[connection._url] : null;
|
||||
var client = socket != null ? socket.client : null;
|
||||
if (client != null) t = client.get(id);
|
||||
}
|
||||
if (t != null) {
|
||||
target = t;
|
||||
if (trackerOverride) {
|
||||
t.setTrackers(urls);
|
||||
} else {
|
||||
for (u in urls) t.addTracker(u);
|
||||
}
|
||||
} else if (id == null) {
|
||||
target = null;
|
||||
if (trackerOverride) {
|
||||
TorrentClient.defaultTrackers = urls;
|
||||
} else {
|
||||
TorrentClient.mergeTrackers(TorrentClient.defaultTrackers, urls);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setTrackerProperty(prop:String):Void {
|
||||
var tracker:Dynamic = inputs[1].get();
|
||||
if (tracker == null) return;
|
||||
var v = inputs[2].get();
|
||||
if (v == null) return;
|
||||
var field = prop == "interval" ? "intervalSec" : prop;
|
||||
var val:Dynamic = field == "trackerId" ? Std.string(v)
|
||||
: Std.int(Std.parseFloat(Std.string(v)));
|
||||
var setter:Dynamic = field == "intervalSec"
|
||||
&& Reflect.field(tracker, "intervalSec") == null
|
||||
? Reflect.field(tracker, "setAnnounceInterval") : null;
|
||||
if (setter != null) {
|
||||
try {
|
||||
Reflect.callMethod(tracker, setter, [(val:Int) * 1000]);
|
||||
} catch(e:Dynamic) {}
|
||||
} else {
|
||||
Reflect.setField(tracker, field, val);
|
||||
}
|
||||
target = tracker;
|
||||
}
|
||||
#end
|
||||
|
||||
override function get(from:Int):Dynamic {
|
||||
#if lnx_torrent
|
||||
return switch (from) {
|
||||
case 1: target;
|
||||
default: throw "Unreachable";
|
||||
}
|
||||
#else
|
||||
return null;
|
||||
#end
|
||||
}
|
||||
}
|
||||
74
leenkx/Sources/leenkx/logicnode/TorrentClientInfoNode.hx
Normal file
74
leenkx/Sources/leenkx/logicnode/TorrentClientInfoNode.hx
Normal file
@ -0,0 +1,74 @@
|
||||
package leenkx.logicnode;
|
||||
#if lnx_torrent
|
||||
import leenkx.network.Leenkx;
|
||||
import leenkx.network.torrent.Torrent;
|
||||
#end
|
||||
|
||||
class TorrentClientInfoNode extends LogicNode {
|
||||
var stats:Dynamic;
|
||||
var list:Array<Dynamic>;
|
||||
|
||||
public function new(tree:LogicTree) {
|
||||
super(tree);
|
||||
}
|
||||
|
||||
override function run(from:Int) {
|
||||
#if lnx_torrent
|
||||
list = [];
|
||||
stats = {downRate: 0.0, upRate: 0.0, progress: 0.0, ratio: 0.0};
|
||||
var connection = inputs[1].get();
|
||||
if (connection != null) {
|
||||
var socket = Leenkx.connections[connection._url];
|
||||
var client = socket != null ? socket.client : null;
|
||||
if (client != null) collect(client);
|
||||
}
|
||||
#end
|
||||
runOutput(0);
|
||||
}
|
||||
|
||||
#if lnx_torrent
|
||||
function collect(client:Dynamic) {
|
||||
var down = 0.0;
|
||||
var up = 0.0;
|
||||
var received = 0.0;
|
||||
var sent = 0.0;
|
||||
var total = 0.0;
|
||||
var left = 0.0;
|
||||
var torrents:Map<String, Torrent> = client.torrents;
|
||||
for (t in torrents) {
|
||||
list.push(t);
|
||||
var s = t.getStats();
|
||||
down += s.downRate;
|
||||
up += s.upRate;
|
||||
received += s.downloaded;
|
||||
sent += s.uploaded;
|
||||
if (t.meta != null) {
|
||||
total += t.meta.totalLength;
|
||||
left += s.left;
|
||||
}
|
||||
}
|
||||
stats = {
|
||||
downRate: down,
|
||||
upRate: up,
|
||||
progress: total > 0 ? 1.0 - left / total : 0.0,
|
||||
ratio: received > 0 ? sent / received : 0.0
|
||||
};
|
||||
}
|
||||
#end
|
||||
|
||||
override function get(from:Int):Dynamic {
|
||||
#if lnx_torrent
|
||||
return switch (from) {
|
||||
case 1: list;
|
||||
case 2: list.length;
|
||||
case 3: stats.downRate;
|
||||
case 4: stats.upRate;
|
||||
case 5: stats.progress;
|
||||
case 6: stats.ratio;
|
||||
default: throw "Unreachable";
|
||||
}
|
||||
#else
|
||||
return null;
|
||||
#end
|
||||
}
|
||||
}
|
||||
93
leenkx/Sources/leenkx/logicnode/TorrentControlNode.hx
Normal file
93
leenkx/Sources/leenkx/logicnode/TorrentControlNode.hx
Normal file
@ -0,0 +1,93 @@
|
||||
package leenkx.logicnode;
|
||||
#if lnx_torrent
|
||||
import leenkx.network.Leenkx;
|
||||
import leenkx.network.torrent.Torrent;
|
||||
#end
|
||||
|
||||
class TorrentControlNode extends LogicNode {
|
||||
public var property0:String;
|
||||
var net_Url:String;
|
||||
|
||||
public function new(tree:LogicTree) {
|
||||
super(tree);
|
||||
}
|
||||
|
||||
override function run(from:Int) {
|
||||
#if lnx_torrent
|
||||
var connection = inputs[1].get();
|
||||
if (connection == null) return;
|
||||
var id = inputs[2].get();
|
||||
if (id == null) return;
|
||||
net_Url = connection._url;
|
||||
|
||||
var socket = Leenkx.connections[connection._url];
|
||||
if (socket == null) return;
|
||||
var client = socket.client;
|
||||
var t = client != null ? client.get(id) : (Std.isOfType(id, Torrent) ? id : null);
|
||||
var action = property0 != null ? property0 : "remove";
|
||||
|
||||
var file:Dynamic = inputs.length > 3 ? inputs[3].get() : null;
|
||||
var t2 = t;
|
||||
var startB = 0;
|
||||
var endB = -1;
|
||||
if (file != null) {
|
||||
startB = file.offset;
|
||||
endB = file.offset + file.length - 1;
|
||||
if (t2 == null) t2 = file.torrent;
|
||||
} else {
|
||||
var sv = inputs.length > 4 ? inputs[4].get() : null;
|
||||
var ev = inputs.length > 5 ? inputs[5].get() : null;
|
||||
if (sv != null && Std.string(sv) != "") {
|
||||
startB = Std.int(Std.parseFloat(Std.string(sv)));
|
||||
}
|
||||
if (ev != null && Std.string(ev) != "") {
|
||||
endB = Std.int(Std.parseFloat(Std.string(ev)));
|
||||
}
|
||||
}
|
||||
var pv = inputs.length > 6 ? inputs[6].get() : null;
|
||||
var priority = pv != null && Std.string(pv) != "" ? Std.int(Std.parseFloat(Std.string(pv))) : null;
|
||||
|
||||
switch (action) {
|
||||
case "start":
|
||||
if (t != null) t.start();
|
||||
case "stop":
|
||||
if (t != null) t.stop();
|
||||
case "select", "critical", "deselect":
|
||||
if (t2 != null) {
|
||||
if (endB < 0) {
|
||||
endB = t2.meta != null ? t2.meta.totalLength - 1 : 0;
|
||||
}
|
||||
if (action == "critical") {
|
||||
t2.critical(startB, endB);
|
||||
} else if (action == "select") {
|
||||
t2.select(startB, endB, priority);
|
||||
} else {
|
||||
t2.deselect(startB, endB, priority);
|
||||
}
|
||||
}
|
||||
case "add-peer":
|
||||
var peer = inputs.length > 3 ? inputs[3].get() : null;
|
||||
if (t != null && peer != null) {
|
||||
t.addPeer(Std.string(peer));
|
||||
}
|
||||
case "add-web-seed":
|
||||
var url = inputs.length > 3 ? inputs[3].get() : null;
|
||||
if (t != null && url != null) {
|
||||
t.addWebSeed(Std.string(url));
|
||||
}
|
||||
case "remove", "remove-data":
|
||||
var hex = t != null ? t.infoHashHex : Std.string(id);
|
||||
if (hex == null) hex = Std.string(id);
|
||||
if (client != null) {
|
||||
client.remove(hex, action == "remove-data");
|
||||
}
|
||||
if (t != null && Leenkx.torrent.get(net_Url) == t) {
|
||||
Leenkx.torrent.remove(net_Url);
|
||||
Leenkx.file.remove(net_Url);
|
||||
}
|
||||
default:
|
||||
}
|
||||
#end
|
||||
runOutput(0);
|
||||
}
|
||||
}
|
||||
74
leenkx/Sources/leenkx/logicnode/TorrentInfoNode.hx
Normal file
74
leenkx/Sources/leenkx/logicnode/TorrentInfoNode.hx
Normal file
@ -0,0 +1,74 @@
|
||||
package leenkx.logicnode;
|
||||
#if lnx_torrent
|
||||
import leenkx.network.Leenkx;
|
||||
import leenkx.network.torrent.Torrent;
|
||||
import leenkx.network.torrent.TorrentClient;
|
||||
#end
|
||||
|
||||
class TorrentInfoNode extends LogicNode {
|
||||
public var torrent:Dynamic;
|
||||
public var net_Url:String;
|
||||
|
||||
public function new(tree:LogicTree) {
|
||||
super(tree);
|
||||
}
|
||||
|
||||
override function run(from:Int) {
|
||||
#if lnx_torrent
|
||||
var id = inputs[2].get();
|
||||
if (id == null) return;
|
||||
torrent = null;
|
||||
|
||||
if (Std.isOfType(id, Torrent)) {
|
||||
torrent = id;
|
||||
} else {
|
||||
var connection = inputs[1].get();
|
||||
if (connection == null) return;
|
||||
net_Url = connection._url;
|
||||
var socket = Leenkx.connections[net_Url];
|
||||
if (socket == null || socket.client == null) return;
|
||||
torrent = socket.client.get(id);
|
||||
}
|
||||
#end
|
||||
runOutput(0);
|
||||
}
|
||||
|
||||
override function get(from:Int):Dynamic {
|
||||
#if lnx_torrent
|
||||
var t:Torrent = torrent;
|
||||
return switch (from) {
|
||||
case 1: t != null ? t.infoHashHex : null;
|
||||
case 2: t != null && t.meta != null ? t.meta.name : null;
|
||||
case 3: t != null ? t.numPeers : 0;
|
||||
case 4: progress(t);
|
||||
case 5: t != null ? t.getStats().downloaded : 0.0;
|
||||
case 6: t != null ? t.getStats().uploaded : 0.0;
|
||||
case 7: t != null ? t.done : false;
|
||||
case 8: t != null ? t.magnetURI : null;
|
||||
case 9: t != null ? t.files : null;
|
||||
case 10: t != null && t.meta != null ? t.meta.numPieces : 0;
|
||||
case 11: t != null && t.meta != null ? t.meta.pieceLength : 0;
|
||||
case 12: t != null && t.meta != null ? t.meta.totalLength : 0;
|
||||
case 13: t != null && t.meta != null ? t.meta.comment : null;
|
||||
case 14: t != null && t.meta != null ? t.meta.createdBy : null;
|
||||
case 15: t != null && t.meta != null ? t.meta.protocolVersion : 1;
|
||||
case 16: t != null ? t.infoHashV2Hex : null;
|
||||
case 17: t != null ? t.getStats().downRate : 0.0;
|
||||
case 18: t != null ? t.getStats().upRate : 0.0;
|
||||
case 19: t != null ? t.stopped : false;
|
||||
case 20: t;
|
||||
default: throw "Unreachable";
|
||||
}
|
||||
#else
|
||||
return null;
|
||||
#end
|
||||
}
|
||||
|
||||
#if lnx_torrent
|
||||
function progress(t:Torrent):Float {
|
||||
if (t == null || t.meta == null || t.meta.totalLength == 0) return 0;
|
||||
var stats = t.getStats();
|
||||
return 1.0 - stats.left / t.meta.totalLength;
|
||||
}
|
||||
#end
|
||||
}
|
||||
@ -63,10 +63,10 @@ class Buffer {
|
||||
public function readUntil(delimiter:String):Bytes {
|
||||
var dl = delimiter.length;
|
||||
|
||||
for (i in 0 ... available - dl) {
|
||||
for (i in 0 ... available - dl + 1) {
|
||||
var matched = true;
|
||||
for (j in 0 ... dl) {
|
||||
if (peekByte(currentOffset + i + j + 1) == delimiter.charCodeAt(j)) {
|
||||
if (peekByte(i + j) == delimiter.charCodeAt(j)) {
|
||||
continue;
|
||||
}
|
||||
matched = false;
|
||||
@ -74,7 +74,8 @@ class Buffer {
|
||||
}
|
||||
|
||||
if (matched) {
|
||||
var bytes = readBytes(i + dl + 1);
|
||||
var bytes = readBytes(i);
|
||||
readBytes(dl);
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
@ -85,7 +86,19 @@ class Buffer {
|
||||
public function readBytes(count:Int):Bytes {
|
||||
var count2 = Std.int(Math.min(count, available));
|
||||
var out = Bytes.alloc(count2);
|
||||
for (n in 0 ... count2) out.set(n, readByte());
|
||||
var written = 0;
|
||||
while (written < count2) {
|
||||
if (currentData == null || currentOffset >= currentData.length) {
|
||||
currentOffset = 0;
|
||||
currentData = chunks.shift();
|
||||
}
|
||||
var n = Std.int(Math.min(count2 - written, currentData.length - currentOffset));
|
||||
out.blit(written, currentData, currentOffset, n);
|
||||
written += n;
|
||||
currentOffset += n;
|
||||
available -= n;
|
||||
}
|
||||
length = available;
|
||||
return out;
|
||||
}
|
||||
|
||||
@ -115,16 +128,23 @@ class Buffer {
|
||||
}
|
||||
|
||||
public function peekByte(offset:Int):Int {
|
||||
if (available <= 0) throw 'No bytes available';
|
||||
var tempOffset = offset;
|
||||
var tempData = chunks[0];
|
||||
if (tempData == null) {
|
||||
tempData = currentData;
|
||||
if (available <= 0 || offset < 0 || offset >= available) throw 'No bytes available';
|
||||
var tempData = currentData;
|
||||
var chunkIndex = -1;
|
||||
var tempOffset:Int;
|
||||
if (tempData != null && currentOffset < tempData.length) {
|
||||
tempOffset = currentOffset + offset;
|
||||
} else {
|
||||
tempData = chunks[0];
|
||||
chunkIndex = 0;
|
||||
tempOffset = offset;
|
||||
}
|
||||
var chunkIndex = 0;
|
||||
while (tempOffset >= tempData.length) {
|
||||
tempOffset -= tempData.length;
|
||||
chunkIndex++;
|
||||
if (chunkIndex >= chunks.length){
|
||||
trace('No bytes available');
|
||||
}
|
||||
tempData = chunks[chunkIndex];
|
||||
}
|
||||
return tempData.get(tempOffset);
|
||||
@ -154,9 +174,9 @@ class Buffer {
|
||||
|
||||
public function endsWith(e:String):Bool {
|
||||
var i = available - e.length;
|
||||
var n = currentOffset;
|
||||
var n = 0;
|
||||
|
||||
if (i <= 0) {
|
||||
if (i < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
package leenkx.network;
|
||||
|
||||
class Handler extends WebSocketCommon {
|
||||
public var validateHandshake:(HttpRequest, HttpResponse, (HttpResponse) -> Void) -> Void = null;
|
||||
|
||||
public function new(socket:SocketImpl) {
|
||||
super(socket);
|
||||
isClient = false;
|
||||
|
||||
@ -4,6 +4,7 @@ class HttpHeader {
|
||||
public static inline var SEC_WEBSOCKET_KEY:String = "Sec-WebSocket-Key";
|
||||
public static inline var SEC_WEBSOSCKET_ACCEPT:String = "Sec-WebSocket-Accept";
|
||||
public static inline var SEC_WEBSOSCKET_VERSION:String = "Sec-WebSocket-Version";
|
||||
public static inline var SEC_WEBSOCKET_PROTOCOL:String = "Sec-WebSocket-Protocol";
|
||||
public static inline var UPGRADE:String = "Upgrade";
|
||||
public static inline var HOST:String = "Host";
|
||||
public static inline var CONNECTION:String = "Connection";
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
package leenkx.network;
|
||||
|
||||
#if lnx_torrent
|
||||
import leenkx.network.Types;
|
||||
import haxe.io.Bytes;
|
||||
import iron.object.Object;
|
||||
import leenkx.system.Event;
|
||||
import leenkx.network.Buffer;
|
||||
import leenkx.network.torrent.LeenkxClient;
|
||||
|
||||
@:expose
|
||||
class Leenkx {
|
||||
@ -20,24 +22,20 @@ class Leenkx {
|
||||
public static var onPingEvent: String = "Leenkx.onPing";
|
||||
public static var onLeftEvent: String = "Leenkx.onLeft";
|
||||
public static var onTimeOutEvent: String = "Leenkx.onTimeOut";
|
||||
public static var onRpcEvent: String = "Leenkx.onRpc";
|
||||
public static var onRpcResponseEvent: String = "Leenkx.onRpcResponse";
|
||||
public static var onWireLeftEvent: String = "Leenkx.onWireLeft";
|
||||
public static var onWireSeenEvent: String = "Leenkx.onWireSeen";
|
||||
public static var onTorrentEvent: String = "Leenkx.onTorrent";
|
||||
public static var onTorrentAddedEvent: String = "Leenkx.onTorrentAdded";
|
||||
public static var onTrackerEvent: String = "Leenkx.onTracker";
|
||||
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>();
|
||||
#end
|
||||
public static var lxNew:Void->Void;
|
||||
|
||||
public static var peers:Map<String, String> = [];
|
||||
public static var data:Map<String, Dynamic> = [];
|
||||
public static var id:Map<String, String> = [];
|
||||
public static var torrent:Map<String, Dynamic> = [];
|
||||
public static var file:Map<String, Dynamic> = [];
|
||||
|
||||
public function new(net_Url: String, net_object: Object) {
|
||||
if (net_Url != null && net_object != null) {
|
||||
|
||||
@ -54,224 +52,65 @@ class Leenkx {
|
||||
}
|
||||
|
||||
if (object != null) {
|
||||
final loadEvent = Event.get(Leenkx.onLoadEvent);
|
||||
final openEvent = Event.get(Leenkx.onOpenEvent);
|
||||
final messageEvent = Event.get(Leenkx.onMessageEvent);
|
||||
final errorEvent = Event.get(Leenkx.onErrorEvent);
|
||||
final closeEvent = Event.get(Leenkx.onCloseEvent);
|
||||
final seenEvent = Event.get(Leenkx.onSeenEvent);
|
||||
final serverEvent = Event.get(Leenkx.onServerEvent);
|
||||
final connectionsEvent = Event.get(Leenkx.onConnectionsEvent);
|
||||
final pingEvent = Event.get(Leenkx.onPingEvent);
|
||||
final leftEvent = Event.get(Leenkx.onLeftEvent);
|
||||
final timeoutEvent = Event.get(Leenkx.onTimeOutEvent);
|
||||
final rpcEvent = Event.get(Leenkx.onRpcEvent);
|
||||
final rpcresponseEvent = Event.get(Leenkx.onRpcResponseEvent);
|
||||
final wireleftEvent = Event.get(Leenkx.onWireLeftEvent);
|
||||
final wireseenEvent = Event.get(Leenkx.onWireSeenEvent);
|
||||
final torrentEvent = Event.get(Leenkx.onTorrentEvent);
|
||||
final trackerEvent = Event.get(Leenkx.onTrackerEvent);
|
||||
final announceEvent = Event.get(Leenkx.onAnnounceEvent);
|
||||
final torrentDoneEvent = Event.get(Leenkx.onTorrentDoneEvent);
|
||||
var uid = object.uid;
|
||||
|
||||
Leenkx.connections[net_Url].onopen = function() {
|
||||
if (openEvent != null) {
|
||||
for (e in openEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
Event.send(Leenkx.onOpenEvent, uid);
|
||||
};
|
||||
Leenkx.connections[net_Url].onmessage = function() {
|
||||
if (messageEvent != null) {
|
||||
for (e in messageEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
Event.send(Leenkx.onMessageEvent, uid);
|
||||
};
|
||||
Leenkx.connections[net_Url].onerror = function() {
|
||||
if (errorEvent != null) {
|
||||
for (e in errorEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
Event.send(Leenkx.onErrorEvent, uid);
|
||||
};
|
||||
Leenkx.connections[net_Url].onclose = function() {
|
||||
if (closeEvent != null) {
|
||||
for (e in closeEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
Event.send(Leenkx.onCloseEvent, uid);
|
||||
};
|
||||
Leenkx.connections[net_Url].onseen = function() {
|
||||
if (seenEvent != null) {
|
||||
for (e in seenEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
Event.send(Leenkx.onSeenEvent, uid);
|
||||
};
|
||||
Leenkx.connections[net_Url].onserver = function() {
|
||||
if (serverEvent != null) {
|
||||
for (e in serverEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
Event.send(Leenkx.onServerEvent, uid);
|
||||
};
|
||||
Leenkx.connections[net_Url].onconnections = function() {
|
||||
if (connectionsEvent != null) {
|
||||
for (e in connectionsEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
Event.send(Leenkx.onConnectionsEvent, uid);
|
||||
};
|
||||
Leenkx.connections[net_Url].onping = function() {
|
||||
if (pingEvent != null) {
|
||||
for (e in pingEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
Event.send(Leenkx.onPingEvent, uid);
|
||||
};
|
||||
Leenkx.connections[net_Url].onleft = function() {
|
||||
if (leftEvent != null) {
|
||||
for (e in leftEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
Event.send(Leenkx.onLeftEvent, uid);
|
||||
};
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Event.send(Leenkx.onTimeOutEvent, uid);
|
||||
};
|
||||
Leenkx.connections[net_Url].onwireleft = function() {
|
||||
if (wireleftEvent != null) {
|
||||
for (e in wireleftEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
Event.send(Leenkx.onWireLeftEvent, uid);
|
||||
};
|
||||
Leenkx.connections[net_Url].onwireseen = function() {
|
||||
if (wireseenEvent != null) {
|
||||
for (e in wireseenEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
Event.send(Leenkx.onWireSeenEvent, uid);
|
||||
};
|
||||
Leenkx.connections[net_Url].ontorrent = function() {
|
||||
if (torrentEvent != null) {
|
||||
for (e in torrentEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Event.send(Leenkx.onTorrentEvent, uid);
|
||||
};
|
||||
Leenkx.connections[net_Url].ontorrentadded = function() {
|
||||
Event.send(Leenkx.onTorrentAddedEvent, uid);
|
||||
};
|
||||
Leenkx.connections[net_Url].ontracker = function() {
|
||||
if (trackerEvent != null) {
|
||||
for (e in trackerEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Event.send(Leenkx.onTrackerEvent, uid);
|
||||
};
|
||||
Leenkx.connections[net_Url].onannounce = function() {
|
||||
if (announceEvent != null) {
|
||||
for (e in announceEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
Event.send(Leenkx.onAnnounceEvent, uid);
|
||||
};
|
||||
Leenkx.connections[net_Url].onload = function() {
|
||||
if (loadEvent != null) {
|
||||
for (e in loadEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Event.send(Leenkx.onLoadEvent, uid);
|
||||
};
|
||||
Leenkx.connections[net_Url].ontorrentdone = function() {
|
||||
if (torrentDoneEvent != null) {
|
||||
for (e in torrentDoneEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
Event.send(Leenkx.onTorrentDoneEvent, uid);
|
||||
};
|
||||
|
||||
#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[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[connectionUrl].onload();
|
||||
}, function(err: kha.AssetError) {
|
||||
trace("ERROR loading Leenkx.js: " + err.url + " - " + err.error);
|
||||
Leenkx.connections[connectionUrl].onload();
|
||||
});
|
||||
}
|
||||
#end
|
||||
Leenkx.connections[connectionUrl].onload();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -281,11 +120,11 @@ class Leenkx {
|
||||
|
||||
class LeenkxSocket {
|
||||
public var _url:String;
|
||||
public var client:haxe.DynamicAccess<Dynamic>;
|
||||
public var client:LeenkxClient;
|
||||
public var buffer:Dynamic;
|
||||
public var onopen:Void->Void;
|
||||
public var onclose:Void->Void;
|
||||
public var onmessage:Void->Void;
|
||||
public var onmessage:Void->Void;
|
||||
public var onerror:Void->Void;
|
||||
public var onseen:Void->Void;
|
||||
public var onserver:Void->Void;
|
||||
@ -293,11 +132,10 @@ class LeenkxSocket {
|
||||
public var onping:Void->Void;
|
||||
public var onleft:Void->Void;
|
||||
public var ontimeout:Void->Void;
|
||||
public var onrpc:Void->Void;
|
||||
public var onrpcresponse:Void->Void;
|
||||
public var onwireleft:Void->Void;
|
||||
public var onwireseen:Void->Void;
|
||||
public var ontorrent:Void->Void;
|
||||
public var ontorrentadded:Void->Void;
|
||||
public var ontracker:Void->Void;
|
||||
public var onannounce:Void->Void;
|
||||
public var onload:Void->Void;
|
||||
@ -308,3 +146,4 @@ class LeenkxSocket {
|
||||
}
|
||||
|
||||
}
|
||||
#end
|
||||
|
||||
@ -1,51 +0,0 @@
|
||||
package leenkx.network;
|
||||
|
||||
class Log {
|
||||
public static inline var INFO:Int = 0x000001;
|
||||
public static inline var DEBUG:Int = 0x000010;
|
||||
public static inline var DATA:Int = 0x000100;
|
||||
|
||||
public static var mask:Int = 0;
|
||||
|
||||
#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
|
||||
|
||||
public static function info(data:String, id:String = null) {
|
||||
if (mask & INFO != INFO) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (id != null) {
|
||||
logFn('INFO :: ID-${id} :: ${data}');
|
||||
} else {
|
||||
logFn('INFO :: ${data}');
|
||||
}
|
||||
}
|
||||
|
||||
public static function debug(data:String, id:String = null) {
|
||||
if (mask & DEBUG != DEBUG) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (id != null) {
|
||||
logFn('DEBUG :: ID-${id} :: ${data}');
|
||||
} else {
|
||||
logFn('DEBUG :: ${data}');
|
||||
}
|
||||
}
|
||||
|
||||
public static function data(data:String, id:String = null) {
|
||||
if (mask & DATA != DATA) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (id != null) {
|
||||
logFn('DATA :: ID-${id}\n------------------------------\n${data}\n------------------------------');
|
||||
} else {
|
||||
logFn('${data}');
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -136,18 +136,43 @@ import haxe.io.Bytes;
|
||||
|
||||
class WebSocket {
|
||||
private var _url:String;
|
||||
|
||||
private var _protocols:Array<String> = null;
|
||||
private var _ws:js.html.WebSocket = null;
|
||||
|
||||
public function new(url:String, immediateOpen=true) {
|
||||
public var state(get, null): State;
|
||||
function get_state() {
|
||||
if (_ws == null) return Closed;
|
||||
switch (_ws.readyState) {
|
||||
case js.html.WebSocket.CONNECTING:
|
||||
return Handshake;
|
||||
case js.html.WebSocket.OPEN:
|
||||
return Body;
|
||||
}
|
||||
|
||||
return Closed;
|
||||
}
|
||||
|
||||
public var protocol(get, null): String;
|
||||
function get_protocol() {
|
||||
if (_ws == null)
|
||||
return null;
|
||||
return _ws.protocol;
|
||||
}
|
||||
|
||||
public function new(url:String, immediateOpen=true, protocols:Array<String> = null) {
|
||||
_url = url;
|
||||
_protocols = protocols;
|
||||
if (immediateOpen) {
|
||||
open();
|
||||
}
|
||||
}
|
||||
|
||||
private function createSocket() {
|
||||
return new js.html.WebSocket(_url);
|
||||
if (_protocols == null) {
|
||||
return new js.html.WebSocket(_url);
|
||||
}
|
||||
|
||||
return new js.html.WebSocket(_url, _protocols);
|
||||
}
|
||||
|
||||
public function open() {
|
||||
@ -292,6 +317,10 @@ class WebSocket extends WebSocketCommon {
|
||||
public var _host:String;
|
||||
public var _port:Int = 0;
|
||||
public var _path:String;
|
||||
public var _search:String;
|
||||
|
||||
private var _protocols:Array<String>;
|
||||
public var protocol(default, null):String = null;
|
||||
|
||||
private var _processThread:Thread;
|
||||
private var _encodedKey:String = "wskey";
|
||||
@ -300,8 +329,9 @@ class WebSocket extends WebSocketCommon {
|
||||
|
||||
public var additionalHeaders(get, null):Map<String, String>;
|
||||
|
||||
public function new(url:String, immediateOpen=true) {
|
||||
public function new(url:String, immediateOpen=true, protocols:Array<String> = null) {
|
||||
parseUrl(url);
|
||||
_protocols = protocols;
|
||||
|
||||
super(createSocket());
|
||||
|
||||
@ -310,40 +340,42 @@ class WebSocket extends WebSocketCommon {
|
||||
}
|
||||
}
|
||||
|
||||
inline private function parseUrl(url)
|
||||
inline private function parseUrl(url:String)
|
||||
{
|
||||
/** TO DO - FIND OUT WHAT IS BREAKING REGEX IN THE NEW PCRE2 FOR HL C
|
||||
|
||||
var urlRegExp = ~/^(\w+?):\/\/([\w\.-]+)(:(\d+))?(\/.*)?$/;
|
||||
|
||||
if ( ! urlRegExp.match(url)) {
|
||||
var sep = url.indexOf("://");
|
||||
if (sep < 0) {
|
||||
throw 'Uri not matching websocket URL "${url}"';
|
||||
}
|
||||
_protocol = url.substr(0, sep);
|
||||
var rest = url.substr(sep + 3);
|
||||
|
||||
_protocol = urlRegExp.matched(1);
|
||||
var pathStart = rest.length;
|
||||
var slashIdx = rest.indexOf("/");
|
||||
var qIdx = rest.indexOf("?");
|
||||
if (slashIdx >= 0 && slashIdx < pathStart) pathStart = slashIdx;
|
||||
if (qIdx >= 0 && qIdx < pathStart) pathStart = qIdx;
|
||||
|
||||
_host = urlRegExp.matched(2);
|
||||
var hostPort = rest.substr(0, pathStart);
|
||||
var pathAndSearch = rest.substr(pathStart);
|
||||
|
||||
var parsedPort = Std.parseInt(urlRegExp.matched(4));
|
||||
if (parsedPort > 0 ) {
|
||||
_port = parsedPort;
|
||||
var colonIdx = hostPort.indexOf(":");
|
||||
if (colonIdx >= 0) {
|
||||
_host = hostPort.substr(0, colonIdx);
|
||||
var parsedPort = Std.parseInt(hostPort.substr(colonIdx + 1));
|
||||
if (parsedPort > 0) {
|
||||
_port = parsedPort;
|
||||
}
|
||||
} else {
|
||||
_host = hostPort;
|
||||
}
|
||||
_path = urlRegExp.matched(5);
|
||||
if (_path == null || _path.length == 0) {
|
||||
_path = "/";
|
||||
|
||||
_path = pathAndSearch;
|
||||
_search = null;
|
||||
var searchIdx = pathAndSearch.indexOf("?");
|
||||
if (searchIdx >= 0) {
|
||||
_path = pathAndSearch.substr(0, searchIdx);
|
||||
_search = pathAndSearch.substr(searchIdx);
|
||||
}
|
||||
**/
|
||||
var urlArr = url.split(":");
|
||||
if ( urlArr.length < 3) {
|
||||
throw 'Uri not matching websocket URL "${url}"';
|
||||
}
|
||||
_protocol = urlArr[0];
|
||||
_host = urlArr[1].substr(2, urlArr[1].length);
|
||||
var parsedPort = Std.parseInt(urlArr[2].split("/")[0]);
|
||||
if (parsedPort > 0 ) {
|
||||
_port = parsedPort;
|
||||
}
|
||||
_path = urlArr[2].substr(urlArr[2].split("/")[0].length, urlArr[2].length);
|
||||
if (_path == null || _path.length == 0) {
|
||||
_path = "/";
|
||||
}
|
||||
@ -388,9 +420,7 @@ class WebSocket extends WebSocketCommon {
|
||||
#else
|
||||
|
||||
haxe.MainLoop.addThread(function() {
|
||||
Log.debug("Thread started", this.id);
|
||||
processLoop(this);
|
||||
Log.debug("Thread ended", this.id);
|
||||
});
|
||||
|
||||
#end
|
||||
@ -399,16 +429,13 @@ class WebSocket extends WebSocketCommon {
|
||||
}
|
||||
|
||||
private function processThread() {
|
||||
Log.debug("Thread started", this.id);
|
||||
var ws:WebSocket = Thread.readMessage(true);
|
||||
processLoop(this);
|
||||
Log.debug("Thread ended", this.id);
|
||||
}
|
||||
|
||||
private function processLoop(ws:WebSocket) {
|
||||
while (ws.state != State.Closed) { // TODO: should think about mutex
|
||||
ws.process();
|
||||
Sys.sleep(.01);
|
||||
}
|
||||
}
|
||||
|
||||
@ -422,8 +449,7 @@ class WebSocket extends WebSocketCommon {
|
||||
public function sendHandshake() {
|
||||
var httpRequest = new HttpRequest();
|
||||
httpRequest.method = "GET";
|
||||
// TODO: should propably be hostname+port+path?
|
||||
httpRequest.uri = _path;
|
||||
httpRequest.uri = (_search != null) ? _path + _search : _path;
|
||||
httpRequest.httpVersion = "HTTP/1.1";
|
||||
|
||||
httpRequest.headers.set(HttpHeader.HOST, _host + ":" + _port);
|
||||
@ -435,6 +461,9 @@ class WebSocket extends WebSocketCommon {
|
||||
httpRequest.headers.set(HttpHeader.CACHE_CONTROL, "no-cache");
|
||||
httpRequest.headers.set(HttpHeader.ORIGIN, _socket.host().host.toString() + ":" + _socket.host().port);
|
||||
|
||||
if (_protocols != null) {
|
||||
httpRequest.headers.set(HttpHeader.SEC_WEBSOCKET_PROTOCOL, _protocols.join(', '));
|
||||
}
|
||||
|
||||
_encodedKey = generateWSKey();
|
||||
httpRequest.headers.set(HttpHeader.SEC_WEBSOCKET_KEY, _encodedKey);
|
||||
@ -487,16 +516,18 @@ class WebSocket extends WebSocketCommon {
|
||||
}
|
||||
}
|
||||
|
||||
var protocol = httpResponse.headers.get(HttpHeader.SEC_WEBSOCKET_PROTOCOL);
|
||||
if (protocol != null) {
|
||||
this.protocol = protocol;
|
||||
}
|
||||
|
||||
_onopenCalled = false;
|
||||
state = State.Head;
|
||||
}
|
||||
|
||||
private function generateWSKey():String {
|
||||
var b = Bytes.alloc(16);
|
||||
for (i in 0...16) {
|
||||
b.set(i, Std.random(255));
|
||||
}
|
||||
return Base64.encode(b);
|
||||
return haxe.crypto.Base64.encode(
|
||||
leenkx.network.torrent.Crypto.randomBytes(16));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -38,7 +38,6 @@ class WebSocketCommon {
|
||||
|
||||
public function send(data:Any) {
|
||||
if (Std.isOfType(data, String)) {
|
||||
Log.data(data, id);
|
||||
sendFrame(Utf8Encoder.encode(data), OpCode.Text);
|
||||
} else if (Std.isOfType(data, Bytes)) {
|
||||
sendFrame(data, OpCode.Binary);
|
||||
@ -118,7 +117,6 @@ class WebSocketCommon {
|
||||
}
|
||||
} else {
|
||||
var stringPayload = Utf8Encoder.decode(unmaskedMessageData);
|
||||
Log.data(stringPayload, id);
|
||||
if (this.onmessage != null) {
|
||||
this.onmessage(StrMessage(stringPayload));
|
||||
}
|
||||
@ -138,14 +136,12 @@ class WebSocketCommon {
|
||||
case State.Closed:
|
||||
close();
|
||||
case _:
|
||||
trace('State not impl: ${state}');
|
||||
}
|
||||
}
|
||||
|
||||
public function close() {
|
||||
if (state != State.Closed) {
|
||||
try {
|
||||
Log.debug("Closed", id);
|
||||
sendFrame(Bytes.alloc(0), OpCode.Close);
|
||||
state = State.Closed;
|
||||
_socket.close();
|
||||
@ -162,7 +158,6 @@ class WebSocketCommon {
|
||||
_socket.output.write(data);
|
||||
_socket.output.flush();
|
||||
} catch (e:Dynamic) {
|
||||
Log.debug(Std.string(e), id);
|
||||
if (onerror != null) {
|
||||
onerror(Std.string(e));
|
||||
}
|
||||
@ -195,17 +190,24 @@ class WebSocketCommon {
|
||||
}
|
||||
|
||||
private static function generateMask() {
|
||||
var maskData = Bytes.alloc(4);
|
||||
maskData.set(0, Std.random(256));
|
||||
maskData.set(1, Std.random(256));
|
||||
maskData.set(2, Std.random(256));
|
||||
maskData.set(3, Std.random(256));
|
||||
return maskData;
|
||||
return leenkx.network.torrent.Crypto.randomBytes(4);
|
||||
}
|
||||
|
||||
private static function applyMask(payload:Bytes, mask:Bytes) {
|
||||
var maskedPayload = Bytes.alloc(payload.length);
|
||||
for (n in 0 ... payload.length) maskedPayload.set(n, payload.get(n) ^ mask.get(n % mask.length));
|
||||
var m0 = mask.get(0), m1 = mask.get(1), m2 = mask.get(2), m3 = mask.get(3);
|
||||
var i = 0;
|
||||
while (i + 3 < payload.length) {
|
||||
maskedPayload.set(i, payload.get(i) ^ m0);
|
||||
maskedPayload.set(i + 1, payload.get(i + 1) ^ m1);
|
||||
maskedPayload.set(i + 2, payload.get(i + 2) ^ m2);
|
||||
maskedPayload.set(i + 3, payload.get(i + 3) ^ m3);
|
||||
i += 4;
|
||||
}
|
||||
while (i < payload.length) {
|
||||
maskedPayload.set(i, payload.get(i) ^ mask.get(i & 3));
|
||||
i++;
|
||||
}
|
||||
return maskedPayload;
|
||||
}
|
||||
|
||||
@ -230,7 +232,6 @@ class WebSocketCommon {
|
||||
try {
|
||||
result = SocketImpl.select([_socket], null, null, 0.01);
|
||||
} catch (e:Dynamic) {
|
||||
Log.debug("Error selecting socket: " + e);
|
||||
needClose = true;
|
||||
}
|
||||
|
||||
@ -238,12 +239,11 @@ class WebSocketCommon {
|
||||
if (result.read.length > 0) {
|
||||
try {
|
||||
while (true) {
|
||||
var data = Bytes.alloc(1024);
|
||||
var data = Bytes.alloc(65536);
|
||||
var read = _socket.input.readBytes(data, 0, data.length);
|
||||
if (read <= 0){
|
||||
break;
|
||||
}
|
||||
Log.debug("Bytes read: " + read, id);
|
||||
_buffer.writeBytes(data.sub(0, read));
|
||||
}
|
||||
} catch (e:Dynamic) {
|
||||
@ -259,7 +259,7 @@ class WebSocketCommon {
|
||||
}
|
||||
#else
|
||||
|
||||
needClose = !(e == 'Blocking' || (Std.isOfType(e, Error) && (e:Error).match(Error.Blocked)));
|
||||
needClose = !(e == 'Blocking' || (Std.isOfType(e, Error) && (e:Error).match(Error.Blocked)) || (state == Handshake && e == haxe.io.Eof));
|
||||
|
||||
#end
|
||||
}
|
||||
@ -273,7 +273,6 @@ class WebSocketCommon {
|
||||
if (needClose == true) { // dont want to send the Close frame here
|
||||
if (state != State.Closed) {
|
||||
try {
|
||||
Log.debug("Closed", id);
|
||||
state = State.Closed;
|
||||
_socket.close();
|
||||
} catch (e:Dynamic) { }
|
||||
@ -288,8 +287,6 @@ class WebSocketCommon {
|
||||
public function sendHttpRequest(httpRequest:HttpRequest) {
|
||||
var data = httpRequest.build();
|
||||
|
||||
Log.data(data, id);
|
||||
|
||||
try {
|
||||
_socket.output.write(Bytes.ofString(data));
|
||||
_socket.output.flush();
|
||||
@ -304,8 +301,6 @@ class WebSocketCommon {
|
||||
public function sendHttpResponse(httpResponse:HttpResponse) {
|
||||
var data = httpResponse.build();
|
||||
|
||||
Log.data(data, id);
|
||||
|
||||
_socket.output.write(Bytes.ofString(data));
|
||||
_socket.output.flush();
|
||||
}
|
||||
@ -325,8 +320,6 @@ class WebSocketCommon {
|
||||
httpRequest.addLine(line);
|
||||
}
|
||||
|
||||
Log.data(httpRequest.toString(), id);
|
||||
|
||||
return httpRequest;
|
||||
}
|
||||
|
||||
@ -346,8 +339,6 @@ class WebSocketCommon {
|
||||
|
||||
}
|
||||
|
||||
Log.data(httpResponse.toString(), id);
|
||||
|
||||
return httpResponse;
|
||||
}
|
||||
|
||||
|
||||
@ -13,7 +13,6 @@ class WebSocketHandler extends Handler {
|
||||
_creationTime = Sys.time();
|
||||
#end
|
||||
_socket.setBlocking(false);
|
||||
Log.debug('New socket handler', id);
|
||||
}
|
||||
|
||||
public override function handle() {
|
||||
@ -23,7 +22,6 @@ class WebSocketHandler extends Handler {
|
||||
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;
|
||||
}
|
||||
@ -70,10 +68,8 @@ class WebSocketHandler extends Handler {
|
||||
httpResponse.headers.set(HttpHeader.CONNECTION, "close");
|
||||
httpResponse.headers.set(HttpHeader.X_WEBSOCKET_REJECT_REASON, 'Unsupported connection header: ${httpRequest.headers.get(HttpHeader.CONNECTION)}.');
|
||||
} else {
|
||||
Log.debug('Handshaking', id);
|
||||
var key = httpRequest.headers.get(HttpHeader.SEC_WEBSOCKET_KEY);
|
||||
var result = makeWSKeyResponse(key);
|
||||
Log.debug('Handshaking key - ${result}', id);
|
||||
|
||||
httpResponse.code = 101;
|
||||
httpResponse.text = "Switching Protocols";
|
||||
@ -82,14 +78,21 @@ class WebSocketHandler extends Handler {
|
||||
httpResponse.headers.set(HttpHeader.SEC_WEBSOSCKET_ACCEPT, result);
|
||||
}
|
||||
|
||||
sendHttpResponse(httpResponse);
|
||||
function callback(httpResponse: HttpResponse) {
|
||||
sendHttpResponse(httpResponse);
|
||||
|
||||
if (httpResponse.code == 101) {
|
||||
_onopenCalled = false;
|
||||
state = State.Head;
|
||||
Log.debug('Connected', id);
|
||||
if (httpResponse.code == 101) {
|
||||
_onopenCalled = false;
|
||||
state = State.Head;
|
||||
} else {
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
if (validateHandshake != null) {
|
||||
validateHandshake(httpRequest, httpResponse, callback);
|
||||
} else {
|
||||
close();
|
||||
callback(httpResponse);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -60,7 +60,6 @@ class WebSocketServer
|
||||
_serverSocket.bind(new sys.net.Host(_host), _port);
|
||||
#end
|
||||
_serverSocket.listen(_maxConnections);
|
||||
Log.info('Starting server - ${_host}:${_port} (maxConnections: ${_maxConnections})');
|
||||
|
||||
#if kha_krom
|
||||
kha.Scheduler.addTimeTask(function() {
|
||||
@ -114,7 +113,6 @@ class WebSocketServer
|
||||
var handler = new T(socket);
|
||||
handlers.push(handler);
|
||||
|
||||
Log.debug("Adding to web server handler to list - total: " + handlers.length, handler.id);
|
||||
if (onClientAdded != null) {
|
||||
onClientAdded(handler);
|
||||
}
|
||||
@ -174,7 +172,6 @@ class WebSocketServer
|
||||
|
||||
for (h in toRemove) {
|
||||
handlers.remove(h);
|
||||
Log.debug("Removing web server handler from list - total: " + handlers.length, h.id);
|
||||
if (onClientRemoved != null) {
|
||||
onClientRemoved(h);
|
||||
}
|
||||
@ -186,7 +183,6 @@ class WebSocketServer
|
||||
while (_handlersClosed.length > 0) {
|
||||
var h = _handlersClosed.shift();
|
||||
handlers.remove(h);
|
||||
Log.debug("Removing web server handler from list - total: " + handlers.length, h.id);
|
||||
if (onClientRemoved != null) {
|
||||
onClientRemoved(h);
|
||||
}
|
||||
|
||||
@ -52,8 +52,15 @@ class KromSecureSocket extends KromSocket {
|
||||
}
|
||||
|
||||
override public function connect(host:KromHost, port:Int):Void {
|
||||
super.connect(host, port);
|
||||
enableSSL();
|
||||
setBlocking(true);
|
||||
try {
|
||||
super.connect(host, port);
|
||||
enableSSL();
|
||||
setBlocking(false);
|
||||
} catch (e:Dynamic) {
|
||||
setBlocking(false);
|
||||
trace('Failed Krom Host');
|
||||
}
|
||||
}
|
||||
|
||||
public function isSSLEnabled():Bool {
|
||||
|
||||
@ -43,6 +43,9 @@ class KromSocket {
|
||||
}
|
||||
|
||||
private function setSocketId(id:Int):Void {
|
||||
if (_socketId >= 0) {
|
||||
krom_socket_close(_socketId);
|
||||
}
|
||||
_socketId = id;
|
||||
if (_socketId >= 0) {
|
||||
input = new KromSocketInput(this);
|
||||
@ -247,3 +250,29 @@ class KromHost {
|
||||
return host;
|
||||
}
|
||||
}
|
||||
// TODO: Replace all ticks timers and intervals for events
|
||||
class KromPump {
|
||||
static var ticks:Array<Void -> Void> = [];
|
||||
static var timer:haxe.Timer = null;
|
||||
|
||||
public static function add(tick:Void -> Void):Void {
|
||||
if (!ticks.contains(tick)) ticks.push(tick);
|
||||
if (timer == null) {
|
||||
timer = new haxe.Timer(10);
|
||||
timer.run = runAll;
|
||||
}
|
||||
}
|
||||
public static function remove(tick:Void -> Void):Void {
|
||||
ticks.remove(tick);
|
||||
if (ticks.length == 0 && timer != null) {
|
||||
timer.stop();
|
||||
timer = null;
|
||||
}
|
||||
}
|
||||
static function runAll():Void {
|
||||
var snapshot = ticks.copy();
|
||||
for (tick in snapshot) {
|
||||
if (ticks.contains(tick)) tick();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
70
leenkx/Sources/leenkx/network/krom/KromUdpSocket.hx
Normal file
70
leenkx/Sources/leenkx/network/krom/KromUdpSocket.hx
Normal file
@ -0,0 +1,70 @@
|
||||
package leenkx.network.krom;
|
||||
|
||||
import haxe.io.Bytes;
|
||||
|
||||
@:native("krom_udp_create") extern function krom_udp_create():Int;
|
||||
@:native("krom_udp_bind") extern function krom_udp_bind(id:Int,
|
||||
addr:String, port:Int):Bool;
|
||||
@:native("krom_udp_sendto") extern function krom_udp_sendto(id:Int,
|
||||
host:String, port:Int, data:js.lib.ArrayBuffer):Int;
|
||||
@:native("krom_udp_recvfrom") extern function krom_udp_recvfrom(id:Int,
|
||||
maxLen:Int):Dynamic;
|
||||
@:native("krom_socket_close") extern function krom_udp_close(id:Int):Void;
|
||||
@:native("krom_socket_set_blocking")
|
||||
extern function krom_udp_set_blocking(id:Int, blocking:Bool):Void;
|
||||
|
||||
class KromUdpSocket {
|
||||
private var _socketId:Int = -1;
|
||||
|
||||
public function new() {
|
||||
_socketId = krom_udp_create();
|
||||
setBlocking(false);
|
||||
}
|
||||
|
||||
public function bind(host:KromSocket.KromHost, port:Int):Void {
|
||||
if (_socketId >= 0) {
|
||||
if (!krom_udp_bind(_socketId, host.host, port)) {
|
||||
trace("UDP bind failed to " + host.host + ":" + port);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function sendTo(data:Bytes, pos:Int, len:Int,
|
||||
host:KromSocket.KromHost, port:Int):Int {
|
||||
if (_socketId < 0) return 0;
|
||||
var arrayBuffer:js.lib.ArrayBuffer = data.getData();
|
||||
var exact:js.lib.ArrayBuffer =
|
||||
untyped arrayBuffer.slice(pos, pos + len);
|
||||
return krom_udp_sendto(_socketId, host.host, port, exact);
|
||||
}
|
||||
|
||||
public function recvFrom(maxLength:Int):{data:Bytes, host:String,
|
||||
port:Int} {
|
||||
if (_socketId < 0) return null;
|
||||
var result:Dynamic = krom_udp_recvfrom(_socketId, maxLength);
|
||||
if (result == -1) {
|
||||
_socketId = -1;
|
||||
trace("Closed");
|
||||
return null;
|
||||
}
|
||||
if (result == null || Std.isOfType(result, Int)) return null;
|
||||
return {
|
||||
data: Bytes.ofData(result.data),
|
||||
host: result.host,
|
||||
port: result.port
|
||||
};
|
||||
}
|
||||
|
||||
public function setBlocking(blocking:Bool):Void {
|
||||
if (_socketId >= 0) {
|
||||
krom_udp_set_blocking(_socketId, blocking);
|
||||
}
|
||||
}
|
||||
|
||||
public function close():Void {
|
||||
if (_socketId >= 0) {
|
||||
krom_udp_close(_socketId);
|
||||
_socketId = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
209
leenkx/Sources/leenkx/network/torrent/Aegis128x4.hx
Normal file
209
leenkx/Sources/leenkx/network/torrent/Aegis128x4.hx
Normal file
@ -0,0 +1,209 @@
|
||||
package leenkx.network.torrent;
|
||||
|
||||
import haxe.io.Bytes;
|
||||
|
||||
class Aegis128x4 {
|
||||
static inline var D = 4;
|
||||
static inline var RATE = 128;
|
||||
|
||||
static var C0 = Bytes.ofHex("000101020305080d1522375990e97962");
|
||||
static var C1 = Bytes.ofHex("db3d18556dc22ff12011314273b528dd");
|
||||
|
||||
var v:Bytes;
|
||||
var nv:Bytes;
|
||||
var t:Bytes;
|
||||
var t2:Bytes;
|
||||
|
||||
public function new(key:Bytes, nonce:Bytes) {
|
||||
v = Bytes.alloc(512);
|
||||
nv = Bytes.alloc(512);
|
||||
t = Bytes.alloc(16);
|
||||
t2 = Bytes.alloc(16);
|
||||
init(key, nonce);
|
||||
}
|
||||
|
||||
static inline function off(j:Int, i:Int):Int {
|
||||
return (j * D + i) * 16;
|
||||
}
|
||||
|
||||
static inline function xtime(x:Int):Int {
|
||||
return ((x << 1) ^ ((x >> 7) * 0x1b)) & 0xff;
|
||||
}
|
||||
|
||||
static function xorInto(a:Bytes, ao:Int, b:Bytes, bo:Int, out:Bytes, oo:Int):Void {
|
||||
for (k in 0...16) out.set(oo + k, a.get(ao + k) ^ b.get(bo + k));
|
||||
}
|
||||
|
||||
function aesRound(inOff:Int, rk:Bytes, rkOff:Int, out:Bytes, outOff:Int):Void {
|
||||
for (c in 0...4) for (r in 0...4)
|
||||
t2.set(4 * c + r, SBOX[v.get(inOff + 4 * ((c + r) & 3) + r)]);
|
||||
for (c in 0...4) {
|
||||
var b0 = t2.get(4 * c);
|
||||
var b1 = t2.get(4 * c + 1);
|
||||
var b2 = t2.get(4 * c + 2);
|
||||
var b3 = t2.get(4 * c + 3);
|
||||
out.set(outOff + 4 * c, (xtime(b0) ^ xtime(b1) ^ b1 ^ b2 ^ b3) ^ rk.get(rkOff + 4 * c));
|
||||
out.set(outOff + 4 * c + 1, (b0 ^ xtime(b1) ^ xtime(b2) ^ b2 ^ b3) ^ rk.get(rkOff + 4 * c + 1));
|
||||
out.set(outOff + 4 * c + 2, (b0 ^ b1 ^ xtime(b2) ^ xtime(b3) ^ b3) ^ rk.get(rkOff + 4 * c + 2));
|
||||
out.set(outOff + 4 * c + 3, (xtime(b0) ^ b0 ^ b1 ^ b2 ^ xtime(b3)) ^ rk.get(rkOff + 4 * c + 3));
|
||||
}
|
||||
}
|
||||
|
||||
function update(m0:Bytes, m0off:Int, m1:Bytes, m1off:Int):Void {
|
||||
for (i in 0...D) {
|
||||
xorInto(v, off(0, i), m0, m0off + i * 16, t, 0);
|
||||
aesRound(off(7, i), t, 0, nv, off(0, i));
|
||||
aesRound(off(0, i), v, off(1, i), nv, off(1, i));
|
||||
aesRound(off(1, i), v, off(2, i), nv, off(2, i));
|
||||
aesRound(off(2, i), v, off(3, i), nv, off(3, i));
|
||||
xorInto(v, off(4, i), m1, m1off + i * 16, t, 0);
|
||||
aesRound(off(3, i), t, 0, nv, off(4, i));
|
||||
aesRound(off(4, i), v, off(5, i), nv, off(5, i));
|
||||
aesRound(off(5, i), v, off(6, i), nv, off(6, i));
|
||||
aesRound(off(6, i), v, off(7, i), nv, off(7, i));
|
||||
}
|
||||
v.blit(0, nv, 0, 512);
|
||||
}
|
||||
|
||||
function init(key:Bytes, nonce:Bytes):Void {
|
||||
var kn = Bytes.alloc(16);
|
||||
var kc0 = Bytes.alloc(16);
|
||||
var kc1 = Bytes.alloc(16);
|
||||
for (k in 0...16) {
|
||||
kn.set(k, key.get(k) ^ nonce.get(k));
|
||||
kc0.set(k, key.get(k) ^ C0.get(k));
|
||||
kc1.set(k, key.get(k) ^ C1.get(k));
|
||||
}
|
||||
for (i in 0...D) {
|
||||
v.blit(off(0, i), kn, 0, 16);
|
||||
v.blit(off(1, i), C1, 0, 16);
|
||||
v.blit(off(2, i), C0, 0, 16);
|
||||
v.blit(off(3, i), C1, 0, 16);
|
||||
v.blit(off(4, i), kn, 0, 16);
|
||||
v.blit(off(5, i), kc0, 0, 16);
|
||||
v.blit(off(6, i), kc1, 0, 16);
|
||||
v.blit(off(7, i), kc0, 0, 16);
|
||||
}
|
||||
var m0 = Bytes.alloc(64);
|
||||
var m1 = Bytes.alloc(64);
|
||||
for (i in 0...D) {
|
||||
m0.blit(i * 16, nonce, 0, 16);
|
||||
m1.blit(i * 16, key, 0, 16);
|
||||
}
|
||||
for (r in 0...10) {
|
||||
for (i in 0...D) {
|
||||
v.set(off(3, i), v.get(off(3, i)) ^ i);
|
||||
v.set(off(3, i) + 1, v.get(off(3, i) + 1) ^ (D - 1));
|
||||
v.set(off(7, i), v.get(off(7, i)) ^ i);
|
||||
v.set(off(7, i) + 1, v.get(off(7, i) + 1) ^ (D - 1));
|
||||
}
|
||||
update(m0, 0, m1, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public function absorb(ai:Bytes, aiOff:Int):Void {
|
||||
update(ai, aiOff, ai, aiOff + 64);
|
||||
}
|
||||
|
||||
public function enc(xi:Bytes, xiOff:Int):Bytes {
|
||||
var out = Bytes.alloc(RATE);
|
||||
for (i in 0...D) {
|
||||
for (k in 0...16) {
|
||||
var z0 = v.get(off(1, i) + k) ^ v.get(off(6, i) + k)
|
||||
^ (v.get(off(2, i) + k) & v.get(off(3, i) + k));
|
||||
var z1 = v.get(off(2, i) + k) ^ v.get(off(5, i) + k)
|
||||
^ (v.get(off(6, i) + k) & v.get(off(7, i) + k));
|
||||
out.set(i * 16 + k, xi.get(xiOff + i * 16 + k) ^ z0);
|
||||
out.set(64 + i * 16 + k, xi.get(xiOff + 64 + i * 16 + k) ^ z1);
|
||||
}
|
||||
}
|
||||
update(xi, xiOff, xi, xiOff + 64);
|
||||
return out;
|
||||
}
|
||||
|
||||
public function nextBlock():Bytes {
|
||||
var out = Bytes.alloc(RATE);
|
||||
for (i in 0...D) {
|
||||
for (k in 0...16) {
|
||||
out.set(i * 16 + k, v.get(off(1, i) + k) ^ v.get(off(6, i) + k)
|
||||
^ (v.get(off(2, i) + k) & v.get(off(3, i) + k)));
|
||||
out.set(64 + i * 16 + k, v.get(off(2, i) + k) ^ v.get(off(5, i) + k)
|
||||
^ (v.get(off(6, i) + k) & v.get(off(7, i) + k)));
|
||||
}
|
||||
}
|
||||
for (i in 0...D) {
|
||||
aesRound(off(7, i), v, off(0, i), nv, off(0, i));
|
||||
aesRound(off(0, i), v, off(1, i), nv, off(1, i));
|
||||
aesRound(off(1, i), v, off(2, i), nv, off(2, i));
|
||||
aesRound(off(2, i), v, off(3, i), nv, off(3, i));
|
||||
aesRound(off(3, i), v, off(4, i), nv, off(4, i));
|
||||
aesRound(off(4, i), v, off(5, i), nv, off(5, i));
|
||||
aesRound(off(5, i), v, off(6, i), nv, off(6, i));
|
||||
aesRound(off(6, i), v, off(7, i), nv, off(7, i));
|
||||
}
|
||||
v.blit(0, nv, 0, 512);
|
||||
return out;
|
||||
}
|
||||
|
||||
public static function selfTest():Bool {
|
||||
var key = Bytes.ofHex("000102030405060708090a0b0c0d0e0f");
|
||||
var nonce = Bytes.ofHex("101112131415161718191a1b1c1d1e1f");
|
||||
var a = new Aegis128x4(key, nonce);
|
||||
var expected = Bytes.ofHex(
|
||||
"924eb07635003a37e6c6575ba8ce1929c8b6a5d91475445e936d48e794be0ce2" +
|
||||
"fcd37d050e24084befe3bbb219d647602e9f58cfb893a8800220242c373a8b18" +
|
||||
"1a1f60c4fab64e5471dc72edfcf6fe6bc1e525ebea2d6375a9edd045dce96381" +
|
||||
"97a3e25abd228a44d4a14a6d3fe9185cc2d4cf7f4287a98744645674265d4ca8" +
|
||||
"7bb50c534f6ec4780530ff1cce8a16e87b08d57557da0b5ef7b5f7d98b0ba189" +
|
||||
"6bfcac34ddb68404821a4d665303cb0fd95626f6dfad1aed7467622c38529932" +
|
||||
"af339fd2d50ee45fc47665c647cf6586d0669b39d140f0e118a4a511efe2f95a" +
|
||||
"7a94330f35c194fadda2a87e42cdeccc233b640d1f4d56e2757e72c1a9d8ecb1" +
|
||||
"9f93737d699ba05c11e94f2b201bef5e61caf387cf7cfd3f8300ac7680ccfd76" +
|
||||
"5825a671ecef03b7a9c98a601ae3211587a1fe4d558161a8f4c38731f3223032" +
|
||||
"7a5aca78d636c05bbc702b2980196ab6915d868408495d07eb527789f282c575" +
|
||||
"d0947bfbc1d3309cdffc9be1503aea628834ea57a15b9fbdc0245464a4b8cbef" +
|
||||
"e46f4cf71a95ac45b6f0823e3aba1a868c4ecef682fc44a8eba911b3fc7d99f9" +
|
||||
"a4fb61e2c928a2ca760b8772f2ea5f2e3d34ea89da73caa3016c280500a155a3" +
|
||||
"85075f0080e9d618e7eb40f57c32d9f7d2ab2b320c6e93b155a3787cb83e5281" +
|
||||
"0b3af0250ae36831a1b072e499929bcb5cce4d00329d69f1aae36aa541347512");
|
||||
if (expected.compare(a.v) != 0) {
|
||||
return false;
|
||||
}
|
||||
var ad = Bytes.alloc(128);
|
||||
for (k in 0...8) ad.set(k, [1, 2, 3, 4][k & 3]);
|
||||
a.absorb(ad, 0);
|
||||
var msg = Bytes.alloc(128);
|
||||
for (k in 0...120) msg.set(k, [4, 5, 6, 7][k & 3]);
|
||||
var ct = a.enc(msg, 0);
|
||||
var expectedCt = Bytes.ofHex(
|
||||
"e836118562f4479c9d35c17356a833114c21f9aa39e4dda5e5c87f4152a00fce" +
|
||||
"9a7c38f832eafe8b1c12f8a7cf12a81a1ad8a9c24ba9dedfbdaa586ffea67ddc" +
|
||||
"801ea97d9ab4a872f42d0e352e2713dacd609f9442c17517c5a29daf3e2a3fac" +
|
||||
"4ff6b1380c4e46df7b086af6ce6bc1ed594b8dd64aed2a7e");
|
||||
for (k in 0...120) {
|
||||
if (ct.get(k) != expectedCt.get(k)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static var SBOX = [
|
||||
0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
|
||||
0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
|
||||
0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
|
||||
0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
|
||||
0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
|
||||
0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
|
||||
0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
|
||||
0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
|
||||
0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
|
||||
0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
|
||||
0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
|
||||
0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
|
||||
0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
|
||||
0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
|
||||
0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
|
||||
0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16
|
||||
];
|
||||
}
|
||||
119
leenkx/Sources/leenkx/network/torrent/Base58.hx
Normal file
119
leenkx/Sources/leenkx/network/torrent/Base58.hx
Normal file
@ -0,0 +1,119 @@
|
||||
package leenkx.network.torrent;
|
||||
|
||||
import haxe.io.Bytes;
|
||||
|
||||
class Base58 {
|
||||
|
||||
static var ALPHABET:String = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
||||
static var ALPHABET_MAP:Map<Int, Int> = {
|
||||
var m = new Map<Int, Int>();
|
||||
for (i in 0...ALPHABET.length) {
|
||||
m.set(ALPHABET.charCodeAt(i), i);
|
||||
}
|
||||
m;
|
||||
};
|
||||
|
||||
public static function encode(bytes:Bytes):String {
|
||||
if (bytes.length == 0) return "";
|
||||
|
||||
var zeros = 0;
|
||||
while (zeros < bytes.length && bytes.get(zeros) == 0) zeros++;
|
||||
|
||||
var size = Math.ceil(bytes.length * 138 / 100 + 1);
|
||||
var b58 = [for (i in 0...size) 0];
|
||||
|
||||
for (i in zeros...bytes.length) {
|
||||
var carry = bytes.get(i);
|
||||
for (j in 0...size) {
|
||||
var idx = size - 1 - j;
|
||||
carry += b58[idx] << 8;
|
||||
b58[idx] = carry % 58;
|
||||
carry = Std.int(carry / 58);
|
||||
}
|
||||
}
|
||||
|
||||
var leadingZeros = 0;
|
||||
for (i in 0...size) {
|
||||
if (b58[i] != 0) break;
|
||||
leadingZeros++;
|
||||
}
|
||||
|
||||
var sb = new StringBuf();
|
||||
for (i in 0...zeros) sb.addChar(ALPHABET.charCodeAt(0));
|
||||
for (i in leadingZeros...size) {
|
||||
sb.addChar(ALPHABET.charCodeAt(b58[i]));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static function decode(str:String):Bytes {
|
||||
if (str.length == 0) return Bytes.alloc(0);
|
||||
|
||||
var zeros = 0;
|
||||
while (zeros < str.length && str.charCodeAt(zeros) == ALPHABET.charCodeAt(0)) zeros++;
|
||||
|
||||
var size = Math.ceil(str.length * 733 / 1000 + 1);
|
||||
var bytes = [for (i in 0...size) 0];
|
||||
|
||||
for (i in zeros...str.length) {
|
||||
var ch = str.charCodeAt(i);
|
||||
if (!ALPHABET_MAP.exists(ch)) {
|
||||
throw "Base58: invalid character " + str.charAt(i);
|
||||
}
|
||||
var carry = ALPHABET_MAP.get(ch);
|
||||
for (j in 0...size) {
|
||||
var idx = size - 1 - j;
|
||||
carry += bytes[idx] * 58;
|
||||
bytes[idx] = carry & 0xff;
|
||||
carry = carry >> 8;
|
||||
}
|
||||
}
|
||||
|
||||
var leadingZeros = 0;
|
||||
for (i in 0...size) {
|
||||
if (bytes[i] != 0) break;
|
||||
leadingZeros++;
|
||||
}
|
||||
|
||||
var result = Bytes.alloc(zeros + size - leadingZeros);
|
||||
for (i in 0...zeros) result.set(i, 0);
|
||||
for (i in 0...(size - leadingZeros)) {
|
||||
result.set(zeros + i, bytes[leadingZeros + i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
class Base58Check {
|
||||
|
||||
static var CHECKSUM_LEN = 4;
|
||||
|
||||
public static function encode(bytes:Bytes):String {
|
||||
var hash = sha256sha256(bytes);
|
||||
var payload = Bytes.alloc(bytes.length + CHECKSUM_LEN);
|
||||
payload.blit(0, bytes, 0, bytes.length);
|
||||
payload.blit(bytes.length, hash, 0, CHECKSUM_LEN);
|
||||
return Base58.encode(payload);
|
||||
}
|
||||
|
||||
public static function decode(str:String):Bytes {
|
||||
var payload = Base58.decode(str);
|
||||
if (payload.length < CHECKSUM_LEN) {
|
||||
throw "Base58Check: too short";
|
||||
}
|
||||
var data = payload.sub(0, payload.length - CHECKSUM_LEN);
|
||||
var checksum = payload.sub(payload.length - CHECKSUM_LEN, CHECKSUM_LEN);
|
||||
var hash = sha256sha256(data);
|
||||
for (i in 0...CHECKSUM_LEN) {
|
||||
if (hash.get(i) != checksum.get(i)) {
|
||||
throw "Base58Check: checksum mismatch";
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
static function sha256sha256(bytes:Bytes):Bytes {
|
||||
var h1 = haxe.crypto.Sha256.make(bytes);
|
||||
return haxe.crypto.Sha256.make(h1);
|
||||
}
|
||||
}
|
||||
182
leenkx/Sources/leenkx/network/torrent/Bencode.hx
Normal file
182
leenkx/Sources/leenkx/network/torrent/Bencode.hx
Normal file
@ -0,0 +1,182 @@
|
||||
package leenkx.network.torrent;
|
||||
|
||||
import haxe.io.Bytes;
|
||||
|
||||
class RawDict {
|
||||
public var pairs:Array<{key:Bytes, val:Dynamic}> = [];
|
||||
public function new() {}
|
||||
}
|
||||
|
||||
class Bencode {
|
||||
|
||||
public static function encode(value:Dynamic):Bytes {
|
||||
var out = new haxe.io.BytesOutput();
|
||||
encodeValue(out, value);
|
||||
return out.getBytes();
|
||||
}
|
||||
|
||||
public static function encodeToString(value:Dynamic):String {
|
||||
return encode(value).toString();
|
||||
}
|
||||
|
||||
static function encodeValue(out:haxe.io.BytesOutput, value:Dynamic):Void {
|
||||
if (Std.isOfType(value, Bool)) {
|
||||
encodeInt(out, value ? 1 : 0);
|
||||
} else if (Std.isOfType(value, Int) || Std.isOfType(value, Float)) {
|
||||
encodeInt(out, value);
|
||||
} else if (Std.isOfType(value, String)) {
|
||||
encodeString(out, value);
|
||||
} else if (Std.isOfType(value, Bytes)) {
|
||||
encodeBytes(out, value);
|
||||
} else if (Std.isOfType(value, RawDict)) {
|
||||
encodeRawDict(out, value);
|
||||
} else if (Std.isOfType(value, Array)) {
|
||||
encodeList(out, value);
|
||||
} else if (value != null && Reflect.isObject(value)) {
|
||||
encodeDict(out, value);
|
||||
} else {
|
||||
throw "Bencode: cannot encode " + Type.typeof(value);
|
||||
}
|
||||
}
|
||||
|
||||
static function encodeInt(out:haxe.io.BytesOutput, value:Float):Void {
|
||||
out.writeString("i");
|
||||
var s = Std.string(value);
|
||||
var dot = s.indexOf(".");
|
||||
if (dot != -1) s = s.substring(0, dot);
|
||||
if (s.indexOf("e") != -1 || s.indexOf("E") != -1) {
|
||||
s = Std.string(Math.floor(value));
|
||||
dot = s.indexOf(".");
|
||||
if (dot != -1) s = s.substring(0, dot);
|
||||
}
|
||||
out.writeString(s);
|
||||
out.writeString("e");
|
||||
}
|
||||
|
||||
static function encodeString(out:haxe.io.BytesOutput, value:String):Void {
|
||||
var bytes = Bytes.ofString(value);
|
||||
encodeBytes(out, bytes);
|
||||
}
|
||||
|
||||
static function encodeBytes(out:haxe.io.BytesOutput, bytes:Bytes):Void {
|
||||
out.writeString(Std.string(bytes.length));
|
||||
out.writeString(":");
|
||||
out.writeBytes(bytes, 0, bytes.length);
|
||||
}
|
||||
|
||||
static function encodeRawDict(out:haxe.io.BytesOutput, dict:RawDict):Void {
|
||||
out.writeString("d");
|
||||
for (p in dict.pairs) {
|
||||
encodeBytes(out, p.key);
|
||||
encodeValue(out, p.val);
|
||||
}
|
||||
out.writeString("e");
|
||||
}
|
||||
|
||||
static function encodeList(out:haxe.io.BytesOutput, arr:Array<Dynamic>):Void {
|
||||
out.writeString("l");
|
||||
for (item in arr) {
|
||||
encodeValue(out, item);
|
||||
}
|
||||
out.writeString("e");
|
||||
}
|
||||
|
||||
static function encodeDict(out:haxe.io.BytesOutput, obj:Dynamic):Void {
|
||||
out.writeString("d");
|
||||
var keys = Reflect.fields(obj);
|
||||
keys.sort(Reflect.compare);
|
||||
for (key in keys) {
|
||||
if (Reflect.field(obj, key) == null) continue;
|
||||
encodeString(out, key);
|
||||
encodeValue(out, Reflect.field(obj, key));
|
||||
}
|
||||
out.writeString("e");
|
||||
}
|
||||
|
||||
public static function decode(bytes:Bytes):Dynamic {
|
||||
return new Decoder(bytes).value();
|
||||
}
|
||||
|
||||
public static function decodeString(str:String):Dynamic {
|
||||
return decode(Bytes.ofString(str));
|
||||
}
|
||||
}
|
||||
|
||||
private class Decoder {
|
||||
static inline var MAX_DECODE_LEN = 10 * 1024 * 1024; // 10 MB
|
||||
|
||||
var data:Bytes;
|
||||
var pos:Int = 0;
|
||||
|
||||
public function new(data:Bytes) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public function value():Dynamic {
|
||||
if (pos >= data.length) throw "Bencode: truncated input";
|
||||
var b = data.get(pos++);
|
||||
switch (b) {
|
||||
case 'i'.code:
|
||||
return decodeInt();
|
||||
case 'l'.code:
|
||||
return decodeList();
|
||||
case 'd'.code:
|
||||
return decodeDict();
|
||||
default:
|
||||
return decodeBytes();
|
||||
}
|
||||
}
|
||||
|
||||
function decodeInt():Float {
|
||||
var start = pos;
|
||||
while (pos < data.length && data.get(pos) != 'e'.code) pos++;
|
||||
if (pos >= data.length) throw "Bencode: unterminated int";
|
||||
var s = data.getString(start, pos - start);
|
||||
pos++;
|
||||
var v = Std.parseFloat(s);
|
||||
if (Math.isNaN(v)) throw "Bencode: bad int " + s;
|
||||
return v;
|
||||
}
|
||||
|
||||
function decodeBytes():Bytes {
|
||||
var start = pos - 1;
|
||||
if (data.get(start) < '0'.code || data.get(start) > '9'.code) {
|
||||
throw "Bencode: bad byte length";
|
||||
}
|
||||
while (pos < data.length && data.get(pos) != ':'.code) pos++;
|
||||
if (pos >= data.length) throw "Bencode: unterminated length";
|
||||
var len = Std.parseInt(data.getString(start, pos - start));
|
||||
pos++;
|
||||
if (len == null || len < 0 || len > MAX_DECODE_LEN
|
||||
|| pos + len > data.length) {
|
||||
throw "Bencode: invalid byte length " + len;
|
||||
}
|
||||
var bytes = Bytes.alloc(len);
|
||||
bytes.blit(0, data, pos, len);
|
||||
pos += len;
|
||||
return bytes;
|
||||
}
|
||||
|
||||
function decodeList():Array<Dynamic> {
|
||||
var arr:Array<Dynamic> = [];
|
||||
while (true) {
|
||||
if (pos >= data.length) throw "Bencode: unterminated list";
|
||||
if (data.get(pos) == 'e'.code) break;
|
||||
arr.push(value());
|
||||
}
|
||||
pos++;
|
||||
return arr;
|
||||
}
|
||||
|
||||
function decodeDict():Dynamic {
|
||||
var obj:Dynamic = {};
|
||||
while (true) {
|
||||
if (pos >= data.length) throw "Bencode: unterminated dict";
|
||||
if (data.get(pos) == 'e'.code) break;
|
||||
var key:Bytes = value();
|
||||
Reflect.setField(obj, key.toString(), value());
|
||||
}
|
||||
pos++;
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
152
leenkx/Sources/leenkx/network/torrent/Crypto.hx
Normal file
152
leenkx/Sources/leenkx/network/torrent/Crypto.hx
Normal file
@ -0,0 +1,152 @@
|
||||
package leenkx.network.torrent;
|
||||
|
||||
import haxe.io.Bytes;
|
||||
import leenkx.network.torrent.Base58.Base58Check;
|
||||
|
||||
class Crypto {
|
||||
|
||||
public static var SEEDPREFIX(default, null) = "490a";
|
||||
public static var ADDRESSPREFIX(default, null) = "55";
|
||||
|
||||
static var drbg:Aegis128x4 = null;
|
||||
static var drbgBuf:Bytes = null;
|
||||
static var drbgPos:Int = 0;
|
||||
static var drbgNative:Int = -1;
|
||||
|
||||
public static function randomBytes(length:Int):Bytes {
|
||||
var bytes = Bytes.alloc(length);
|
||||
#if (js && !kha_krom)
|
||||
var webCrypto:Dynamic = js.Syntax.code("(typeof globalThis !== 'undefined' ? globalThis.crypto : null)");
|
||||
if (webCrypto != null && webCrypto.getRandomValues != null) {
|
||||
var view = new js.lib.Uint8Array(length);
|
||||
webCrypto.getRandomValues(view);
|
||||
for (i in 0...length) bytes.set(i, view[i]);
|
||||
return bytes;
|
||||
}
|
||||
#end
|
||||
#if lnx_datachannel
|
||||
if (drbgNative == -1) {
|
||||
var seed = osEntropy(32);
|
||||
try {
|
||||
drbgNative = datachannel.RTC.aegisAvailable()
|
||||
&& datachannel.RTC.aegisInit(seed.sub(0, 16), seed.sub(16, 16)) ? 1 : 0;
|
||||
} catch (e:Dynamic) {
|
||||
drbgNative = 0;
|
||||
}
|
||||
}
|
||||
if (drbgNative == 1) {
|
||||
return datachannel.RTC.aegisRandom(length);
|
||||
}
|
||||
#end
|
||||
if (drbg == null) {
|
||||
var seed = osEntropy(32);
|
||||
drbg = new Aegis128x4(seed.sub(0, 16), seed.sub(16, 16));
|
||||
drbgBuf = Bytes.alloc(0);
|
||||
drbgPos = 0;
|
||||
}
|
||||
var off = 0;
|
||||
while (off < length) {
|
||||
if (drbgPos >= drbgBuf.length) {
|
||||
drbgBuf = drbg.nextBlock();
|
||||
drbgPos = 0;
|
||||
}
|
||||
var n = Std.int(Math.min(drbgBuf.length - drbgPos, length - off));
|
||||
bytes.blit(off, drbgBuf, drbgPos, n);
|
||||
drbgPos += n;
|
||||
off += n;
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
static function osEntropy(length:Int):Bytes {
|
||||
var bytes = Bytes.alloc(length);
|
||||
#if kha_krom
|
||||
var kromBytes = leenkx.network.torrent.transport.RtcNative.randomBytes(length);
|
||||
if (kromBytes != null) return kromBytes;
|
||||
#end
|
||||
#if sys
|
||||
if (Sys.systemName() != "Windows") {
|
||||
try {
|
||||
var f = sys.io.File.read("/dev/urandom", true);
|
||||
f.readBytes(bytes, 0, length);
|
||||
f.close();
|
||||
return bytes;
|
||||
} catch(e:Dynamic) {}
|
||||
} else {
|
||||
try {
|
||||
var p = new sys.io.Process("powershell", [
|
||||
"-NoProfile", "-Command",
|
||||
"$r = New-Object 'byte[]' " + length + "; [Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($r); -join ($r | ForEach-Object { $_.ToString('x2') })"
|
||||
]);
|
||||
var hex = StringTools.trim(p.stdout.readAll().toString());
|
||||
p.close();
|
||||
if (hex.length == length * 2) {
|
||||
return Bytes.ofHex(hex);
|
||||
}
|
||||
} catch(e:Dynamic) {
|
||||
trace("Windows RNG via PowerShell failed: " + e);
|
||||
}
|
||||
}
|
||||
#end
|
||||
throw "Crypto: OS entropy unavailable. Refusing to generate predictable keys.";
|
||||
}
|
||||
|
||||
public static function hash(data:Bytes):Bytes {
|
||||
return TweetNaCl.hash(data);
|
||||
}
|
||||
|
||||
public static function signDetached(message:Bytes, secretKey:Bytes):Bytes {
|
||||
return TweetNaCl.signDetached(message, secretKey);
|
||||
}
|
||||
|
||||
public static function signDetachedVerify(message:Bytes, signature:Bytes, publicKey:Bytes):Bool {
|
||||
return TweetNaCl.signDetachedVerify(message, signature, publicKey);
|
||||
}
|
||||
|
||||
public static function box(message:Bytes, nonce:Bytes, theirPublicKey:Bytes, mySecretKey:Bytes):Bytes {
|
||||
return TweetNaCl.box(message, nonce, theirPublicKey, mySecretKey);
|
||||
}
|
||||
|
||||
public static function boxOpen(message:Bytes, nonce:Bytes, theirPublicKey:Bytes, mySecretKey:Bytes):Bytes {
|
||||
return TweetNaCl.boxOpen(message, nonce, theirPublicKey, mySecretKey);
|
||||
}
|
||||
|
||||
public static function boxKeyPair():{publicKey:Bytes, secretKey:Bytes} {
|
||||
return TweetNaCl.boxKeyPair();
|
||||
}
|
||||
|
||||
public static function signKeyPairFromSeed(seed:Bytes):{publicKey:Bytes, secretKey:Bytes} {
|
||||
return TweetNaCl.signKeyPairFromSeed(seed);
|
||||
}
|
||||
|
||||
public static var boxNonceLength(get, null):Int;
|
||||
static function get_boxNonceLength():Int {
|
||||
return TweetNaCl.crypto_box_NONCEBYTES;
|
||||
}
|
||||
|
||||
public static function ripemd160(data:Bytes):Bytes {
|
||||
return Ripemd160.make(data);
|
||||
}
|
||||
|
||||
public static function encodeSeed(material:Bytes):String {
|
||||
var prefix = Bytes.ofHex(SEEDPREFIX);
|
||||
var payload = Bytes.alloc(prefix.length + material.length);
|
||||
payload.blit(0, prefix, 0, prefix.length);
|
||||
payload.blit(prefix.length, material, 0, material.length);
|
||||
return Base58Check.encode(payload);
|
||||
}
|
||||
|
||||
public static function encodeAddress(publicKey:Bytes):String {
|
||||
var prefix = Bytes.ofHex(ADDRESSPREFIX);
|
||||
var hash = hash(publicKey);
|
||||
var ripemd = ripemd160(hash);
|
||||
var payload = Bytes.alloc(prefix.length + ripemd.length);
|
||||
payload.blit(0, prefix, 0, prefix.length);
|
||||
payload.blit(prefix.length, ripemd, 0, ripemd.length);
|
||||
return Base58Check.encode(payload);
|
||||
}
|
||||
|
||||
public static function toHex(bytes:Bytes):String {
|
||||
return bytes.toHex();
|
||||
}
|
||||
}
|
||||
21
leenkx/Sources/leenkx/network/torrent/LICENSE.md
Normal file
21
leenkx/Sources/leenkx/network/torrent/LICENSE.md
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Riccardo Mazzucco - All Rights reserved
|
||||
|
||||
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.
|
||||
580
leenkx/Sources/leenkx/network/torrent/LeenkxClient.hx
Normal file
580
leenkx/Sources/leenkx/network/torrent/LeenkxClient.hx
Normal file
@ -0,0 +1,580 @@
|
||||
package leenkx.network.torrent;
|
||||
|
||||
import haxe.io.Bytes;
|
||||
import leenkx.network.torrent.Types;
|
||||
import leenkx.network.torrent.Base58.Base58Check;
|
||||
import leenkx.network.torrent.transport.ITransport;
|
||||
import leenkx.network.torrent.transport.ITransport.TransportOpts;
|
||||
import leenkx.network.torrent.TorrentInfo;
|
||||
|
||||
class LeenkxClient {
|
||||
|
||||
public static var PEERTIMEOUT:Float = 5 * 60 * 1000;
|
||||
public static var defaultHeartbeat:Int = 30000;
|
||||
|
||||
public var identifier:String;
|
||||
public var seed:String;
|
||||
public var pk:String;
|
||||
public var ek:String;
|
||||
public var timeout:Float;
|
||||
public var announce:Array<String>;
|
||||
public var infoHashBinary:String;
|
||||
public var peerIdBinary:String;
|
||||
|
||||
public var keyPair:{publicKey:Bytes, secretKey:Bytes};
|
||||
public var keyPairEncrypt:{publicKey:Bytes, secretKey:Bytes};
|
||||
|
||||
public var peers:Map<String, PeerInfo> = [];
|
||||
public var seen:Map<String, Float> = [];
|
||||
public var pkToAddress:Map<String, String> = [];
|
||||
public var isRelay:Bool = false;
|
||||
public var lastPeerCount:Null<Int> = null;
|
||||
public var lastPeerId:String = null;
|
||||
public var lastPeerIndex:Int = 0;
|
||||
|
||||
public var serverAddress:String = null;
|
||||
public var heartbeatTimer:Null<haxe.Timer> = null;
|
||||
|
||||
public var onOpen:Void -> Void;
|
||||
public var onTorrent:String -> Dynamic -> Void;
|
||||
public var onMessage:String -> Dynamic -> Dynamic -> Void;
|
||||
public var onSeen:String -> Void;
|
||||
public var onLeft:String -> Void;
|
||||
public var onServer:String -> Void;
|
||||
public var onPing:String -> Void;
|
||||
public var onTimeout:String -> Void;
|
||||
public var onConnections:Int -> Void;
|
||||
public var onWireSeen:Int -> Dynamic -> Void;
|
||||
public var onWireLeft:Int -> Dynamic -> Void;
|
||||
public var onTracker:String -> Dynamic -> Void;
|
||||
public var onAnnounce:String -> Void;
|
||||
public var onError:String -> Void;
|
||||
|
||||
public var transport:ITransport;
|
||||
private var deferredOpts:TransportOpts = null;
|
||||
private var heartbeatInterval:Int = 30000;
|
||||
|
||||
public var torrents(get, never):Map<String, Torrent>;
|
||||
public var onTorrentAdded:Torrent -> Void;
|
||||
public var onTorrentDone:Torrent -> Void;
|
||||
var swarm:TorrentClient = null;
|
||||
|
||||
function get_torrents():Map<String, Torrent> {
|
||||
return swarm != null ? swarm.torrents : [];
|
||||
}
|
||||
|
||||
function getSwarm():TorrentClient {
|
||||
if (swarm == null) {
|
||||
swarm = new TorrentClient();
|
||||
swarm.onError = function(e) {
|
||||
if (onError != null) onError(e);
|
||||
};
|
||||
swarm.onTorrentAdded = function(t) {
|
||||
if (onTorrentAdded != null) onTorrentAdded(t);
|
||||
};
|
||||
swarm.onTorrentDone = function(t) {
|
||||
if (onTorrentDone != null) onTorrentDone(t);
|
||||
};
|
||||
}
|
||||
return swarm;
|
||||
}
|
||||
|
||||
public function add(torrentId:Dynamic, ?opts:Dynamic, ?cb:Torrent -> Void):Torrent {
|
||||
return getSwarm().add(torrentId, opts, cb);
|
||||
}
|
||||
|
||||
public function seedMeta(meta:MetaInfo,
|
||||
files:Array<{path:String, data:Bytes}>,
|
||||
?opts:Dynamic, ?cb:Torrent -> Void):Torrent {
|
||||
return getSwarm().seedMeta(meta, files, opts, cb);
|
||||
}
|
||||
|
||||
public function get(id:Dynamic):Torrent {
|
||||
if (Std.isOfType(id, Torrent)) return id;
|
||||
return swarm != null ? swarm.get(id) : null;
|
||||
}
|
||||
|
||||
public function remove(infoHashHex:String, ?deleteData:Bool):Void {
|
||||
if (swarm != null) swarm.remove(infoHashHex, deleteData);
|
||||
}
|
||||
|
||||
public function listen(port:Int):Void {
|
||||
getSwarm().listen(port);
|
||||
}
|
||||
|
||||
public function new(?identifier:String, ?opts:LeenkxOptions) {
|
||||
if (identifier != null && !Std.isOfType(identifier, String)) {
|
||||
opts = cast identifier;
|
||||
identifier = null;
|
||||
}
|
||||
opts = opts != null ? opts : {};
|
||||
|
||||
this.announce = opts.announce != null ? opts.announce : TorrentClient.defaultTrackers;
|
||||
this.timeout = opts.timeout != null ? opts.timeout : PEERTIMEOUT;
|
||||
|
||||
if (opts.seed != null) {
|
||||
this.seed = opts.seed;
|
||||
} else {
|
||||
this.seed = Crypto.encodeSeed(Crypto.randomBytes(32));
|
||||
}
|
||||
|
||||
if (opts.keyPair != null) {
|
||||
this.keyPair = opts.keyPair;
|
||||
} else {
|
||||
var seedBytes = Base58Check.decode(this.seed);
|
||||
var material = seedBytes.sub(2, seedBytes.length - 2);
|
||||
this.keyPair = Crypto.signKeyPairFromSeed(material);
|
||||
}
|
||||
|
||||
this.keyPairEncrypt = Crypto.boxKeyPair();
|
||||
|
||||
this.pk = Base58.encode(this.keyPair.publicKey);
|
||||
this.ek = Base58.encode(this.keyPairEncrypt.publicKey);
|
||||
|
||||
this.identifier = identifier != null ? identifier : this.address();
|
||||
|
||||
var infoHash = TorrentInfo.identifierHash(this.identifier);
|
||||
this.infoHashBinary = TorrentInfo.toBinaryString(infoHash);
|
||||
this.peerIdBinary = TorrentInfo.toBinaryString(TorrentInfo.peerId());
|
||||
|
||||
createTransport(opts, opts.deferJoin == true);
|
||||
|
||||
this.heartbeatInterval = opts.heartbeat != null ? opts.heartbeat : defaultHeartbeat;
|
||||
}
|
||||
|
||||
function createTransport(opts:LeenkxOptions, defer:Bool):Void {
|
||||
var transportOpts:TransportOpts = {
|
||||
announce: this.announce,
|
||||
iceServers: opts.iceServers,
|
||||
tracker: opts.tracker,
|
||||
wtOpts: opts.wtOpts,
|
||||
torrentOpts: opts.torrentOpts, torrent: opts.torrent,
|
||||
pk: this.pk,
|
||||
ek: this.ek,
|
||||
infoHashBinary: this.infoHashBinary,
|
||||
peerIdBinary: this.peerIdBinary
|
||||
};
|
||||
|
||||
var announceUrl = this.announce[0];
|
||||
if (opts.transport == "datachannel") {
|
||||
transport = new leenkx.network.torrent.transport.DatachannelTransport(announceUrl, opts.iceServers);
|
||||
} else {
|
||||
transport = new leenkx.network.torrent.transport.WireTransport(announceUrl, opts.iceServers);
|
||||
}
|
||||
|
||||
transport.onReady = onTransportReady;
|
||||
transport.onPeerConnected = onTransportPeerConnected;
|
||||
transport.onPeerDisconnected = onTransportPeerDisconnected;
|
||||
transport.onPeerData = onTransportPeerData;
|
||||
transport.onPeerHandshake = onTransportPeerHandshake;
|
||||
transport.onTrackerUpdate = onTransportTrackerUpdate;
|
||||
transport.onTrackerAnnounce = onTransportTrackerAnnounce;
|
||||
transport.onError = onTransportError;
|
||||
|
||||
if (defer) {
|
||||
deferredOpts = transportOpts;
|
||||
} else {
|
||||
transport.join(this.identifier, transportOpts);
|
||||
}
|
||||
}
|
||||
|
||||
public function connect():Void {
|
||||
if (deferredOpts != null) {
|
||||
var opts = deferredOpts;
|
||||
deferredOpts = null;
|
||||
transport.join(this.identifier, opts);
|
||||
}
|
||||
this.heartbeat(this.heartbeatInterval);
|
||||
}
|
||||
|
||||
function onTransportReady():Void {
|
||||
if (onTorrent != null) onTorrent(identifier, null);
|
||||
}
|
||||
|
||||
function onTransportPeerConnected(peerId:String, count:Int):Void {
|
||||
lastPeerId = peerId;
|
||||
var prevCount = lastPeerCount;
|
||||
lastPeerCount = count;
|
||||
if (onWireSeen != null) onWireSeen(count, {peerId: peerId});
|
||||
if (count != prevCount) {
|
||||
if (onConnections != null) onConnections(count);
|
||||
}
|
||||
}
|
||||
|
||||
function onTransportPeerDisconnected(count:Int):Void {
|
||||
if (onWireLeft != null) onWireLeft(count, {peerId: lastPeerId});
|
||||
if (count != lastPeerCount) {
|
||||
lastPeerCount = count;
|
||||
if (onConnections != null) onConnections(count);
|
||||
}
|
||||
}
|
||||
|
||||
function onTransportPeerData(senderPk:String, data:Bytes):Void {
|
||||
onMessageData(identifier, data, senderPk);
|
||||
}
|
||||
|
||||
function onTransportPeerHandshake(peerPk:String, peerEk:String):Void {
|
||||
if (peerPk != null && peerPk != "") {
|
||||
sawPeer(peerPk, peerEk, identifier);
|
||||
}
|
||||
}
|
||||
|
||||
function onTransportTrackerUpdate(update:Dynamic):Void {
|
||||
if (onTracker != null) onTracker(identifier, update);
|
||||
}
|
||||
|
||||
function onTransportTrackerAnnounce():Void {
|
||||
if (onAnnounce != null) onAnnounce(identifier);
|
||||
}
|
||||
|
||||
function onTransportError(msg:String):Void {
|
||||
trace("onTransportError: " + msg);
|
||||
if (onError != null) onError(msg);
|
||||
}
|
||||
|
||||
public function address(?pk:Dynamic):String {
|
||||
var pkBytes:Bytes = null;
|
||||
if (pk != null && Std.isOfType(pk, String)) {
|
||||
pkBytes = Base58.decode(pk);
|
||||
} else if (pk != null) {
|
||||
pkBytes = pk;
|
||||
} else {
|
||||
pkBytes = this.keyPair.publicKey;
|
||||
}
|
||||
return Crypto.encodeAddress(pkBytes);
|
||||
}
|
||||
|
||||
function addressFromPk(pkStr:String):String {
|
||||
var cached = pkToAddress.get(pkStr);
|
||||
if (cached != null) return cached;
|
||||
var addr = address(Base58.decode(pkStr));
|
||||
pkToAddress.set(pkStr, addr);
|
||||
return addr;
|
||||
}
|
||||
|
||||
public function encodeSeed(material:Bytes):String {
|
||||
return Crypto.encodeSeed(material);
|
||||
}
|
||||
|
||||
public function ping():Void {
|
||||
var packet = makePacket({y: PacketType.Ping});
|
||||
sendRaw(packet);
|
||||
}
|
||||
|
||||
public function send(?address:String, message:Dynamic):Void {
|
||||
if (message == null) {
|
||||
message = address;
|
||||
address = null;
|
||||
}
|
||||
var packet = makePacket({
|
||||
y: PacketType.Message,
|
||||
v: haxe.Json.stringify(message)
|
||||
});
|
||||
var peerPk:String = null;
|
||||
if (address != null) {
|
||||
if (peers.exists(address)) {
|
||||
peerPk = peers[address].pk;
|
||||
packet = encryptPacket(peerPk, packet);
|
||||
} else {
|
||||
throw address + " not seen - no public key.";
|
||||
}
|
||||
}
|
||||
sendRaw(packet, peerPk);
|
||||
}
|
||||
|
||||
public function sendRawMessage(data:Bytes, ?address:String):Void {
|
||||
var marked = Bytes.alloc(data.length + 1);
|
||||
marked.set(0, 0x00);
|
||||
marked.blit(1, data, 0, data.length);
|
||||
if (transport != null) {
|
||||
if (address != null) {
|
||||
if (!peers.exists(address)) {
|
||||
throw address + " not seen - no public key.";
|
||||
}
|
||||
if (!transport.sendTo(peers[address].pk, marked)) {
|
||||
trace("sendRawMessage: no open channel to " + address);
|
||||
}
|
||||
} else {
|
||||
transport.broadcast(marked);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function heartbeat(?interval:Int):Void {
|
||||
interval = interval != null ? interval : 30000;
|
||||
if (heartbeatTimer != null) heartbeatTimer.stop();
|
||||
heartbeatTimer = new haxe.Timer(interval);
|
||||
heartbeatTimer.run = () -> {
|
||||
ping();
|
||||
var t = now();
|
||||
for (p in [for (k in peers.keys()) k]) {
|
||||
var peerInfo = peers[p];
|
||||
if (peerInfo.last + interval < t) {
|
||||
peers.remove(p);
|
||||
if (onTimeout != null) onTimeout(p);
|
||||
if (onLeft != null) onLeft(p);
|
||||
}
|
||||
}
|
||||
for (h in [for (k in seen.keys()) k]) {
|
||||
if (seen.get(h) + interval < t) {
|
||||
seen.remove(h);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public function destroy(?cb:Void -> Void):Void {
|
||||
if (heartbeatTimer != null) {
|
||||
heartbeatTimer.stop();
|
||||
heartbeatTimer = null;
|
||||
}
|
||||
var packet = makePacket({y: PacketType.Leave});
|
||||
sendRaw(packet);
|
||||
if (swarm != null) {
|
||||
swarm.destroy();
|
||||
swarm = null;
|
||||
}
|
||||
if (transport != null) {
|
||||
transport.destroy(cb);
|
||||
} else if (cb != null) {
|
||||
cb();
|
||||
}
|
||||
}
|
||||
|
||||
public function close(?cb:Void -> Void):Void {
|
||||
destroy(cb);
|
||||
}
|
||||
|
||||
public function connections():Int {
|
||||
if (transport != null) {
|
||||
var count = transport.getPeerCount();
|
||||
if (count != lastPeerCount) {
|
||||
lastPeerCount = count;
|
||||
if (onConnections != null) onConnections(count);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function makePacket(params:Dynamic):Bytes {
|
||||
var p:Dynamic = {
|
||||
t: now(),
|
||||
i: identifier,
|
||||
pk: pk,
|
||||
ek: ek,
|
||||
n: Crypto.randomBytes(8)
|
||||
};
|
||||
for (k in Reflect.fields(params)) {
|
||||
Reflect.setField(p, k, Reflect.field(params, k));
|
||||
}
|
||||
var pe = Bencode.encode(p);
|
||||
var sig = Crypto.signDetached(pe, keyPair.secretKey);
|
||||
return Bencode.encode({s: sig, p: pe});
|
||||
}
|
||||
|
||||
function encryptPacket(peerPk:String, packet:Bytes):Bytes {
|
||||
var addr = addressFromPk(peerPk);
|
||||
if (peers.exists(addr)) {
|
||||
var nonce = Crypto.randomBytes(Crypto.boxNonceLength);
|
||||
var encrypted = Crypto.box(
|
||||
packet,
|
||||
nonce,
|
||||
Base58.decode(peers[addr].ek),
|
||||
keyPairEncrypt.secretKey
|
||||
);
|
||||
return Bencode.encode({
|
||||
n: nonce,
|
||||
ek: Base58.encode(keyPairEncrypt.publicKey),
|
||||
e: encrypted
|
||||
});
|
||||
} else {
|
||||
throw addressFromPk(peerPk) + " not seen - no encryption key.";
|
||||
}
|
||||
}
|
||||
|
||||
function sendRaw(message:Bytes, ?peerPk:String):Void {
|
||||
var hash = Crypto.hash(message).sub(0, 16).toHex();
|
||||
seen.set(hash, now());
|
||||
if (transport != null) {
|
||||
if (peerPk != null) {
|
||||
if (!transport.sendTo(peerPk, message)) {
|
||||
transport.broadcast(message);
|
||||
}
|
||||
} else {
|
||||
transport.broadcast(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onMessageData(identifier:String, message:Bytes, ?senderPk:String):Void {
|
||||
var hash = (message.length > 0 && message.get(0) == 0x00)
|
||||
? null : Crypto.hash(message).sub(0, 16).toHex();
|
||||
var t = now();
|
||||
if (hash == null || !seen.exists(hash)) {
|
||||
if (message.length > 0 && (message.get(0) == 0x00 || message.get(0) == 0x01)) {
|
||||
var isRelayed = message.get(0) == 0x01;
|
||||
var offset = 1;
|
||||
var msgSenderPk:String = senderPk;
|
||||
if (isRelayed) {
|
||||
if (message.length < 2) {
|
||||
seen.set(hash, t);
|
||||
return;
|
||||
}
|
||||
var pkLen = message.get(1);
|
||||
if (2 + pkLen > message.length) {
|
||||
seen.set(hash, t);
|
||||
return;
|
||||
}
|
||||
var claimedPk = message.sub(2, pkLen).toString();
|
||||
if (pkToAddress.exists(claimedPk)) {
|
||||
msgSenderPk = claimedPk;
|
||||
}
|
||||
offset = 2 + pkLen;
|
||||
}
|
||||
var rawPayload = message.sub(offset, message.length - offset);
|
||||
var payload = new leenkx.network.Buffer();
|
||||
payload.writeBytes(rawPayload);
|
||||
if (isRelayed) seen.set(hash, t);
|
||||
var senderAddr = msgSenderPk != null ? pkToAddress.get(msgSenderPk) : null;
|
||||
if (senderAddr == null && msgSenderPk != null && msgSenderPk != "") {
|
||||
senderAddr = addressFromPk(msgSenderPk);
|
||||
}
|
||||
if (onMessage != null) {
|
||||
onMessage(senderAddr, payload, null);
|
||||
}
|
||||
if (isRelay && transport != null && transport.getPeerCount() > 1) {
|
||||
if (isRelayed) {
|
||||
sendRaw(message);
|
||||
} else if (msgSenderPk != null && msgSenderPk != "") {
|
||||
var pkBytes = Bytes.ofString(msgSenderPk);
|
||||
var relayed = Bytes.alloc(1 + 1 + pkBytes.length + rawPayload.length);
|
||||
relayed.set(0, 0x01);
|
||||
relayed.set(1, pkBytes.length);
|
||||
relayed.blit(2, pkBytes, 0, pkBytes.length);
|
||||
relayed.blit(2 + pkBytes.length, rawPayload, 0, rawPayload.length);
|
||||
sendRaw(relayed);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var unpacked:Dynamic = null;
|
||||
try {
|
||||
unpacked = Bencode.decode(message);
|
||||
} catch(e:Dynamic) {
|
||||
trace("Bencode decode error: " + e);
|
||||
seen.set(hash, t);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
var isEncrypted = Reflect.field(unpacked, "e") != null
|
||||
&& Reflect.field(unpacked, "n") != null
|
||||
&& Reflect.field(unpacked, "ek") != null;
|
||||
|
||||
if (isEncrypted) {
|
||||
var ekStr:String = unpacked.ek.toString();
|
||||
var decrypted = Crypto.boxOpen(
|
||||
unpacked.e,
|
||||
unpacked.n,
|
||||
Base58.decode(ekStr),
|
||||
keyPairEncrypt.secretKey
|
||||
);
|
||||
if (decrypted != null) {
|
||||
unpacked = Bencode.decode(decrypted);
|
||||
} else {
|
||||
unpacked = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (unpacked != null && unpacked.p != null) {
|
||||
var packet:Dynamic = Bencode.decode(unpacked.p);
|
||||
var pkStr:String = packet.pk.toString();
|
||||
var idStr:String = packet.i.toString();
|
||||
var checkSig = Crypto.signDetachedVerify(unpacked.p, unpacked.s, Base58.decode(pkStr));
|
||||
var checkId = idStr == identifier;
|
||||
var checkTime = packet.t + timeout > t;
|
||||
|
||||
if (checkSig && checkId && checkTime) {
|
||||
var ekStr:String = packet.ek.toString();
|
||||
sawPeer(pkStr, ekStr, identifier);
|
||||
|
||||
var yStr:String = packet.y != null ? packet.y.toString() : null;
|
||||
if (yStr == PacketType.Message) {
|
||||
var msgStr:String = packet.v.toString();
|
||||
var msgJson:Dynamic = null;
|
||||
try {
|
||||
msgJson = haxe.Json.parse(msgStr);
|
||||
} catch(e) {
|
||||
}
|
||||
if (msgJson != null) {
|
||||
if (onMessage != null) {
|
||||
onMessage(addressFromPk(pkStr), msgJson, packet);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
var payload = new leenkx.network.Buffer();
|
||||
payload.writeBytes(
|
||||
haxe.crypto.Base64.decode(msgStr));
|
||||
if (onMessage != null) {
|
||||
onMessage(addressFromPk(pkStr), payload, packet);
|
||||
}
|
||||
} catch(e:Dynamic) {}
|
||||
}
|
||||
} else if (yStr == PacketType.Ping) {
|
||||
if (onPing != null) onPing(addressFromPk(pkStr));
|
||||
} else if (yStr == PacketType.Leave) {
|
||||
var addr = addressFromPk(pkStr);
|
||||
peers.remove(addr);
|
||||
if (onLeft != null) onLeft(addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (transport != null && transport.getPeerCount() > 1) {
|
||||
sendRaw(message);
|
||||
}
|
||||
} catch(e:Dynamic) {
|
||||
trace("onMessageData error processing packet: " + e);
|
||||
}
|
||||
}
|
||||
seen.set(hash, t);
|
||||
}
|
||||
|
||||
function sawPeer(pk:String, ek:String, identifier:String):Void {
|
||||
var t = now();
|
||||
var addr = addressFromPk(pk);
|
||||
if (addr == address()) return;
|
||||
if (!peers.exists(addr) || peers[addr].last + timeout < t) {
|
||||
var index = 0;
|
||||
var taken = true;
|
||||
while (taken) {
|
||||
taken = false;
|
||||
index++;
|
||||
for (k in peers.keys()) {
|
||||
if (peers[k].index == index) { taken = true; break; }
|
||||
}
|
||||
}
|
||||
peers.set(addr, {pk: pk, ek: ek, last: t, index: index});
|
||||
lastPeerIndex = index;
|
||||
lastPeerId = addr;
|
||||
if (onOpen != null) onOpen();
|
||||
if (onSeen != null) onSeen(addr);
|
||||
if (addr == identifier) {
|
||||
serverAddress = addr;
|
||||
if (onServer != null) onServer(addr);
|
||||
}
|
||||
var packet = makePacket({y: PacketType.Ping});
|
||||
sendRaw(packet);
|
||||
} else {
|
||||
var p = peers.get(addr);
|
||||
p.ek = ek;
|
||||
p.last = t;
|
||||
}
|
||||
}
|
||||
|
||||
static function now():Float {
|
||||
return Date.now().getTime();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user