Merge months of changes into 'newrotation' branch
(and homogeneised the contents of said branch in the process) (plus a couple bugfixes, because what else)
This commit is contained in:
commit
f892fdfd8a
|
@ -21,7 +21,9 @@
|
||||||
uniform sampler2D gbufferD;
|
uniform sampler2D gbufferD;
|
||||||
uniform sampler2D gbuffer0;
|
uniform sampler2D gbuffer0;
|
||||||
uniform sampler2D gbuffer1;
|
uniform sampler2D gbuffer1;
|
||||||
|
#ifdef _gbuffer2
|
||||||
uniform sampler2D gbuffer2;
|
uniform sampler2D gbuffer2;
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef _VoxelAOvar
|
#ifdef _VoxelAOvar
|
||||||
uniform sampler3D voxels;
|
uniform sampler3D voxels;
|
||||||
|
@ -94,7 +96,7 @@ uniform vec3 eye;
|
||||||
uniform vec3 eyeLook;
|
uniform vec3 eyeLook;
|
||||||
|
|
||||||
#ifdef _Clusters
|
#ifdef _Clusters
|
||||||
uniform vec4 lightsArray[maxLights * 2];
|
uniform vec4 lightsArray[maxLights * 3];
|
||||||
#ifdef _Spot
|
#ifdef _Spot
|
||||||
uniform vec4 lightsArraySpot[maxLights];
|
uniform vec4 lightsArraySpot[maxLights];
|
||||||
#endif
|
#endif
|
||||||
|
@ -206,7 +208,9 @@ void main() {
|
||||||
vec3 v = normalize(eye - p);
|
vec3 v = normalize(eye - p);
|
||||||
float dotNV = max(dot(n, v), 0.0);
|
float dotNV = max(dot(n, v), 0.0);
|
||||||
|
|
||||||
|
#ifdef _gbuffer2
|
||||||
vec4 g2 = textureLod(gbuffer2, texCoord, 0.0);
|
vec4 g2 = textureLod(gbuffer2, texCoord, 0.0);
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef _MicroShadowing
|
#ifdef _MicroShadowing
|
||||||
occspec.x = mix(1.0, occspec.x, dotNV); // AO Fresnel
|
occspec.x = mix(1.0, occspec.x, dotNV); // AO Fresnel
|
||||||
|
@ -221,11 +225,13 @@ void main() {
|
||||||
|
|
||||||
vec3 envl = shIrradiance(n, shirr);
|
vec3 envl = shIrradiance(n, shirr);
|
||||||
|
|
||||||
|
#ifdef _gbuffer2
|
||||||
if (g2.b < 0.5) {
|
if (g2.b < 0.5) {
|
||||||
envl = envl;
|
envl = envl;
|
||||||
} else {
|
} else {
|
||||||
envl = vec3(1.0);
|
envl = vec3(1.0);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef _EnvTex
|
#ifdef _EnvTex
|
||||||
envl /= PI;
|
envl /= PI;
|
||||||
|
@ -456,18 +462,19 @@ void main() {
|
||||||
n,
|
n,
|
||||||
v,
|
v,
|
||||||
dotNV,
|
dotNV,
|
||||||
lightsArray[li * 2].xyz, // lp
|
lightsArray[li * 3].xyz, // lp
|
||||||
lightsArray[li * 2 + 1].xyz, // lightCol
|
lightsArray[li * 3 + 1].xyz, // lightCol
|
||||||
albedo,
|
albedo,
|
||||||
roughness,
|
roughness,
|
||||||
occspec.y,
|
occspec.y,
|
||||||
f0
|
f0
|
||||||
#ifdef _ShadowMap
|
#ifdef _ShadowMap
|
||||||
, li, lightsArray[li * 2].w, true // bias
|
// light index, shadow bias, cast_shadows
|
||||||
|
, li, lightsArray[li * 3 + 2].x, lightsArray[li * 3 + 2].z != 0.0
|
||||||
#endif
|
#endif
|
||||||
#ifdef _Spot
|
#ifdef _Spot
|
||||||
, lightsArray[li * 2 + 1].w != 0.0
|
, lightsArray[li * 3 + 2].y != 0.0
|
||||||
, lightsArray[li * 2 + 1].w // cutoff
|
, lightsArray[li * 3 + 2].y // cutoff
|
||||||
, lightsArraySpot[li].w // cutoff - exponent
|
, lightsArraySpot[li].w // cutoff - exponent
|
||||||
, lightsArraySpot[li].xyz // spotDir
|
, lightsArraySpot[li].xyz // spotDir
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -99,7 +99,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "LWVP",
|
"name": "LWVP",
|
||||||
"link": "_biasLightWorldViewProjectionMatrix",
|
"link": "_biasLightWorldViewProjectionMatrixSun",
|
||||||
"ifndef": ["_CSM"],
|
"ifndef": ["_CSM"],
|
||||||
"ifdef": ["_Sun", "_ShadowMap"]
|
"ifdef": ["_Sun", "_ShadowMap"]
|
||||||
},
|
},
|
||||||
|
|
|
@ -29,12 +29,15 @@ uniform int envmapNumMipmaps;
|
||||||
uniform vec3 backgroundCol;
|
uniform vec3 backgroundCol;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef _SMSizeUniform
|
||||||
|
//!uniform vec2 smSizeUniform;
|
||||||
|
#endif
|
||||||
uniform vec2 cameraProj;
|
uniform vec2 cameraProj;
|
||||||
uniform vec3 eye;
|
uniform vec3 eye;
|
||||||
uniform vec3 eyeLook;
|
uniform vec3 eyeLook;
|
||||||
|
|
||||||
#ifdef _Clusters
|
#ifdef _Clusters
|
||||||
uniform vec4 lightsArray[maxLights * 2];
|
uniform vec4 lightsArray[maxLights * 3];
|
||||||
#ifdef _Spot
|
#ifdef _Spot
|
||||||
uniform vec4 lightsArraySpot[maxLights];
|
uniform vec4 lightsArraySpot[maxLights];
|
||||||
#endif
|
#endif
|
||||||
|
@ -255,18 +258,19 @@ void main() {
|
||||||
n,
|
n,
|
||||||
v,
|
v,
|
||||||
dotNV,
|
dotNV,
|
||||||
lightsArray[li * 2].xyz, // lp
|
lightsArray[li * 3].xyz, // lp
|
||||||
lightsArray[li * 2 + 1].xyz, // lightCol
|
lightsArray[li * 3 + 1].xyz, // lightCol
|
||||||
albedo,
|
albedo,
|
||||||
roughness,
|
roughness,
|
||||||
occspec.y,
|
occspec.y,
|
||||||
f0
|
f0
|
||||||
#ifdef _ShadowMap
|
#ifdef _ShadowMap
|
||||||
, li, lightsArray[li * 2].w, true // bias
|
// light index, shadow bias, cast_shadows
|
||||||
|
, li, lightsArray[li * 3 + 2].x, lightsArray[li * 3 + 2].z != 0.0
|
||||||
#endif
|
#endif
|
||||||
#ifdef _Spot
|
#ifdef _Spot
|
||||||
, lightsArray[li * 2 + 1].w != 0.0
|
, lightsArray[li * 3 + 2].y != 0.0
|
||||||
, lightsArray[li * 2 + 1].w // cutoff
|
, lightsArray[li * 3 + 2].y // cutoff
|
||||||
, lightsArraySpot[li].w // cutoff - exponent
|
, lightsArraySpot[li].w // cutoff - exponent
|
||||||
, lightsArraySpot[li].xyz // spotDir
|
, lightsArraySpot[li].xyz // spotDir
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -88,7 +88,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "LWVP",
|
"name": "LWVP",
|
||||||
"link": "_biasLightWorldViewProjectionMatrix",
|
"link": "_biasLightWorldViewProjectionMatrixSun",
|
||||||
"ifndef": ["_CSM"],
|
"ifndef": ["_CSM"],
|
||||||
"ifdef": ["_Sun", "_ShadowMap"]
|
"ifdef": ["_Sun", "_ShadowMap"]
|
||||||
},
|
},
|
||||||
|
@ -107,6 +107,11 @@
|
||||||
"link": "_viewProjectionMatrix",
|
"link": "_viewProjectionMatrix",
|
||||||
"ifdef": ["_SSRS"]
|
"ifdef": ["_SSRS"]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "smSizeUniform",
|
||||||
|
"link": "_shadowMapSize",
|
||||||
|
"ifdef": ["_SMSizeUniform"]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "lightProj",
|
"name": "lightProj",
|
||||||
"link": "_lightPlaneProj",
|
"link": "_lightPlaneProj",
|
||||||
|
|
|
@ -8,6 +8,10 @@ int getClusterI(vec2 tc, float viewz, vec2 cameraPlane) {
|
||||||
float z = log(viewz - cnear + 1.0) / log(cameraPlane.y - cnear + 1.0);
|
float z = log(viewz - cnear + 1.0) / log(cameraPlane.y - cnear + 1.0);
|
||||||
sliceZ = int(z * (clusterSlices.z - 1)) + 1;
|
sliceZ = int(z * (clusterSlices.z - 1)) + 1;
|
||||||
}
|
}
|
||||||
|
// address gap between near plane and cluster near offset
|
||||||
|
else if (viewz >= cameraPlane.x) {
|
||||||
|
sliceZ = 1;
|
||||||
|
}
|
||||||
return int(tc.x * clusterSlices.x) +
|
return int(tc.x * clusterSlices.x) +
|
||||||
int(int(tc.y * clusterSlices.y) * clusterSlices.x) +
|
int(int(tc.y * clusterSlices.y) * clusterSlices.x) +
|
||||||
int(sliceZ * clusterSlices.x * clusterSlices.y);
|
int(sliceZ * clusterSlices.x * clusterSlices.y);
|
||||||
|
|
|
@ -185,17 +185,13 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co
|
||||||
#ifdef _Clusters
|
#ifdef _Clusters
|
||||||
vec4 lPos = LWVPSpotArray[index] * vec4(p + n * bias * 10, 1.0);
|
vec4 lPos = LWVPSpotArray[index] * vec4(p + n * bias * 10, 1.0);
|
||||||
#ifdef _ShadowMapAtlas
|
#ifdef _ShadowMapAtlas
|
||||||
vec3 uv = lPos.xyz / lPos.w;
|
|
||||||
#ifdef _InvY
|
|
||||||
uv.y = 1.0 - uv.y; // invert Y coordinates for direct3d coordinate system
|
|
||||||
#endif
|
|
||||||
direct *= shadowTest(
|
direct *= shadowTest(
|
||||||
#ifndef _SingleAtlas
|
#ifndef _SingleAtlas
|
||||||
shadowMapAtlasSpot
|
shadowMapAtlasSpot
|
||||||
#else
|
#else
|
||||||
shadowMapAtlas
|
shadowMapAtlas
|
||||||
#endif
|
#endif
|
||||||
, uv, bias
|
, lPos.xyz / lPos.w, bias
|
||||||
);
|
);
|
||||||
#else
|
#else
|
||||||
if (index == 0) direct *= shadowTest(shadowMapSpot[0], lPos.xyz / lPos.w, bias);
|
if (index == 0) direct *= shadowTest(shadowMapSpot[0], lPos.xyz / lPos.w, bias);
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef _ShadowMap
|
#ifdef _ShadowMap
|
||||||
#ifdef _SinglePoint
|
#ifdef _SinglePoint
|
||||||
#ifdef _Spot
|
#ifdef _Spot
|
||||||
uniform sampler2DShadow shadowMapSpot[1];
|
uniform sampler2DShadow shadowMapSpot[1];
|
||||||
uniform mat4 LWVPSpot[1];
|
uniform mat4 LWVPSpot[1];
|
||||||
|
@ -16,8 +16,8 @@
|
||||||
uniform samplerCubeShadow shadowMapPoint[1];
|
uniform samplerCubeShadow shadowMapPoint[1];
|
||||||
uniform vec2 lightProj;
|
uniform vec2 lightProj;
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
#ifdef _Clusters
|
#ifdef _Clusters
|
||||||
#ifdef _SingleAtlas
|
#ifdef _SingleAtlas
|
||||||
//!uniform sampler2DShadow shadowMapAtlas;
|
//!uniform sampler2DShadow shadowMapAtlas;
|
||||||
#endif
|
#endif
|
||||||
|
@ -39,7 +39,7 @@
|
||||||
#endif
|
#endif
|
||||||
uniform mat4 LWVPSpotArray[maxLightsCluster];
|
uniform mat4 LWVPSpotArray[maxLightsCluster];
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, const vec3 lp, const vec3 lightCol,
|
vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, const vec3 lp, const vec3 lightCol,
|
||||||
|
@ -80,17 +80,13 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co
|
||||||
#ifdef _Clusters
|
#ifdef _Clusters
|
||||||
vec4 lPos = LWVPSpotArray[index] * vec4(p + n * bias * 10, 1.0);
|
vec4 lPos = LWVPSpotArray[index] * vec4(p + n * bias * 10, 1.0);
|
||||||
#ifdef _ShadowMapAtlas
|
#ifdef _ShadowMapAtlas
|
||||||
vec3 uv = lPos.xyz / lPos.w;
|
|
||||||
#ifdef _InvY
|
|
||||||
uv.y = 1.0 - uv.y; // invert Y coordinates for direct3d coordinate system
|
|
||||||
#endif
|
|
||||||
direct *= shadowTest(
|
direct *= shadowTest(
|
||||||
#ifndef _SingleAtlas
|
#ifndef _SingleAtlas
|
||||||
shadowMapAtlasSpot
|
shadowMapAtlasSpot
|
||||||
#else
|
#else
|
||||||
shadowMapAtlas
|
shadowMapAtlas
|
||||||
#endif
|
#endif
|
||||||
, uv, bias
|
, lPos.xyz / lPos.w, bias
|
||||||
);
|
);
|
||||||
#else
|
#else
|
||||||
if (index == 0) direct *= shadowTest(shadowMapSpot[0], lPos.xyz / lPos.w, bias);
|
if (index == 0) direct *= shadowTest(shadowMapSpot[0], lPos.xyz / lPos.w, bias);
|
||||||
|
@ -106,11 +102,13 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef _ShadowMap
|
#ifdef _ShadowMap
|
||||||
#ifndef _Spot
|
|
||||||
if (receiveShadow) {
|
if (receiveShadow) {
|
||||||
#ifdef _SinglePoint
|
#ifdef _SinglePoint
|
||||||
|
#ifndef _Spot
|
||||||
direct *= PCFCube(shadowMapPoint[0], ld, -l, bias, lightProj, n);
|
direct *= PCFCube(shadowMapPoint[0], ld, -l, bias, lightProj, n);
|
||||||
#endif
|
#endif
|
||||||
|
#endif
|
||||||
#ifdef _Clusters
|
#ifdef _Clusters
|
||||||
#ifdef _ShadowMapAtlas
|
#ifdef _ShadowMapAtlas
|
||||||
direct *= PCFFakeCube(
|
direct *= PCFFakeCube(
|
||||||
|
@ -130,7 +128,6 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#endif
|
|
||||||
|
|
||||||
return direct;
|
return direct;
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,4 +40,9 @@ float attenuate(const float dist) {
|
||||||
// 1.0 / (quadratic * dist * dist);
|
// 1.0 / (quadratic * dist * dist);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float safe_acos(const float x) {
|
||||||
|
// acos is undefined if |x| > 1
|
||||||
|
return acos(clamp(x, -1.0, 1.0));
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -42,7 +42,16 @@ vec2 sampleCube(vec3 dir, out int faceIndex) {
|
||||||
uv = vec2(dir.x < 0.0 ? dir.z : -dir.z, -dir.y);
|
uv = vec2(dir.x < 0.0 ? dir.z : -dir.z, -dir.y);
|
||||||
}
|
}
|
||||||
// downscale uv a little to hide seams
|
// downscale uv a little to hide seams
|
||||||
|
// transform coordinates from clip space to texture space
|
||||||
|
#ifndef _FlipY
|
||||||
return uv * 0.9976 * ma + 0.5;
|
return uv * 0.9976 * ma + 0.5;
|
||||||
|
#else
|
||||||
|
#ifdef HLSL
|
||||||
|
return uv * 0.9976 * ma + 0.5;
|
||||||
|
#else
|
||||||
|
return vec2(uv.x * ma, uv.y * -ma) * 0.9976 + 0.5;
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -87,6 +96,7 @@ float PCFCube(samplerCubeShadow shadowMapCube, const vec3 lp, vec3 ml, const flo
|
||||||
|
|
||||||
#ifdef _ShadowMapAtlas
|
#ifdef _ShadowMapAtlas
|
||||||
// transform "out-of-bounds" coordinates to the correct face/coordinate system
|
// transform "out-of-bounds" coordinates to the correct face/coordinate system
|
||||||
|
// https://www.khronos.org/opengl/wiki/File:CubeMapAxes.png
|
||||||
vec2 transformOffsetedUV(const int faceIndex, out int newFaceIndex, vec2 uv) {
|
vec2 transformOffsetedUV(const int faceIndex, out int newFaceIndex, vec2 uv) {
|
||||||
if (uv.x < 0.0) {
|
if (uv.x < 0.0) {
|
||||||
if (faceIndex == 0) { // X+
|
if (faceIndex == 0) { // X+
|
||||||
|
@ -189,7 +199,7 @@ float PCFFakeCube(sampler2DShadow shadowMap, const vec3 lp, vec3 ml, const float
|
||||||
|
|
||||||
vec4 pointLightTile = pointLightDataArray[lightIndex + faceIndex]; // x: tile X offset, y: tile Y offset, z: tile size relative to atlas
|
vec4 pointLightTile = pointLightDataArray[lightIndex + faceIndex]; // x: tile X offset, y: tile Y offset, z: tile size relative to atlas
|
||||||
vec2 uvtiled = pointLightTile.z * uv + pointLightTile.xy;
|
vec2 uvtiled = pointLightTile.z * uv + pointLightTile.xy;
|
||||||
#ifdef _InvY
|
#ifdef _FlipY
|
||||||
uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system
|
uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -199,15 +209,15 @@ float PCFFakeCube(sampler2DShadow shadowMap, const vec3 lp, vec3 ml, const float
|
||||||
uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(-1.0, 0.0) / smSize)));
|
uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(-1.0, 0.0) / smSize)));
|
||||||
pointLightTile = pointLightDataArray[lightIndex + newFaceIndex];
|
pointLightTile = pointLightDataArray[lightIndex + newFaceIndex];
|
||||||
uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy;
|
uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy;
|
||||||
#ifdef _InvY
|
#ifdef _FlipY
|
||||||
uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system
|
uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system
|
||||||
#endif
|
#endif
|
||||||
result += texture(shadowMap, vec3(pointLightTile.z * uvtiled + pointLightTile.xy, compare));
|
result += texture(shadowMap, vec3(uvtiled, compare));
|
||||||
|
|
||||||
uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(-1.0, 1.0) / smSize)));
|
uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(-1.0, 1.0) / smSize)));
|
||||||
pointLightTile = pointLightDataArray[lightIndex + newFaceIndex];
|
pointLightTile = pointLightDataArray[lightIndex + newFaceIndex];
|
||||||
uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy;
|
uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy;
|
||||||
#ifdef _InvY
|
#ifdef _FlipY
|
||||||
uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system
|
uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system
|
||||||
#endif
|
#endif
|
||||||
result += texture(shadowMap, vec3(uvtiled, compare));
|
result += texture(shadowMap, vec3(uvtiled, compare));
|
||||||
|
@ -215,7 +225,7 @@ float PCFFakeCube(sampler2DShadow shadowMap, const vec3 lp, vec3 ml, const float
|
||||||
uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(0.0, -1.0) / smSize)));
|
uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(0.0, -1.0) / smSize)));
|
||||||
pointLightTile = pointLightDataArray[lightIndex + newFaceIndex];
|
pointLightTile = pointLightDataArray[lightIndex + newFaceIndex];
|
||||||
uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy;
|
uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy;
|
||||||
#ifdef _InvY
|
#ifdef _FlipY
|
||||||
uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system
|
uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system
|
||||||
#endif
|
#endif
|
||||||
result += texture(shadowMap, vec3(uvtiled, compare));
|
result += texture(shadowMap, vec3(uvtiled, compare));
|
||||||
|
@ -223,7 +233,7 @@ float PCFFakeCube(sampler2DShadow shadowMap, const vec3 lp, vec3 ml, const float
|
||||||
uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(-1.0, -1.0) / smSize)));
|
uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(-1.0, -1.0) / smSize)));
|
||||||
pointLightTile = pointLightDataArray[lightIndex + newFaceIndex];
|
pointLightTile = pointLightDataArray[lightIndex + newFaceIndex];
|
||||||
uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy;
|
uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy;
|
||||||
#ifdef _InvY
|
#ifdef _FlipY
|
||||||
uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system
|
uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system
|
||||||
#endif
|
#endif
|
||||||
result += texture(shadowMap, vec3(uvtiled, compare));
|
result += texture(shadowMap, vec3(uvtiled, compare));
|
||||||
|
@ -231,7 +241,7 @@ float PCFFakeCube(sampler2DShadow shadowMap, const vec3 lp, vec3 ml, const float
|
||||||
uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(0.0, 1.0) / smSize)));
|
uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(0.0, 1.0) / smSize)));
|
||||||
pointLightTile = pointLightDataArray[lightIndex + newFaceIndex];
|
pointLightTile = pointLightDataArray[lightIndex + newFaceIndex];
|
||||||
uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy;
|
uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy;
|
||||||
#ifdef _InvY
|
#ifdef _FlipY
|
||||||
uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system
|
uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system
|
||||||
#endif
|
#endif
|
||||||
result += texture(shadowMap, vec3(uvtiled, compare));
|
result += texture(shadowMap, vec3(uvtiled, compare));
|
||||||
|
@ -239,7 +249,7 @@ float PCFFakeCube(sampler2DShadow shadowMap, const vec3 lp, vec3 ml, const float
|
||||||
uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(1.0, -1.0) / smSize)));
|
uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(1.0, -1.0) / smSize)));
|
||||||
pointLightTile = pointLightDataArray[lightIndex + newFaceIndex];
|
pointLightTile = pointLightDataArray[lightIndex + newFaceIndex];
|
||||||
uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy;
|
uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy;
|
||||||
#ifdef _InvY
|
#ifdef _FlipY
|
||||||
uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system
|
uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system
|
||||||
#endif
|
#endif
|
||||||
result += texture(shadowMap, vec3(uvtiled, compare));
|
result += texture(shadowMap, vec3(uvtiled, compare));
|
||||||
|
@ -247,7 +257,7 @@ float PCFFakeCube(sampler2DShadow shadowMap, const vec3 lp, vec3 ml, const float
|
||||||
uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(1.0, 0.0) / smSize)));
|
uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(1.0, 0.0) / smSize)));
|
||||||
pointLightTile = pointLightDataArray[lightIndex + newFaceIndex];
|
pointLightTile = pointLightDataArray[lightIndex + newFaceIndex];
|
||||||
uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy;
|
uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy;
|
||||||
#ifdef _InvY
|
#ifdef _FlipY
|
||||||
uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system
|
uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system
|
||||||
#endif
|
#endif
|
||||||
result += texture(shadowMap, vec3(uvtiled, compare));
|
result += texture(shadowMap, vec3(uvtiled, compare));
|
||||||
|
@ -255,7 +265,7 @@ float PCFFakeCube(sampler2DShadow shadowMap, const vec3 lp, vec3 ml, const float
|
||||||
uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(1.0, 1.0) / smSize)));
|
uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(1.0, 1.0) / smSize)));
|
||||||
pointLightTile = pointLightDataArray[lightIndex + newFaceIndex];
|
pointLightTile = pointLightDataArray[lightIndex + newFaceIndex];
|
||||||
uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy;
|
uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy;
|
||||||
#ifdef _InvY
|
#ifdef _FlipY
|
||||||
uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system
|
uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system
|
||||||
#endif
|
#endif
|
||||||
result += texture(shadowMap, vec3(uvtiled, compare));
|
result += texture(shadowMap, vec3(uvtiled, compare));
|
||||||
|
|
155
Shaders/std/sky.glsl
Normal file
155
Shaders/std/sky.glsl
Normal file
|
@ -0,0 +1,155 @@
|
||||||
|
/* Various sky functions
|
||||||
|
* =====================
|
||||||
|
*
|
||||||
|
* Nishita model is based on https://github.com/wwwtyro/glsl-atmosphere (Unlicense License)
|
||||||
|
*
|
||||||
|
* Changes to the original implementation:
|
||||||
|
* - r and pSun parameters of nishita_atmosphere() are already normalized
|
||||||
|
* - Some original parameters of nishita_atmosphere() are replaced with pre-defined values
|
||||||
|
* - Implemented air, dust and ozone density node parameters (see Blender source)
|
||||||
|
* - Replaced the inner integral calculation with a LUT lookup
|
||||||
|
*
|
||||||
|
* Reference for the sun's limb darkening and ozone calculations:
|
||||||
|
* [Hill] Sebastien Hillaire. Physically Based Sky, Atmosphere and Cloud Rendering in Frostbite
|
||||||
|
* (https://media.contentapi.ea.com/content/dam/eacom/frostbite/files/s2016-pbs-frostbite-sky-clouds-new.pdf)
|
||||||
|
*
|
||||||
|
* Cycles code used for reference: blender/intern/sky/source/sky_nishita.cpp
|
||||||
|
* (https://github.com/blender/blender/blob/4429b4b77ef6754739a3c2b4fabd0537999e9bdc/intern/sky/source/sky_nishita.cpp)
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _SKY_GLSL_
|
||||||
|
#define _SKY_GLSL_
|
||||||
|
|
||||||
|
#include "std/math.glsl"
|
||||||
|
|
||||||
|
uniform sampler2D nishitaLUT;
|
||||||
|
uniform vec2 nishitaDensity;
|
||||||
|
|
||||||
|
#ifndef PI
|
||||||
|
#define PI 3.141592
|
||||||
|
#endif
|
||||||
|
#ifndef HALF_PI
|
||||||
|
#define HALF_PI 1.570796
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define nishita_iSteps 16
|
||||||
|
|
||||||
|
// These values are taken from Cycles code if they
|
||||||
|
// exist there, otherwise they are taken from the example
|
||||||
|
// in the glsl-atmosphere repo
|
||||||
|
#define nishita_sun_intensity 22.0
|
||||||
|
#define nishita_atmo_radius 6420e3
|
||||||
|
#define nishita_rayleigh_scale 8e3
|
||||||
|
#define nishita_rayleigh_coeff vec3(5.5e-6, 13.0e-6, 22.4e-6)
|
||||||
|
#define nishita_mie_scale 1.2e3
|
||||||
|
#define nishita_mie_coeff 2e-5
|
||||||
|
#define nishita_mie_dir 0.76 // Aerosols anisotropy ("direction")
|
||||||
|
#define nishita_mie_dir_sq 0.5776 // Squared aerosols anisotropy
|
||||||
|
|
||||||
|
// Values from [Hill: 60]
|
||||||
|
#define sun_limb_darkening_col vec3(0.397, 0.503, 0.652)
|
||||||
|
|
||||||
|
vec3 nishita_lookupLUT(const float height, const float sunTheta) {
|
||||||
|
vec2 coords = vec2(
|
||||||
|
sqrt(height * (1 / nishita_atmo_radius)),
|
||||||
|
0.5 + 0.5 * sign(sunTheta - HALF_PI) * sqrt(abs(sunTheta * (1 / HALF_PI) - 1))
|
||||||
|
);
|
||||||
|
return textureLod(nishitaLUT, coords, 0.0).rgb;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* See raySphereIntersection() in armory/Sources/renderpath/Nishita.hx */
|
||||||
|
vec2 nishita_rsi(const vec3 r0, const vec3 rd, const float sr) {
|
||||||
|
float a = dot(rd, rd);
|
||||||
|
float b = 2.0 * dot(rd, r0);
|
||||||
|
float c = dot(r0, r0) - (sr * sr);
|
||||||
|
float d = (b*b) - 4.0*a*c;
|
||||||
|
|
||||||
|
// If d < 0.0 the ray does not intersect the sphere
|
||||||
|
return (d < 0.0) ? vec2(1e5,-1e5) : vec2((-b - sqrt(d))/(2.0*a), (-b + sqrt(d))/(2.0*a));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* r: normalized ray direction
|
||||||
|
* r0: ray origin
|
||||||
|
* pSun: normalized sun direction
|
||||||
|
* rPlanet: planet radius
|
||||||
|
*/
|
||||||
|
vec3 nishita_atmosphere(const vec3 r, const vec3 r0, const vec3 pSun, const float rPlanet) {
|
||||||
|
// Calculate the step size of the primary ray
|
||||||
|
vec2 p = nishita_rsi(r0, r, nishita_atmo_radius);
|
||||||
|
if (p.x > p.y) return vec3(0.0);
|
||||||
|
p.y = min(p.y, nishita_rsi(r0, r, rPlanet).x);
|
||||||
|
float iStepSize = (p.y - p.x) / float(nishita_iSteps);
|
||||||
|
|
||||||
|
// Primary ray time
|
||||||
|
float iTime = 0.0;
|
||||||
|
|
||||||
|
// Accumulators for Rayleigh and Mie scattering.
|
||||||
|
vec3 totalRlh = vec3(0,0,0);
|
||||||
|
vec3 totalMie = vec3(0,0,0);
|
||||||
|
|
||||||
|
// Optical depth accumulators for the primary ray
|
||||||
|
float iOdRlh = 0.0;
|
||||||
|
float iOdMie = 0.0;
|
||||||
|
|
||||||
|
// Calculate the Rayleigh and Mie phases
|
||||||
|
float mu = dot(r, pSun);
|
||||||
|
float mumu = mu * mu;
|
||||||
|
float pRlh = 3.0 / (16.0 * PI) * (1.0 + mumu);
|
||||||
|
float pMie = 3.0 / (8.0 * PI) * ((1.0 - nishita_mie_dir_sq) * (mumu + 1.0)) / (pow(1.0 + nishita_mie_dir_sq - 2.0 * mu * nishita_mie_dir, 1.5) * (2.0 + nishita_mie_dir_sq));
|
||||||
|
|
||||||
|
// Sample the primary ray
|
||||||
|
for (int i = 0; i < nishita_iSteps; i++) {
|
||||||
|
|
||||||
|
// Calculate the primary ray sample position and height
|
||||||
|
vec3 iPos = r0 + r * (iTime + iStepSize * 0.5);
|
||||||
|
float iHeight = length(iPos) - rPlanet;
|
||||||
|
|
||||||
|
// Calculate the optical depth of the Rayleigh and Mie scattering for this step
|
||||||
|
float odStepRlh = exp(-iHeight / nishita_rayleigh_scale) * nishitaDensity.x * iStepSize;
|
||||||
|
float odStepMie = exp(-iHeight / nishita_mie_scale) * nishitaDensity.y * iStepSize;
|
||||||
|
|
||||||
|
// Accumulate optical depth
|
||||||
|
iOdRlh += odStepRlh;
|
||||||
|
iOdMie += odStepMie;
|
||||||
|
|
||||||
|
// Idea behind this: "Rotate" everything by iPos (-> iPos is the new zenith) and then all calculations for the
|
||||||
|
// inner integral only depend on the sample height (iHeight) and sunTheta (angle between sun and new zenith).
|
||||||
|
float sunTheta = safe_acos(dot(normalize(iPos), normalize(pSun)));
|
||||||
|
vec3 jAttn = nishita_lookupLUT(iHeight, sunTheta);
|
||||||
|
|
||||||
|
// Calculate attenuation
|
||||||
|
vec3 iAttn = exp(-(
|
||||||
|
nishita_mie_coeff * iOdMie
|
||||||
|
+ nishita_rayleigh_coeff * iOdRlh
|
||||||
|
// + 0 for ozone
|
||||||
|
));
|
||||||
|
vec3 attn = iAttn * jAttn;
|
||||||
|
|
||||||
|
// Apply dithering to reduce visible banding
|
||||||
|
attn *= 0.98 + rand(r.xy) * 0.04;
|
||||||
|
|
||||||
|
// Accumulate scattering
|
||||||
|
totalRlh += odStepRlh * attn;
|
||||||
|
totalMie += odStepMie * attn;
|
||||||
|
|
||||||
|
iTime += iStepSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nishita_sun_intensity * (pRlh * nishita_rayleigh_coeff * totalRlh + pMie * nishita_mie_coeff * totalMie);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 sun_disk(const vec3 n, const vec3 light_dir, const float disk_size, const float intensity) {
|
||||||
|
// Normalized SDF
|
||||||
|
float dist = distance(n, light_dir) / disk_size;
|
||||||
|
|
||||||
|
// Darken the edges of the sun
|
||||||
|
// [Hill: 28, 60] (according to [Nec96])
|
||||||
|
float invDist = 1.0 - dist;
|
||||||
|
float mu = sqrt(invDist * invDist);
|
||||||
|
vec3 limb_darkening = 1.0 - (1.0 - pow(vec3(mu), sun_limb_darkening_col));
|
||||||
|
|
||||||
|
return 1 + (1.0 - step(1.0, dist)) * nishita_sun_intensity * intensity * limb_darkening;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -12,7 +12,7 @@ uniform sampler2D gbufferD;
|
||||||
uniform sampler2D snoise;
|
uniform sampler2D snoise;
|
||||||
|
|
||||||
#ifdef _Clusters
|
#ifdef _Clusters
|
||||||
uniform vec4 lightsArray[maxLights * 2];
|
uniform vec4 lightsArray[maxLights * 3];
|
||||||
#ifdef _Spot
|
#ifdef _Spot
|
||||||
uniform vec4 lightsArraySpot[maxLights];
|
uniform vec4 lightsArraySpot[maxLights];
|
||||||
#endif
|
#endif
|
||||||
|
@ -44,7 +44,15 @@ uniform vec2 cameraPlane;
|
||||||
uniform vec3 sunDir;
|
uniform vec3 sunDir;
|
||||||
uniform vec3 sunCol;
|
uniform vec3 sunCol;
|
||||||
#ifdef _ShadowMap
|
#ifdef _ShadowMap
|
||||||
|
#ifdef _ShadowMapAtlas
|
||||||
|
#ifndef _SingleAtlas
|
||||||
|
uniform sampler2DShadow shadowMapAtlasSun;
|
||||||
|
#else
|
||||||
|
uniform sampler2DShadow shadowMapAtlas;
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
uniform sampler2DShadow shadowMap;
|
uniform sampler2DShadow shadowMap;
|
||||||
|
#endif
|
||||||
uniform float shadowsBias;
|
uniform float shadowsBias;
|
||||||
#ifdef _CSM
|
#ifdef _CSM
|
||||||
//!uniform vec4 casData[shadowmapCascades * 4 + 4];
|
//!uniform vec4 casData[shadowmapCascades * 4 + 4];
|
||||||
|
@ -95,7 +103,17 @@ void rayStep(inout vec3 curPos, inout float curOpticalDepth, inout float scatter
|
||||||
#endif
|
#endif
|
||||||
vec4 lPos = LWVP * vec4(curPos, 1.0);
|
vec4 lPos = LWVP * vec4(curPos, 1.0);
|
||||||
lPos.xyz /= lPos.w;
|
lPos.xyz /= lPos.w;
|
||||||
visibility = texture(shadowMap, vec3(lPos.xy, lPos.z - shadowsBias));
|
visibility = texture(
|
||||||
|
#ifdef _ShadowMapAtlas
|
||||||
|
#ifndef _SingleAtlas
|
||||||
|
shadowMapAtlasSun
|
||||||
|
#else
|
||||||
|
shadowMapAtlas
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
shadowMap
|
||||||
|
#endif
|
||||||
|
, vec3(lPos.xy, lPos.z - shadowsBias));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef _SinglePoint
|
#ifdef _SinglePoint
|
||||||
|
|
|
@ -63,7 +63,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "LWVP",
|
"name": "LWVP",
|
||||||
"link": "_biasLightWorldViewProjectionMatrix",
|
"link": "_biasLightWorldViewProjectionMatrixSun",
|
||||||
"ifndef": ["_CSM"],
|
"ifndef": ["_CSM"],
|
||||||
"ifdef": ["_Sun", "_ShadowMap"]
|
"ifdef": ["_Sun", "_ShadowMap"]
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
package armory.logicnode;
|
package armory.logicnode;
|
||||||
|
|
||||||
import armory.trait.physics.PhysicsConstraint;
|
|
||||||
#if arm_physics
|
|
||||||
import armory.trait.physics.bullet.PhysicsConstraint.ConstraintType;
|
|
||||||
import armory.trait.physics.bullet.PhysicsConstraint.ConstraintAxis;
|
|
||||||
#end
|
|
||||||
import iron.object.Object;
|
import iron.object.Object;
|
||||||
import armory.trait.physics.RigidBody;
|
|
||||||
import armory.logicnode.PhysicsConstraintNode;
|
#if arm_physics
|
||||||
|
import armory.trait.physics.PhysicsConstraint;
|
||||||
|
import armory.trait.physics.bullet.PhysicsConstraint.ConstraintType;
|
||||||
|
#end
|
||||||
|
|
||||||
class AddPhysicsConstraintNode extends LogicNode {
|
class AddPhysicsConstraintNode extends LogicNode {
|
||||||
|
|
||||||
|
@ -21,115 +19,96 @@ class AddPhysicsConstraintNode extends LogicNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
override function run(from: Int) {
|
override function run(from: Int) {
|
||||||
var pivotObject:Object = inputs[1].get();
|
var pivotObject: Object = inputs[1].get();
|
||||||
rb1 = inputs[2].get();
|
rb1 = inputs[2].get();
|
||||||
rb2 = inputs[3].get();
|
rb2 = inputs[3].get();
|
||||||
var disableCollisions: Bool = inputs[4].get();
|
|
||||||
var breakable: Bool = inputs[5].get();
|
|
||||||
var breakingThreshold: Float = inputs[6].get();
|
|
||||||
var type: ConstraintType = 0;
|
|
||||||
|
|
||||||
if (pivotObject == null || rb1 == null || rb2 == null) return;
|
if (pivotObject == null || rb1 == null || rb2 == null) return;
|
||||||
|
|
||||||
#if arm_physics
|
#if arm_physics
|
||||||
|
|
||||||
|
var disableCollisions: Bool = inputs[4].get();
|
||||||
|
var breakable: Bool = inputs[5].get();
|
||||||
|
var breakingThreshold: Float = inputs[6].get();
|
||||||
|
var type: ConstraintType = 0;
|
||||||
|
|
||||||
var con: PhysicsConstraint = pivotObject.getTrait(PhysicsConstraint);
|
var con: PhysicsConstraint = pivotObject.getTrait(PhysicsConstraint);
|
||||||
if(con == null)
|
if (con == null) {
|
||||||
{
|
switch (property0) {
|
||||||
switch(property0)
|
case "Fixed": type = Fixed;
|
||||||
{
|
case "Point": type = Point;
|
||||||
case 'Fixed':
|
case "Hinge": type = Hinge;
|
||||||
type = Fixed;
|
case "Slider": type = Slider;
|
||||||
case 'Point':
|
case "Piston": type = Piston;
|
||||||
type = Point;
|
case "Generic Spring": type = Generic;
|
||||||
case 'Hinge':
|
|
||||||
type = Hinge;
|
|
||||||
case 'Slider':
|
|
||||||
type = Slider;
|
|
||||||
case 'Piston':
|
|
||||||
type = Piston;
|
|
||||||
case 'Generic Spring':
|
|
||||||
type = Generic;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(! breakable) breakingThreshold = 0.0;
|
if (!breakable) breakingThreshold = 0.0;
|
||||||
|
|
||||||
if(type != Generic) {
|
if (type != Generic) {
|
||||||
|
|
||||||
con = new PhysicsConstraint(rb1, rb2, type, disableCollisions, breakingThreshold);
|
con = new PhysicsConstraint(rb1, rb2, type, disableCollisions, breakingThreshold);
|
||||||
|
|
||||||
switch (type)
|
switch (type) {
|
||||||
{
|
|
||||||
case Hinge:
|
case Hinge:
|
||||||
var setLimit:Bool = inputs[7].get();
|
var setLimit: Bool = inputs[7].get();
|
||||||
var low:Float = inputs[8].get();
|
var low: Float = inputs[8].get();
|
||||||
var up:Float = inputs[9].get();
|
var up: Float = inputs[9].get();
|
||||||
con.setHingeConstraintLimits(setLimit, low, up);
|
con.setHingeConstraintLimits(setLimit, low, up);
|
||||||
|
|
||||||
case Slider:
|
case Slider:
|
||||||
var setLimit:Bool = inputs[7].get();
|
var setLimit: Bool = inputs[7].get();
|
||||||
var low:Float = inputs[8].get();
|
var low: Float = inputs[8].get();
|
||||||
var up:Float = inputs[9].get();
|
var up: Float = inputs[9].get();
|
||||||
con.setSliderConstraintLimits(setLimit, low, up);
|
con.setSliderConstraintLimits(setLimit, low, up);
|
||||||
|
|
||||||
case Piston:
|
case Piston:
|
||||||
var setLinLimit:Bool = inputs[7].get();
|
var setLinLimit: Bool = inputs[7].get();
|
||||||
var linLow:Float = inputs[8].get();
|
var linLow: Float = inputs[8].get();
|
||||||
var linUp:Float = inputs[9].get();
|
var linUp: Float = inputs[9].get();
|
||||||
var setAngLimit:Bool = inputs[10].get();
|
var setAngLimit: Bool = inputs[10].get();
|
||||||
var angLow:Float = inputs[11].get();
|
var angLow: Float = inputs[11].get();
|
||||||
var angUp:Float = inputs[12].get();
|
var angUp: Float = inputs[12].get();
|
||||||
con.setPistonConstraintLimits(setLinLimit, linLow, linUp, setAngLimit, angLow, angUp);
|
con.setPistonConstraintLimits(setLinLimit, linLow, linUp, setAngLimit, angLow, angUp);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else {
|
||||||
{
|
|
||||||
var spring: Bool = false;
|
var spring: Bool = false;
|
||||||
var prop: PhysicsConstraintNode;
|
var prop: PhysicsConstraintNode;
|
||||||
for(inp in 7...inputs.length)
|
|
||||||
{
|
for (inp in 7...inputs.length) {
|
||||||
prop = inputs[inp].get();
|
prop = inputs[inp].get();
|
||||||
if(prop == null) continue;
|
if (prop == null) continue;
|
||||||
if(prop.isSpring)
|
if (prop.isSpring) {
|
||||||
{
|
|
||||||
spring = true;
|
spring = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(spring) {
|
if (spring) {
|
||||||
con = new PhysicsConstraint(rb1, rb2, GenericSpring, disableCollisions, breakingThreshold);
|
con = new PhysicsConstraint(rb1, rb2, GenericSpring, disableCollisions, breakingThreshold);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
con = new PhysicsConstraint(rb1, rb2, Generic, disableCollisions, breakingThreshold);
|
con = new PhysicsConstraint(rb1, rb2, Generic, disableCollisions, breakingThreshold);
|
||||||
}
|
}
|
||||||
|
|
||||||
for(inp in 7...inputs.length)
|
for (inp in 7...inputs.length) {
|
||||||
{
|
|
||||||
prop = inputs[inp].get();
|
prop = inputs[inp].get();
|
||||||
if(prop == null) continue;
|
if (prop == null) continue;
|
||||||
(inp + ': ');
|
|
||||||
|
|
||||||
if(prop.isSpring)
|
if (prop.isSpring) {
|
||||||
{
|
|
||||||
con.setSpringParams(prop.isSpring, prop.value1, prop.value2, prop.axis, prop.isAngular);
|
con.setSpringParams(prop.isSpring, prop.value1, prop.value2, prop.axis, prop.isAngular);
|
||||||
}
|
}
|
||||||
else
|
else {
|
||||||
{
|
|
||||||
con.setGenericConstraintLimits(true, prop.value1, prop.value2, prop.axis, prop.isAngular);
|
con.setGenericConstraintLimits(true, prop.value1, prop.value2, prop.axis, prop.isAngular);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pivotObject.addTrait(con);
|
pivotObject.addTrait(con);
|
||||||
|
|
||||||
}
|
}
|
||||||
#end
|
#end
|
||||||
|
|
||||||
runOutput(0);
|
runOutput(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
package armory.logicnode;
|
package armory.logicnode;
|
||||||
|
|
||||||
|
import iron.object.Object;
|
||||||
|
|
||||||
#if arm_physics
|
#if arm_physics
|
||||||
|
import armory.trait.physics.RigidBody;
|
||||||
import armory.trait.physics.bullet.RigidBody.Shape;
|
import armory.trait.physics.bullet.RigidBody.Shape;
|
||||||
#end
|
#end
|
||||||
import iron.object.Object;
|
|
||||||
import armory.trait.physics.RigidBody;
|
|
||||||
|
|
||||||
class AddRigidBodyNode extends LogicNode {
|
class AddRigidBodyNode extends LogicNode {
|
||||||
|
|
||||||
public var property0: String;//Shape
|
public var property0: String; //Shape
|
||||||
public var property1: String;//Advanced
|
public var property1: Bool; //Advanced
|
||||||
public var object: Object;
|
public var object: Object;
|
||||||
|
|
||||||
public function new(tree: LogicTree) {
|
public function new(tree: LogicTree) {
|
||||||
|
@ -18,6 +20,10 @@ class AddRigidBodyNode extends LogicNode {
|
||||||
|
|
||||||
override function run(from: Int) {
|
override function run(from: Int) {
|
||||||
object = inputs[1].get();
|
object = inputs[1].get();
|
||||||
|
if (object == null) return;
|
||||||
|
|
||||||
|
#if arm_physics
|
||||||
|
|
||||||
var mass: Float = inputs[2].get();
|
var mass: Float = inputs[2].get();
|
||||||
var active: Bool = inputs[3].get();
|
var active: Bool = inputs[3].get();
|
||||||
var animated: Bool = inputs[4].get();
|
var animated: Bool = inputs[4].get();
|
||||||
|
@ -38,8 +44,7 @@ class AddRigidBodyNode extends LogicNode {
|
||||||
|
|
||||||
var shape: Shape = 1;
|
var shape: Shape = 1;
|
||||||
|
|
||||||
if(property1 == 'true')
|
if (property1) {
|
||||||
{
|
|
||||||
margin = inputs[9].get();
|
margin = inputs[9].get();
|
||||||
marginLen = inputs[10].get();
|
marginLen = inputs[10].get();
|
||||||
linDamp = inputs[11].get();
|
linDamp = inputs[11].get();
|
||||||
|
@ -49,50 +54,34 @@ class AddRigidBodyNode extends LogicNode {
|
||||||
angVelThreshold = inputs[15].get();
|
angVelThreshold = inputs[15].get();
|
||||||
group = inputs[16].get();
|
group = inputs[16].get();
|
||||||
mask = inputs[17].get();
|
mask = inputs[17].get();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (object == null) return;
|
|
||||||
|
|
||||||
#if arm_physics
|
|
||||||
var rb: RigidBody = object.getTrait(RigidBody);
|
var rb: RigidBody = object.getTrait(RigidBody);
|
||||||
if((group < 0) || (group > 32)) group = 1; //Limiting max groups to 32
|
if ((group < 0) || (group > 32)) group = 1; //Limiting max groups to 32
|
||||||
if((mask < 0) || (mask > 32)) mask = 1; //Limiting max masks to 32
|
if ((mask < 0) || (mask > 32)) mask = 1; //Limiting max masks to 32
|
||||||
if(rb == null)
|
if (rb == null) {
|
||||||
{
|
switch (property0) {
|
||||||
|
case "Box": shape = Box;
|
||||||
switch (property0){
|
case "Sphere": shape = Sphere;
|
||||||
|
case "Capsule": shape = Capsule;
|
||||||
case 'Box':
|
case "Cone": shape = Cone;
|
||||||
shape = Box;
|
case "Cylinder": shape = Cylinder;
|
||||||
case 'Sphere':
|
case "Convex Hull": shape = ConvexHull;
|
||||||
shape = Sphere;
|
case "Mesh": shape = Mesh;
|
||||||
case 'Capsule':
|
|
||||||
shape = Capsule;
|
|
||||||
case 'Cone':
|
|
||||||
shape = Cone;
|
|
||||||
case 'Cylinder':
|
|
||||||
shape = Cylinder;
|
|
||||||
case 'Convex Hull':
|
|
||||||
shape = ConvexHull;
|
|
||||||
case 'Mesh':
|
|
||||||
shape = Mesh;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rb = new RigidBody(shape, mass, friction, bounciness, group, mask);
|
rb = new RigidBody(shape, mass, friction, bounciness, group, mask);
|
||||||
rb.animated = animated;
|
rb.animated = animated;
|
||||||
rb.staticObj = ! active;
|
rb.staticObj = !active;
|
||||||
rb.isTriggerObject(trigger);
|
rb.isTriggerObject(trigger);
|
||||||
if(property1 == 'true')
|
|
||||||
{
|
if (property1) {
|
||||||
rb.linearDamping = linDamp;
|
rb.linearDamping = linDamp;
|
||||||
rb.angularDamping = angDamp;
|
rb.angularDamping = angDamp;
|
||||||
if(margin) rb.collisionMargin = marginLen;
|
if (margin) rb.collisionMargin = marginLen;
|
||||||
if(useDeactiv) {
|
if (useDeactiv) {
|
||||||
rb.setUpDeactivation(true, linearVelThreshold, angVelThreshold, 0.0);
|
rb.setUpDeactivation(true, linearVelThreshold, angVelThreshold, 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
object.addTrait(rb);
|
object.addTrait(rb);
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package armory.logicnode;
|
package armory.logicnode;
|
||||||
|
|
||||||
|
import iron.math.Quat;
|
||||||
|
import iron.math.Vec4;
|
||||||
import iron.object.Object;
|
import iron.object.Object;
|
||||||
import iron.object.BoneAnimation;
|
import iron.object.BoneAnimation;
|
||||||
import iron.math.Mat4;
|
import iron.math.Mat4;
|
||||||
|
@ -26,19 +28,28 @@ class BoneFKNode extends LogicNode {
|
||||||
var anim = object.animation != null ? cast(object.animation, BoneAnimation) : null;
|
var anim = object.animation != null ? cast(object.animation, BoneAnimation) : null;
|
||||||
if (anim == null) anim = object.getParentArmature(object.name);
|
if (anim == null) anim = object.getParentArmature(object.name);
|
||||||
|
|
||||||
// Manipulating bone in world space
|
// Get bone in armature
|
||||||
var bone = anim.getBone(boneName);
|
var bone = anim.getBone(boneName);
|
||||||
m = anim.getBoneMat(bone);
|
|
||||||
w = anim.getAbsMat(bone);
|
|
||||||
|
|
||||||
function moveBone() {
|
function moveBone() {
|
||||||
m.setFrom(w);
|
|
||||||
m.multmat(transform);
|
|
||||||
iw.getInverse(w);
|
|
||||||
m.multmat(iw);
|
|
||||||
|
|
||||||
// anim.removeUpdate(moveBone);
|
var t2 = Mat4.identity();
|
||||||
// notified = false;
|
var loc= new Vec4();
|
||||||
|
var rot = new Quat();
|
||||||
|
var scl = new Vec4();
|
||||||
|
|
||||||
|
//Set scale to Armature scale. Bone scaling not yet implemented
|
||||||
|
t2.setFrom(transform);
|
||||||
|
t2.decompose(loc, rot, scl);
|
||||||
|
scl = object.transform.world.getScale();
|
||||||
|
t2.compose(loc, rot, scl);
|
||||||
|
|
||||||
|
//Set the bone local transform from world transform
|
||||||
|
anim.setBoneMatFromWorldMat(t2, bone);
|
||||||
|
|
||||||
|
//Remove this method from animation loop after FK
|
||||||
|
anim.removeUpdate(moveBone);
|
||||||
|
notified = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!notified) {
|
if (!notified) {
|
||||||
|
|
|
@ -7,6 +7,13 @@ import iron.math.Vec4;
|
||||||
class BoneIKNode extends LogicNode {
|
class BoneIKNode extends LogicNode {
|
||||||
|
|
||||||
var goal: Vec4;
|
var goal: Vec4;
|
||||||
|
var pole: Vec4;
|
||||||
|
var poleEnabled: Bool;
|
||||||
|
var chainLength: Int;
|
||||||
|
var maxIterartions: Int;
|
||||||
|
var precision: Float;
|
||||||
|
var rollAngle: Float;
|
||||||
|
|
||||||
var notified = false;
|
var notified = false;
|
||||||
|
|
||||||
public function new(tree: LogicTree) {
|
public function new(tree: LogicTree) {
|
||||||
|
@ -19,6 +26,12 @@ class BoneIKNode extends LogicNode {
|
||||||
var object: Object = inputs[1].get();
|
var object: Object = inputs[1].get();
|
||||||
var boneName: String = inputs[2].get();
|
var boneName: String = inputs[2].get();
|
||||||
goal = inputs[3].get();
|
goal = inputs[3].get();
|
||||||
|
poleEnabled = inputs[4].get();
|
||||||
|
pole = inputs[5].get();
|
||||||
|
chainLength = inputs[6].get();
|
||||||
|
maxIterartions = inputs[7].get();
|
||||||
|
precision = inputs[8].get();
|
||||||
|
rollAngle = inputs[9].get();
|
||||||
|
|
||||||
if (object == null || goal == null) return;
|
if (object == null || goal == null) return;
|
||||||
var anim = object.animation != null ? cast(object.animation, BoneAnimation) : null;
|
var anim = object.animation != null ? cast(object.animation, BoneAnimation) : null;
|
||||||
|
@ -26,11 +39,15 @@ class BoneIKNode extends LogicNode {
|
||||||
|
|
||||||
var bone = anim.getBone(boneName);
|
var bone = anim.getBone(boneName);
|
||||||
|
|
||||||
function solveBone() {
|
if(! poleEnabled) pole = null;
|
||||||
anim.solveIK(bone, goal);
|
|
||||||
|
|
||||||
// anim.removeUpdate(solveBone);
|
function solveBone() {
|
||||||
// notified = false;
|
//Solve IK
|
||||||
|
anim.solveIK(bone, goal, precision, maxIterartions, chainLength, pole, rollAngle);
|
||||||
|
|
||||||
|
//Remove this method from animation loop after IK
|
||||||
|
anim.removeUpdate(solveBone);
|
||||||
|
notified = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!notified) {
|
if (!notified) {
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
package armory.logicnode;
|
package armory.logicnode;
|
||||||
|
|
||||||
|
import kha.FastFloat;
|
||||||
|
|
||||||
class ClampNode extends LogicNode {
|
class ClampNode extends LogicNode {
|
||||||
|
|
||||||
public function new(tree: LogicTree) {
|
public function new(tree: LogicTree) {
|
||||||
super(tree);
|
super(tree);
|
||||||
}
|
}
|
||||||
|
|
||||||
override function get(from: Int): Dynamic {
|
override function get(from: Int): FastFloat {
|
||||||
var value: kha.FastFloat = inputs[0].get();
|
var value = inputs[0].get();
|
||||||
var min: kha.FastFloat = inputs[1].get();
|
var min = inputs[1].get();
|
||||||
var max: kha.FastFloat = inputs[2].get();
|
var max = inputs[2].get();
|
||||||
|
|
||||||
if (value == null || min == null || max == null) return null;
|
return value < min ? min : value > max ? max : value;
|
||||||
|
|
||||||
value <= min ? return min : value >= max ? return max : return value;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,9 +19,9 @@ class CompareNode extends LogicNode {
|
||||||
|
|
||||||
switch (property0) {
|
switch (property0) {
|
||||||
case "Equal":
|
case "Equal":
|
||||||
cond = Std.is(v1, Vec4) ? v1.equals(v2) : v1 == v2;
|
cond = Std.isOfType(v1, Vec4) ? v1.equals(v2) : v1 == v2;
|
||||||
case "Almost Equal":
|
case "Almost Equal":
|
||||||
cond = Std.is(v1, Vec4) ? v1.almostEquals(v2, property1) : Math.abs(v1 - v2) < property1;
|
cond = Std.isOfType(v1, Vec4) ? v1.almostEquals(v2, property1) : Math.abs(v1 - v2) < property1;
|
||||||
case "Greater":
|
case "Greater":
|
||||||
cond = v1 > v2;
|
cond = v1 > v2;
|
||||||
case "Greater Equal":
|
case "Greater Equal":
|
||||||
|
|
|
@ -18,9 +18,9 @@ class GateNode extends LogicNode {
|
||||||
|
|
||||||
switch (property0) {
|
switch (property0) {
|
||||||
case "Equal":
|
case "Equal":
|
||||||
cond = Std.is(v1, Vec4) ? v1.equals(v2) : v1 == v2;
|
cond = Std.isOfType(v1, Vec4) ? v1.equals(v2) : v1 == v2;
|
||||||
case "Almost Equal":
|
case "Almost Equal":
|
||||||
cond = Std.is(v1, Vec4) ? v1.almostEquals(v2, property1) : Math.abs(v1 - v2) < property1;
|
cond = Std.isOfType(v1, Vec4) ? v1.almostEquals(v2, property1) : Math.abs(v1 - v2) < property1;
|
||||||
case "Greater":
|
case "Greater":
|
||||||
cond = v1 > v2;
|
cond = v1 > v2;
|
||||||
case "Greater Equal":
|
case "Greater Equal":
|
||||||
|
|
31
Sources/armory/logicnode/GetBoneFkIkOnlyNode.hx
Normal file
31
Sources/armory/logicnode/GetBoneFkIkOnlyNode.hx
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
package armory.logicnode;
|
||||||
|
|
||||||
|
import iron.object.Object;
|
||||||
|
import iron.object.BoneAnimation;
|
||||||
|
|
||||||
|
class GetBoneFkIkOnlyNode extends LogicNode {
|
||||||
|
|
||||||
|
public function new(tree: LogicTree) {
|
||||||
|
super(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
override function get(from: Int): Bool {
|
||||||
|
#if arm_skin
|
||||||
|
|
||||||
|
var object: Object = inputs[0].get();
|
||||||
|
var boneName: String = inputs[1].get();
|
||||||
|
|
||||||
|
if (object == null) return null;
|
||||||
|
var anim = object.animation != null ? cast(object.animation, BoneAnimation) : null;
|
||||||
|
if (anim == null) anim = object.getParentArmature(object.name);
|
||||||
|
|
||||||
|
// Get bone in armature
|
||||||
|
var bone = anim.getBone(boneName);
|
||||||
|
|
||||||
|
//Get bone transform in world coordinates
|
||||||
|
return bone.is_ik_fk_only;
|
||||||
|
|
||||||
|
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
}
|
30
Sources/armory/logicnode/GetBoneTransformNode.hx
Normal file
30
Sources/armory/logicnode/GetBoneTransformNode.hx
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
package armory.logicnode;
|
||||||
|
|
||||||
|
import iron.object.Object;
|
||||||
|
import iron.object.BoneAnimation;
|
||||||
|
import iron.math.Mat4;
|
||||||
|
|
||||||
|
class GetBoneTransformNode extends LogicNode {
|
||||||
|
|
||||||
|
public function new(tree: LogicTree) {
|
||||||
|
super(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
override function get(from: Int): Mat4 {
|
||||||
|
#if arm_skin
|
||||||
|
|
||||||
|
var object: Object = inputs[0].get();
|
||||||
|
var boneName: String = inputs[1].get();
|
||||||
|
|
||||||
|
if (object == null) return null;
|
||||||
|
var anim = object.animation != null ? cast(object.animation, BoneAnimation) : null;
|
||||||
|
if (anim == null) anim = object.getParentArmature(object.name);
|
||||||
|
|
||||||
|
// Get bone in armature
|
||||||
|
var bone = anim.getBone(boneName);
|
||||||
|
|
||||||
|
return anim.getAbsWorldMat(bone);
|
||||||
|
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
}
|
33
Sources/armory/logicnode/GetGamepadStartedNode.hx
Normal file
33
Sources/armory/logicnode/GetGamepadStartedNode.hx
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
package armory.logicnode;
|
||||||
|
|
||||||
|
import iron.system.Input;
|
||||||
|
|
||||||
|
class GetGamepadStartedNode extends LogicNode {
|
||||||
|
|
||||||
|
var buttonStarted: Null<String>;
|
||||||
|
|
||||||
|
public function new(tree: LogicTree) {
|
||||||
|
super(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
override function run(from: Int) {
|
||||||
|
var g = Input.getGamepad(inputs[0].get());
|
||||||
|
|
||||||
|
buttonStarted = null;
|
||||||
|
|
||||||
|
for (b in Gamepad.buttons) {
|
||||||
|
if (g.started(b)) {
|
||||||
|
buttonStarted = b;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buttonStarted != null) {
|
||||||
|
runOutput(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override function get(from: Int) {
|
||||||
|
return buttonStarted;
|
||||||
|
}
|
||||||
|
}
|
24
Sources/armory/logicnode/GetInputMapKeyNode.hx
Normal file
24
Sources/armory/logicnode/GetInputMapKeyNode.hx
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
package armory.logicnode;
|
||||||
|
|
||||||
|
import armory.system.InputMap;
|
||||||
|
|
||||||
|
class GetInputMapKeyNode extends LogicNode {
|
||||||
|
|
||||||
|
public function new(tree: LogicTree) {
|
||||||
|
super(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
override function get(from: Int): Dynamic {
|
||||||
|
var inputMap = inputs[0].get();
|
||||||
|
var key = inputs[1].get();
|
||||||
|
|
||||||
|
var k = InputMap.getInputMapKey(inputMap, key);
|
||||||
|
|
||||||
|
if (k != null) {
|
||||||
|
if (from == 0) return k.scale;
|
||||||
|
else if (from == 1) return k.deadzone;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
32
Sources/armory/logicnode/GetKeyboardStartedNode.hx
Normal file
32
Sources/armory/logicnode/GetKeyboardStartedNode.hx
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
package armory.logicnode;
|
||||||
|
|
||||||
|
import iron.system.Input;
|
||||||
|
|
||||||
|
class GetKeyboardStartedNode extends LogicNode {
|
||||||
|
|
||||||
|
var kb = Input.getKeyboard();
|
||||||
|
var keyStarted: Null<String>;
|
||||||
|
|
||||||
|
public function new(tree: LogicTree) {
|
||||||
|
super(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
override function run(from: Int) {
|
||||||
|
keyStarted = null;
|
||||||
|
|
||||||
|
for (k in Keyboard.keys) {
|
||||||
|
if (kb.started(k)) {
|
||||||
|
keyStarted = k;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keyStarted != null) {
|
||||||
|
runOutput(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override function get(from: Int) {
|
||||||
|
return keyStarted;
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,9 +10,22 @@ class GetLocationNode extends LogicNode {
|
||||||
|
|
||||||
override function get(from: Int): Dynamic {
|
override function get(from: Int): Dynamic {
|
||||||
var object: Object = inputs[0].get();
|
var object: Object = inputs[0].get();
|
||||||
|
var relative: Bool = inputs[1].get();
|
||||||
|
|
||||||
if (object == null) return null;
|
if (object == null) return null;
|
||||||
|
|
||||||
return object.transform.world.getLoc();
|
var loc = object.transform.world.getLoc();
|
||||||
|
|
||||||
|
if (relative && object.parent != null) {
|
||||||
|
loc.sub(object.parent.transform.world.getLoc()); // Add parent location influence
|
||||||
|
|
||||||
|
// Convert loc to parent local space
|
||||||
|
var dotX = loc.dot(object.parent.transform.right());
|
||||||
|
var dotY = loc.dot(object.parent.transform.look());
|
||||||
|
var dotZ = loc.dot(object.parent.transform.up());
|
||||||
|
loc.set(dotX, dotY, dotZ);
|
||||||
|
}
|
||||||
|
|
||||||
|
return loc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
32
Sources/armory/logicnode/GetMouseStartedNode.hx
Normal file
32
Sources/armory/logicnode/GetMouseStartedNode.hx
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
package armory.logicnode;
|
||||||
|
|
||||||
|
import iron.system.Input;
|
||||||
|
|
||||||
|
class GetMouseStartedNode extends LogicNode {
|
||||||
|
|
||||||
|
var m = Input.getMouse();
|
||||||
|
var buttonStarted: Null<String>;
|
||||||
|
|
||||||
|
public function new(tree: LogicTree) {
|
||||||
|
super(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
override function run(from: Int) {
|
||||||
|
buttonStarted = null;
|
||||||
|
|
||||||
|
for (b in Mouse.buttons) {
|
||||||
|
if (m.started(b)) {
|
||||||
|
buttonStarted = b;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buttonStarted != null) {
|
||||||
|
runOutput(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override function get(from: Int) {
|
||||||
|
return buttonStarted;
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,35 +15,6 @@ class GetObjectNode extends LogicNode {
|
||||||
override function get(from: Int): Dynamic {
|
override function get(from: Int): Dynamic {
|
||||||
var objectName: String = inputs[0].get();
|
var objectName: String = inputs[0].get();
|
||||||
|
|
||||||
if (property0 == null || property0 == iron.Scene.active.raw.name) {
|
|
||||||
return iron.Scene.active.getChild(objectName);
|
return iron.Scene.active.getChild(objectName);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if arm_json
|
|
||||||
property0 += ".json";
|
|
||||||
#elseif arm_compress
|
|
||||||
property0 += ".lz4";
|
|
||||||
#end
|
|
||||||
|
|
||||||
var outObj: Null<Object> = null;
|
|
||||||
|
|
||||||
// Create the object in the active scene if it is from an inactive scene
|
|
||||||
iron.data.Data.getSceneRaw(property0, (rawScene: TSceneFormat) -> {
|
|
||||||
var objData: Null<TObj> = null;
|
|
||||||
|
|
||||||
for (o in rawScene.objects) {
|
|
||||||
if (o.name == objectName) {
|
|
||||||
objData = o;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (objData == null) return;
|
|
||||||
|
|
||||||
iron.Scene.active.createObject(objData, rawScene, null, null, (newObj: Object) -> {
|
|
||||||
outObj = newObj;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return outObj;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,19 +6,21 @@ class GetSystemName extends LogicNode {
|
||||||
super(tree);
|
super(tree);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function equalsCI(a : String, b : String) return a.toLowerCase() == b.toLowerCase();
|
|
||||||
|
|
||||||
override function get(from: Int): Dynamic {
|
override function get(from: Int): Dynamic {
|
||||||
|
var systemName: String = kha.System.systemId;
|
||||||
|
|
||||||
return switch (from) {
|
return switch (from) {
|
||||||
case 0: systemName;
|
case 0: systemName;
|
||||||
case 1: equalsCI(kha.System.systemId, 'Windows');
|
case 1: equalsCI(systemName, 'Windows');
|
||||||
case 2: equalsCI(kha.System.systemId, 'Linux');
|
case 2: equalsCI(systemName, 'Linux');
|
||||||
case 3: equalsCI(kha.System.systemId, 'Mac');
|
case 3: equalsCI(systemName, 'Mac');
|
||||||
case 4: equalsCI(kha.System.systemId, 'HTML5');
|
case 4: equalsCI(systemName, 'HTML5');
|
||||||
case 5: equalsCI(kha.System.systemId, 'Android');
|
case 5: equalsCI(systemName, 'Android');
|
||||||
default: null;
|
default: null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline function equalsCI(a: String, b: String): Bool {
|
||||||
|
return a.toLowerCase() == b.toLowerCase();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,12 +16,12 @@ class GetTraitNameNode extends LogicNode {
|
||||||
case 0: {
|
case 0: {
|
||||||
// Check CanvasScript
|
// Check CanvasScript
|
||||||
var cname = cast Type.resolveClass("armory.trait.internal.CanvasScript");
|
var cname = cast Type.resolveClass("armory.trait.internal.CanvasScript");
|
||||||
if (Std.is(trait, cname)) {
|
if (Std.isOfType(trait, cname)) {
|
||||||
return trait.cnvName;
|
return trait.cnvName;
|
||||||
}
|
}
|
||||||
// Check WasmScript
|
// Check WasmScript
|
||||||
var cname = cast Type.resolveClass("armory.trait.internal.WasmScript");
|
var cname = cast Type.resolveClass("armory.trait.internal.WasmScript");
|
||||||
if (Std.is(trait, cname)) {
|
if (Std.isOfType(trait, cname)) {
|
||||||
return trait.wasmName;
|
return trait.wasmName;
|
||||||
}
|
}
|
||||||
// Other
|
// Other
|
||||||
|
|
|
@ -1,30 +1,135 @@
|
||||||
package armory.logicnode;
|
package armory.logicnode;
|
||||||
|
|
||||||
|
#if arm_patch @:keep @:keepSub #end
|
||||||
class LogicNode {
|
class LogicNode {
|
||||||
|
|
||||||
var tree: LogicTree;
|
var tree: LogicTree;
|
||||||
var inputs: Array<LogicNodeInput> = [];
|
var inputs: Array<LogicNodeLink> = [];
|
||||||
var outputs: Array<Array<LogicNode>> = [];
|
var outputs: Array<Array<LogicNodeLink>> = [];
|
||||||
|
|
||||||
#if arm_debug
|
#if (arm_debug || arm_patch)
|
||||||
public var name = "";
|
public var name = "";
|
||||||
|
|
||||||
|
#if (arm_debug)
|
||||||
public function watch(b: Bool) { // Watch in debug console
|
public function watch(b: Bool) { // Watch in debug console
|
||||||
var nodes = armory.trait.internal.DebugConsole.watchNodes;
|
var nodes = armory.trait.internal.DebugConsole.watchNodes;
|
||||||
b ? nodes.push(this) : nodes.remove(this);
|
b ? nodes.push(this) : nodes.remove(this);
|
||||||
}
|
}
|
||||||
#end
|
#end
|
||||||
|
#end
|
||||||
|
|
||||||
public function new(tree: LogicTree) {
|
public function new(tree: LogicTree) {
|
||||||
this.tree = tree;
|
this.tree = tree;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function addInput(node: LogicNode, from: Int) {
|
/**
|
||||||
inputs.push(new LogicNodeInput(node, from));
|
Resize the inputs array to a given size to minimize dynamic
|
||||||
|
reallocation and over-allocation later.
|
||||||
|
**/
|
||||||
|
inline function preallocInputs(amount: Int) {
|
||||||
|
this.inputs.resize(amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function addOutputs(nodes: Array<LogicNode>) {
|
/**
|
||||||
outputs.push(nodes);
|
Resize the outputs array to a given size to minimize dynamic
|
||||||
|
reallocation and over-allocation later.
|
||||||
|
**/
|
||||||
|
inline function preallocOutputs(amount: Int) {
|
||||||
|
this.outputs.resize(amount);
|
||||||
|
for (i in 0...outputs.length) {
|
||||||
|
outputs[i] = [];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Add a link between to nodes to the tree.
|
||||||
|
**/
|
||||||
|
public static function addLink(fromNode: LogicNode, toNode: LogicNode, fromIndex: Int, toIndex: Int): LogicNodeLink {
|
||||||
|
var link = new LogicNodeLink(fromNode, toNode, fromIndex, toIndex);
|
||||||
|
|
||||||
|
if (toNode.inputs.length <= toIndex) {
|
||||||
|
toNode.inputs.resize(toIndex + 1);
|
||||||
|
}
|
||||||
|
toNode.inputs[toIndex] = link;
|
||||||
|
|
||||||
|
var fromNodeOuts = fromNode.outputs;
|
||||||
|
var outLen = fromNodeOuts.length;
|
||||||
|
if (outLen <= fromIndex) {
|
||||||
|
fromNodeOuts.resize(fromIndex + 1);
|
||||||
|
|
||||||
|
// Initialize with empty arrays
|
||||||
|
for (i in outLen...fromIndex + 1) {
|
||||||
|
fromNodeOuts[i] = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fromNodeOuts[fromIndex].push(link);
|
||||||
|
|
||||||
|
return link;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if arm_patch
|
||||||
|
/**
|
||||||
|
Removes a link from the tree.
|
||||||
|
**/
|
||||||
|
static function removeLink(link: LogicNodeLink) {
|
||||||
|
link.fromNode.outputs[link.fromIndex].remove(link);
|
||||||
|
|
||||||
|
// Reuse the same link and connect a default input node to it.
|
||||||
|
// That's why this function is only available in arm_patch mode, we need
|
||||||
|
// access to the link's type and value.
|
||||||
|
link.fromNode = LogicNode.createSocketDefaultNode(link.toNode.tree, link.toType, link.toValue);
|
||||||
|
link.fromIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Removes all inputs and their links from this node.
|
||||||
|
Warning: this function changes the amount of node inputs to 0!
|
||||||
|
**/
|
||||||
|
function clearInputs() {
|
||||||
|
for (link in inputs) {
|
||||||
|
link.fromNode.outputs[link.fromIndex].remove(link);
|
||||||
|
}
|
||||||
|
inputs.resize(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Removes all outputs and their links from this node.
|
||||||
|
Warning: this function changes the amount of node inputs to 0!
|
||||||
|
**/
|
||||||
|
function clearOutputs() {
|
||||||
|
for (links in outputs) {
|
||||||
|
for (link in links) {
|
||||||
|
var defaultNode = LogicNode.createSocketDefaultNode(tree, link.toType, link.toValue);
|
||||||
|
link.fromNode = defaultNode;
|
||||||
|
link.fromIndex = 0;
|
||||||
|
defaultNode.outputs[0] = [link];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
outputs.resize(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Creates a default node for a socket so that get() and set() can be
|
||||||
|
used without null checks.
|
||||||
|
Loosely equivalent to `make_logic.build_default_node()` in Python.
|
||||||
|
**/
|
||||||
|
static inline function createSocketDefaultNode(tree: LogicTree, socketType: String, value: Dynamic): LogicNode {
|
||||||
|
// Make sure to not add these nodes to the LogicTree.nodes array as they
|
||||||
|
// won't be garbage collected then if unlinked later.
|
||||||
|
return switch (socketType) {
|
||||||
|
case "VECTOR": new armory.logicnode.VectorNode(tree, value[0], value[1], value[2]);
|
||||||
|
case "RGBA": new armory.logicnode.ColorNode(tree, value[0], value[1], value[2], value[3]);
|
||||||
|
case "RGB": new armory.logicnode.ColorNode(tree, value[0], value[1], value[2]);
|
||||||
|
case "VALUE": new armory.logicnode.FloatNode(tree, value);
|
||||||
|
case "INT": new armory.logicnode.IntegerNode(tree, value);
|
||||||
|
case "BOOLEAN": new armory.logicnode.BooleanNode(tree, value);
|
||||||
|
case "STRING": new armory.logicnode.StringNode(tree, value);
|
||||||
|
case "NONE": new armory.logicnode.NullNode(tree);
|
||||||
|
case "OBJECT": new armory.logicnode.ObjectNode(tree, value);
|
||||||
|
default: new armory.logicnode.DynamicNode(tree, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#end
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Called when this node is activated.
|
Called when this node is activated.
|
||||||
|
@ -38,42 +143,45 @@ class LogicNode {
|
||||||
**/
|
**/
|
||||||
function runOutput(i: Int) {
|
function runOutput(i: Int) {
|
||||||
if (i >= outputs.length) return;
|
if (i >= outputs.length) return;
|
||||||
for (o in outputs[i]) {
|
for (outLink in outputs[i]) {
|
||||||
// Check which input activated the node
|
outLink.toNode.run(outLink.toIndex);
|
||||||
for (j in 0...o.inputs.length) {
|
|
||||||
if (o.inputs[j].node == this) {
|
|
||||||
o.run(j);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@:allow(armory.logicnode.LogicNodeInput)
|
@:allow(armory.logicnode.LogicNodeLink)
|
||||||
function get(from: Int): Dynamic { return this; }
|
function get(from: Int): Dynamic { return this; }
|
||||||
|
|
||||||
@:allow(armory.logicnode.LogicNodeInput)
|
@:allow(armory.logicnode.LogicNodeLink)
|
||||||
function set(value: Dynamic) {}
|
function set(value: Dynamic) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
class LogicNodeInput {
|
@:allow(armory.logicnode.LogicNode)
|
||||||
|
@:allow(armory.logicnode.LogicTree)
|
||||||
|
class LogicNodeLink {
|
||||||
|
|
||||||
@:allow(armory.logicnode.LogicNode)
|
var fromNode: LogicNode;
|
||||||
var node: LogicNode;
|
var toNode: LogicNode;
|
||||||
var from: Int; // Socket index
|
var fromIndex: Int;
|
||||||
|
var toIndex: Int;
|
||||||
|
|
||||||
public function new(node: LogicNode, from: Int) {
|
#if arm_patch
|
||||||
this.node = node;
|
var fromType: String;
|
||||||
this.from = from;
|
var toType: String;
|
||||||
|
var toValue: Dynamic;
|
||||||
|
#end
|
||||||
|
|
||||||
|
inline function new(fromNode: LogicNode, toNode: LogicNode, fromIndex: Int, toIndex: Int) {
|
||||||
|
this.fromNode = fromNode;
|
||||||
|
this.toNode = toNode;
|
||||||
|
this.fromIndex = fromIndex;
|
||||||
|
this.toIndex = toIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
@:allow(armory.logicnode.LogicNode)
|
inline function get(): Dynamic {
|
||||||
function get(): Dynamic {
|
return fromNode.get(fromIndex);
|
||||||
return node.get(from);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@:allow(armory.logicnode.LogicNode)
|
inline function set(value: Dynamic) {
|
||||||
function set(value: Dynamic) {
|
fromNode.set(value);
|
||||||
node.set(value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,26 @@ package armory.logicnode;
|
||||||
|
|
||||||
class LogicTree extends iron.Trait {
|
class LogicTree extends iron.Trait {
|
||||||
|
|
||||||
|
#if arm_patch
|
||||||
|
/**
|
||||||
|
Stores all trait instances of the tree via its name.
|
||||||
|
**/
|
||||||
|
public static var nodeTrees = new Map<String, Array<LogicTree>>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
[node name => logic node] for later node replacement for live patching.
|
||||||
|
**/
|
||||||
|
public var nodes: Map<String, LogicNode>;
|
||||||
|
#end
|
||||||
|
|
||||||
public var loopBreak = false; // Trigger break from loop nodes
|
public var loopBreak = false; // Trigger break from loop nodes
|
||||||
|
|
||||||
public function new() {
|
public function new() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
#if arm_patch
|
||||||
|
nodes = new Map<String, LogicNode>();
|
||||||
|
#end
|
||||||
}
|
}
|
||||||
|
|
||||||
public function add() {}
|
public function add() {}
|
||||||
|
|
|
@ -3,7 +3,7 @@ package armory.logicnode;
|
||||||
class MathNode extends LogicNode {
|
class MathNode extends LogicNode {
|
||||||
|
|
||||||
public var property0: String; // Operation
|
public var property0: String; // Operation
|
||||||
public var property1: String; // Clamp
|
public var property1: Bool; // Clamp
|
||||||
|
|
||||||
public function new(tree: LogicTree) {
|
public function new(tree: LogicTree) {
|
||||||
super(tree);
|
super(tree);
|
||||||
|
@ -80,7 +80,7 @@ class MathNode extends LogicNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Clamp
|
// Clamp
|
||||||
if (property1 == "true") r = r < 0.0 ? 0.0 : (r > 1.0 ? 1.0 : r);
|
if (property1) r = r < 0.0 ? 0.0 : (r > 1.0 ? 1.0 : r);
|
||||||
|
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,31 @@ package armory.logicnode;
|
||||||
|
|
||||||
class MergeNode extends LogicNode {
|
class MergeNode extends LogicNode {
|
||||||
|
|
||||||
|
/** Execution mode. **/
|
||||||
|
public var property0: String;
|
||||||
|
|
||||||
|
var lastInputIndex = -1;
|
||||||
|
|
||||||
public function new(tree: LogicTree) {
|
public function new(tree: LogicTree) {
|
||||||
super(tree);
|
super(tree);
|
||||||
|
tree.notifyOnLateUpdate(lateUpdate);
|
||||||
}
|
}
|
||||||
|
|
||||||
override function run(from: Int) {
|
override function run(from: Int) {
|
||||||
|
// Check if there already were executions on the same frame
|
||||||
|
if (lastInputIndex != -1 && property0 == "once_per_frame") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastInputIndex = from;
|
||||||
runOutput(0);
|
runOutput(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override function get(from: Int): Dynamic {
|
||||||
|
return lastInputIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
function lateUpdate() {
|
||||||
|
lastInputIndex = -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ class MixNode extends LogicNode {
|
||||||
|
|
||||||
public var property0: String; // Type
|
public var property0: String; // Type
|
||||||
public var property1: String; // Ease
|
public var property1: String; // Ease
|
||||||
public var property2: String; // Clamp
|
public var property2: Bool; // Clamp
|
||||||
|
|
||||||
var ease: Float->Float = null;
|
var ease: Float->Float = null;
|
||||||
|
|
||||||
|
@ -50,7 +50,9 @@ class MixNode extends LogicNode {
|
||||||
var v2: Float = inputs[2].get();
|
var v2: Float = inputs[2].get();
|
||||||
var f = v1 + (v2 - v1) * ease(k);
|
var f = v1 + (v2 - v1) * ease(k);
|
||||||
|
|
||||||
if (property2 == "true") f = f < 0 ? 0 : f > 1 ? 1 : f;
|
// Clamp
|
||||||
|
if (property2) f = f < 0 ? 0 : f > 1 ? 1 : f;
|
||||||
|
|
||||||
return f;
|
return f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ class ObjectNode extends LogicNode {
|
||||||
override function set(value: Dynamic) {
|
override function set(value: Dynamic) {
|
||||||
if (inputs.length > 0) inputs[0].set(value);
|
if (inputs.length > 0) inputs[0].set(value);
|
||||||
else {
|
else {
|
||||||
objectName = value.name;
|
objectName = value != null ? value.name : "";
|
||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import armory.trait.internal.CanvasScript;
|
||||||
import iron.Scene;
|
import iron.Scene;
|
||||||
|
|
||||||
#if arm_ui
|
#if arm_ui
|
||||||
import zui.Canvas.Anchor;
|
import armory.ui.Canvas.Anchor;
|
||||||
#end
|
#end
|
||||||
|
|
||||||
class OnCanvasElementNode extends LogicNode {
|
class OnCanvasElementNode extends LogicNode {
|
||||||
|
|
35
Sources/armory/logicnode/OnInputMapNode.hx
Normal file
35
Sources/armory/logicnode/OnInputMapNode.hx
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
package armory.logicnode;
|
||||||
|
|
||||||
|
import armory.system.InputMap;
|
||||||
|
|
||||||
|
class OnInputMapNode extends LogicNode {
|
||||||
|
|
||||||
|
var inputMap: Null<InputMap>;
|
||||||
|
|
||||||
|
public function new(tree: LogicTree) {
|
||||||
|
super(tree);
|
||||||
|
|
||||||
|
tree.notifyOnUpdate(update);
|
||||||
|
}
|
||||||
|
|
||||||
|
function update() {
|
||||||
|
var i = inputs[0].get();
|
||||||
|
|
||||||
|
inputMap = InputMap.getInputMap(i);
|
||||||
|
|
||||||
|
if (inputMap != null) {
|
||||||
|
if (inputMap.started()) {
|
||||||
|
runOutput(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inputMap.released()) {
|
||||||
|
runOutput(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override function get(from: Int): Dynamic {
|
||||||
|
if (from == 2) return inputMap.value();
|
||||||
|
else return inputMap.lastKeyPressed;
|
||||||
|
}
|
||||||
|
}
|
22
Sources/armory/logicnode/OncePerFrameNode.hx
Normal file
22
Sources/armory/logicnode/OncePerFrameNode.hx
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
package armory.logicnode;
|
||||||
|
|
||||||
|
class OncePerFrameNode extends LogicNode {
|
||||||
|
|
||||||
|
var c = false;
|
||||||
|
|
||||||
|
public function new(tree: LogicTree) {
|
||||||
|
super(tree);
|
||||||
|
tree.notifyOnUpdate(update);
|
||||||
|
}
|
||||||
|
|
||||||
|
override function run(from: Int) {
|
||||||
|
if(c) {
|
||||||
|
c = false;
|
||||||
|
runOutput(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function update() {
|
||||||
|
c = true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,7 +8,7 @@ class PauseTraitNode extends LogicNode {
|
||||||
|
|
||||||
override function run(from: Int) {
|
override function run(from: Int) {
|
||||||
var trait: Dynamic = inputs[1].get();
|
var trait: Dynamic = inputs[1].get();
|
||||||
if (trait == null || !Std.is(trait, LogicTree)) return;
|
if (trait == null || !Std.isOfType(trait, LogicTree)) return;
|
||||||
|
|
||||||
cast(trait, LogicTree).pause();
|
cast(trait, LogicTree).pause();
|
||||||
|
|
||||||
|
|
|
@ -3,50 +3,39 @@ package armory.logicnode;
|
||||||
#if arm_physics
|
#if arm_physics
|
||||||
import armory.trait.physics.bullet.PhysicsConstraint.ConstraintAxis;
|
import armory.trait.physics.bullet.PhysicsConstraint.ConstraintAxis;
|
||||||
#end
|
#end
|
||||||
import iron.object.Object;
|
|
||||||
|
|
||||||
class PhysicsConstraintNode extends LogicNode {
|
class PhysicsConstraintNode extends LogicNode {
|
||||||
|
|
||||||
public var property0: String;//Linear or Angular
|
public var property0: String; //Linear or Angular
|
||||||
public var property1: String;//Axis
|
public var property1: String; //Axis
|
||||||
public var property2: String;//Is a spring
|
public var property2: Bool; //Is a spring
|
||||||
public var value1: Float;//Lower limit or Spring Stiffness
|
|
||||||
public var value2: Float;//Upper limit or Spring Damping
|
#if arm_physics
|
||||||
|
public var value1: Float; //Lower limit or Spring Stiffness
|
||||||
|
public var value2: Float; //Upper limit or Spring Damping
|
||||||
public var isAngular: Bool;
|
public var isAngular: Bool;
|
||||||
public var axis: ConstraintAxis;
|
public var axis: ConstraintAxis;
|
||||||
public var isSpring: Bool;
|
public var isSpring: Bool;
|
||||||
|
#end
|
||||||
|
|
||||||
public function new(tree: LogicTree) {
|
public function new(tree: LogicTree) {
|
||||||
super(tree);
|
super(tree);
|
||||||
}
|
}
|
||||||
|
|
||||||
override function get(from: Int): PhysicsConstraintNode {
|
override function get(from: Int): PhysicsConstraintNode {
|
||||||
|
#if arm_physics
|
||||||
value1 = inputs[0].get();
|
value1 = inputs[0].get();
|
||||||
value2 = inputs[1].get();
|
value2 = inputs[1].get();
|
||||||
|
|
||||||
if(property0 == 'Linear') {
|
isAngular = property0 != "Linear";
|
||||||
isAngular = false;
|
isSpring = property2;
|
||||||
}
|
|
||||||
else{
|
|
||||||
isAngular = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(property2 == 'true'){
|
switch (property1) {
|
||||||
isSpring = true;
|
case "X": axis = X;
|
||||||
|
case "Y": axis = Y;
|
||||||
|
case "Z": axis = Z;
|
||||||
}
|
}
|
||||||
else {
|
#end
|
||||||
isSpring = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (property1){
|
|
||||||
case 'X':
|
|
||||||
axis = X;
|
|
||||||
case 'Y':
|
|
||||||
axis = Y;
|
|
||||||
case 'Z':
|
|
||||||
axis = Z;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,10 +11,10 @@ class QuaternionNode extends LogicNode {
|
||||||
super(tree);
|
super(tree);
|
||||||
|
|
||||||
if (x != null) {
|
if (x != null) {
|
||||||
addInput(new FloatNode(tree, x), 0);
|
LogicNode.addLink(new FloatNode(tree, x), this, 0, 0);
|
||||||
addInput(new FloatNode(tree, y), 0);
|
LogicNode.addLink(new FloatNode(tree, y), this, 0, 1);
|
||||||
addInput(new FloatNode(tree, z), 0);
|
LogicNode.addLink(new FloatNode(tree, z), this, 0, 2);
|
||||||
addInput(new FloatNode(tree, w), 0);
|
LogicNode.addLink(new FloatNode(tree, w), this, 0, 3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
19
Sources/armory/logicnode/RemoveInputMapKeyNode.hx
Normal file
19
Sources/armory/logicnode/RemoveInputMapKeyNode.hx
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
package armory.logicnode;
|
||||||
|
|
||||||
|
import armory.system.InputMap;
|
||||||
|
|
||||||
|
class RemoveInputMapKeyNode extends LogicNode {
|
||||||
|
|
||||||
|
public function new(tree: LogicTree) {
|
||||||
|
super(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
override function run(from: Int) {
|
||||||
|
var inputMap = inputs[1].get();
|
||||||
|
var key = inputs[2].get();
|
||||||
|
|
||||||
|
if (InputMap.removeInputMapKey(inputMap, key)) {
|
||||||
|
runOutput(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,7 +8,7 @@ class ResumeTraitNode extends LogicNode {
|
||||||
|
|
||||||
override function run(from: Int) {
|
override function run(from: Int) {
|
||||||
var trait: Dynamic = inputs[1].get();
|
var trait: Dynamic = inputs[1].get();
|
||||||
if (trait == null || !Std.is(trait, LogicTree)) return;
|
if (trait == null || !Std.isOfType(trait, LogicTree)) return;
|
||||||
|
|
||||||
cast(trait, LogicTree).resume();
|
cast(trait, LogicTree).resume();
|
||||||
|
|
||||||
|
|
37
Sources/armory/logicnode/SelectNode.hx
Normal file
37
Sources/armory/logicnode/SelectNode.hx
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
package armory.logicnode;
|
||||||
|
|
||||||
|
class SelectNode extends LogicNode {
|
||||||
|
|
||||||
|
/** Execution mode. **/
|
||||||
|
public var property0: String;
|
||||||
|
|
||||||
|
var value: Dynamic = null;
|
||||||
|
|
||||||
|
public function new(tree: LogicTree) {
|
||||||
|
super(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
override function run(from: Int) {
|
||||||
|
// Get value according to the activated input (run() can only be called
|
||||||
|
// if the execution mode is from_input).
|
||||||
|
value = inputs[from + Std.int(inputs.length / 2)].get();
|
||||||
|
|
||||||
|
runOutput(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
override function get(from: Int): Dynamic {
|
||||||
|
if (property0 == "from_index") {
|
||||||
|
var index = inputs[0].get() + 2;
|
||||||
|
|
||||||
|
// Return default value for invalid index
|
||||||
|
if (index < 2 || index >= inputs.length) {
|
||||||
|
return inputs[1].get();
|
||||||
|
}
|
||||||
|
|
||||||
|
return inputs[index].get();
|
||||||
|
}
|
||||||
|
|
||||||
|
// from_input
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
35
Sources/armory/logicnode/SetBoneFkIkOnlyNode.hx
Normal file
35
Sources/armory/logicnode/SetBoneFkIkOnlyNode.hx
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
package armory.logicnode;
|
||||||
|
|
||||||
|
import iron.math.Quat;
|
||||||
|
import iron.math.Vec4;
|
||||||
|
import iron.object.Object;
|
||||||
|
import iron.object.BoneAnimation;
|
||||||
|
|
||||||
|
class SetBoneFkIkOnlyNode extends LogicNode {
|
||||||
|
|
||||||
|
public function new(tree: LogicTree) {
|
||||||
|
super(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
override function run(from: Int) {
|
||||||
|
#if arm_skin
|
||||||
|
|
||||||
|
var object: Object = inputs[1].get();
|
||||||
|
var boneName: String = inputs[2].get();
|
||||||
|
var fk_ik_only: Bool = inputs[3].get();
|
||||||
|
|
||||||
|
if (object == null) return;
|
||||||
|
var anim = object.animation != null ? cast(object.animation, BoneAnimation) : null;
|
||||||
|
if (anim == null) anim = object.getParentArmature(object.name);
|
||||||
|
|
||||||
|
// Get bone in armature
|
||||||
|
var bone = anim.getBone(boneName);
|
||||||
|
|
||||||
|
//Set bone animated by FK or IK only
|
||||||
|
bone.is_ik_fk_only = fk_ik_only;
|
||||||
|
|
||||||
|
runOutput(0);
|
||||||
|
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
}
|
46
Sources/armory/logicnode/SetInputMapKeyNode.hx
Normal file
46
Sources/armory/logicnode/SetInputMapKeyNode.hx
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
package armory.logicnode;
|
||||||
|
|
||||||
|
import armory.system.InputMap;
|
||||||
|
|
||||||
|
class SetInputMapKeyNode extends LogicNode {
|
||||||
|
|
||||||
|
public var property0: String;
|
||||||
|
|
||||||
|
public function new(tree: LogicTree) {
|
||||||
|
super(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
override function run(from: Int) {
|
||||||
|
var inputMap = inputs[1].get();
|
||||||
|
var key = inputs[2].get();
|
||||||
|
var scale = inputs[3].get();
|
||||||
|
var deadzone = inputs[4].get();
|
||||||
|
var index = inputs[5].get();
|
||||||
|
|
||||||
|
var i = InputMap.getInputMap(inputMap);
|
||||||
|
|
||||||
|
if (i == null) {
|
||||||
|
i = InputMap.addInputMap(inputMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
var k = InputMap.getInputMapKey(inputMap, key);
|
||||||
|
|
||||||
|
if (k == null) {
|
||||||
|
switch(property0) {
|
||||||
|
case "keyboard": k = i.addKeyboard(key, scale);
|
||||||
|
case "mouse": k = i.addMouse(key, scale, deadzone);
|
||||||
|
case "gamepad": {
|
||||||
|
k = i.addGamepad(key, scale, deadzone);
|
||||||
|
k.setIndex(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
k.scale = scale;
|
||||||
|
k.deadzone = deadzone;
|
||||||
|
k.setIndex(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
runOutput(0);
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,9 +13,21 @@ class SetLocationNode extends LogicNode {
|
||||||
override function run(from: Int) {
|
override function run(from: Int) {
|
||||||
var object: Object = inputs[1].get();
|
var object: Object = inputs[1].get();
|
||||||
var vec: Vec4 = inputs[2].get();
|
var vec: Vec4 = inputs[2].get();
|
||||||
|
var relative: Bool = inputs[3].get();
|
||||||
|
|
||||||
if (object == null || vec == null) return;
|
if (object == null || vec == null) return;
|
||||||
|
|
||||||
|
if (!relative && object.parent != null) {
|
||||||
|
var loc = vec.clone();
|
||||||
|
loc.sub(object.parent.transform.world.getLoc()); // Remove parent location influence
|
||||||
|
|
||||||
|
// Convert vec to parent local space
|
||||||
|
var dotX = loc.dot(object.parent.transform.right());
|
||||||
|
var dotY = loc.dot(object.parent.transform.look());
|
||||||
|
var dotZ = loc.dot(object.parent.transform.up());
|
||||||
|
vec.set(dotX, dotY, dotZ);
|
||||||
|
}
|
||||||
|
|
||||||
object.transform.loc.setFrom(vec);
|
object.transform.loc.setFrom(vec);
|
||||||
object.transform.buildMatrix();
|
object.transform.buildMatrix();
|
||||||
|
|
||||||
|
|
|
@ -1,40 +1,40 @@
|
||||||
package armory.logicnode;
|
package armory.logicnode;
|
||||||
|
|
||||||
|
import iron.Scene;
|
||||||
import iron.data.MaterialData;
|
import iron.data.MaterialData;
|
||||||
import iron.object.Object;
|
import iron.object.Object;
|
||||||
|
import armory.trait.internal.UniformsManager;
|
||||||
|
|
||||||
class SetMaterialImageParamNode extends LogicNode {
|
class SetMaterialImageParamNode extends LogicNode {
|
||||||
|
|
||||||
static var registered = false;
|
|
||||||
static var map = new Map<MaterialData, Map<String, kha.Image>>();
|
|
||||||
|
|
||||||
public function new(tree: LogicTree) {
|
public function new(tree: LogicTree) {
|
||||||
super(tree);
|
super(tree);
|
||||||
if (!registered) {
|
|
||||||
registered = true;
|
|
||||||
iron.object.Uniforms.externalTextureLinks.push(textureLink);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override function run(from: Int) {
|
override function run(from: Int) {
|
||||||
var mat = inputs[1].get();
|
var perObject: Null<Bool>;
|
||||||
if (mat == null) return;
|
|
||||||
var entry = map.get(mat);
|
var object = inputs[1].get();
|
||||||
if (entry == null) {
|
if(object == null) return;
|
||||||
entry = new Map();
|
|
||||||
map.set(mat, entry);
|
perObject = inputs[2].get();
|
||||||
|
if(perObject == null) perObject = false;
|
||||||
|
|
||||||
|
var mat = inputs[3].get();
|
||||||
|
if(mat == null) return;
|
||||||
|
|
||||||
|
if(! perObject){
|
||||||
|
UniformsManager.removeObjectFromMap(object, Texture);
|
||||||
|
object = Scene.active.root;
|
||||||
}
|
}
|
||||||
|
|
||||||
iron.data.Data.getImage(inputs[3].get(), function(image: kha.Image) {
|
var img = inputs[5].get();
|
||||||
entry.set(inputs[2].get(), image); // Node name, value
|
if(img == null) return;
|
||||||
|
iron.data.Data.getImage(img, function(image: kha.Image) {
|
||||||
|
UniformsManager.setTextureValue(mat, object, inputs[4].get(), image);
|
||||||
});
|
});
|
||||||
|
|
||||||
runOutput(0);
|
runOutput(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static function textureLink(object: Object, mat: MaterialData, link: String): kha.Image {
|
|
||||||
if (mat == null) return null;
|
|
||||||
var entry = map.get(mat);
|
|
||||||
if (entry == null) return null;
|
|
||||||
return entry.get(link);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,38 +1,35 @@
|
||||||
package armory.logicnode;
|
package armory.logicnode;
|
||||||
|
|
||||||
|
import iron.Scene;
|
||||||
import iron.math.Vec4;
|
import iron.math.Vec4;
|
||||||
import iron.data.MaterialData;
|
import iron.data.MaterialData;
|
||||||
import iron.object.Object;
|
import iron.object.Object;
|
||||||
|
import armory.trait.internal.UniformsManager;
|
||||||
|
|
||||||
class SetMaterialRgbParamNode extends LogicNode {
|
class SetMaterialRgbParamNode extends LogicNode {
|
||||||
|
|
||||||
static var registered = false;
|
|
||||||
static var map = new Map<MaterialData, Map<String, Vec4>>();
|
|
||||||
|
|
||||||
public function new(tree: LogicTree) {
|
public function new(tree: LogicTree) {
|
||||||
super(tree);
|
super(tree);
|
||||||
if (!registered) {
|
|
||||||
registered = true;
|
|
||||||
iron.object.Uniforms.externalVec3Links.push(vec3Link);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override function run(from: Int) {
|
override function run(from: Int) {
|
||||||
var mat = inputs[1].get();
|
var perObject: Null<Bool>;
|
||||||
if (mat == null) return;
|
|
||||||
var entry = map.get(mat);
|
var object = inputs[1].get();
|
||||||
if (entry == null) {
|
if(object == null) return;
|
||||||
entry = new Map();
|
|
||||||
map.set(mat, entry);
|
perObject = inputs[2].get();
|
||||||
}
|
if(perObject == null) perObject = false;
|
||||||
entry.set(inputs[2].get(), inputs[3].get()); // Node name, value
|
|
||||||
runOutput(0);
|
var mat = inputs[3].get();
|
||||||
|
if(mat == null) return;
|
||||||
|
|
||||||
|
if(! perObject){
|
||||||
|
UniformsManager.removeObjectFromMap(object, Vector);
|
||||||
|
object = Scene.active.root;
|
||||||
}
|
}
|
||||||
|
|
||||||
static function vec3Link(object: Object, mat: MaterialData, link: String): iron.math.Vec4 {
|
UniformsManager.setVec3Value(mat, object, inputs[4].get(), inputs[5].get());
|
||||||
if (mat == null) return null;
|
runOutput(0);
|
||||||
var entry = map.get(mat);
|
|
||||||
if (entry == null) return null;
|
|
||||||
return entry.get(link);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,37 +1,35 @@
|
||||||
package armory.logicnode;
|
package armory.logicnode;
|
||||||
|
|
||||||
|
import iron.Scene;
|
||||||
import iron.data.MaterialData;
|
import iron.data.MaterialData;
|
||||||
import iron.object.Object;
|
import iron.object.Object;
|
||||||
|
import armory.trait.internal.UniformsManager;
|
||||||
|
|
||||||
class SetMaterialValueParamNode extends LogicNode {
|
class SetMaterialValueParamNode extends LogicNode {
|
||||||
|
|
||||||
static var registered = false;
|
|
||||||
static var map = new Map<MaterialData, Map<String, Null<kha.FastFloat>>>();
|
|
||||||
|
|
||||||
public function new(tree: LogicTree) {
|
public function new(tree: LogicTree) {
|
||||||
super(tree);
|
super(tree);
|
||||||
if (!registered) {
|
|
||||||
registered = true;
|
|
||||||
iron.object.Uniforms.externalFloatLinks.push(floatLink);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override function run(from: Int) {
|
override function run(from: Int) {
|
||||||
var mat = inputs[1].get();
|
var perObject: Null<Bool>;
|
||||||
if (mat == null) return;
|
|
||||||
var entry = map.get(mat);
|
var object = inputs[1].get();
|
||||||
if (entry == null) {
|
if(object == null) return;
|
||||||
entry = new Map();
|
|
||||||
map.set(mat, entry);
|
perObject = inputs[2].get();
|
||||||
|
if(perObject == null) perObject = false;
|
||||||
|
|
||||||
|
var mat = inputs[3].get();
|
||||||
|
if(mat == null) return;
|
||||||
|
|
||||||
|
if(! perObject){
|
||||||
|
UniformsManager.removeObjectFromMap(object, Float);
|
||||||
|
object = Scene.active.root;
|
||||||
}
|
}
|
||||||
entry.set(inputs[2].get(), inputs[3].get()); // Node name, value
|
|
||||||
|
UniformsManager.setFloatValue(mat, object, inputs[4].get(), inputs[5].get());
|
||||||
runOutput(0);
|
runOutput(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static function floatLink(object: Object, mat: MaterialData, link: String): Null<kha.FastFloat> {
|
|
||||||
if (mat == null) return null;
|
|
||||||
var entry = map.get(mat);
|
|
||||||
if (entry == null) return null;
|
|
||||||
return entry.get(link);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,8 +14,9 @@ class SetParentNode extends LogicNode {
|
||||||
|
|
||||||
var parent: Object;
|
var parent: Object;
|
||||||
var isUnparent = false;
|
var isUnparent = false;
|
||||||
if (Std.is(inputs[2].node, ObjectNode)) {
|
|
||||||
var parentNode = cast(inputs[2].node, ObjectNode);
|
if (Std.isOfType(inputs[2].fromNode, ObjectNode)) {
|
||||||
|
var parentNode = cast(inputs[2].fromNode, ObjectNode);
|
||||||
isUnparent = parentNode.objectName == "";
|
isUnparent = parentNode.objectName == "";
|
||||||
}
|
}
|
||||||
if (isUnparent) parent = iron.Scene.active.root;
|
if (isUnparent) parent = iron.Scene.active.root;
|
||||||
|
|
|
@ -10,7 +10,7 @@ class SetTraitPausedNode extends LogicNode {
|
||||||
var trait: Dynamic = inputs[1].get();
|
var trait: Dynamic = inputs[1].get();
|
||||||
var paused: Bool = inputs[2].get();
|
var paused: Bool = inputs[2].get();
|
||||||
|
|
||||||
if (trait == null || !Std.is(trait, LogicTree)) return;
|
if (trait == null || !Std.isOfType(trait, LogicTree)) return;
|
||||||
|
|
||||||
paused ? cast(trait, LogicTree).pause() : cast(trait, LogicTree).resume();
|
paused ? cast(trait, LogicTree).pause() : cast(trait, LogicTree).resume();
|
||||||
|
|
||||||
|
|
72
Sources/armory/logicnode/SpawnObjectByNameNode.hx
Normal file
72
Sources/armory/logicnode/SpawnObjectByNameNode.hx
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
package armory.logicnode;
|
||||||
|
|
||||||
|
import iron.data.SceneFormat.TSceneFormat;
|
||||||
|
import iron.data.Data;
|
||||||
|
import iron.object.Object;
|
||||||
|
import iron.math.Mat4;
|
||||||
|
import armory.trait.physics.RigidBody;
|
||||||
|
|
||||||
|
class SpawnObjectByNameNode extends LogicNode {
|
||||||
|
|
||||||
|
var object: Object;
|
||||||
|
var matrices: Array<Mat4> = [];
|
||||||
|
|
||||||
|
/** Scene from which to take the object **/
|
||||||
|
public var property0: Null<String>;
|
||||||
|
|
||||||
|
public function new(tree: LogicTree) {
|
||||||
|
super(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
override function run(from: Int) {
|
||||||
|
var objectName = inputs[1].get();
|
||||||
|
if (objectName == null) return;
|
||||||
|
|
||||||
|
#if arm_json
|
||||||
|
property0 += ".json";
|
||||||
|
#elseif arm_compress
|
||||||
|
property0 += ".lz4";
|
||||||
|
#end
|
||||||
|
|
||||||
|
var m: Mat4 = inputs[2].get();
|
||||||
|
matrices.push(m != null ? m.clone() : null);
|
||||||
|
var spawnChildren: Bool = inputs.length > 3 ? inputs[3].get() : true; // TODO
|
||||||
|
|
||||||
|
Data.getSceneRaw(property0, (rawScene: TSceneFormat) -> {
|
||||||
|
|
||||||
|
//Check if object with given name present in the specified scene
|
||||||
|
var objPresent: Bool = false;
|
||||||
|
|
||||||
|
for (o in rawScene.objects) {
|
||||||
|
if (o.name == objectName) {
|
||||||
|
objPresent = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (! objPresent) return;
|
||||||
|
|
||||||
|
//Spawn object if present
|
||||||
|
iron.Scene.active.spawnObject(objectName, null, function(o: Object) {
|
||||||
|
object = o;
|
||||||
|
var matrix = matrices.pop(); // Async spawn in a loop, order is non-stable
|
||||||
|
if (matrix != null) {
|
||||||
|
object.transform.setMatrix(matrix);
|
||||||
|
#if arm_physics
|
||||||
|
var rigidBody = object.getTrait(RigidBody);
|
||||||
|
if (rigidBody != null) {
|
||||||
|
object.transform.buildMatrix();
|
||||||
|
rigidBody.syncTransform();
|
||||||
|
}
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
object.visible = true;
|
||||||
|
runOutput(0);
|
||||||
|
}, spawnChildren, rawScene);
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
override function get(from: Int): Dynamic {
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,7 +7,7 @@ class VectorMixNode extends LogicNode {
|
||||||
|
|
||||||
public var property0: String; // Type
|
public var property0: String; // Type
|
||||||
public var property1: String; // Ease
|
public var property1: String; // Ease
|
||||||
public var property2: String; // Clamp
|
public var property2: Bool; // Clamp
|
||||||
|
|
||||||
var v = new Vec4();
|
var v = new Vec4();
|
||||||
|
|
||||||
|
@ -57,7 +57,7 @@ class VectorMixNode extends LogicNode {
|
||||||
v.y = v1.y + (v2.y - v1.y) * f;
|
v.y = v1.y + (v2.y - v1.y) * f;
|
||||||
v.z = v1.z + (v2.z - v1.z) * f;
|
v.z = v1.z + (v2.z - v1.z) * f;
|
||||||
|
|
||||||
if (property2 == "true") v.clamp(0, 1);
|
if (property2) v.clamp(0, 1);
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,9 +10,9 @@ class VectorNode extends LogicNode {
|
||||||
super(tree);
|
super(tree);
|
||||||
|
|
||||||
if (x != null) {
|
if (x != null) {
|
||||||
addInput(new FloatNode(tree, x), 0);
|
LogicNode.addLink(new FloatNode(tree, x), this, 0, 0);
|
||||||
addInput(new FloatNode(tree, y), 0);
|
LogicNode.addLink(new FloatNode(tree, y), this, 0, 1);
|
||||||
addInput(new FloatNode(tree, z), 0);
|
LogicNode.addLink(new FloatNode(tree, z), this, 0, 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,6 @@ package armory.logicnode;
|
||||||
import iron.object.Object;
|
import iron.object.Object;
|
||||||
import iron.math.Vec4;
|
import iron.math.Vec4;
|
||||||
|
|
||||||
using armory.object.TransformExtension;
|
|
||||||
|
|
||||||
class VectorToObjectOrientationNode extends LogicNode {
|
class VectorToObjectOrientationNode extends LogicNode {
|
||||||
|
|
||||||
public function new(tree: LogicTree) {
|
public function new(tree: LogicTree) {
|
||||||
|
@ -18,7 +16,7 @@ class VectorToObjectOrientationNode extends LogicNode {
|
||||||
|
|
||||||
if (object == null || vec == null) return null;
|
if (object == null || vec == null) return null;
|
||||||
|
|
||||||
return object.transform.worldVecToOrientation(vec);
|
return vec.applyQuat(object.transform.rot);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,6 @@ package armory.logicnode;
|
||||||
import iron.math.Vec4;
|
import iron.math.Vec4;
|
||||||
import iron.object.Object;
|
import iron.object.Object;
|
||||||
|
|
||||||
using armory.object.TransformExtension;
|
|
||||||
|
|
||||||
class WorldVectorToLocalSpaceNode extends LogicNode {
|
class WorldVectorToLocalSpaceNode extends LogicNode {
|
||||||
|
|
||||||
public function new(tree: LogicTree) {
|
public function new(tree: LogicTree) {
|
||||||
|
@ -17,7 +15,8 @@ class WorldVectorToLocalSpaceNode extends LogicNode {
|
||||||
|
|
||||||
if (object == null || worldVec == null) return null;
|
if (object == null || worldVec == null) return null;
|
||||||
|
|
||||||
var localVec: Vec4 = new Vec4();
|
var localVec = new Vec4();
|
||||||
|
localVec.sub(object.transform.world.getLoc());
|
||||||
|
|
||||||
localVec.x = worldVec.dot(object.transform.right());
|
localVec.x = worldVec.dot(object.transform.right());
|
||||||
localVec.y = worldVec.dot(object.transform.look());
|
localVec.y = worldVec.dot(object.transform.look());
|
||||||
|
|
|
@ -72,4 +72,13 @@ class Helper {
|
||||||
if (value <= leftMin) return rightMin;
|
if (value <= leftMin) return rightMin;
|
||||||
return map(value, leftMin, leftMax, rightMin, rightMax);
|
return map(value, leftMin, leftMax, rightMin, rightMax);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Return the sign of the given value represented as `1.0` (positive value)
|
||||||
|
or `-1.0` (negative value). The sign of `0` is `0`.
|
||||||
|
**/
|
||||||
|
public static inline function sign(value: Float): Float {
|
||||||
|
if (value == 0) return 0;
|
||||||
|
return (value < 0) ? -1.0 : 1.0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,32 +11,40 @@ class Uniforms {
|
||||||
|
|
||||||
public static function register() {
|
public static function register() {
|
||||||
iron.object.Uniforms.externalTextureLinks = [textureLink];
|
iron.object.Uniforms.externalTextureLinks = [textureLink];
|
||||||
iron.object.Uniforms.externalVec2Links = [];
|
iron.object.Uniforms.externalVec2Links = [vec2Link];
|
||||||
iron.object.Uniforms.externalVec3Links = [vec3Link];
|
iron.object.Uniforms.externalVec3Links = [vec3Link];
|
||||||
iron.object.Uniforms.externalVec4Links = [];
|
iron.object.Uniforms.externalVec4Links = [];
|
||||||
iron.object.Uniforms.externalFloatLinks = [floatLink];
|
iron.object.Uniforms.externalFloatLinks = [floatLink];
|
||||||
iron.object.Uniforms.externalIntLinks = [];
|
iron.object.Uniforms.externalIntLinks = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function textureLink(object: Object, mat: MaterialData, link: String): kha.Image {
|
public static function textureLink(object: Object, mat: MaterialData, link: String): Null<kha.Image> {
|
||||||
|
switch (link) {
|
||||||
|
case "_nishitaLUT": {
|
||||||
|
if (armory.renderpath.Nishita.data == null) armory.renderpath.Nishita.recompute(Scene.active.world);
|
||||||
|
return armory.renderpath.Nishita.data.lut;
|
||||||
|
}
|
||||||
#if arm_ltc
|
#if arm_ltc
|
||||||
if (link == "_ltcMat") {
|
case "_ltcMat": {
|
||||||
if (armory.data.ConstData.ltcMatTex == null) armory.data.ConstData.initLTC();
|
if (armory.data.ConstData.ltcMatTex == null) armory.data.ConstData.initLTC();
|
||||||
return armory.data.ConstData.ltcMatTex;
|
return armory.data.ConstData.ltcMatTex;
|
||||||
}
|
}
|
||||||
else if (link == "_ltcMag") {
|
case "_ltcMag": {
|
||||||
if (armory.data.ConstData.ltcMagTex == null) armory.data.ConstData.initLTC();
|
if (armory.data.ConstData.ltcMagTex == null) armory.data.ConstData.initLTC();
|
||||||
return armory.data.ConstData.ltcMagTex;
|
return armory.data.ConstData.ltcMagTex;
|
||||||
}
|
}
|
||||||
#end
|
#end
|
||||||
|
}
|
||||||
|
|
||||||
var target = iron.RenderPath.active.renderTargets.get(link.endsWith("_depth") ? link.substr(0, link.length - 6) : link);
|
var target = iron.RenderPath.active.renderTargets.get(link.endsWith("_depth") ? link.substr(0, link.length - 6) : link);
|
||||||
return target != null ? target.image : null;
|
return target != null ? target.image : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function vec3Link(object: Object, mat: MaterialData, link: String): iron.math.Vec4 {
|
public static function vec3Link(object: Object, mat: MaterialData, link: String): Null<iron.math.Vec4> {
|
||||||
var v: Vec4 = null;
|
var v: Vec4 = null;
|
||||||
|
switch (link) {
|
||||||
#if arm_hosek
|
#if arm_hosek
|
||||||
if (link == "_hosekA") {
|
case "_hosekA": {
|
||||||
if (armory.renderpath.HosekWilkie.data == null) {
|
if (armory.renderpath.HosekWilkie.data == null) {
|
||||||
armory.renderpath.HosekWilkie.recompute(Scene.active.world);
|
armory.renderpath.HosekWilkie.recompute(Scene.active.world);
|
||||||
}
|
}
|
||||||
|
@ -47,7 +55,7 @@ class Uniforms {
|
||||||
v.z = armory.renderpath.HosekWilkie.data.A.z;
|
v.z = armory.renderpath.HosekWilkie.data.A.z;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (link == "_hosekB") {
|
case "_hosekB": {
|
||||||
if (armory.renderpath.HosekWilkie.data == null) {
|
if (armory.renderpath.HosekWilkie.data == null) {
|
||||||
armory.renderpath.HosekWilkie.recompute(Scene.active.world);
|
armory.renderpath.HosekWilkie.recompute(Scene.active.world);
|
||||||
}
|
}
|
||||||
|
@ -58,7 +66,7 @@ class Uniforms {
|
||||||
v.z = armory.renderpath.HosekWilkie.data.B.z;
|
v.z = armory.renderpath.HosekWilkie.data.B.z;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (link == "_hosekC") {
|
case "_hosekC": {
|
||||||
if (armory.renderpath.HosekWilkie.data == null) {
|
if (armory.renderpath.HosekWilkie.data == null) {
|
||||||
armory.renderpath.HosekWilkie.recompute(Scene.active.world);
|
armory.renderpath.HosekWilkie.recompute(Scene.active.world);
|
||||||
}
|
}
|
||||||
|
@ -69,7 +77,7 @@ class Uniforms {
|
||||||
v.z = armory.renderpath.HosekWilkie.data.C.z;
|
v.z = armory.renderpath.HosekWilkie.data.C.z;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (link == "_hosekD") {
|
case "_hosekD": {
|
||||||
if (armory.renderpath.HosekWilkie.data == null) {
|
if (armory.renderpath.HosekWilkie.data == null) {
|
||||||
armory.renderpath.HosekWilkie.recompute(Scene.active.world);
|
armory.renderpath.HosekWilkie.recompute(Scene.active.world);
|
||||||
}
|
}
|
||||||
|
@ -80,7 +88,7 @@ class Uniforms {
|
||||||
v.z = armory.renderpath.HosekWilkie.data.D.z;
|
v.z = armory.renderpath.HosekWilkie.data.D.z;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (link == "_hosekE") {
|
case "_hosekE": {
|
||||||
if (armory.renderpath.HosekWilkie.data == null) {
|
if (armory.renderpath.HosekWilkie.data == null) {
|
||||||
armory.renderpath.HosekWilkie.recompute(Scene.active.world);
|
armory.renderpath.HosekWilkie.recompute(Scene.active.world);
|
||||||
}
|
}
|
||||||
|
@ -91,7 +99,7 @@ class Uniforms {
|
||||||
v.z = armory.renderpath.HosekWilkie.data.E.z;
|
v.z = armory.renderpath.HosekWilkie.data.E.z;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (link == "_hosekF") {
|
case "_hosekF": {
|
||||||
if (armory.renderpath.HosekWilkie.data == null) {
|
if (armory.renderpath.HosekWilkie.data == null) {
|
||||||
armory.renderpath.HosekWilkie.recompute(Scene.active.world);
|
armory.renderpath.HosekWilkie.recompute(Scene.active.world);
|
||||||
}
|
}
|
||||||
|
@ -102,7 +110,7 @@ class Uniforms {
|
||||||
v.z = armory.renderpath.HosekWilkie.data.F.z;
|
v.z = armory.renderpath.HosekWilkie.data.F.z;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (link == "_hosekG") {
|
case "_hosekG": {
|
||||||
if (armory.renderpath.HosekWilkie.data == null) {
|
if (armory.renderpath.HosekWilkie.data == null) {
|
||||||
armory.renderpath.HosekWilkie.recompute(Scene.active.world);
|
armory.renderpath.HosekWilkie.recompute(Scene.active.world);
|
||||||
}
|
}
|
||||||
|
@ -113,7 +121,7 @@ class Uniforms {
|
||||||
v.z = armory.renderpath.HosekWilkie.data.G.z;
|
v.z = armory.renderpath.HosekWilkie.data.G.z;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (link == "_hosekH") {
|
case "_hosekH": {
|
||||||
if (armory.renderpath.HosekWilkie.data == null) {
|
if (armory.renderpath.HosekWilkie.data == null) {
|
||||||
armory.renderpath.HosekWilkie.recompute(Scene.active.world);
|
armory.renderpath.HosekWilkie.recompute(Scene.active.world);
|
||||||
}
|
}
|
||||||
|
@ -124,7 +132,7 @@ class Uniforms {
|
||||||
v.z = armory.renderpath.HosekWilkie.data.H.z;
|
v.z = armory.renderpath.HosekWilkie.data.H.z;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (link == "_hosekI") {
|
case "_hosekI": {
|
||||||
if (armory.renderpath.HosekWilkie.data == null) {
|
if (armory.renderpath.HosekWilkie.data == null) {
|
||||||
armory.renderpath.HosekWilkie.recompute(Scene.active.world);
|
armory.renderpath.HosekWilkie.recompute(Scene.active.world);
|
||||||
}
|
}
|
||||||
|
@ -135,7 +143,7 @@ class Uniforms {
|
||||||
v.z = armory.renderpath.HosekWilkie.data.I.z;
|
v.z = armory.renderpath.HosekWilkie.data.I.z;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (link == "_hosekZ") {
|
case "_hosekZ": {
|
||||||
if (armory.renderpath.HosekWilkie.data == null) {
|
if (armory.renderpath.HosekWilkie.data == null) {
|
||||||
armory.renderpath.HosekWilkie.recompute(Scene.active.world);
|
armory.renderpath.HosekWilkie.recompute(Scene.active.world);
|
||||||
}
|
}
|
||||||
|
@ -148,7 +156,7 @@ class Uniforms {
|
||||||
}
|
}
|
||||||
#end
|
#end
|
||||||
#if rp_voxelao
|
#if rp_voxelao
|
||||||
if (link == "_cameraPositionSnap") {
|
case "_cameraPositionSnap": {
|
||||||
v = iron.object.Uniforms.helpVec;
|
v = iron.object.Uniforms.helpVec;
|
||||||
var camera = iron.Scene.active.camera;
|
var camera = iron.Scene.active.camera;
|
||||||
v.set(camera.transform.worldx(), camera.transform.worldy(), camera.transform.worldz());
|
v.set(camera.transform.worldx(), camera.transform.worldy(), camera.transform.worldz());
|
||||||
|
@ -160,27 +168,47 @@ class Uniforms {
|
||||||
v.set(Math.floor(v.x / f) * f, Math.floor(v.y / f) * f, Math.floor(v.z / f) * f);
|
v.set(Math.floor(v.x / f) * f, Math.floor(v.y / f) * f, Math.floor(v.z / f) * f);
|
||||||
}
|
}
|
||||||
#end
|
#end
|
||||||
|
}
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function vec2Link(object: Object, mat: MaterialData, link: String): Null<iron.math.Vec4> {
|
||||||
|
var v: Vec4 = null;
|
||||||
|
switch (link) {
|
||||||
|
case "_nishitaDensity": {
|
||||||
|
var w = Scene.active.world;
|
||||||
|
if (w != null) {
|
||||||
|
v = iron.object.Uniforms.helpVec;
|
||||||
|
// We only need Rayleigh and Mie density in the sky shader -> Vec2
|
||||||
|
v.x = w.raw.nishita_density[0];
|
||||||
|
v.y = w.raw.nishita_density[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function floatLink(object: Object, mat: MaterialData, link: String): Null<kha.FastFloat> {
|
public static function floatLink(object: Object, mat: MaterialData, link: String): Null<kha.FastFloat> {
|
||||||
|
switch (link) {
|
||||||
#if rp_dynres
|
#if rp_dynres
|
||||||
if (link == "_dynamicScale") {
|
case "_dynamicScale": {
|
||||||
return armory.renderpath.DynamicResolutionScale.dynamicScale;
|
return armory.renderpath.DynamicResolutionScale.dynamicScale;
|
||||||
}
|
}
|
||||||
#end
|
#end
|
||||||
#if arm_debug
|
#if arm_debug
|
||||||
if (link == "_debugFloat") {
|
case "_debugFloat": {
|
||||||
return armory.trait.internal.DebugConsole.debugFloat;
|
return armory.trait.internal.DebugConsole.debugFloat;
|
||||||
}
|
}
|
||||||
#end
|
#end
|
||||||
#if rp_voxelao
|
#if rp_voxelao
|
||||||
if (link == "_voxelBlend") { // Blend current and last voxels
|
case "_voxelBlend": { // Blend current and last voxels
|
||||||
var freq = armory.renderpath.RenderPathCreator.voxelFreq;
|
var freq = armory.renderpath.RenderPathCreator.voxelFreq;
|
||||||
return (armory.renderpath.RenderPathCreator.voxelFrame % freq) / freq;
|
return (armory.renderpath.RenderPathCreator.voxelFrame % freq) / freq;
|
||||||
}
|
}
|
||||||
#end
|
#end
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,7 +57,11 @@ class Inc {
|
||||||
break;
|
break;
|
||||||
if (LightObject.discardLightCulled(light)) continue;
|
if (LightObject.discardLightCulled(light)) continue;
|
||||||
if (light.data.raw.type == "point") {
|
if (light.data.raw.type == "point") {
|
||||||
for(k in 0...light.tileOffsetX.length) {
|
if (!light.data.raw.cast_shadow) {
|
||||||
|
j += 4 * 6;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for(k in 0...6) {
|
||||||
LightObject.pointLightsData[j ] = light.tileOffsetX[k]; // posx
|
LightObject.pointLightsData[j ] = light.tileOffsetX[k]; // posx
|
||||||
LightObject.pointLightsData[j + 1] = light.tileOffsetY[k]; // posy
|
LightObject.pointLightsData[j + 1] = light.tileOffsetY[k]; // posy
|
||||||
LightObject.pointLightsData[j + 2] = light.tileScale[k]; // tile scale factor relative to atlas
|
LightObject.pointLightsData[j + 2] = light.tileScale[k]; // tile scale factor relative to atlas
|
||||||
|
@ -108,10 +112,17 @@ class Inc {
|
||||||
lastFrame = RenderPath.active.frame;
|
lastFrame = RenderPath.active.frame;
|
||||||
#end
|
#end
|
||||||
// add new lights to the atlases
|
// add new lights to the atlases
|
||||||
|
#if arm_debug
|
||||||
|
beginShadowsLogicProfile();
|
||||||
|
// reset data on rejected lights
|
||||||
|
for (atlas in ShadowMapAtlas.shadowMapAtlases) {
|
||||||
|
atlas.rejectedLights = [];
|
||||||
|
}
|
||||||
|
#end
|
||||||
for (light in iron.Scene.active.lights) {
|
for (light in iron.Scene.active.lights) {
|
||||||
if (!light.lightInAtlas && !light.culledLight && light.visible && light.shadowMapScale > 0.0
|
if (!light.lightInAtlas && !light.culledLight && light.visible && light.shadowMapScale > 0.0
|
||||||
&& light.data.raw.strength > 0.0 && light.data.raw.cast_shadow) {
|
&& light.data.raw.strength > 0.0 && light.data.raw.cast_shadow) {
|
||||||
light.lightInAtlas = ShadowMapAtlas.addLight(light);
|
ShadowMapAtlas.addLight(light);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// update point light data before rendering
|
// update point light data before rendering
|
||||||
|
@ -151,8 +162,8 @@ class Inc {
|
||||||
// set the tile offset for this tile and every linked tile to this one
|
// set the tile offset for this tile and every linked tile to this one
|
||||||
var j = 0;
|
var j = 0;
|
||||||
tile.forEachTileLinked(function (lTile) {
|
tile.forEachTileLinked(function (lTile) {
|
||||||
tile.light.tileOffsetX[j] = (lTile.coordsX == 0) ? 0.0 : lTile.coordsX / atlas.sizew;
|
tile.light.tileOffsetX[j] = lTile.coordsX / atlas.sizew;
|
||||||
tile.light.tileOffsetY[j] = (lTile.coordsY == 0) ? 0.0 : lTile.coordsY / atlas.sizew;
|
tile.light.tileOffsetY[j] = lTile.coordsY / atlas.sizew;
|
||||||
tile.light.tileScale[j] = lTile.size / atlas.sizew;
|
tile.light.tileScale[j] = lTile.size / atlas.sizew;
|
||||||
j++;
|
j++;
|
||||||
});
|
});
|
||||||
|
@ -164,6 +175,9 @@ class Inc {
|
||||||
var face = 0;
|
var face = 0;
|
||||||
var faces = ShadowMapTile.tilesLightType(tile.light.data.raw.type);
|
var faces = ShadowMapTile.tilesLightType(tile.light.data.raw.type);
|
||||||
|
|
||||||
|
#if arm_debug
|
||||||
|
beginShadowsRenderProfile();
|
||||||
|
#end
|
||||||
tile.forEachTileLinked(function (lTile) {
|
tile.forEachTileLinked(function (lTile) {
|
||||||
if (faces > 1) {
|
if (faces > 1) {
|
||||||
#if arm_csm
|
#if arm_csm
|
||||||
|
@ -180,6 +194,9 @@ class Inc {
|
||||||
|
|
||||||
path.drawMeshesStream("shadowmap");
|
path.drawMeshesStream("shadowmap");
|
||||||
});
|
});
|
||||||
|
#if arm_debug
|
||||||
|
endShadowsRenderProfile();
|
||||||
|
#end
|
||||||
|
|
||||||
path.currentFace = -1;
|
path.currentFace = -1;
|
||||||
}
|
}
|
||||||
|
@ -202,6 +219,9 @@ class Inc {
|
||||||
tile.freeTile();
|
tile.freeTile();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#if arm_debug
|
||||||
|
endShadowsLogicProfile();
|
||||||
|
#end
|
||||||
#end // rp_shadowmap
|
#end // rp_shadowmap
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
|
@ -517,6 +537,25 @@ class Inc {
|
||||||
return null;
|
return null;
|
||||||
#end
|
#end
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if arm_debug
|
||||||
|
public static var shadowsLogicTime = 0.0;
|
||||||
|
public static var shadowsRenderTime = 0.0;
|
||||||
|
static var startShadowsLogicTime = 0.0;
|
||||||
|
static var startShadowsRenderTime = 0.0;
|
||||||
|
static var callBackSetup = false;
|
||||||
|
static function setupEndFrameCallback() {
|
||||||
|
if (!callBackSetup) {
|
||||||
|
callBackSetup = true;
|
||||||
|
iron.App.endFrameCallbacks.push(endFrame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static function beginShadowsLogicProfile() { setupEndFrameCallback(); startShadowsLogicTime = kha.Scheduler.realTime(); }
|
||||||
|
static function beginShadowsRenderProfile() { startShadowsRenderTime = kha.Scheduler.realTime(); }
|
||||||
|
static function endShadowsLogicProfile() { shadowsLogicTime += kha.Scheduler.realTime() - startShadowsLogicTime - shadowsRenderTime; }
|
||||||
|
static function endShadowsRenderProfile() { shadowsRenderTime += kha.Scheduler.realTime() - startShadowsRenderTime; }
|
||||||
|
public static function endFrame() { shadowsLogicTime = 0; shadowsRenderTime = 0; }
|
||||||
|
#end
|
||||||
}
|
}
|
||||||
|
|
||||||
#if arm_shadowmap_atlas
|
#if arm_shadowmap_atlas
|
||||||
|
@ -534,12 +573,17 @@ class ShadowMapAtlas {
|
||||||
public var activeTiles: Array<ShadowMapTile> = [];
|
public var activeTiles: Array<ShadowMapTile> = [];
|
||||||
public var depth = 1;
|
public var depth = 1;
|
||||||
#if arm_shadowmap_atlas_lod
|
#if arm_shadowmap_atlas_lod
|
||||||
static var tileSizes: Array<Int> = [];
|
var tileSizes: Array<Int> = [];
|
||||||
static var tileSizeFactor: Array<Float> = [];
|
var tileSizeFactor: Array<Float> = [];
|
||||||
#end
|
#end
|
||||||
public var updateRenderTarget = false;
|
public var updateRenderTarget = false;
|
||||||
public static var shadowMapAtlases:Map<String, ShadowMapAtlas> = new Map(); // map a shadowmap atlas to their light type
|
public static var shadowMapAtlases:Map<String, ShadowMapAtlas> = new Map(); // map a shadowmap atlas to their light type
|
||||||
|
|
||||||
|
#if arm_debug
|
||||||
|
public var lightType: String;
|
||||||
|
public var rejectedLights: Array<LightObject> = [];
|
||||||
|
#end
|
||||||
|
|
||||||
function new(light: LightObject) {
|
function new(light: LightObject) {
|
||||||
|
|
||||||
var maxTileSize = shadowMapAtlasSize(light);
|
var maxTileSize = shadowMapAtlasSize(light);
|
||||||
|
@ -549,10 +593,17 @@ class ShadowMapAtlas {
|
||||||
this.maxAtlasSizeConst = getMaxAtlasSize(light.data.raw.type);
|
this.maxAtlasSizeConst = getMaxAtlasSize(light.data.raw.type);
|
||||||
|
|
||||||
#if arm_shadowmap_atlas_lod
|
#if arm_shadowmap_atlas_lod
|
||||||
if (tileSizes.length == 0)
|
|
||||||
computeTileSizes(maxTileSize, depth);
|
computeTileSizes(maxTileSize, depth);
|
||||||
#end
|
#end
|
||||||
|
|
||||||
|
#if arm_debug
|
||||||
|
#if arm_shadowmap_atlas_single_map
|
||||||
|
this.lightType = "any";
|
||||||
|
#else
|
||||||
|
this.lightType = light.data.raw.type;
|
||||||
|
#end
|
||||||
|
#end
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -560,11 +611,7 @@ class ShadowMapAtlas {
|
||||||
* @param light of type LightObject to be added to an yatlas
|
* @param light of type LightObject to be added to an yatlas
|
||||||
* @return if the light was added succesfully
|
* @return if the light was added succesfully
|
||||||
*/
|
*/
|
||||||
public static function addLight(light: LightObject): Bool {
|
public static function addLight(light: LightObject) {
|
||||||
// check if light can be added based on culling
|
|
||||||
if (light.culledLight || light.shadowMapScale == 0.0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
var atlasName = shadowMapAtlasName(light.data.raw.type);
|
var atlasName = shadowMapAtlasName(light.data.raw.type);
|
||||||
var atlas = shadowMapAtlases.get(atlasName);
|
var atlas = shadowMapAtlases.get(atlasName);
|
||||||
if (atlas == null) {
|
if (atlas == null) {
|
||||||
|
@ -575,11 +622,21 @@ class ShadowMapAtlas {
|
||||||
|
|
||||||
// find a free tile for this light
|
// find a free tile for this light
|
||||||
var mainTile = ShadowMapTile.assignTiles(light, atlas, null);
|
var mainTile = ShadowMapTile.assignTiles(light, atlas, null);
|
||||||
if (mainTile == null)
|
if (mainTile == null) {
|
||||||
return false;
|
#if arm_debug
|
||||||
// push main tile to active tiles
|
if (!atlas.rejectedLights.contains(light))
|
||||||
|
atlas.rejectedLights.push(light);
|
||||||
|
#end
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
atlas.activeTiles.push(mainTile);
|
atlas.activeTiles.push(mainTile);
|
||||||
return true;
|
// notify the tile on light remove
|
||||||
|
light.tileNotifyOnRemove = mainTile.notifyOnLightRemove;
|
||||||
|
// notify atlas when this tile is freed
|
||||||
|
mainTile.notifyOnFree = atlas.freeActiveTile;
|
||||||
|
// "lock" light to make sure it's not eligible to be added again
|
||||||
|
light.lightInAtlas = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline function shadowMapAtlasSize(light:LightObject):Int {
|
static inline function shadowMapAtlasSize(light:LightObject):Int {
|
||||||
|
@ -602,17 +659,17 @@ class ShadowMapAtlas {
|
||||||
}
|
}
|
||||||
|
|
||||||
#if arm_shadowmap_atlas_lod
|
#if arm_shadowmap_atlas_lod
|
||||||
static function computeTileSizes(maxTileSize: Int, depth: Int): Void {
|
function computeTileSizes(maxTileSize: Int, depth: Int): Void {
|
||||||
// find the highest value based on the calculation done in the cluster code
|
// find the highest value based on the calculation done in the cluster code
|
||||||
var base = LightObject.zToShadowMapScale(0, 16);
|
var base = LightObject.zToShadowMapScale(0, 16);
|
||||||
var subdiv = base / depth;
|
var subdiv = base / depth;
|
||||||
for(i in 0...depth){
|
for(i in 0...depth){
|
||||||
tileSizes.push(Std.int(maxTileSize / Math.pow(2, i)));
|
this.tileSizes.push(Std.int(maxTileSize / Math.pow(2, i)));
|
||||||
tileSizeFactor.push(base);
|
this.tileSizeFactor.push(base);
|
||||||
base -= subdiv;
|
base -= subdiv;
|
||||||
}
|
}
|
||||||
tileSizes.push(0);
|
this.tileSizes.push(0);
|
||||||
tileSizeFactor.push(0.0);
|
this.tileSizeFactor.push(0.0);
|
||||||
}
|
}
|
||||||
#end
|
#end
|
||||||
|
|
||||||
|
@ -658,7 +715,9 @@ class ShadowMapAtlas {
|
||||||
|
|
||||||
public static inline function getMaxAtlasSize(type: String): Int {
|
public static inline function getMaxAtlasSize(type: String): Int {
|
||||||
#if arm_shadowmap_atlas_single_map
|
#if arm_shadowmap_atlas_single_map
|
||||||
#if (rp_shadowmap_atlas_max_size == 1024)
|
#if (rp_shadowmap_atlas_max_size == 512)
|
||||||
|
return 512;
|
||||||
|
#elseif (rp_shadowmap_atlas_max_size == 1024)
|
||||||
return 1024;
|
return 1024;
|
||||||
#elseif (rp_shadowmap_atlas_max_size == 2048)
|
#elseif (rp_shadowmap_atlas_max_size == 2048)
|
||||||
return 2048;
|
return 2048;
|
||||||
|
@ -685,7 +744,9 @@ class ShadowMapAtlas {
|
||||||
#end
|
#end
|
||||||
}
|
}
|
||||||
case "spot": {
|
case "spot": {
|
||||||
#if (rp_shadowmap_atlas_max_size_spot == 1024)
|
#if (rp_shadowmap_atlas_max_size_spot == 512)
|
||||||
|
return 512;
|
||||||
|
#elseif (rp_shadowmap_atlas_max_size_spot == 1024)
|
||||||
return 1024;
|
return 1024;
|
||||||
#elseif (rp_shadowmap_atlas_max_size_spot == 2048)
|
#elseif (rp_shadowmap_atlas_max_size_spot == 2048)
|
||||||
return 2048;
|
return 2048;
|
||||||
|
@ -698,7 +759,9 @@ class ShadowMapAtlas {
|
||||||
#end
|
#end
|
||||||
}
|
}
|
||||||
case "sun": {
|
case "sun": {
|
||||||
#if (rp_shadowmap_atlas_max_size_sun == 1024)
|
#if (rp_shadowmap_atlas_max_size_sun == 512)
|
||||||
|
return 512;
|
||||||
|
#elseif (rp_shadowmap_atlas_max_size_sun == 1024)
|
||||||
return 1024;
|
return 1024;
|
||||||
#elseif (rp_shadowmap_atlas_max_size_sun == 2048)
|
#elseif (rp_shadowmap_atlas_max_size_sun == 2048)
|
||||||
return 2048;
|
return 2048;
|
||||||
|
@ -711,7 +774,9 @@ class ShadowMapAtlas {
|
||||||
#end
|
#end
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
#if (rp_shadowmap_atlas_max_size == 1024)
|
#if (rp_shadowmap_atlas_max_size == 512)
|
||||||
|
return 512;
|
||||||
|
#elseif (rp_shadowmap_atlas_max_size == 1024)
|
||||||
return 1024;
|
return 1024;
|
||||||
#elseif (rp_shadowmap_atlas_max_size == 2048)
|
#elseif (rp_shadowmap_atlas_max_size == 2048)
|
||||||
return 2048;
|
return 2048;
|
||||||
|
@ -727,6 +792,10 @@ class ShadowMapAtlas {
|
||||||
}
|
}
|
||||||
#end
|
#end
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function freeActiveTile(tile: ShadowMapTile) {
|
||||||
|
activeTiles.remove(tile);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ShadowMapTile {
|
class ShadowMapTile {
|
||||||
|
@ -792,7 +861,6 @@ class ShadowMapTile {
|
||||||
static inline function findCreateTiles(light: LightObject, oldTile: ShadowMapTile, atlas: ShadowMapAtlas, tilesPerLightType: Int, tileSize: Int): Array<ShadowMapTile> {
|
static inline function findCreateTiles(light: LightObject, oldTile: ShadowMapTile, atlas: ShadowMapAtlas, tilesPerLightType: Int, tileSize: Int): Array<ShadowMapTile> {
|
||||||
var tilesFound: Array<ShadowMapTile> = [];
|
var tilesFound: Array<ShadowMapTile> = [];
|
||||||
|
|
||||||
var updateAtlas = false;
|
|
||||||
while (tilesFound.length < tilesPerLightType) {
|
while (tilesFound.length < tilesPerLightType) {
|
||||||
findTiles(light, oldTile, atlas.tiles, tileSize, tilesPerLightType, tilesFound);
|
findTiles(light, oldTile, atlas.tiles, tileSize, tilesPerLightType, tilesFound);
|
||||||
|
|
||||||
|
@ -946,6 +1014,11 @@ class ShadowMapTile {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function notifyOnLightRemove() {
|
||||||
|
unlockLight = true;
|
||||||
|
freeTile();
|
||||||
|
}
|
||||||
|
|
||||||
inline function lockTile(light: LightObject): Void {
|
inline function lockTile(light: LightObject): Void {
|
||||||
if (this.light != null)
|
if (this.light != null)
|
||||||
return;
|
return;
|
||||||
|
@ -959,6 +1032,7 @@ class ShadowMapTile {
|
||||||
}
|
}
|
||||||
|
|
||||||
public var unlockLight: Bool = false;
|
public var unlockLight: Bool = false;
|
||||||
|
public var notifyOnFree: ShadowMapTile -> Void;
|
||||||
|
|
||||||
public function freeTile(): Void {
|
public function freeTile(): Void {
|
||||||
// prevent duplicates
|
// prevent duplicates
|
||||||
|
@ -984,6 +1058,11 @@ class ShadowMapTile {
|
||||||
tempTile.linkedTile = null;
|
tempTile.linkedTile = null;
|
||||||
tempTile = linkedTile;
|
tempTile = linkedTile;
|
||||||
}
|
}
|
||||||
|
// notify atlas that this tile has been freed
|
||||||
|
if (notifyOnFree != null) {
|
||||||
|
notifyOnFree(this);
|
||||||
|
notifyOnFree = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public inline function forEachTileLinked(action: ShadowMapTile->Void): Void {
|
public inline function forEachTileLinked(action: ShadowMapTile->Void): Void {
|
||||||
|
|
226
Sources/armory/renderpath/Nishita.hx
Normal file
226
Sources/armory/renderpath/Nishita.hx
Normal file
|
@ -0,0 +1,226 @@
|
||||||
|
package armory.renderpath;
|
||||||
|
|
||||||
|
import kha.FastFloat;
|
||||||
|
import kha.arrays.Float32Array;
|
||||||
|
import kha.graphics4.TextureFormat;
|
||||||
|
import kha.graphics4.Usage;
|
||||||
|
|
||||||
|
import iron.data.WorldData;
|
||||||
|
import iron.math.Vec2;
|
||||||
|
import iron.math.Vec3;
|
||||||
|
|
||||||
|
import armory.math.Helper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Utility class to control the Nishita sky model.
|
||||||
|
**/
|
||||||
|
class Nishita {
|
||||||
|
|
||||||
|
public static var data: NishitaData = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Recomputes the nishita lookup table after the density settings changed.
|
||||||
|
Do not call this method on every frame (it's slow)!
|
||||||
|
**/
|
||||||
|
public static function recompute(world: WorldData) {
|
||||||
|
if (world == null || world.raw.nishita_density == null) return;
|
||||||
|
if (data == null) data = new NishitaData();
|
||||||
|
|
||||||
|
var density = world.raw.nishita_density;
|
||||||
|
data.computeLUT(new Vec3(density[0], density[1], density[2]));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets the sky's density parameters and calls `recompute()` afterwards. **/
|
||||||
|
public static function setDensity(world: WorldData, densityAir: FastFloat, densityDust: FastFloat, densityOzone: FastFloat) {
|
||||||
|
if (world == null) return;
|
||||||
|
|
||||||
|
if (world.raw.nishita_density == null) world.raw.nishita_density = new Float32Array(3);
|
||||||
|
var density = world.raw.nishita_density;
|
||||||
|
density[0] = Helper.clamp(densityAir, 0, 10);
|
||||||
|
density[1] = Helper.clamp(densityDust, 0, 10);
|
||||||
|
density[2] = Helper.clamp(densityOzone, 0, 10);
|
||||||
|
|
||||||
|
recompute(world);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
This class holds the precalculated result of the inner scattering integral
|
||||||
|
of the Nishita sky model. The outer integral is calculated in
|
||||||
|
[`armory/Shaders/std/sky.glsl`](https://github.com/armory3d/armory/blob/master/Shaders/std/sky.glsl).
|
||||||
|
|
||||||
|
@see `armory.renderpath.Nishita`
|
||||||
|
**/
|
||||||
|
class NishitaData {
|
||||||
|
|
||||||
|
public var lut: kha.Image;
|
||||||
|
|
||||||
|
/**
|
||||||
|
The amount of individual sample heights stored in the LUT (and the width
|
||||||
|
of the LUT image).
|
||||||
|
**/
|
||||||
|
public static var lutHeightSteps = 128;
|
||||||
|
/**
|
||||||
|
The amount of individual sun angle steps stored in the LUT (and the
|
||||||
|
height of the LUT image).
|
||||||
|
**/
|
||||||
|
public static var lutAngleSteps = 128;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Amount of steps for calculating the inner scattering integral. Heigher
|
||||||
|
values are more precise but take longer to compute.
|
||||||
|
**/
|
||||||
|
public static var jSteps = 8;
|
||||||
|
|
||||||
|
/** Radius of the atmosphere in meters. **/
|
||||||
|
public static var radiusAtmo = 6420000;
|
||||||
|
/**
|
||||||
|
Radius of the planet in meters. The default value is the earth radius as
|
||||||
|
defined in Cycles.
|
||||||
|
**/
|
||||||
|
public static var radiusPlanet = 6360000;
|
||||||
|
|
||||||
|
/** Rayleigh scattering coefficient. **/
|
||||||
|
public static var rayleighCoeff = new Vec3(5.5e-6, 13.0e-6, 22.4e-6);
|
||||||
|
/** Rayleigh scattering scale parameter. **/
|
||||||
|
public static var rayleighScale = 8e3;
|
||||||
|
|
||||||
|
/** Mie scattering coefficient. **/
|
||||||
|
public static var mieCoeff = 2e-5;
|
||||||
|
/** Mie scattering scale parameter. **/
|
||||||
|
public static var mieScale = 1.2e3;
|
||||||
|
|
||||||
|
/** Ozone scattering coefficient. **/
|
||||||
|
// The ozone absorption coefficients are taken from Cycles code.
|
||||||
|
// Because Cycles calculates 21 wavelengths, we use the coefficients
|
||||||
|
// which are closest to the RGB wavelengths (645nm, 510nm, 440nm).
|
||||||
|
// Precalculating values by simulating Blender's spec_to_xyz() function
|
||||||
|
// to include all 21 wavelengths gave unrealistic results.
|
||||||
|
public static var ozoneCoeff = new Vec3(1.59051840791988e-6, 0.00000096707041180970, 0.00000007309568762914);
|
||||||
|
|
||||||
|
public function new() {}
|
||||||
|
|
||||||
|
/** Approximates the density of ozone for a given sample height. **/
|
||||||
|
function getOzoneDensity(height: FastFloat): FastFloat {
|
||||||
|
// Values are taken from Cycles code
|
||||||
|
if (height < 10000.0 || height >= 40000.0) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
if (height < 25000.0) {
|
||||||
|
return (height - 10000.0) / 15000.0;
|
||||||
|
}
|
||||||
|
return -((height - 40000.0) / 15000.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Ray-sphere intersection test that assumes the sphere is centered at the
|
||||||
|
origin. There is no intersection when result.x > result.y. Otherwise
|
||||||
|
this function returns the distances to the two intersection points,
|
||||||
|
which might be equal.
|
||||||
|
**/
|
||||||
|
function raySphereIntersection(rayOrigin: Vec3, rayDirection: Vec3, sphereRadius: Int): Vec2 {
|
||||||
|
// Algorithm is described here: https://en.wikipedia.org/wiki/Line%E2%80%93sphere_intersection
|
||||||
|
var a = rayDirection.dot(rayDirection);
|
||||||
|
var b = 2.0 * rayDirection.dot(rayOrigin);
|
||||||
|
var c = rayOrigin.dot(rayOrigin) - (sphereRadius * sphereRadius);
|
||||||
|
var d = (b * b) - 4.0 * a * c;
|
||||||
|
|
||||||
|
// Ray does not intersect the sphere
|
||||||
|
if (d < 0.0) return new Vec2(1e5, -1e5);
|
||||||
|
|
||||||
|
return new Vec2(
|
||||||
|
(-b - Math.sqrt(d)) / (2.0 * a),
|
||||||
|
(-b + Math.sqrt(d)) / (2.0 * a)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Computes the LUT texture for the given density values.
|
||||||
|
@param density 3D vector of air density, dust density, ozone density
|
||||||
|
**/
|
||||||
|
public function computeLUT(density: Vec3) {
|
||||||
|
var imageData = new haxe.io.Float32Array(lutHeightSteps * lutAngleSteps * 4);
|
||||||
|
|
||||||
|
for (x in 0...lutHeightSteps) {
|
||||||
|
var height = (x / (lutHeightSteps - 1));
|
||||||
|
|
||||||
|
// Use quadratic height for better horizon precision
|
||||||
|
height *= height;
|
||||||
|
height *= radiusAtmo; // Denormalize
|
||||||
|
|
||||||
|
for (y in 0...lutAngleSteps) {
|
||||||
|
var sunTheta = y / (lutAngleSteps - 1) * 2 - 1;
|
||||||
|
|
||||||
|
// Improve horizon precision
|
||||||
|
// See https://sebh.github.io/publications/egsr2020.pdf (5.3)
|
||||||
|
sunTheta = Helper.sign(sunTheta) * sunTheta * sunTheta;
|
||||||
|
sunTheta = sunTheta * Math.PI / 2 + Math.PI / 2; // Denormalize
|
||||||
|
|
||||||
|
var jODepth = sampleSecondaryRay(height, sunTheta, density);
|
||||||
|
|
||||||
|
var pixelIndex = (x + y * lutHeightSteps) * 4;
|
||||||
|
imageData[pixelIndex + 0] = jODepth.x;
|
||||||
|
imageData[pixelIndex + 1] = jODepth.y;
|
||||||
|
imageData[pixelIndex + 2] = jODepth.z;
|
||||||
|
imageData[pixelIndex + 3] = 1.0; // Unused
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lut = kha.Image.fromBytes(imageData.view.buffer, lutHeightSteps, lutAngleSteps, TextureFormat.RGBA128, Usage.StaticUsage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Calculates the integral for the secondary ray.
|
||||||
|
**/
|
||||||
|
public function sampleSecondaryRay(height: FastFloat, sunTheta: FastFloat, density: Vec3): Vec3 {
|
||||||
|
// Reconstruct values from the shader
|
||||||
|
var iPos = new Vec3(0, 0, height + radiusPlanet);
|
||||||
|
var pSun = new Vec3(0.0, Math.sin(sunTheta), Math.cos(sunTheta)).normalize();
|
||||||
|
|
||||||
|
var jTime: FastFloat = 0.0;
|
||||||
|
var jStepSize: FastFloat = raySphereIntersection(iPos, pSun, radiusAtmo).y / jSteps;
|
||||||
|
|
||||||
|
// Optical depth accumulators for the secondary ray (Rayleigh, Mie, ozone)
|
||||||
|
var jODepth = new Vec3();
|
||||||
|
|
||||||
|
for (i in 0...jSteps) {
|
||||||
|
|
||||||
|
// Calculate the secondary ray sample position and height
|
||||||
|
var jPos = iPos.clone().add(pSun.clone().mult(jTime + jStepSize * 0.5));
|
||||||
|
var jHeight = jPos.length() - radiusPlanet;
|
||||||
|
|
||||||
|
// Accumulate optical depth
|
||||||
|
var optDepthRayleigh = Math.exp(-jHeight / rayleighScale) * density.x;
|
||||||
|
var optDepthMie = Math.exp(-jHeight / mieScale) * density.y;
|
||||||
|
var optDepthOzone = getOzoneDensity(jHeight) * density.z;
|
||||||
|
jODepth.addf(optDepthRayleigh, optDepthMie, optDepthOzone);
|
||||||
|
|
||||||
|
jTime += jStepSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
jODepth.mult(jStepSize);
|
||||||
|
|
||||||
|
// Precalculate a part of the secondary attenuation.
|
||||||
|
// For one variable (e.g. x) in the vector, the formula is as follows:
|
||||||
|
//
|
||||||
|
// attn.x = exp(-(coeffX * (firstOpticalDepth.x + secondOpticalDepth.x)))
|
||||||
|
//
|
||||||
|
// We can split that up via:
|
||||||
|
//
|
||||||
|
// attn.x = exp(-(coeffX * firstOpticalDepth.x + coeffX * secondOpticalDepth.x))
|
||||||
|
// = exp(-(coeffX * firstOpticalDepth.x)) * exp(-(coeffX * secondOpticalDepth.x))
|
||||||
|
//
|
||||||
|
// The first factor of the resulting multiplication is calculated in the
|
||||||
|
// shader, but we can already precalculate the second one. As a side
|
||||||
|
// effect this keeps the range of the LUT values small because we don't
|
||||||
|
// store the optical depth but the attenuation.
|
||||||
|
var jAttenuation = new Vec3();
|
||||||
|
var mie = mieCoeff * jODepth.y;
|
||||||
|
jAttenuation.addf(mie, mie, mie);
|
||||||
|
jAttenuation.add(rayleighCoeff.clone().mult(jODepth.x));
|
||||||
|
jAttenuation.add(ozoneCoeff.clone().mult(jODepth.z));
|
||||||
|
jAttenuation.exp(jAttenuation.mult(-1));
|
||||||
|
|
||||||
|
return jAttenuation;
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,347 +4,223 @@ import kha.FastFloat;
|
||||||
import iron.system.Input;
|
import iron.system.Input;
|
||||||
|
|
||||||
class InputMap {
|
class InputMap {
|
||||||
var commands = new Map<String, Null<Array<InputCommand>>>();
|
|
||||||
|
static var inputMaps = new Map<String, InputMap>();
|
||||||
|
|
||||||
|
public var keys(default, null) = new Array<InputMapKey>();
|
||||||
|
public var lastKeyPressed(default, null) = "";
|
||||||
|
|
||||||
public function new() {}
|
public function new() {}
|
||||||
|
|
||||||
public function addKeyboard(config: String) {
|
public static function getInputMap(inputMap: String): Null<InputMap> {
|
||||||
var command = new KeyboardCommand();
|
if (inputMaps.exists(inputMap)) {
|
||||||
return addCustomCommand(command, config);
|
return inputMaps[inputMap];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function addGamepad(config: String) {
|
return null;
|
||||||
var command = new GamepadCommand();
|
|
||||||
return addCustomCommand(command, config);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function addCustomCommand(command: InputCommand, config: String) {
|
public static function addInputMap(inputMap: String): InputMap {
|
||||||
if (commands[config] == null) commands[config] = new Array<InputCommand>();
|
return inputMaps[inputMap] = new InputMap();
|
||||||
commands[config].push(command);
|
|
||||||
return command;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
class ActionMap extends InputMap {
|
public static function getInputMapKey(inputMap: String, key: String): Null<InputMapKey> {
|
||||||
|
if (inputMaps.exists(inputMap)) {
|
||||||
public inline function started(config: String) {
|
for (k in inputMaps[inputMap].keys) {
|
||||||
var started = false;
|
if (k.key == key) {
|
||||||
|
return k;
|
||||||
for (c in commands[config]) {
|
}
|
||||||
if (c.started()) {
|
|
||||||
started = true;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return started;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public inline function released(config: String) {
|
public static function removeInputMapKey(inputMap: String, key: String): Bool {
|
||||||
var released = false;
|
if (inputMaps.exists(inputMap)) {
|
||||||
|
var i = inputMaps[inputMap];
|
||||||
|
|
||||||
for (c in commands[config]) {
|
for (k in i.keys) {
|
||||||
if (c.released()) {
|
if (k.key == key) {
|
||||||
released = true;
|
return i.removeKey(k);
|
||||||
break;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return released;
|
return false;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class AxisMap extends InputMap {
|
|
||||||
var scale: FastFloat = 1.0;
|
|
||||||
|
|
||||||
public inline function getAxis(config: String) {
|
|
||||||
var axis = 0.0;
|
|
||||||
|
|
||||||
for (c in commands[config]) {
|
|
||||||
var tempAxis = c.getAxis();
|
|
||||||
|
|
||||||
if (tempAxis != 0.0 && tempAxis != axis) {
|
|
||||||
axis += tempAxis;
|
|
||||||
scale = c.getScale();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return axis;
|
public function addKeyboard(key: String, scale: FastFloat = 1.0): InputMapKey {
|
||||||
|
return addKey(new KeyboardKey(key, scale));
|
||||||
}
|
}
|
||||||
|
|
||||||
public inline function getScale() {
|
public function addMouse(key: String, scale: FastFloat = 1.0, deadzone: FastFloat = 0.0): InputMapKey {
|
||||||
return scale;
|
return addKey(new MouseKey(key, scale, deadzone));
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class InputCommand {
|
|
||||||
var keys = new Array<String>();
|
|
||||||
var modifiers = new Array<String>();
|
|
||||||
var displacementKeys = new Array<String>();
|
|
||||||
var displacementModifiers = new Array<String>();
|
|
||||||
var deadzone: FastFloat = 0.0;
|
|
||||||
var scale: FastFloat = 1.0;
|
|
||||||
|
|
||||||
public function new() {}
|
|
||||||
|
|
||||||
public function setKeys(keys: Array<String>) {
|
|
||||||
return this.keys = keys;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setMods(modifiers: Array<String>) {
|
public function addGamepad(key: String, scale: FastFloat = 1.0, deadzone: FastFloat = 0.0): InputMapKey {
|
||||||
return this.modifiers = modifiers;
|
return addKey(new GamepadKey(key, scale, deadzone));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setDisplacementKeys(keys: Array<String>) {
|
public function addKey(key: InputMapKey): InputMapKey {
|
||||||
return displacementKeys = keys;
|
keys.push(key);
|
||||||
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setDisplacementMods(modifiers: Array<String>) {
|
public function removeKey(key: InputMapKey): Bool {
|
||||||
return displacementModifiers = modifiers;
|
return keys.remove(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setDeadzone(deadzone: FastFloat) {
|
public function value(): FastFloat {
|
||||||
return this.deadzone = deadzone;
|
var v = 0.0;
|
||||||
|
|
||||||
|
for (k in keys) {
|
||||||
|
v += k.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setScale(scale: FastFloat) {
|
return v;
|
||||||
return this.scale = scale;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getScale() {
|
|
||||||
return scale;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function started() {
|
public function started() {
|
||||||
|
for (k in keys) {
|
||||||
|
if (k.started()) {
|
||||||
|
lastKeyPressed = k.key;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function released() {
|
public function released() {
|
||||||
|
for (k in keys) {
|
||||||
|
if (k.released()) {
|
||||||
|
lastKeyPressed = k.key;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class InputMapKey {
|
||||||
|
|
||||||
|
public var key: String;
|
||||||
|
public var scale: FastFloat;
|
||||||
|
public var deadzone: FastFloat;
|
||||||
|
|
||||||
|
public function new(key: String, scale = 1.0, deadzone = 0.0) {
|
||||||
|
this.key = key.toLowerCase();
|
||||||
|
this.scale = scale;
|
||||||
|
this.deadzone = deadzone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function started(): Bool {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getAxis(): FastFloat {
|
public function released(): Bool {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function value(): FastFloat {
|
||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setIndex(index: Int) {}
|
||||||
|
|
||||||
|
function evalDeadzone(value: FastFloat): FastFloat {
|
||||||
|
var v = 0.0;
|
||||||
|
|
||||||
|
if (value > deadzone) {
|
||||||
|
v = value - deadzone;
|
||||||
|
|
||||||
|
} else if (value < -deadzone) {
|
||||||
|
v = value + deadzone;
|
||||||
|
}
|
||||||
|
|
||||||
|
return v * scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
function evalPressure(value: FastFloat): FastFloat {
|
||||||
|
var v = value - deadzone;
|
||||||
|
|
||||||
|
if (v > 0.0) {
|
||||||
|
v /= (1.0 - deadzone);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
v = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class KeyboardCommand extends InputCommand {
|
class KeyboardKey extends InputMapKey {
|
||||||
var keyboard = Input.getKeyboard();
|
|
||||||
var mouse = Input.getMouse();
|
var kb = Input.getKeyboard();
|
||||||
|
|
||||||
public inline override function started() {
|
public inline override function started() {
|
||||||
for (k in keys) {
|
return kb.started(key);
|
||||||
if (keyboard.started(k)) {
|
|
||||||
for (m in modifiers) {
|
|
||||||
if (!keyboard.down(m)) return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (m in displacementModifiers) {
|
|
||||||
if (!mouse.down(m)) return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (k in displacementKeys) {
|
|
||||||
if (mouse.started(k)) {
|
|
||||||
for (m in modifiers) {
|
|
||||||
if (!keyboard.down(m)) return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (m in displacementModifiers) {
|
|
||||||
if (!mouse.down(m)) return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public inline override function released() {
|
public inline override function released() {
|
||||||
for (k in keys) {
|
return kb.released(key);
|
||||||
if (keyboard.released(k)) {
|
|
||||||
for (m in modifiers) {
|
|
||||||
if (!keyboard.down(m)) return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (m in displacementModifiers) {
|
public inline override function value(): FastFloat {
|
||||||
if (!mouse.down(m)) return false;
|
return kb.down(key) ? scale : 0.0;
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (k in displacementKeys) {
|
|
||||||
if (mouse.released(k)) {
|
|
||||||
for (m in modifiers) {
|
|
||||||
if (!keyboard.down(m)) return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (m in displacementModifiers) {
|
|
||||||
if (!mouse.down(m)) return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public inline override function getAxis() {
|
|
||||||
var axis = 0.0;
|
|
||||||
var movementX = mouse.movementX;
|
|
||||||
var movementY = mouse.movementY;
|
|
||||||
var wheelDelta = mouse.wheelDelta;
|
|
||||||
|
|
||||||
for (k in keys) {
|
|
||||||
if (keyboard.down(k)) {
|
|
||||||
axis++;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (m in modifiers) {
|
|
||||||
if (keyboard.down(m)) {
|
|
||||||
axis --;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (k in displacementKeys) {
|
|
||||||
switch (k) {
|
|
||||||
case "moved x": if (movementX > deadzone) axis++;
|
|
||||||
case "moved y": if (movementY > deadzone) axis--;
|
|
||||||
case "wheel": if (wheelDelta < -deadzone) axis++;
|
|
||||||
case "movement x": if (movementX > deadzone) return movementX - deadzone;
|
|
||||||
case "movement y": if (movementY > deadzone) return movementY - deadzone;
|
|
||||||
default: {
|
|
||||||
if (mouse.down(k)) {
|
|
||||||
axis ++;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (m in displacementModifiers) {
|
|
||||||
switch (m) {
|
|
||||||
case "moved x": if (movementX < -deadzone) axis--;
|
|
||||||
case "moved y": if (movementY < -deadzone) axis++;
|
|
||||||
case "wheel": if (wheelDelta > deadzone) axis--;
|
|
||||||
case "movement x": if (movementX < -deadzone) return movementX + deadzone;
|
|
||||||
case "movement y": if (movementY < -deadzone) return movementY + deadzone;
|
|
||||||
default: {
|
|
||||||
if (mouse.down(m)) {
|
|
||||||
axis --;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return axis > 1 ? 1 : axis < -1 ? -1 : axis;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class GamepadCommand extends InputCommand {
|
class MouseKey extends InputMapKey {
|
||||||
var gamepad = Input.getGamepad(0);
|
|
||||||
|
var m = Input.getMouse();
|
||||||
|
|
||||||
public inline override function started() {
|
public inline override function started() {
|
||||||
for (k in keys) {
|
return m.started(key);
|
||||||
if (gamepad.started(k)) {
|
|
||||||
for (m in modifiers) {
|
|
||||||
if (gamepad.down(m) < deadzone) return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public inline override function released() {
|
public inline override function released() {
|
||||||
for (k in keys) {
|
return m.released(key);
|
||||||
if (gamepad.released(k)) {
|
|
||||||
for (m in modifiers) {
|
|
||||||
if (gamepad.down(m) < deadzone) return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
public override function value(): FastFloat {
|
||||||
|
return switch (key) {
|
||||||
|
case "movement x": evalDeadzone(m.movementX);
|
||||||
|
case "movement y": evalDeadzone(m.movementY);
|
||||||
|
case "wheel": evalDeadzone(m.wheelDelta);
|
||||||
|
default: m.down(key) ? scale : 0.0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return false;
|
|
||||||
}
|
class GamepadKey extends InputMapKey {
|
||||||
|
|
||||||
public inline override function getAxis() {
|
var g = Input.getGamepad();
|
||||||
var axis = 0.0;
|
|
||||||
var rsMovementX = gamepad.rightStick.movementX;
|
public inline override function started() {
|
||||||
var rsMovementY = gamepad.rightStick.movementY;
|
return g.started(key);
|
||||||
var lsMovementX = gamepad.leftStick.movementX;
|
}
|
||||||
var lsMovementY = gamepad.leftStick.movementY;
|
|
||||||
var rtPressure = gamepad.down("r2") > 0.0 ? (gamepad.down("r2") - deadzone) / (1 - deadzone) : 0.0;
|
public inline override function released() {
|
||||||
var ltPressure = gamepad.down("l2") > 0.0 ? (gamepad.down("r2") - deadzone) / (1 - deadzone) : 0.0;
|
return g.released(key);
|
||||||
|
}
|
||||||
for (k in keys) {
|
|
||||||
switch(k) {
|
public override function value(): FastFloat {
|
||||||
case "rtPressure": axis += rtPressure;
|
return switch(key) {
|
||||||
case "ltPressure": axis += ltPressure;
|
case "ls movement x": evalDeadzone(g.leftStick.movementX);
|
||||||
default: {
|
case "ls movement y": evalDeadzone(g.leftStick.movementY);
|
||||||
if (gamepad.down(k) > deadzone) {
|
case "rs movement x": evalDeadzone(g.rightStick.movementX);
|
||||||
axis++;
|
case "rs movement y": evalDeadzone(g.rightStick.movementY);
|
||||||
break;
|
case "lt pressure": evalDeadzone(evalPressure(g.down("l2")));
|
||||||
}
|
case "rt pressure": evalDeadzone(evalPressure(g.down("r2")));
|
||||||
}
|
default: evalDeadzone(g.down(key));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (m in modifiers) {
|
public override function setIndex(index: Int) {
|
||||||
switch (m) {
|
g = Input.getGamepad(index);
|
||||||
case "rtPressure": axis -= rtPressure;
|
}
|
||||||
case "ltPressure": axis -= ltPressure;
|
|
||||||
default: {
|
|
||||||
if (gamepad.down(m) > deadzone) {
|
|
||||||
axis--;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (k in displacementKeys) {
|
|
||||||
switch(k) {
|
|
||||||
case "rs moved x": if (rsMovementX > deadzone) axis++;
|
|
||||||
case "rs moved y": if (rsMovementY > deadzone) axis++;
|
|
||||||
case "ls moved x": if (lsMovementX > deadzone) axis++;
|
|
||||||
case "ls moved y": if (lsMovementY > deadzone) axis++;
|
|
||||||
case "rs movement x": if (rsMovementX > deadzone) return rsMovementX - deadzone;
|
|
||||||
case "rs movement y": if (rsMovementY > deadzone) return rsMovementY - deadzone;
|
|
||||||
case "ls movement x": if (lsMovementX > deadzone) return lsMovementX - deadzone;
|
|
||||||
case "ls movement y": if (lsMovementY > deadzone) return lsMovementY - deadzone;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (m in displacementModifiers) {
|
|
||||||
switch (m) {
|
|
||||||
case "rs moved x": if (rsMovementX < -deadzone) axis--;
|
|
||||||
case "rs moved y": if (rsMovementY < -deadzone) axis--;
|
|
||||||
case "ls moved x": if (lsMovementX < -deadzone) axis--;
|
|
||||||
case "ls moved y": if (lsMovementY < -deadzone) axis--;
|
|
||||||
case "rs movement x": if (rsMovementX < -deadzone) return rsMovementX + deadzone;
|
|
||||||
case "rs movement y": if (rsMovementY < -deadzone) return rsMovementY + deadzone;
|
|
||||||
case "ls movement x": if (lsMovementX < -deadzone) return lsMovementX + deadzone;
|
|
||||||
case "ls movement y": if (lsMovementY < -deadzone) return lsMovementY + deadzone;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return axis > 1 ? 1 : axis < -1 ? -1 : axis;
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -94,6 +94,10 @@ class Logic {
|
||||||
var v = createClassInstance(node.type, [tree]);
|
var v = createClassInstance(node.type, [tree]);
|
||||||
nodeMap.set(name, v);
|
nodeMap.set(name, v);
|
||||||
|
|
||||||
|
#if arm_patch
|
||||||
|
tree.nodes.set(name, v);
|
||||||
|
#end
|
||||||
|
|
||||||
// Properties
|
// Properties
|
||||||
for (i in 0...5) {
|
for (i in 0...5) {
|
||||||
for (b in node.buttons) {
|
for (b in node.buttons) {
|
||||||
|
@ -103,9 +107,13 @@ class Logic {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@:privateAccess v.preallocInputs(node.inputs.length);
|
||||||
|
@:privateAccess v.preallocOutputs(node.outputs.length);
|
||||||
|
|
||||||
// Create inputs
|
// Create inputs
|
||||||
var inp_node: armory.logicnode.LogicNode = null;
|
var inp_node: armory.logicnode.LogicNode = null;
|
||||||
var inp_from = 0;
|
var inp_from = 0;
|
||||||
|
var from_type: String;
|
||||||
for (i in 0...node.inputs.length) {
|
for (i in 0...node.inputs.length) {
|
||||||
var inp = node.inputs[i];
|
var inp = node.inputs[i];
|
||||||
// Is linked - find node
|
// Is linked - find node
|
||||||
|
@ -117,6 +125,7 @@ class Logic {
|
||||||
for (i in 0...n.outputs.length) {
|
for (i in 0...n.outputs.length) {
|
||||||
if (n.outputs[i] == socket) {
|
if (n.outputs[i] == socket) {
|
||||||
inp_from = i;
|
inp_from = i;
|
||||||
|
from_type = socket.type;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -124,28 +133,34 @@ class Logic {
|
||||||
else { // Not linked - create node with default values
|
else { // Not linked - create node with default values
|
||||||
inp_node = build_default_node(inp);
|
inp_node = build_default_node(inp);
|
||||||
inp_from = 0;
|
inp_from = 0;
|
||||||
|
from_type = inp.type;
|
||||||
}
|
}
|
||||||
// Add input
|
// Add input
|
||||||
v.addInput(inp_node, inp_from);
|
var link = LogicNode.addLink(inp_node, v, inp_from, i);
|
||||||
|
#if arm_patch
|
||||||
|
link.fromType = from_type;
|
||||||
|
link.toType = inp.type;
|
||||||
|
link.toValue = getSocketDefaultValue(inp);
|
||||||
|
#end
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create outputs
|
// Create outputs
|
||||||
for (out in node.outputs) {
|
for (i in 0...node.outputs.length) {
|
||||||
var outNodes: Array<armory.logicnode.LogicNode> = [];
|
var out = node.outputs[i];
|
||||||
var ls = getOutputLinks(out);
|
var ls = getOutputLinks(out);
|
||||||
if (ls != null && ls.length > 0) {
|
|
||||||
for (l in ls) {
|
// Linked outputs are already handled after iterating over inputs
|
||||||
var n = getNode(l.to_id);
|
// above, so only unconnected outputs are handled here
|
||||||
var out_name = build_node(n);
|
if (ls == null || ls.length == 0) {
|
||||||
outNodes.push(nodeMap.get(out_name));
|
var link = LogicNode.addLink(v, build_default_node(out), i, 0);
|
||||||
|
|
||||||
|
#if arm_patch
|
||||||
|
link.fromType = out.type;
|
||||||
|
link.toType = out.type;
|
||||||
|
link.toValue = getSocketDefaultValue(out);
|
||||||
|
#end
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else { // Not linked - create node with default values
|
|
||||||
outNodes.push(build_default_node(out));
|
|
||||||
}
|
|
||||||
// Add outputs
|
|
||||||
v.addOutputs(outNodes);
|
|
||||||
}
|
|
||||||
|
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
@ -212,6 +227,22 @@ class Logic {
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static function getSocketDefaultValue(socket: TNodeSocket): Any {
|
||||||
|
|
||||||
|
var v: armory.logicnode.LogicNode = null;
|
||||||
|
|
||||||
|
return switch (socket.type) {
|
||||||
|
case "OBJECT" | "VALUE" | "INT" | "BOOLEAN" | "STRING":
|
||||||
|
socket.default_value;
|
||||||
|
case "VECTOR" | "RGB":
|
||||||
|
socket.default_value == null ? [0, 0, 0] : [socket.default_value[0], socket.default_value[1], socket.default_value[2]];
|
||||||
|
case "RGBA":
|
||||||
|
socket.default_value == null ? [0, 0, 0, 1] : [socket.default_value[0], socket.default_value[1], socket.default_value[2], socket.default_value[3]];
|
||||||
|
default:
|
||||||
|
null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static function createClassInstance(className: String, args: Array<Dynamic>): Dynamic {
|
static function createClassInstance(className: String, args: Array<Dynamic>): Dynamic {
|
||||||
var cname = Type.resolveClass(packageName + "." + className);
|
var cname = Type.resolveClass(packageName + "." + className);
|
||||||
if (cname == null) return null;
|
if (cname == null) return null;
|
||||||
|
|
|
@ -6,6 +6,9 @@ import iron.math.Vec4;
|
||||||
|
|
||||||
class ArcBall extends Trait {
|
class ArcBall extends Trait {
|
||||||
|
|
||||||
|
@prop
|
||||||
|
public var axis = new Vec4(0, 0, 1);
|
||||||
|
|
||||||
public function new() {
|
public function new() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
@ -17,10 +20,8 @@ class ArcBall extends Trait {
|
||||||
|
|
||||||
var mouse = Input.getMouse();
|
var mouse = Input.getMouse();
|
||||||
if (mouse.down()) {
|
if (mouse.down()) {
|
||||||
object.transform.rotate(new Vec4(0, 0, 1), -mouse.movementX / 100);
|
object.transform.rotate(axis, -mouse.movementX / 100);
|
||||||
object.transform.buildMatrix();
|
|
||||||
object.transform.rotate(object.transform.world.right(), -mouse.movementY / 100);
|
object.transform.rotate(object.transform.world.right(), -mouse.movementY / 100);
|
||||||
object.transform.buildMatrix();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@ class FollowCamera extends iron.Trait {
|
||||||
trace("FollowCamera error, unable to set target object");
|
trace("FollowCamera error, unable to set target object");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Std.is(object, iron.object.CameraObject)) {
|
if (Std.isOfType(object, iron.object.CameraObject)) {
|
||||||
disabled = true;
|
disabled = true;
|
||||||
trace("FollowCamera error, this trait should not be placed directly on a camera objet. It should be placed on another object such as an Empty. The camera should be placed as a child to the Empty object with offset, creating a camera boom.");
|
trace("FollowCamera error, this trait should not be placed directly on a camera objet. It should be placed on another object such as an Empty. The camera should be placed as a child to the Empty object with offset, creating a camera boom.");
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package armory.trait;
|
||||||
|
|
||||||
import iron.Trait;
|
import iron.Trait;
|
||||||
import iron.system.Input;
|
import iron.system.Input;
|
||||||
|
import iron.math.Vec3;
|
||||||
import iron.math.Vec4;
|
import iron.math.Vec4;
|
||||||
import iron.math.Mat4;
|
import iron.math.Mat4;
|
||||||
import iron.math.RayCaster;
|
import iron.math.RayCaster;
|
||||||
|
@ -14,6 +15,11 @@ class PhysicsDrag extends Trait {
|
||||||
public function new() { super(); }
|
public function new() { super(); }
|
||||||
#else
|
#else
|
||||||
|
|
||||||
|
@prop public var linearLowerLimit = new Vec3(0,0,0);
|
||||||
|
@prop public var linearUpperLimit = new Vec3(0,0,0);
|
||||||
|
@prop public var angularLowerLimit = new Vec3(-10,-10,-10);
|
||||||
|
@prop public var angularUpperLimit = new Vec3(10,10,10);
|
||||||
|
|
||||||
var pickConstraint: bullet.Bt.Generic6DofConstraint = null;
|
var pickConstraint: bullet.Bt.Generic6DofConstraint = null;
|
||||||
var pickDist: Float;
|
var pickDist: Float;
|
||||||
var pickedBody: RigidBody = null;
|
var pickedBody: RigidBody = null;
|
||||||
|
@ -56,10 +62,10 @@ class PhysicsDrag extends Trait {
|
||||||
tr.setOrigin(localPivot);
|
tr.setOrigin(localPivot);
|
||||||
|
|
||||||
pickConstraint = new bullet.Bt.Generic6DofConstraint(b.body, tr, false);
|
pickConstraint = new bullet.Bt.Generic6DofConstraint(b.body, tr, false);
|
||||||
pickConstraint.setLinearLowerLimit(new bullet.Bt.Vector3(0, 0, 0));
|
pickConstraint.setLinearLowerLimit(new bullet.Bt.Vector3(linearLowerLimit.x, linearLowerLimit.y, linearLowerLimit.z));
|
||||||
pickConstraint.setLinearUpperLimit(new bullet.Bt.Vector3(0, 0, 0));
|
pickConstraint.setLinearUpperLimit(new bullet.Bt.Vector3(linearUpperLimit.x, linearUpperLimit.y, linearUpperLimit.z));
|
||||||
pickConstraint.setAngularLowerLimit(new bullet.Bt.Vector3(-10, -10, -10));
|
pickConstraint.setAngularLowerLimit(new bullet.Bt.Vector3(angularLowerLimit.x, angularLowerLimit.y, angularLowerLimit.z));
|
||||||
pickConstraint.setAngularUpperLimit(new bullet.Bt.Vector3(10, 10, 10));
|
pickConstraint.setAngularUpperLimit(new bullet.Bt.Vector3(angularUpperLimit.x, angularUpperLimit.y, angularUpperLimit.z));
|
||||||
physics.world.addConstraint(pickConstraint, false);
|
physics.world.addConstraint(pickConstraint, false);
|
||||||
|
|
||||||
/*pickConstraint.setParam(4, 0.8, 0);
|
/*pickConstraint.setParam(4, 0.8, 0);
|
||||||
|
|
|
@ -11,6 +11,7 @@ class Bridge {
|
||||||
public static var Input = iron.system.Input;
|
public static var Input = iron.system.Input;
|
||||||
public static var Object = iron.object.Object;
|
public static var Object = iron.object.Object;
|
||||||
public static var Data = iron.data.Data;
|
public static var Data = iron.data.Data;
|
||||||
|
public static var Vec4 = iron.math.Vec4;
|
||||||
public static function log(s: String) { trace(s); };
|
public static function log(s: String) { trace(s); };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ package armory.trait.internal;
|
||||||
import iron.Trait;
|
import iron.Trait;
|
||||||
#if arm_ui
|
#if arm_ui
|
||||||
import zui.Zui;
|
import zui.Zui;
|
||||||
import zui.Canvas;
|
import armory.ui.Canvas;
|
||||||
#end
|
#end
|
||||||
|
|
||||||
class CanvasScript extends Trait {
|
class CanvasScript extends Trait {
|
||||||
|
@ -29,7 +29,7 @@ class CanvasScript extends Trait {
|
||||||
iron.data.Data.getBlob(canvasName + ".json", function(blob: kha.Blob) {
|
iron.data.Data.getBlob(canvasName + ".json", function(blob: kha.Blob) {
|
||||||
|
|
||||||
iron.data.Data.getBlob("_themes.json", function(tBlob: kha.Blob) {
|
iron.data.Data.getBlob("_themes.json", function(tBlob: kha.Blob) {
|
||||||
if (tBlob.get_length() != 0) {
|
if (@:privateAccess tBlob.get_length() != 0) {
|
||||||
Canvas.themes = haxe.Json.parse(tBlob.toString());
|
Canvas.themes = haxe.Json.parse(tBlob.toString());
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -37,25 +37,32 @@ class CanvasScript extends Trait {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Canvas.themes.length == 0) {
|
if (Canvas.themes.length == 0) {
|
||||||
Canvas.themes.push(zui.Themes.light);
|
Canvas.themes.push(armory.ui.Themes.light);
|
||||||
}
|
}
|
||||||
|
|
||||||
iron.data.Data.getFont(font, function(f: kha.Font) {
|
iron.data.Data.getFont(font, function(defaultFont: kha.Font) {
|
||||||
var c: TCanvas = haxe.Json.parse(blob.toString());
|
var c: TCanvas = haxe.Json.parse(blob.toString());
|
||||||
if (c.theme == null) c.theme = Canvas.themes[0].NAME;
|
if (c.theme == null) c.theme = Canvas.themes[0].NAME;
|
||||||
cui = new Zui({font: f, theme: Canvas.getTheme(c.theme)});
|
cui = new Zui({font: defaultFont, theme: Canvas.getTheme(c.theme)});
|
||||||
|
|
||||||
if (c.assets == null || c.assets.length == 0) canvas = c;
|
if (c.assets == null || c.assets.length == 0) canvas = c;
|
||||||
else { // Load canvas assets
|
else { // Load canvas assets
|
||||||
var loaded = 0;
|
var loaded = 0;
|
||||||
for (asset in c.assets) {
|
for (asset in c.assets) {
|
||||||
var file = asset.name;
|
var file = asset.name;
|
||||||
|
if (Canvas.isFontAsset(file)) {
|
||||||
|
iron.data.Data.getFont(file, function(f: kha.Font) {
|
||||||
|
Canvas.assetMap.set(asset.id, f);
|
||||||
|
if (++loaded >= c.assets.length) canvas = c;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
iron.data.Data.getImage(file, function(image: kha.Image) {
|
iron.data.Data.getImage(file, function(image: kha.Image) {
|
||||||
Canvas.assetMap.set(asset.id, image);
|
Canvas.assetMap.set(asset.id, image);
|
||||||
if (++loaded >= c.assets.length) canvas = c;
|
if (++loaded >= c.assets.length) canvas = c;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -140,7 +147,7 @@ class CanvasScript extends Trait {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Contains data
|
// Contains data
|
||||||
@:access(zui.Canvas)
|
@:access(armory.ui.Canvas)
|
||||||
@:access(zui.Handle)
|
@:access(zui.Handle)
|
||||||
public function getHandle(name: String): Handle {
|
public function getHandle(name: String): Handle {
|
||||||
// Consider this a temporary solution
|
// Consider this a temporary solution
|
||||||
|
|
|
@ -8,6 +8,10 @@ import iron.object.MeshObject;
|
||||||
import zui.Zui;
|
import zui.Zui;
|
||||||
import zui.Id;
|
import zui.Id;
|
||||||
using armory.object.TransformExtension;
|
using armory.object.TransformExtension;
|
||||||
|
#if arm_shadowmap_atlas
|
||||||
|
import armory.renderpath.Inc.ShadowMapTile;
|
||||||
|
import armory.renderpath.Inc.ShadowMapAtlas;
|
||||||
|
#end
|
||||||
#end
|
#end
|
||||||
|
|
||||||
#if arm_debug
|
#if arm_debug
|
||||||
|
@ -61,6 +65,15 @@ class DebugConsole extends Trait {
|
||||||
var shortcut_scale_in = kha.input.KeyCode.OpenBracket;
|
var shortcut_scale_in = kha.input.KeyCode.OpenBracket;
|
||||||
var shortcut_scale_out = kha.input.KeyCode.CloseBracket;
|
var shortcut_scale_out = kha.input.KeyCode.CloseBracket;
|
||||||
|
|
||||||
|
#if arm_shadowmap_atlas
|
||||||
|
var lightColorMap: Map<String, Int> = new Map();
|
||||||
|
var lightColorMapCount = 0;
|
||||||
|
var smaLogicTime = 0.0;
|
||||||
|
var smaLogicTimeAvg = 0.0;
|
||||||
|
var smaRenderTime = 0.0;
|
||||||
|
var smaRenderTimeAvg = 0.0;
|
||||||
|
#end
|
||||||
|
|
||||||
public function new(scaleFactor = 1.0, scaleDebugConsole = 1.0, positionDebugConsole = 2, visibleDebugConsole = 1,
|
public function new(scaleFactor = 1.0, scaleDebugConsole = 1.0, positionDebugConsole = 2, visibleDebugConsole = 1,
|
||||||
keyCodeVisible = kha.input.KeyCode.Tilde, keyCodeScaleIn = kha.input.KeyCode.OpenBracket, keyCodeScaleOut = kha.input.KeyCode.CloseBracket) {
|
keyCodeVisible = kha.input.KeyCode.Tilde, keyCodeScaleIn = kha.input.KeyCode.OpenBracket, keyCodeScaleOut = kha.input.KeyCode.CloseBracket) {
|
||||||
super();
|
super();
|
||||||
|
@ -423,6 +436,7 @@ class DebugConsole extends Trait {
|
||||||
light.data.raw.strength = ui.slider(lightHandle, "Strength", 0.0, 5.0, true) * 10;
|
light.data.raw.strength = ui.slider(lightHandle, "Strength", 0.0, 5.0, true) * 10;
|
||||||
#if arm_shadowmap_atlas
|
#if arm_shadowmap_atlas
|
||||||
ui.text("status: " + (light.culledLight ? "culled" : "rendered"));
|
ui.text("status: " + (light.culledLight ? "culled" : "rendered"));
|
||||||
|
ui.text("shadow map size: " + light.data.raw.shadowmap_size);
|
||||||
#end
|
#end
|
||||||
}
|
}
|
||||||
else if (Std.is(selectedObject, iron.object.CameraObject)) {
|
else if (Std.is(selectedObject, iron.object.CameraObject)) {
|
||||||
|
@ -473,6 +487,16 @@ class DebugConsole extends Trait {
|
||||||
ui.text("Physics");
|
ui.text("Physics");
|
||||||
ui.text(Math.round(physTimeAvg * 10000) / 10 + " ms", Align.Right);
|
ui.text(Math.round(physTimeAvg * 10000) / 10 + " ms", Align.Right);
|
||||||
|
|
||||||
|
#if arm_shadowmap_atlas
|
||||||
|
ui.row(lrow);
|
||||||
|
ui.text("Shadow Map Atlas (Logic)");
|
||||||
|
ui.text(Math.round(smaLogicTimeAvg * 10000) / 10 + " ms", Align.Right);
|
||||||
|
|
||||||
|
ui.row(lrow);
|
||||||
|
ui.text("Shadow Map Atlas (Render)");
|
||||||
|
ui.text(Math.round(smaRenderTimeAvg * 10000) / 10 + " ms", Align.Right);
|
||||||
|
#end
|
||||||
|
|
||||||
ui.unindent();
|
ui.unindent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -520,6 +544,182 @@ class DebugConsole extends Trait {
|
||||||
ui.unindent();
|
ui.unindent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if arm_shadowmap_atlas
|
||||||
|
if (ui.panel(Id.handle({selected: false}), "Shadow Map Atlases")) {
|
||||||
|
inline function highLightNext(color: kha.Color = null) {
|
||||||
|
ui.g.color = color != null ? color : -13882324;
|
||||||
|
ui.g.fillRect(ui._x, ui._y, ui._windowW, ui.ELEMENT_H());
|
||||||
|
ui.g.color = 0xffffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline function drawScale(text: String, y: Float, fromX: Float, toX: Float, bottom = false) {
|
||||||
|
var _off = bottom ? -4 : 4;
|
||||||
|
ui.g.drawLine(fromX, y, toX, y);
|
||||||
|
ui.g.drawLine(fromX, y, fromX, y + _off);
|
||||||
|
ui.g.drawLine(toX, y, toX, y + _off);
|
||||||
|
|
||||||
|
var _w = ui._w;
|
||||||
|
ui._w = Std.int(Math.abs(toX - fromX));
|
||||||
|
ui.text(text, Align.Center);
|
||||||
|
ui._w = _w;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* create a kha Color from HSV (Hue, Saturation, Value)
|
||||||
|
* @param h expected Hue from [0, 1].
|
||||||
|
* @param s expected Saturation from [0, 1].
|
||||||
|
* @param v expected Value from [0, 1].
|
||||||
|
* @return kha.Color
|
||||||
|
*/
|
||||||
|
function colorFromHSV(h: Float, s: Float, v: Float): kha.Color {
|
||||||
|
// https://stackoverflow.com/a/17243070
|
||||||
|
var r = 0.0; var g = 0.0; var b = 0.0;
|
||||||
|
|
||||||
|
var i = Math.floor(h * 6);
|
||||||
|
var f = h * 6 - i;
|
||||||
|
var p = v * (1 - s);
|
||||||
|
var q = v * (1 - f * s);
|
||||||
|
var t = v * (1 - (1 - f) * s);
|
||||||
|
|
||||||
|
switch (i % 6) {
|
||||||
|
case 0: { r = v; g = t; b = p; }
|
||||||
|
case 1: { r = q; g = v; b = p; }
|
||||||
|
case 2: { r = p; g = v; b = t; }
|
||||||
|
case 3: { r = p; g = q; b = v; }
|
||||||
|
case 4: { r = t; g = p; b = v; }
|
||||||
|
case 5: { r = v; g = p; b = q; }
|
||||||
|
}
|
||||||
|
|
||||||
|
return kha.Color.fromFloats(r, g, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawTiles(tile: ShadowMapTile, atlas: ShadowMapAtlas, atlasVisualSize: Float) {
|
||||||
|
var color: Null<kha.Color> = kha.Color.fromFloats(0.1, 0.1, 0.1);
|
||||||
|
var borderColor = color;
|
||||||
|
var tileScale = (tile.size / atlas.sizew) * atlasVisualSize; //* 0.95;
|
||||||
|
var x = (tile.coordsX / atlas.sizew) * atlasVisualSize;
|
||||||
|
var y = (tile.coordsY / atlas.sizew) * atlasVisualSize;
|
||||||
|
|
||||||
|
if (tile.light != null) {
|
||||||
|
color = lightColorMap.get(tile.light.name);
|
||||||
|
if (color == null) {
|
||||||
|
color = colorFromHSV(Math.random(), 0.7, Math.random() * 0.5 + 0.5);
|
||||||
|
|
||||||
|
lightColorMap.set(tile.light.name, color);
|
||||||
|
lightColorMapCount++;
|
||||||
|
}
|
||||||
|
ui.fill(x + tileScale * 0.019, y + tileScale * 0.03, tileScale * 0.96, tileScale * 0.96, color);
|
||||||
|
}
|
||||||
|
ui.rect(x, y, tileScale, tileScale, borderColor);
|
||||||
|
|
||||||
|
#if arm_shadowmap_atlas_lod
|
||||||
|
// draw children tiles
|
||||||
|
for (t in tile.tiles)
|
||||||
|
drawTiles(t, atlas, atlasVisualSize);
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.indent(false);
|
||||||
|
ui.text("Constants:");
|
||||||
|
highLightNext();
|
||||||
|
ui.text('Tiles Used Per Point Light: ${ ShadowMapTile.tilesLightType("point") }');
|
||||||
|
ui.text('Tiles Used Per Spot Light: ${ ShadowMapTile.tilesLightType("spot") }');
|
||||||
|
highLightNext();
|
||||||
|
ui.text('Tiles Used For Sun: ${ ShadowMapTile.tilesLightType("sun") }');
|
||||||
|
ui.unindent(false);
|
||||||
|
|
||||||
|
ui.indent(false);
|
||||||
|
var i = 0;
|
||||||
|
for (atlas in ShadowMapAtlas.shadowMapAtlases) {
|
||||||
|
if (ui.panel(Id.handle({selected: false}).nest(i), atlas.target )) {
|
||||||
|
ui.indent(false);
|
||||||
|
// Draw visual representation of the atlas
|
||||||
|
var atlasVisualSize = ui._windowW * 0.92;
|
||||||
|
|
||||||
|
drawScale('${atlas.sizew}px', ui._y + ui.ELEMENT_H() * 0.9, ui._x, ui._x + atlasVisualSize);
|
||||||
|
|
||||||
|
// reset light color map when lights are removed
|
||||||
|
if (lightColorMapCount > iron.Scene.active.lights.length) {
|
||||||
|
lightColorMap = new Map();
|
||||||
|
lightColorMapCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (tile in atlas.tiles)
|
||||||
|
drawTiles(tile, atlas, atlasVisualSize);
|
||||||
|
// set vertical space for atlas visual representation
|
||||||
|
ui._y += atlasVisualSize + 3;
|
||||||
|
|
||||||
|
var tilesRow = atlas.currTileOffset == 0 ? 1 : atlas.currTileOffset;
|
||||||
|
var tileScale = atlasVisualSize / tilesRow;
|
||||||
|
drawScale('${atlas.baseTileSizeConst}px', ui._y, ui._x, ui._x + tileScale, true);
|
||||||
|
|
||||||
|
// general atlas information
|
||||||
|
highLightNext();
|
||||||
|
ui.text('Max Atlas Size: ${atlas.maxAtlasSizeConst}, ${atlas.maxAtlasSizeConst} px');
|
||||||
|
highLightNext();
|
||||||
|
|
||||||
|
// show detailed information per light
|
||||||
|
if (ui.panel(Id.handle({selected: false}).nest(i).nest(0), "Lights in Atlas")) {
|
||||||
|
ui.indent(false);
|
||||||
|
var j = 1;
|
||||||
|
for (tile in atlas.activeTiles) {
|
||||||
|
var textCol = ui.t.TEXT_COL;
|
||||||
|
var lightCol = lightColorMap.get(tile.light.name);
|
||||||
|
if (lightCol != null)
|
||||||
|
ui.t.TEXT_COL = lightCol;
|
||||||
|
#if arm_shadowmap_atlas_lod
|
||||||
|
if (ui.panel(Id.handle({selected: false}).nest(i).nest(j), tile.light.name)) {
|
||||||
|
ui.t.TEXT_COL = textCol;
|
||||||
|
ui.indent(false);
|
||||||
|
ui.text('Shadow Map Size: ${tile.size}, ${tile.size} px');
|
||||||
|
ui.unindent(false);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
ui.indent(false);
|
||||||
|
ui.text(tile.light.name);
|
||||||
|
ui.unindent(false);
|
||||||
|
#end
|
||||||
|
ui.t.TEXT_COL = textCol;
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
ui.unindent(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// show unused tiles statistics
|
||||||
|
#if arm_shadowmap_atlas_lod
|
||||||
|
// WIP
|
||||||
|
#else
|
||||||
|
var unusedTiles = atlas.tiles.length;
|
||||||
|
#if arm_shadowmap_atlas_single_map
|
||||||
|
for (tile in atlas.activeTiles)
|
||||||
|
unusedTiles -= ShadowMapTile.tilesLightType(tile.light.data.raw.type);
|
||||||
|
#else
|
||||||
|
unusedTiles -= atlas.activeTiles.length * ShadowMapTile.tilesLightType(atlas.lightType);
|
||||||
|
#end
|
||||||
|
ui.text('Unused tiles: ${unusedTiles}');
|
||||||
|
#end
|
||||||
|
|
||||||
|
var rejectedLightsNames = "";
|
||||||
|
if (atlas.rejectedLights.length > 0) {
|
||||||
|
for (l in atlas.rejectedLights)
|
||||||
|
rejectedLightsNames += l.name + ", ";
|
||||||
|
rejectedLightsNames = rejectedLightsNames.substr(0, rejectedLightsNames.length - 2);
|
||||||
|
highLightNext(kha.Color.fromFloats(0.447, 0.247, 0.188));
|
||||||
|
ui.text('Not enough space in atlas for ${atlas.rejectedLights.length} light${atlas.rejectedLights.length > 1 ? "s" : ""}:');
|
||||||
|
ui.indent();
|
||||||
|
ui.text(${rejectedLightsNames});
|
||||||
|
ui.unindent(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.unindent(false);
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
ui.unindent(false);
|
||||||
|
ui.unindent(false);
|
||||||
|
}
|
||||||
|
#end
|
||||||
|
|
||||||
if (ui.panel(Id.handle({selected: false}), "Render Targets")) {
|
if (ui.panel(Id.handle({selected: false}), "Render Targets")) {
|
||||||
ui.indent();
|
ui.indent();
|
||||||
#if (kha_opengl || kha_webgl)
|
#if (kha_opengl || kha_webgl)
|
||||||
|
@ -682,6 +882,14 @@ class DebugConsole extends Trait {
|
||||||
animTimeAvg = animTime / frames;
|
animTimeAvg = animTime / frames;
|
||||||
physTimeAvg = physTime / frames;
|
physTimeAvg = physTime / frames;
|
||||||
|
|
||||||
|
#if arm_shadowmap_atlas
|
||||||
|
smaLogicTimeAvg = smaLogicTime / frames;
|
||||||
|
smaLogicTime = 0;
|
||||||
|
|
||||||
|
smaRenderTimeAvg = smaRenderTime / frames;
|
||||||
|
smaRenderTime = 0;
|
||||||
|
#end
|
||||||
|
|
||||||
totalTime = 0;
|
totalTime = 0;
|
||||||
renderPathTime = 0;
|
renderPathTime = 0;
|
||||||
updateTime = 0;
|
updateTime = 0;
|
||||||
|
@ -706,6 +914,10 @@ class DebugConsole extends Trait {
|
||||||
#if arm_physics
|
#if arm_physics
|
||||||
physTime += armory.trait.physics.PhysicsWorld.physTime;
|
physTime += armory.trait.physics.PhysicsWorld.physTime;
|
||||||
#end
|
#end
|
||||||
|
#if arm_shadowmap_atlas
|
||||||
|
smaLogicTime += armory.renderpath.Inc.shadowsLogicTime;
|
||||||
|
smaRenderTime += armory.renderpath.Inc.shadowsRenderTime;
|
||||||
|
#end
|
||||||
}
|
}
|
||||||
|
|
||||||
static function roundfp(f: Float, precision = 2): Float {
|
static function roundfp(f: Float, precision = 2): Float {
|
||||||
|
|
|
@ -1,8 +1,17 @@
|
||||||
package armory.trait.internal;
|
package armory.trait.internal;
|
||||||
|
|
||||||
|
import armory.logicnode.LogicNode;
|
||||||
|
import armory.logicnode.LogicTree;
|
||||||
|
|
||||||
|
|
||||||
|
#if arm_patch @:expose("LivePatch") #end
|
||||||
|
@:access(armory.logicnode.LogicNode)
|
||||||
|
@:access(armory.logicnode.LogicNodeLink)
|
||||||
class LivePatch extends iron.Trait {
|
class LivePatch extends iron.Trait {
|
||||||
|
|
||||||
#if arm_patch
|
#if !arm_patch
|
||||||
|
public function new() { super(); }
|
||||||
|
#else
|
||||||
|
|
||||||
static var patchId = 0;
|
static var patchId = 0;
|
||||||
|
|
||||||
|
@ -23,9 +32,164 @@ class LivePatch extends iron.Trait {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#else
|
public static function patchCreateNodeLink(treeName: String, fromNodeName: String, toNodeName: String, fromIndex: Int, toIndex: Int) {
|
||||||
|
if (!LogicTree.nodeTrees.exists(treeName)) return;
|
||||||
|
var trees = LogicTree.nodeTrees[treeName];
|
||||||
|
|
||||||
public function new() { super(); }
|
for (tree in trees) {
|
||||||
|
var fromNode = tree.nodes[fromNodeName];
|
||||||
|
var toNode = tree.nodes[toNodeName];
|
||||||
|
if (fromNode == null || toNode == null) return;
|
||||||
|
|
||||||
|
LogicNode.addLink(fromNode, toNode, fromIndex, toIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function patchSetNodeLinks(treeName: String, nodeName: String, inputDatas: Array<Dynamic>, outputDatas: Array<Array<Dynamic>>) {
|
||||||
|
if (!LogicTree.nodeTrees.exists(treeName)) return;
|
||||||
|
var trees = LogicTree.nodeTrees[treeName];
|
||||||
|
|
||||||
|
for (tree in trees) {
|
||||||
|
var node = tree.nodes[nodeName];
|
||||||
|
if (node == null) return;
|
||||||
|
|
||||||
|
node.clearInputs();
|
||||||
|
node.clearOutputs();
|
||||||
|
|
||||||
|
for (inputData in inputDatas) {
|
||||||
|
var fromNode: LogicNode;
|
||||||
|
var fromIndex: Int;
|
||||||
|
|
||||||
|
if (inputData.isLinked) {
|
||||||
|
fromNode = tree.nodes[inputData.fromNode];
|
||||||
|
if (fromNode == null) continue;
|
||||||
|
fromIndex = inputData.fromIndex;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
fromNode = LogicNode.createSocketDefaultNode(node.tree, inputData.socketType, inputData.socketValue);
|
||||||
|
fromIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
LogicNode.addLink(fromNode, node, fromIndex, inputData.toIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (outputData in outputDatas) {
|
||||||
|
for (linkData in outputData) {
|
||||||
|
var toNode: LogicNode;
|
||||||
|
var toIndex: Int;
|
||||||
|
|
||||||
|
if (linkData.isLinked) {
|
||||||
|
toNode = tree.nodes[linkData.toNode];
|
||||||
|
if (toNode == null) continue;
|
||||||
|
toIndex = linkData.toIndex;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
toNode = LogicNode.createSocketDefaultNode(node.tree, linkData.socketType, linkData.socketValue);
|
||||||
|
toIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
LogicNode.addLink(node, toNode, linkData.fromIndex, toIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function patchUpdateNodeProp(treeName: String, nodeName: String, propName: String, value: Dynamic) {
|
||||||
|
if (!LogicTree.nodeTrees.exists(treeName)) return;
|
||||||
|
var trees = LogicTree.nodeTrees[treeName];
|
||||||
|
|
||||||
|
for (tree in trees) {
|
||||||
|
var node = tree.nodes[nodeName];
|
||||||
|
if (node == null) return;
|
||||||
|
|
||||||
|
Reflect.setField(node, propName, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function patchUpdateNodeInputVal(treeName: String, nodeName: String, socketIndex: Int, value: Dynamic) {
|
||||||
|
if (!LogicTree.nodeTrees.exists(treeName)) return;
|
||||||
|
var trees = LogicTree.nodeTrees[treeName];
|
||||||
|
|
||||||
|
for (tree in trees) {
|
||||||
|
var node = tree.nodes[nodeName];
|
||||||
|
if (node == null) return;
|
||||||
|
|
||||||
|
node.inputs[socketIndex].set(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function patchNodeDelete(treeName: String, nodeName: String) {
|
||||||
|
if (!LogicTree.nodeTrees.exists(treeName)) return;
|
||||||
|
var trees = LogicTree.nodeTrees[treeName];
|
||||||
|
|
||||||
|
for (tree in trees) {
|
||||||
|
var node = tree.nodes[nodeName];
|
||||||
|
if (node == null) return;
|
||||||
|
|
||||||
|
node.clearOutputs();
|
||||||
|
node.clearInputs();
|
||||||
|
tree.nodes.remove(nodeName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function patchNodeCreate(treeName: String, nodeName: String, nodeType: String, propDatas: Array<Array<Dynamic>>, inputDatas: Array<Array<Dynamic>>, outputDatas: Array<Array<Dynamic>>) {
|
||||||
|
if (!LogicTree.nodeTrees.exists(treeName)) return;
|
||||||
|
var trees = LogicTree.nodeTrees[treeName];
|
||||||
|
|
||||||
|
for (tree in trees) {
|
||||||
|
// No further constructor parameters required here, all variable nodes
|
||||||
|
// use optional further parameters and all values are set later in this
|
||||||
|
// function.
|
||||||
|
var newNode: LogicNode = Type.createInstance(Type.resolveClass(nodeType), [tree]);
|
||||||
|
newNode.name = nodeName;
|
||||||
|
tree.nodes[nodeName] = newNode;
|
||||||
|
|
||||||
|
for (propData in propDatas) {
|
||||||
|
Reflect.setField(newNode, propData[0], propData[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var i = 0;
|
||||||
|
for (inputData in inputDatas) {
|
||||||
|
LogicNode.addLink(LogicNode.createSocketDefaultNode(newNode.tree, inputData[0], inputData[1]), newNode, 0, i++);
|
||||||
|
}
|
||||||
|
|
||||||
|
i = 0;
|
||||||
|
for (outputData in outputDatas) {
|
||||||
|
LogicNode.addLink(newNode, LogicNode.createSocketDefaultNode(newNode.tree, outputData[0], outputData[1]), i++, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function patchNodeCopy(treeName: String, nodeName: String, newNodeName: String, copyProps: Array<String>, inputDatas: Array<Array<Dynamic>>, outputDatas: Array<Array<Dynamic>>) {
|
||||||
|
if (!LogicTree.nodeTrees.exists(treeName)) return;
|
||||||
|
var trees = LogicTree.nodeTrees[treeName];
|
||||||
|
|
||||||
|
for (tree in trees) {
|
||||||
|
var node = tree.nodes[nodeName];
|
||||||
|
if (node == null) return;
|
||||||
|
|
||||||
|
// No further constructor parameters required here, all variable nodes
|
||||||
|
// use optional further parameters and all values are set later in this
|
||||||
|
// function.
|
||||||
|
var newNode: LogicNode = Type.createInstance(Type.getClass(node), [tree]);
|
||||||
|
newNode.name = newNodeName;
|
||||||
|
tree.nodes[newNodeName] = newNode;
|
||||||
|
|
||||||
|
for (propName in copyProps) {
|
||||||
|
Reflect.setField(newNode, propName, Reflect.field(node, propName));
|
||||||
|
}
|
||||||
|
|
||||||
|
var i = 0;
|
||||||
|
for (inputData in inputDatas) {
|
||||||
|
LogicNode.addLink(LogicNode.createSocketDefaultNode(newNode.tree, inputData[0], inputData[1]), newNode, 0, i++);
|
||||||
|
}
|
||||||
|
|
||||||
|
i = 0;
|
||||||
|
for (outputData in outputDatas) {
|
||||||
|
LogicNode.addLink(newNode, LogicNode.createSocketDefaultNode(newNode.tree, outputData[0], outputData[1]), i++, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#end
|
#end
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,14 +2,29 @@ package armory.trait.internal;
|
||||||
|
|
||||||
import kha.Image;
|
import kha.Image;
|
||||||
import kha.Video;
|
import kha.Video;
|
||||||
|
|
||||||
import iron.Trait;
|
import iron.Trait;
|
||||||
import iron.object.MeshObject;
|
import iron.object.MeshObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Replaces the diffuse texture of the first material of the trait's object
|
||||||
|
with a video texture.
|
||||||
|
|
||||||
|
@see https://github.com/armory3d/armory_examples/tree/master/material_movie
|
||||||
|
**/
|
||||||
class MovieTexture extends Trait {
|
class MovieTexture extends Trait {
|
||||||
|
|
||||||
|
/**
|
||||||
|
Caches all render targets used by this trait for re-use when having
|
||||||
|
multiple videos of the same size. The lookup only takes place on trait
|
||||||
|
initialization.
|
||||||
|
|
||||||
|
Map layout: `[width => [height => image]]`
|
||||||
|
**/
|
||||||
|
static var imageCache: Map<Int, Map<Int, Image>> = new Map();
|
||||||
|
|
||||||
var video: Video;
|
var video: Video;
|
||||||
public static var image: Image;
|
var image: Image;
|
||||||
public static var created = false;
|
|
||||||
|
|
||||||
var videoName: String;
|
var videoName: String;
|
||||||
|
|
||||||
|
@ -33,20 +48,29 @@ class MovieTexture extends Trait {
|
||||||
|
|
||||||
this.videoName = videoName;
|
this.videoName = videoName;
|
||||||
|
|
||||||
if (!created) {
|
|
||||||
created = true;
|
|
||||||
notifyOnInit(init);
|
notifyOnInit(init);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
iron.data.Data.getVideo(videoName, function(vid: kha.Video) {
|
iron.data.Data.getVideo(videoName, function(vid: kha.Video) {
|
||||||
video = vid;
|
video = vid;
|
||||||
video.play(true);
|
video.play(true);
|
||||||
|
|
||||||
image = Image.createRenderTarget(getPower2(video.width()), getPower2(video.height()));
|
var w = getPower2(video.width());
|
||||||
|
var h = getPower2(video.height());
|
||||||
|
|
||||||
var o = cast(object, iron.object.MeshObject);
|
// Lazily fill the outer map
|
||||||
|
var hMap: Map<Int, Image> = imageCache[w];
|
||||||
|
if (hMap == null) {
|
||||||
|
imageCache[w] = new Map<Int, Image>();
|
||||||
|
}
|
||||||
|
|
||||||
|
image = imageCache[w][h];
|
||||||
|
if (image == null) {
|
||||||
|
imageCache[w][h] = image = Image.createRenderTarget(w, h);
|
||||||
|
}
|
||||||
|
|
||||||
|
var o = cast(object, MeshObject);
|
||||||
o.materials[0].contexts[0].textures[0] = image; // Override diffuse texture
|
o.materials[0].contexts[0].textures[0] = image; // Override diffuse texture
|
||||||
notifyOnRender2D(render);
|
notifyOnRender2D(render);
|
||||||
});
|
});
|
||||||
|
|
320
Sources/armory/trait/internal/UniformsManager.hx
Normal file
320
Sources/armory/trait/internal/UniformsManager.hx
Normal file
|
@ -0,0 +1,320 @@
|
||||||
|
package armory.trait.internal;
|
||||||
|
|
||||||
|
import iron.object.MeshObject;
|
||||||
|
import iron.Trait;
|
||||||
|
import kha.Image;
|
||||||
|
import iron.math.Vec4;
|
||||||
|
import iron.data.MaterialData;
|
||||||
|
import iron.Scene;
|
||||||
|
import iron.object.Object;
|
||||||
|
import iron.object.Uniforms;
|
||||||
|
|
||||||
|
|
||||||
|
class UniformsManager extends Trait{
|
||||||
|
|
||||||
|
static var floatsRegistered = false;
|
||||||
|
static var floatsMap = new Map<Object, Map<MaterialData, Map<String, Null<kha.FastFloat>>>>();
|
||||||
|
|
||||||
|
static var vectorsRegistered = false;
|
||||||
|
static var vectorsMap = new Map<Object, Map<MaterialData, Map<String, Vec4>>>();
|
||||||
|
|
||||||
|
static var texturesRegistered = false;
|
||||||
|
static var texturesMap = new Map<Object, Map<MaterialData, Map<String, kha.Image>>>();
|
||||||
|
|
||||||
|
static var sceneRemoveInitalized = false;
|
||||||
|
|
||||||
|
public var uniformExists = false;
|
||||||
|
|
||||||
|
public function new(){
|
||||||
|
super();
|
||||||
|
|
||||||
|
notifyOnInit(init);
|
||||||
|
|
||||||
|
notifyOnRemove(removeObject);
|
||||||
|
|
||||||
|
if(! sceneRemoveInitalized){
|
||||||
|
|
||||||
|
Scene.active.notifyOnRemove(removeScene);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
var materials = cast(object, MeshObject).materials;
|
||||||
|
|
||||||
|
for (material in materials){
|
||||||
|
|
||||||
|
var exists = registerShaderUniforms(material);
|
||||||
|
if(exists) {
|
||||||
|
uniformExists = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static function removeScene() {
|
||||||
|
|
||||||
|
removeObjectFromAllMaps(Scene.active.root);
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeObject() {
|
||||||
|
|
||||||
|
removeObjectFromAllMaps(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper method to register float, vec3 and texture getter functions
|
||||||
|
static function register(type: UniformType){
|
||||||
|
|
||||||
|
switch (type){
|
||||||
|
case Float:{
|
||||||
|
if(! floatsRegistered){
|
||||||
|
floatsRegistered = true;
|
||||||
|
Uniforms.externalFloatLinks.push(floatLink);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case Vector:{
|
||||||
|
if(! vectorsRegistered){
|
||||||
|
vectorsRegistered = true;
|
||||||
|
Uniforms.externalVec3Links.push(vec3Link);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case Texture:{
|
||||||
|
if(! texturesRegistered){
|
||||||
|
texturesRegistered = true;
|
||||||
|
Uniforms.externalTextureLinks.push(textureLink);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register and map shader uniforms if it is an armory shader parameter
|
||||||
|
public static function registerShaderUniforms(material: MaterialData) : Bool {
|
||||||
|
|
||||||
|
var uniformExist = false;
|
||||||
|
|
||||||
|
if(! floatsMap.exists(Scene.active.root)) floatsMap.set(Scene.active.root, null);
|
||||||
|
if(! vectorsMap.exists(Scene.active.root)) vectorsMap.set(Scene.active.root, null);
|
||||||
|
if(! texturesMap.exists(Scene.active.root)) texturesMap.set(Scene.active.root, null);
|
||||||
|
|
||||||
|
for(context in material.shader.raw.contexts){ // For each context in shader
|
||||||
|
for (constant in context.constants){ // For each constant in the context
|
||||||
|
if(constant.is_arm_parameter){ // Check if armory parameter
|
||||||
|
|
||||||
|
uniformExist = true;
|
||||||
|
var object = Scene.active.root; // Map default uniforms to scene root
|
||||||
|
|
||||||
|
switch (constant.type){
|
||||||
|
case "float":{
|
||||||
|
var link = constant.link;
|
||||||
|
var value = constant.float;
|
||||||
|
setFloatValue(material, object, link, value);
|
||||||
|
register(Float);
|
||||||
|
}
|
||||||
|
|
||||||
|
case "vec3":{
|
||||||
|
|
||||||
|
var vec = new Vec4();
|
||||||
|
vec.x = constant.vec3.get(0);
|
||||||
|
vec.y = constant.vec3.get(1);
|
||||||
|
vec.z = constant.vec3.get(2);
|
||||||
|
|
||||||
|
setVec3Value(material, object, constant.link, vec);
|
||||||
|
register(Vector);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (texture in context.texture_units){
|
||||||
|
if(texture.is_arm_parameter){ // Check if armory parameter
|
||||||
|
|
||||||
|
uniformExist = true;
|
||||||
|
var object = Scene.active.root; // Map default texture to scene root
|
||||||
|
|
||||||
|
if(texture.default_image_file == null){
|
||||||
|
setTextureValue(material, object, texture.link, null);
|
||||||
|
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
iron.data.Data.getImage(texture.default_image_file, function(image: kha.Image) {
|
||||||
|
setTextureValue(material, object, texture.link, image);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
register(Texture);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return uniformExist;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to set map Object -> Material -> Link -> FLoat
|
||||||
|
public static function setFloatValue(material: MaterialData, object: Object, link: String, value: Null<kha.FastFloat>){
|
||||||
|
|
||||||
|
if(object == null || material == null || link == null) return;
|
||||||
|
|
||||||
|
var map = floatsMap;
|
||||||
|
|
||||||
|
var matMap = map.get(object);
|
||||||
|
if (matMap == null) {
|
||||||
|
matMap = new Map();
|
||||||
|
map.set(object, matMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
var entry = matMap.get(material);
|
||||||
|
if (entry == null) {
|
||||||
|
entry = new Map();
|
||||||
|
matMap.set(material, entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.set(link, value); // parameter name, value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to set map Object -> Material -> Link -> Vec3
|
||||||
|
public static function setVec3Value(material: MaterialData, object: Object, link: String, value: Vec4){
|
||||||
|
|
||||||
|
if(object == null || material == null || link == null) return;
|
||||||
|
|
||||||
|
var map = vectorsMap;
|
||||||
|
|
||||||
|
var matMap = map.get(object);
|
||||||
|
if (matMap == null) {
|
||||||
|
matMap = new Map();
|
||||||
|
map.set(object, matMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
var entry = matMap.get(material);
|
||||||
|
if (entry == null) {
|
||||||
|
entry = new Map();
|
||||||
|
matMap.set(material, entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.set(link, value); // parameter name, value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to set map Object -> Material -> Link -> Texture
|
||||||
|
public static function setTextureValue(material: MaterialData, object: Object, link: String, value: kha.Image){
|
||||||
|
|
||||||
|
if(object == null || material == null || link == null) return;
|
||||||
|
|
||||||
|
var map = texturesMap;
|
||||||
|
|
||||||
|
var matMap = map.get(object);
|
||||||
|
if (matMap == null) {
|
||||||
|
matMap = new Map();
|
||||||
|
map.set(object, matMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
var entry = matMap.get(material);
|
||||||
|
if (entry == null) {
|
||||||
|
entry = new Map();
|
||||||
|
matMap.set(material, entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.set(link, value); // parameter name, value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mehtod to get object specific material parameter float value
|
||||||
|
static function floatLink(object: Object, mat: MaterialData, link: String): Null<kha.FastFloat> {
|
||||||
|
|
||||||
|
if(object == null || mat == null) return null;
|
||||||
|
|
||||||
|
if(! floatsMap.exists(object)){
|
||||||
|
object = Scene.active.root;
|
||||||
|
}
|
||||||
|
|
||||||
|
var material = floatsMap.get(object);
|
||||||
|
if (material == null) return null;
|
||||||
|
|
||||||
|
var entry = material.get(mat);
|
||||||
|
if (entry == null) return null;
|
||||||
|
|
||||||
|
return entry.get(link);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mehtod to get object specific material parameter vec3 value
|
||||||
|
static function vec3Link(object: Object, mat: MaterialData, link: String): iron.math.Vec4 {
|
||||||
|
|
||||||
|
if(object == null || mat == null) return null;
|
||||||
|
|
||||||
|
if(! vectorsMap.exists(object)){
|
||||||
|
object = Scene.active.root;
|
||||||
|
}
|
||||||
|
|
||||||
|
var material = vectorsMap.get(object);
|
||||||
|
if (material == null) return null;
|
||||||
|
|
||||||
|
var entry = material.get(mat);
|
||||||
|
if (entry == null) return null;
|
||||||
|
|
||||||
|
return entry.get(link);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mehtod to get object specific material parameter texture value
|
||||||
|
static function textureLink(object: Object, mat: MaterialData, link: String): kha.Image {
|
||||||
|
|
||||||
|
if(object == null || mat == null) return null;
|
||||||
|
|
||||||
|
if(! texturesMap.exists(object)){
|
||||||
|
object = Scene.active.root;
|
||||||
|
}
|
||||||
|
|
||||||
|
var material = texturesMap.get(object);
|
||||||
|
if (material == null) return null;
|
||||||
|
|
||||||
|
var entry = material.get(mat);
|
||||||
|
if (entry == null) return null;
|
||||||
|
|
||||||
|
return entry.get(link);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns complete map of float value material paramets
|
||||||
|
public static function getFloatsMap():Map<Object, Map<MaterialData, Map<String, Null<kha.FastFloat>>>>{
|
||||||
|
|
||||||
|
return floatsMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns complete map of vec3 value material paramets
|
||||||
|
public static function getVectorsMap():Map<Object, Map<MaterialData, Map<String, Vec4>>>{
|
||||||
|
|
||||||
|
return vectorsMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns complete map of texture value material paramets
|
||||||
|
public static function getTexturesMap():Map<Object, Map<MaterialData, Map<String, kha.Image>>>{
|
||||||
|
|
||||||
|
return texturesMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove all object specific material paramenter keys
|
||||||
|
public static function removeObjectFromAllMaps(object: Object) {
|
||||||
|
|
||||||
|
floatsMap.remove(object);
|
||||||
|
vectorsMap.remove(object);
|
||||||
|
texturesMap.remove(object);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove object specific material paramenter keys
|
||||||
|
public static function removeObjectFromMap(object: Object, type: UniformType) {
|
||||||
|
|
||||||
|
switch (type){
|
||||||
|
case Float: floatsMap.remove(object);
|
||||||
|
|
||||||
|
case Vector: vectorsMap.remove(object);
|
||||||
|
|
||||||
|
case Texture: texturesMap.remove(object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@:enum abstract UniformType(Int) from Int to Int {
|
||||||
|
var Float = 0;
|
||||||
|
var Vector = 1;
|
||||||
|
var Texture = 2;
|
||||||
|
}
|
|
@ -18,8 +18,9 @@ class PhysicsConstraintExportHelper extends iron.Trait {
|
||||||
var breakingThreshold: Float;
|
var breakingThreshold: Float;
|
||||||
var limits: Array<Float>;
|
var limits: Array<Float>;
|
||||||
var constraintAdded: Bool = false;
|
var constraintAdded: Bool = false;
|
||||||
|
var relativeConstraint: Bool = false;
|
||||||
|
|
||||||
public function new(body1: String, body2: String, type: Int, disableCollisions: Bool, breakingThreshold: Float, limits: Array<Float> = null) {
|
public function new(body1: String, body2: String, type: Int, disableCollisions: Bool, breakingThreshold: Float, relatieConstraint: Bool = false, limits: Array<Float> = null) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.body1 = body1;
|
this.body1 = body1;
|
||||||
|
@ -27,14 +28,26 @@ class PhysicsConstraintExportHelper extends iron.Trait {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.disableCollisions = disableCollisions;
|
this.disableCollisions = disableCollisions;
|
||||||
this.breakingThreshold = breakingThreshold;
|
this.breakingThreshold = breakingThreshold;
|
||||||
|
this.relativeConstraint = relatieConstraint;
|
||||||
this.limits = limits;
|
this.limits = limits;
|
||||||
notifyOnInit(init);
|
notifyOnInit(init);
|
||||||
notifyOnUpdate(update);
|
notifyOnUpdate(update);
|
||||||
}
|
}
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
var target1 = Scene.active.getChild(body1);
|
var target1;
|
||||||
var target2 = Scene.active.getChild(body2);
|
var target2;
|
||||||
|
|
||||||
|
if(relativeConstraint) {
|
||||||
|
|
||||||
|
target1 = object.parent.getChild(body1);
|
||||||
|
target2 = object.parent.getChild(body2);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
target1 = Scene.active.getChild(body1);
|
||||||
|
target2 = Scene.active.getChild(body2);
|
||||||
|
}
|
||||||
object.addTrait(new PhysicsConstraint(target1, target2, type, disableCollisions, breakingThreshold, limits));
|
object.addTrait(new PhysicsConstraint(target1, target2, type, disableCollisions, breakingThreshold, limits));
|
||||||
constraintAdded = true;
|
constraintAdded = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,7 +52,6 @@ class PhysicsWorld extends Trait {
|
||||||
public var rbMap: Map<Int, RigidBody>;
|
public var rbMap: Map<Int, RigidBody>;
|
||||||
public var conMap: Map<Int, PhysicsConstraint>;
|
public var conMap: Map<Int, PhysicsConstraint>;
|
||||||
public var timeScale = 1.0;
|
public var timeScale = 1.0;
|
||||||
var timeStep = 1 / 60;
|
|
||||||
var maxSteps = 1;
|
var maxSteps = 1;
|
||||||
public var solverIterations = 10;
|
public var solverIterations = 10;
|
||||||
public var hitPointWorld = new Vec4();
|
public var hitPointWorld = new Vec4();
|
||||||
|
@ -67,7 +66,7 @@ class PhysicsWorld extends Trait {
|
||||||
public static var physTime = 0.0;
|
public static var physTime = 0.0;
|
||||||
#end
|
#end
|
||||||
|
|
||||||
public function new(timeScale = 1.0, timeStep = 1 / 60, solverIterations = 10) {
|
public function new(timeScale = 1.0, maxSteps = 10, solverIterations = 10) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
if (nullvec) {
|
if (nullvec) {
|
||||||
|
@ -81,8 +80,7 @@ class PhysicsWorld extends Trait {
|
||||||
sceneRemoved = false;
|
sceneRemoved = false;
|
||||||
|
|
||||||
this.timeScale = timeScale;
|
this.timeScale = timeScale;
|
||||||
this.timeStep = timeStep;
|
this.maxSteps = maxSteps;
|
||||||
maxSteps = timeStep < 1 / 60 ? 10 : 1;
|
|
||||||
this.solverIterations = solverIterations;
|
this.solverIterations = solverIterations;
|
||||||
|
|
||||||
// First scene
|
// First scene
|
||||||
|
@ -269,7 +267,13 @@ class PhysicsWorld extends Trait {
|
||||||
|
|
||||||
if (preUpdates != null) for (f in preUpdates) f();
|
if (preUpdates != null) for (f in preUpdates) f();
|
||||||
|
|
||||||
world.stepSimulation(timeStep, maxSteps, t);
|
//Bullet physics fixed timescale
|
||||||
|
var fixedTime = 1.0 / 60;
|
||||||
|
|
||||||
|
//This condition must be satisfied to not loose time
|
||||||
|
var currMaxSteps = t < (fixedTime * maxSteps) ? maxSteps : 1;
|
||||||
|
|
||||||
|
world.stepSimulation(t, currMaxSteps, fixedTime);
|
||||||
updateContacts();
|
updateContacts();
|
||||||
|
|
||||||
for (rb in rbMap) @:privateAccess rb.physicsUpdate();
|
for (rb in rbMap) @:privateAccess rb.physicsUpdate();
|
||||||
|
|
|
@ -149,7 +149,7 @@ class RigidBody extends iron.Trait {
|
||||||
if (ready) return;
|
if (ready) return;
|
||||||
ready = true;
|
ready = true;
|
||||||
|
|
||||||
if (!Std.is(object, MeshObject)) return; // No mesh data
|
if (!Std.isOfType(object, MeshObject)) return; // No mesh data
|
||||||
|
|
||||||
transform = object.transform;
|
transform = object.transform;
|
||||||
physics = armory.trait.physics.PhysicsWorld.active;
|
physics = armory.trait.physics.PhysicsWorld.active;
|
||||||
|
|
415
Sources/armory/ui/Canvas.hx
Normal file
415
Sources/armory/ui/Canvas.hx
Normal file
|
@ -0,0 +1,415 @@
|
||||||
|
package armory.ui;
|
||||||
|
|
||||||
|
import zui.Zui;
|
||||||
|
|
||||||
|
using kha.graphics2.GraphicsExtension;
|
||||||
|
|
||||||
|
@:access(zui.Zui)
|
||||||
|
class Canvas {
|
||||||
|
|
||||||
|
public static var assetMap = new Map<Int, Dynamic>(); // kha.Image | kha.Font
|
||||||
|
public static var themes = new Array<zui.Themes.TTheme>();
|
||||||
|
static var events:Array<String> = [];
|
||||||
|
|
||||||
|
public static var screenW = -1;
|
||||||
|
public static var screenH = -1;
|
||||||
|
public static var locale = "en";
|
||||||
|
static var _ui: Zui;
|
||||||
|
static var h = new zui.Zui.Handle(); // TODO: needs one handle per canvas
|
||||||
|
|
||||||
|
public static function draw(ui: Zui, canvas: TCanvas, g: kha.graphics2.Graphics): Array<String> {
|
||||||
|
|
||||||
|
screenW = kha.System.windowWidth();
|
||||||
|
screenH = kha.System.windowHeight();
|
||||||
|
|
||||||
|
events.resize(0);
|
||||||
|
|
||||||
|
_ui = ui;
|
||||||
|
|
||||||
|
g.end();
|
||||||
|
ui.begin(g); // Bake elements
|
||||||
|
g.begin(false);
|
||||||
|
|
||||||
|
ui.g = g;
|
||||||
|
|
||||||
|
for (elem in canvas.elements) {
|
||||||
|
if (elem.parent == null) drawElement(ui, canvas, elem);
|
||||||
|
}
|
||||||
|
|
||||||
|
g.end();
|
||||||
|
ui.end(); // Finish drawing
|
||||||
|
g.begin(false);
|
||||||
|
|
||||||
|
return events;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function drawElement(ui: Zui, canvas: TCanvas, element: TElement, px = 0.0, py = 0.0) {
|
||||||
|
|
||||||
|
if (element == null || element.visible == false) return;
|
||||||
|
|
||||||
|
var anchorOffset = getAnchorOffset(canvas, element);
|
||||||
|
px += anchorOffset[0];
|
||||||
|
py += anchorOffset[1];
|
||||||
|
|
||||||
|
ui._x = canvas.x + scaled(element.x) + px;
|
||||||
|
ui._y = canvas.y + scaled(element.y) + py;
|
||||||
|
ui._w = scaled(element.width);
|
||||||
|
|
||||||
|
var rotated = element.rotation != null && element.rotation != 0;
|
||||||
|
if (rotated) ui.g.pushRotation(element.rotation, ui._x + scaled(element.width) / 2, ui._y + scaled(element.height) / 2);
|
||||||
|
|
||||||
|
var font = ui.ops.font;
|
||||||
|
var fontAsset = isFontAsset(element.asset);
|
||||||
|
if (fontAsset) ui.ops.font = getAsset(canvas, element.asset);
|
||||||
|
|
||||||
|
switch (element.type) {
|
||||||
|
case Text:
|
||||||
|
var size = ui.fontSize;
|
||||||
|
|
||||||
|
ui.fontSize = scaled(element.height);
|
||||||
|
ui.t.TEXT_COL = getColor(element.color_text, getTheme(canvas.theme).TEXT_COL);
|
||||||
|
ui.text(getText(canvas, element), element.alignment);
|
||||||
|
|
||||||
|
ui.fontSize = size;
|
||||||
|
|
||||||
|
case Button:
|
||||||
|
var eh = ui.t.ELEMENT_H;
|
||||||
|
var bh = ui.t.BUTTON_H;
|
||||||
|
ui.t.ELEMENT_H = element.height;
|
||||||
|
ui.t.BUTTON_H = element.height;
|
||||||
|
ui.t.BUTTON_COL = getColor(element.color, getTheme(canvas.theme).BUTTON_COL);
|
||||||
|
ui.t.BUTTON_TEXT_COL = getColor(element.color_text, getTheme(canvas.theme).BUTTON_TEXT_COL);
|
||||||
|
ui.t.BUTTON_HOVER_COL = getColor(element.color_hover, getTheme(canvas.theme).BUTTON_HOVER_COL);
|
||||||
|
ui.t.BUTTON_PRESSED_COL = getColor(element.color_press, getTheme(canvas.theme).BUTTON_PRESSED_COL);
|
||||||
|
if (ui.button(getText(canvas, element), element.alignment)) {
|
||||||
|
var e = element.event;
|
||||||
|
if (e != null && e != "") events.push(e);
|
||||||
|
}
|
||||||
|
ui.t.ELEMENT_H = eh;
|
||||||
|
ui.t.BUTTON_H = bh;
|
||||||
|
|
||||||
|
case Image:
|
||||||
|
var image = getAsset(canvas, element.asset);
|
||||||
|
if (image != null && !fontAsset) {
|
||||||
|
ui.imageScrollAlign = false;
|
||||||
|
var tint = element.color != null ? element.color : 0xffffffff;
|
||||||
|
if (ui.image(image, tint, scaled(element.height)) == zui.Zui.State.Released) {
|
||||||
|
var e = element.event;
|
||||||
|
if (e != null && e != "") events.push(e);
|
||||||
|
}
|
||||||
|
ui.imageScrollAlign = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
case FRectangle:
|
||||||
|
var col = ui.g.color;
|
||||||
|
ui.g.color = getColor(element.color, getTheme(canvas.theme).BUTTON_COL);
|
||||||
|
ui.g.fillRect(ui._x, ui._y, ui._w, scaled(element.height));
|
||||||
|
ui.g.color = col;
|
||||||
|
|
||||||
|
case FCircle:
|
||||||
|
var col = ui.g.color;
|
||||||
|
ui.g.color = getColor(element.color, getTheme(canvas.theme).BUTTON_COL);
|
||||||
|
ui.g.fillCircle(ui._x + (scaled(element.width) / 2), ui._y + (scaled(element.height) / 2), ui._w / 2);
|
||||||
|
ui.g.color = col;
|
||||||
|
|
||||||
|
case Rectangle:
|
||||||
|
var col = ui.g.color;
|
||||||
|
ui.g.color = getColor(element.color, getTheme(canvas.theme).BUTTON_COL);
|
||||||
|
ui.g.drawRect(ui._x, ui._y, ui._w, scaled(element.height), element.strength);
|
||||||
|
ui.g.color = col;
|
||||||
|
|
||||||
|
case Circle:
|
||||||
|
var col = ui.g.color;
|
||||||
|
ui.g.color = getColor(element.color, getTheme(canvas.theme).BUTTON_COL);
|
||||||
|
ui.g.drawCircle(ui._x+(scaled(element.width) / 2), ui._y + (scaled(element.height) / 2), ui._w / 2, element.strength);
|
||||||
|
ui.g.color = col;
|
||||||
|
|
||||||
|
case FTriangle:
|
||||||
|
var col = ui.g.color;
|
||||||
|
ui.g.color = getColor(element.color, getTheme(canvas.theme).BUTTON_COL);
|
||||||
|
ui.g.fillTriangle(ui._x + (ui._w / 2), ui._y, ui._x, ui._y + scaled(element.height), ui._x + ui._w, ui._y + scaled(element.height));
|
||||||
|
ui.g.color = col;
|
||||||
|
|
||||||
|
case Triangle:
|
||||||
|
var col = ui.g.color;
|
||||||
|
ui.g.color = getColor(element.color, getTheme(canvas.theme).BUTTON_COL);
|
||||||
|
ui.g.drawLine(ui._x + (ui._w / 2), ui._y, ui._x, ui._y + scaled(element.height), element.strength);
|
||||||
|
ui.g.drawLine(ui._x, ui._y + scaled(element.height), ui._x + ui._w, ui._y + scaled(element.height), element.strength);
|
||||||
|
ui.g.drawLine(ui._x + ui._w, ui._y + scaled(element.height), ui._x + (ui._w / 2), ui._y, element.strength);
|
||||||
|
ui.g.color = col;
|
||||||
|
|
||||||
|
case Check:
|
||||||
|
ui.t.TEXT_COL = getColor(element.color_text, getTheme(canvas.theme).TEXT_COL);
|
||||||
|
ui.t.ACCENT_COL = getColor(element.color, getTheme(canvas.theme).BUTTON_COL);
|
||||||
|
ui.t.ACCENT_HOVER_COL = getColor(element.color_hover, getTheme(canvas.theme).BUTTON_HOVER_COL);
|
||||||
|
ui.check(h.nest(element.id), getText(canvas, element));
|
||||||
|
|
||||||
|
case Radio:
|
||||||
|
ui.t.TEXT_COL = getColor(element.color_text, getTheme(canvas.theme).TEXT_COL);
|
||||||
|
ui.t.ACCENT_COL = getColor(element.color, getTheme(canvas.theme).BUTTON_COL);
|
||||||
|
ui.t.ACCENT_HOVER_COL = getColor(element.color_hover, getTheme(canvas.theme).BUTTON_HOVER_COL);
|
||||||
|
zui.Ext.inlineRadio(ui, h.nest(element.id), getText(canvas, element).split(";"));
|
||||||
|
|
||||||
|
case Combo:
|
||||||
|
ui.t.TEXT_COL = getColor(element.color_text, getTheme(canvas.theme).TEXT_COL);
|
||||||
|
ui.t.LABEL_COL = getColor(element.color_text, getTheme(canvas.theme).TEXT_COL);
|
||||||
|
ui.t.ACCENT_COL = getColor(element.color, getTheme(canvas.theme).BUTTON_COL);
|
||||||
|
ui.t.SEPARATOR_COL = getColor(element.color, getTheme(canvas.theme).BUTTON_COL);
|
||||||
|
ui.t.ACCENT_HOVER_COL = getColor(element.color_hover, getTheme(canvas.theme).BUTTON_HOVER_COL);
|
||||||
|
ui.combo(h.nest(element.id), getText(canvas, element).split(";"));
|
||||||
|
|
||||||
|
case Slider:
|
||||||
|
ui.t.TEXT_COL = getColor(element.color_text, getTheme(canvas.theme).TEXT_COL);
|
||||||
|
ui.t.LABEL_COL = getColor(element.color_text, getTheme(canvas.theme).TEXT_COL);
|
||||||
|
ui.t.ACCENT_COL = getColor(element.color, getTheme(canvas.theme).BUTTON_COL);
|
||||||
|
ui.t.ACCENT_HOVER_COL = getColor(element.color_hover, getTheme(canvas.theme).BUTTON_HOVER_COL);
|
||||||
|
ui.slider(h.nest(element.id), getText(canvas, element), 0.0, 1.0, true, 100, true, element.alignment);
|
||||||
|
|
||||||
|
case TextInput:
|
||||||
|
ui.t.TEXT_COL = getColor(element.color_text, getTheme(canvas.theme).TEXT_COL);
|
||||||
|
ui.t.LABEL_COL = getColor(element.color_text, getTheme(canvas.theme).TEXT_COL);
|
||||||
|
ui.t.ACCENT_COL = getColor(element.color, getTheme(canvas.theme).BUTTON_COL);
|
||||||
|
ui.t.ACCENT_HOVER_COL = getColor(element.color_hover, getTheme(canvas.theme).BUTTON_HOVER_COL);
|
||||||
|
ui.textInput(h.nest(element.id), getText(canvas, element), element.alignment);
|
||||||
|
if (h.nest(element.id).changed) {
|
||||||
|
var e = element.event;
|
||||||
|
if (e != null && e != "") events.push(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
case TextArea:
|
||||||
|
ui.t.TEXT_COL = getColor(element.color_text, getTheme(canvas.theme).TEXT_COL);
|
||||||
|
ui.t.LABEL_COL = getColor(element.color_text, getTheme(canvas.theme).TEXT_COL);
|
||||||
|
ui.t.ACCENT_COL = getColor(element.color, getTheme(canvas.theme).BUTTON_COL);
|
||||||
|
ui.t.ACCENT_HOVER_COL = getColor(element.color_hover, getTheme(canvas.theme).BUTTON_HOVER_COL);
|
||||||
|
h.nest(element.id).text = getText(canvas, element);
|
||||||
|
zui.Ext.textArea(ui,h.nest(element.id), element.alignment,element.editable);
|
||||||
|
if (h.nest(element.id).changed) {
|
||||||
|
var e = element.event;
|
||||||
|
if (e != null && e != "") events.push(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
case KeyInput:
|
||||||
|
ui.t.TEXT_COL = getColor(element.color_text, getTheme(canvas.theme).TEXT_COL);
|
||||||
|
ui.t.LABEL_COL = getColor(element.color_text, getTheme(canvas.theme).TEXT_COL);
|
||||||
|
ui.t.ACCENT_COL = getColor(element.color, getTheme(canvas.theme).BUTTON_COL);
|
||||||
|
ui.t.ACCENT_HOVER_COL = getColor(element.color_hover, getTheme(canvas.theme).BUTTON_HOVER_COL);
|
||||||
|
Ext.keyInput(ui, h.nest(element.id), getText(canvas, element));
|
||||||
|
|
||||||
|
case ProgressBar:
|
||||||
|
var col = ui.g.color;
|
||||||
|
var progress = element.progress_at;
|
||||||
|
var totalprogress = element.progress_total;
|
||||||
|
ui.g.color = getColor(element.color_progress, getTheme(canvas.theme).TEXT_COL);
|
||||||
|
ui.g.fillRect(ui._x, ui._y, ui._w / totalprogress * Math.min(progress, totalprogress), scaled(element.height));
|
||||||
|
ui.g.color = getColor(element.color, getTheme(canvas.theme).BUTTON_COL);
|
||||||
|
ui.g.drawRect(ui._x, ui._y, ui._w, scaled(element.height), element.strength);
|
||||||
|
ui.g.color = col;
|
||||||
|
|
||||||
|
case CProgressBar:
|
||||||
|
var col = ui.g.color;
|
||||||
|
var progress = element.progress_at;
|
||||||
|
var totalprogress = element.progress_total;
|
||||||
|
ui.g.color = getColor(element.color_progress, getTheme(canvas.theme).TEXT_COL);
|
||||||
|
ui.g.drawArc(ui._x + (scaled(element.width) / 2), ui._y + (scaled(element.height) / 2), ui._w / 2, -Math.PI / 2, ((Math.PI * 2) / totalprogress * progress) - Math.PI / 2, element.strength);
|
||||||
|
ui.g.color = getColor(element.color, getTheme(canvas.theme).BUTTON_COL);
|
||||||
|
ui.g.fillCircle(ui._x + (scaled(element.width) / 2), ui._y + (scaled(element.height) / 2), (ui._w / 2) - 10);
|
||||||
|
ui.g.color = col;
|
||||||
|
case Empty:
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.ops.font = font;
|
||||||
|
|
||||||
|
if (element.children != null) {
|
||||||
|
for (id in element.children) {
|
||||||
|
drawElement(ui, canvas, elemById(canvas, id), scaled(element.x) + px, scaled(element.y) + py);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rotated) ui.g.popTransformation();
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline function getText(canvas: TCanvas, e: TElement): String {
|
||||||
|
return e.text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getAsset(canvas: TCanvas, asset: String): Dynamic { // kha.Image | kha.Font {
|
||||||
|
for (a in canvas.assets) if (a.name == asset) return assetMap.get(a.id);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static var elemId = -1;
|
||||||
|
public static function getElementId(canvas: TCanvas): Int {
|
||||||
|
if (elemId == -1) for (e in canvas.elements) if (elemId < e.id) elemId = e.id;
|
||||||
|
return ++elemId;
|
||||||
|
}
|
||||||
|
|
||||||
|
static var assetId = -1;
|
||||||
|
public static function getAssetId(canvas: TCanvas): Int {
|
||||||
|
if (assetId == -1) for (a in canvas.assets) if (assetId < a.id) assetId = a.id;
|
||||||
|
return ++assetId;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function elemById(canvas: TCanvas, id: Int): TElement {
|
||||||
|
for (e in canvas.elements) if (e.id == id) return e;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline function scaled(f: Float): Int {
|
||||||
|
return Std.int(f * _ui.SCALE());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static inline function isFontAsset(assetName: Null<String>): Bool {
|
||||||
|
return assetName != null && StringTools.endsWith(assetName.toLowerCase(), ".ttf");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static inline function getColor(color: Null<Int>, defaultColor: Int): Int {
|
||||||
|
return color != null ? color : defaultColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getTheme(theme: String): zui.Themes.TTheme {
|
||||||
|
for (t in Canvas.themes) {
|
||||||
|
if (t.NAME == theme) return t;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Returns the positional scaled offset of the given element based on its anchor setting.
|
||||||
|
@param canvas The canvas object
|
||||||
|
@param element The element
|
||||||
|
@return Array<Float> [xOffset, yOffset]
|
||||||
|
**/
|
||||||
|
public static function getAnchorOffset(canvas: TCanvas, element: TElement): Array<Float> {
|
||||||
|
var boxWidth, boxHeight: Float;
|
||||||
|
var offsetX = 0.0;
|
||||||
|
var offsetY = 0.0;
|
||||||
|
|
||||||
|
if (element.parent == null) {
|
||||||
|
boxWidth = canvas.width;
|
||||||
|
boxHeight = canvas.height;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var parent = elemById(canvas, element.parent);
|
||||||
|
boxWidth = scaled(parent.width);
|
||||||
|
boxHeight = scaled(parent.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (element.anchor) {
|
||||||
|
case Top:
|
||||||
|
offsetX += boxWidth / 2 - scaled(element.width) / 2;
|
||||||
|
case TopRight:
|
||||||
|
offsetX += boxWidth - scaled(element.width);
|
||||||
|
case CenterLeft:
|
||||||
|
offsetY += boxHeight / 2 - scaled(element.height) / 2;
|
||||||
|
case Center:
|
||||||
|
offsetX += boxWidth / 2 - scaled(element.width) / 2;
|
||||||
|
offsetY += boxHeight / 2 - scaled(element.height) / 2;
|
||||||
|
case CenterRight:
|
||||||
|
offsetX += boxWidth - scaled(element.width);
|
||||||
|
offsetY += boxHeight / 2 - scaled(element.height) / 2;
|
||||||
|
case BottomLeft:
|
||||||
|
offsetY += boxHeight - scaled(element.height);
|
||||||
|
case Bottom:
|
||||||
|
offsetX += boxWidth / 2 - scaled(element.width) / 2;
|
||||||
|
offsetY += boxHeight - scaled(element.height);
|
||||||
|
case BottomRight:
|
||||||
|
offsetX += boxWidth - scaled(element.width);
|
||||||
|
offsetY += boxHeight - scaled(element.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [offsetX, offsetY];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef TCanvas = {
|
||||||
|
var name: String;
|
||||||
|
var x: Float;
|
||||||
|
var y: Float;
|
||||||
|
var width: Int;
|
||||||
|
var height: Int;
|
||||||
|
var elements: Array<TElement>;
|
||||||
|
var theme: String;
|
||||||
|
@:optional var assets: Array<TAsset>;
|
||||||
|
@:optional var locales: Array<TLocale>;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef TElement = {
|
||||||
|
var id: Int;
|
||||||
|
var type: ElementType;
|
||||||
|
var name: String;
|
||||||
|
var x: Float;
|
||||||
|
var y: Float;
|
||||||
|
var width: Int;
|
||||||
|
var height: Int;
|
||||||
|
@:optional var rotation: Null<kha.FastFloat>;
|
||||||
|
@:optional var text: String;
|
||||||
|
@:optional var event: String;
|
||||||
|
// null = follow theme settings
|
||||||
|
@:optional var color: Null<Int>;
|
||||||
|
@:optional var color_text: Null<Int>;
|
||||||
|
@:optional var color_hover: Null<Int>;
|
||||||
|
@:optional var color_press: Null<Int>;
|
||||||
|
@:optional var color_progress: Null<Int>;
|
||||||
|
@:optional var progress_at: Null<Int>;
|
||||||
|
@:optional var progress_total: Null<Int>;
|
||||||
|
@:optional var strength: Null<Int>;
|
||||||
|
@:optional var alignment: Null<Int>;
|
||||||
|
@:optional var anchor: Null<Int>;
|
||||||
|
@:optional var parent: Null<Int>; // id
|
||||||
|
@:optional var children: Array<Int>; // ids
|
||||||
|
@:optional var asset: String;
|
||||||
|
@:optional var visible: Null<Bool>;
|
||||||
|
@:optional var editable: Null<Bool>;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef TAsset = {
|
||||||
|
var id: Int;
|
||||||
|
var name: String;
|
||||||
|
var file: String;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef TLocale = {
|
||||||
|
var name: String; // "en"
|
||||||
|
var texts: Array<TTranslatedText>;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef TTranslatedText = {
|
||||||
|
var id: Int; // element id
|
||||||
|
var text: String;
|
||||||
|
}
|
||||||
|
|
||||||
|
@:enum abstract ElementType(Int) from Int to Int {
|
||||||
|
var Text = 0;
|
||||||
|
var Image = 1;
|
||||||
|
var Button = 2;
|
||||||
|
var Empty = 3;
|
||||||
|
// var HLayout = 4;
|
||||||
|
// var VLayout = 5;
|
||||||
|
var Check = 6;
|
||||||
|
var Radio = 7;
|
||||||
|
var Combo = 8;
|
||||||
|
var Slider = 9;
|
||||||
|
var TextInput = 10;
|
||||||
|
var KeyInput = 11;
|
||||||
|
var FRectangle = 12;
|
||||||
|
var Rectangle = 13;
|
||||||
|
var FCircle = 14;
|
||||||
|
var Circle = 15;
|
||||||
|
var FTriangle = 16;
|
||||||
|
var Triangle = 17;
|
||||||
|
var ProgressBar = 18;
|
||||||
|
var CProgressBar = 19;
|
||||||
|
var TextArea = 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
@:enum abstract Anchor(Int) from Int to Int {
|
||||||
|
var TopLeft = 0;
|
||||||
|
var Top = 1;
|
||||||
|
var TopRight = 2;
|
||||||
|
var CenterLeft = 3;
|
||||||
|
var Center = 4;
|
||||||
|
var CenterRight = 5;
|
||||||
|
var BottomLeft = 6;
|
||||||
|
var Bottom = 7;
|
||||||
|
var BottomRight = 8;
|
||||||
|
}
|
331
Sources/armory/ui/Ext.hx
Normal file
331
Sources/armory/ui/Ext.hx
Normal file
|
@ -0,0 +1,331 @@
|
||||||
|
package armory.ui;
|
||||||
|
|
||||||
|
import zui.Zui;
|
||||||
|
import kha.input.Keyboard;
|
||||||
|
import kha.input.KeyCode;
|
||||||
|
|
||||||
|
typedef ListOpts = {
|
||||||
|
?addCb: String->Void,
|
||||||
|
?removeCb: Int->Void,
|
||||||
|
?getNameCb: Int->String,
|
||||||
|
?setNameCb: Int->String->Void,
|
||||||
|
?getLabelCb: Int->String,
|
||||||
|
?itemDrawCb: Handle->Int->Void,
|
||||||
|
?showRadio: Bool, // false
|
||||||
|
?editable: Bool, // true
|
||||||
|
?showAdd: Bool, // true
|
||||||
|
?addLabel: String // 'Add'
|
||||||
|
}
|
||||||
|
|
||||||
|
@:access(zui.Zui)
|
||||||
|
class Ext {
|
||||||
|
public static function keyInput(ui: Zui, handle: Handle, label = "", align: Align = Left): Int {
|
||||||
|
if (!ui.isVisible(ui.ELEMENT_H())) {
|
||||||
|
ui.endElement();
|
||||||
|
return Std.int(handle.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
var hover = ui.getHover();
|
||||||
|
if (hover && Zui.onTextHover != null) Zui.onTextHover();
|
||||||
|
ui.g.color = hover ? ui.t.ACCENT_HOVER_COL : ui.t.ACCENT_COL; // Text bg
|
||||||
|
ui.drawRect(ui.g, ui.t.FILL_ACCENT_BG, ui._x + ui.buttonOffsetY, ui._y + ui.buttonOffsetY, ui._w - ui.buttonOffsetY * 2, ui.BUTTON_H());
|
||||||
|
|
||||||
|
var startEdit = ui.getReleased() || ui.tabPressed;
|
||||||
|
if (ui.textSelectedHandle != handle && startEdit) ui.startTextEdit(handle);
|
||||||
|
if (ui.textSelectedHandle == handle) Ext.listenToKey(ui, handle);
|
||||||
|
else handle.changed = false;
|
||||||
|
|
||||||
|
if (label != "") {
|
||||||
|
ui.g.color = ui.t.LABEL_COL; // Label
|
||||||
|
var labelAlign = align == Align.Right ? Align.Left : Align.Right;
|
||||||
|
var xOffset = labelAlign == Align.Left ? 7 : 0;
|
||||||
|
ui.drawString(ui.g, label, xOffset, 0, labelAlign);
|
||||||
|
}
|
||||||
|
|
||||||
|
handle.text = Ext.keycodeToString(Std.int(handle.value));
|
||||||
|
|
||||||
|
ui.g.color = ui.t.TEXT_COL; // Text
|
||||||
|
ui.textSelectedHandle != handle ? ui.drawString(ui.g, handle.text, null, 0, align) : ui.drawString(ui.g, ui.textSelected, null, 0, align);
|
||||||
|
|
||||||
|
ui.endElement();
|
||||||
|
|
||||||
|
return Std.int(handle.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static function listenToKey(ui: Zui, handle: Handle) {
|
||||||
|
if (ui.isKeyDown) {
|
||||||
|
handle.value = ui.key;
|
||||||
|
handle.changed = ui.changed = true;
|
||||||
|
|
||||||
|
ui.textSelectedHandle = null;
|
||||||
|
ui.isTyping = false;
|
||||||
|
|
||||||
|
if (Keyboard.get() != null) Keyboard.get().hide();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ui.textSelected = "Press a key...";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function list(ui: Zui, handle: Handle, ar: Array<Dynamic>, ?opts: ListOpts ): Int {
|
||||||
|
var selected = 0;
|
||||||
|
if (opts == null) opts = {};
|
||||||
|
|
||||||
|
var addCb = opts.addCb != null ? opts.addCb : function(name: String) ar.push(name);
|
||||||
|
var removeCb = opts.removeCb != null ? opts.removeCb : function(i: Int) ar.splice(i, 1);
|
||||||
|
var getNameCb = opts.getNameCb != null ? opts.getNameCb : function(i: Int) return ar[i];
|
||||||
|
var setNameCb = opts.setNameCb != null ? opts.setNameCb : function(i: Int, name: String) ar[i] = name;
|
||||||
|
var getLabelCb = opts.getLabelCb != null ? opts.getLabelCb : function(i: Int) return "";
|
||||||
|
var itemDrawCb = opts.itemDrawCb;
|
||||||
|
var showRadio = opts.showRadio != null ? opts.showRadio : false;
|
||||||
|
var editable = opts.editable != null ? opts.editable : true;
|
||||||
|
var showAdd = opts.showAdd != null ? opts.showAdd : true;
|
||||||
|
var addLabel = opts.addLabel != null ? opts.addLabel : "Add";
|
||||||
|
|
||||||
|
var i = 0;
|
||||||
|
while (i < ar.length) {
|
||||||
|
if (showRadio) { // Prepend ratio button
|
||||||
|
ui.row([0.12, 0.68, 0.2]);
|
||||||
|
if (ui.radio(handle.nest(0), i, "")) {
|
||||||
|
selected = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else ui.row([0.8, 0.2]);
|
||||||
|
|
||||||
|
var itemHandle = handle.nest(i);
|
||||||
|
itemHandle.text = getNameCb(i);
|
||||||
|
editable ? setNameCb(i, ui.textInput(itemHandle, getLabelCb(i))) : ui.text(getNameCb(i));
|
||||||
|
if (ui.button("X")) removeCb(i);
|
||||||
|
else i++;
|
||||||
|
|
||||||
|
if (itemDrawCb != null) itemDrawCb(itemHandle.nest(i), i - 1);
|
||||||
|
}
|
||||||
|
if (showAdd && ui.button(addLabel)) addCb("untitled");
|
||||||
|
|
||||||
|
return selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function panelList(ui: Zui, handle: Handle, ar: Array<Dynamic>,
|
||||||
|
addCb: String->Void = null,
|
||||||
|
removeCb: Int->Void = null,
|
||||||
|
getNameCb: Int->String = null,
|
||||||
|
setNameCb: Int->String->Void = null,
|
||||||
|
itemDrawCb: Handle->Int->Void = null,
|
||||||
|
editable = true,
|
||||||
|
showAdd = true,
|
||||||
|
addLabel: String = "Add") {
|
||||||
|
|
||||||
|
if (addCb == null) addCb = function(name: String) { ar.push(name); };
|
||||||
|
if (removeCb == null) removeCb = function(i: Int) { ar.splice(i, 1); };
|
||||||
|
if (getNameCb == null) getNameCb = function(i: Int) { return ar[i]; };
|
||||||
|
if (setNameCb == null) setNameCb = function(i: Int, name: String) { ar[i] = name; };
|
||||||
|
|
||||||
|
var i = 0;
|
||||||
|
while (i < ar.length) {
|
||||||
|
ui.row([0.12, 0.68, 0.2]);
|
||||||
|
var expanded = ui.panel(handle.nest(i), "");
|
||||||
|
|
||||||
|
var itemHandle = handle.nest(i);
|
||||||
|
editable ? setNameCb(i, ui.textInput(itemHandle, getNameCb(i))) : ui.text(getNameCb(i));
|
||||||
|
if (ui.button("X")) removeCb(i);
|
||||||
|
else i++;
|
||||||
|
|
||||||
|
if (itemDrawCb != null && expanded) itemDrawCb(itemHandle.nest(i), i - 1);
|
||||||
|
}
|
||||||
|
if (showAdd && ui.button(addLabel)) {
|
||||||
|
addCb("untitled");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function colorField(ui: Zui, handle:Handle, alpha = false): Int {
|
||||||
|
ui.g.color = handle.color;
|
||||||
|
|
||||||
|
ui.drawRect(ui.g, true, ui._x + 2, ui._y + ui.buttonOffsetY, ui._w - 4, ui.BUTTON_H());
|
||||||
|
ui.g.color = ui.getHover() ? ui.t.ACCENT_HOVER_COL : ui.t.ACCENT_COL;
|
||||||
|
ui.drawRect(ui.g, false, ui._x + 2, ui._y + ui.buttonOffsetY, ui._w - 4, ui.BUTTON_H(), 1.0);
|
||||||
|
|
||||||
|
if (ui.getStarted()) {
|
||||||
|
Popup.showCustom(
|
||||||
|
new Zui(ui.ops),
|
||||||
|
function(ui:Zui) {
|
||||||
|
zui.Ext.colorWheel(ui, handle, alpha);
|
||||||
|
},
|
||||||
|
Std.int(ui.inputX), Std.int(ui.inputY), 200, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.endElement();
|
||||||
|
return handle.color;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function colorPicker(ui: Zui, handle: Handle, alpha = false): Int {
|
||||||
|
var r = ui.slider(handle.nest(0, {value: handle.color.R}), "R", 0, 1, true);
|
||||||
|
var g = ui.slider(handle.nest(1, {value: handle.color.G}), "G", 0, 1, true);
|
||||||
|
var b = ui.slider(handle.nest(2, {value: handle.color.B}), "B", 0, 1, true);
|
||||||
|
var a = handle.color.A;
|
||||||
|
if (alpha) a = ui.slider(handle.nest(3, {value: a}), "A", 0, 1, true);
|
||||||
|
var col = kha.Color.fromFloats(r, g, b, a);
|
||||||
|
ui.text("", Right, col);
|
||||||
|
return col;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Keycodes can be found here: http://api.kha.tech/kha/input/KeyCode.html
|
||||||
|
**/
|
||||||
|
static function keycodeToString(keycode: Int): String {
|
||||||
|
return switch (keycode) {
|
||||||
|
default: String.fromCharCode(keycode);
|
||||||
|
case -1: "None";
|
||||||
|
case KeyCode.Unknown: "Unknown";
|
||||||
|
case KeyCode.Back: "Back";
|
||||||
|
case KeyCode.Cancel: "Cancel";
|
||||||
|
case KeyCode.Help: "Help";
|
||||||
|
case KeyCode.Backspace: "Backspace";
|
||||||
|
case KeyCode.Tab: "Tab";
|
||||||
|
case KeyCode.Clear: "Clear";
|
||||||
|
case KeyCode.Return: "Return";
|
||||||
|
case KeyCode.Shift: "Shift";
|
||||||
|
case KeyCode.Control: "Ctrl";
|
||||||
|
case KeyCode.Alt: "Alt";
|
||||||
|
case KeyCode.Pause: "Pause";
|
||||||
|
case KeyCode.CapsLock: "CapsLock";
|
||||||
|
case KeyCode.Kana: "Kana";
|
||||||
|
// case KeyCode.Hangul: "Hangul"; // Hangul == Kana
|
||||||
|
case KeyCode.Eisu: "Eisu";
|
||||||
|
case KeyCode.Junja: "Junja";
|
||||||
|
case KeyCode.Final: "Final";
|
||||||
|
case KeyCode.Hanja: "Hanja";
|
||||||
|
// case KeyCode.Kanji: "Kanji"; // Kanji == Hanja
|
||||||
|
case KeyCode.Escape: "Esc";
|
||||||
|
case KeyCode.Convert: "Convert";
|
||||||
|
case KeyCode.NonConvert: "NonConvert";
|
||||||
|
case KeyCode.Accept: "Accept";
|
||||||
|
case KeyCode.ModeChange: "ModeChange";
|
||||||
|
case KeyCode.Space: "Space";
|
||||||
|
case KeyCode.PageUp: "PageUp";
|
||||||
|
case KeyCode.PageDown: "PageDown";
|
||||||
|
case KeyCode.End: "End";
|
||||||
|
case KeyCode.Home: "Home";
|
||||||
|
case KeyCode.Left: "Left";
|
||||||
|
case KeyCode.Up: "Up";
|
||||||
|
case KeyCode.Right: "Right";
|
||||||
|
case KeyCode.Down: "Down";
|
||||||
|
case KeyCode.Select: "Select";
|
||||||
|
case KeyCode.Print: "Print";
|
||||||
|
case KeyCode.Execute: "Execute";
|
||||||
|
case KeyCode.PrintScreen: "PrintScreen";
|
||||||
|
case KeyCode.Insert: "Insert";
|
||||||
|
case KeyCode.Delete: "Delete";
|
||||||
|
case KeyCode.Colon: "Colon";
|
||||||
|
case KeyCode.Semicolon: "Semicolon";
|
||||||
|
case KeyCode.LessThan: "LessThan";
|
||||||
|
case KeyCode.Equals: "Equals";
|
||||||
|
case KeyCode.GreaterThan: "GreaterThan";
|
||||||
|
case KeyCode.QuestionMark: "QuestionMark";
|
||||||
|
case KeyCode.At: "At";
|
||||||
|
case KeyCode.Win: "Win";
|
||||||
|
case KeyCode.ContextMenu: "ContextMenu";
|
||||||
|
case KeyCode.Sleep: "Sleep";
|
||||||
|
case KeyCode.Numpad0: "Numpad0";
|
||||||
|
case KeyCode.Numpad1: "Numpad1";
|
||||||
|
case KeyCode.Numpad2: "Numpad2";
|
||||||
|
case KeyCode.Numpad3: "Numpad3";
|
||||||
|
case KeyCode.Numpad4: "Numpad4";
|
||||||
|
case KeyCode.Numpad5: "Numpad5";
|
||||||
|
case KeyCode.Numpad6: "Numpad6";
|
||||||
|
case KeyCode.Numpad7: "Numpad7";
|
||||||
|
case KeyCode.Numpad8: "Numpad8";
|
||||||
|
case KeyCode.Numpad9: "Numpad9";
|
||||||
|
case KeyCode.Multiply: "Multiply";
|
||||||
|
case KeyCode.Add: "Add";
|
||||||
|
case KeyCode.Separator: "Separator";
|
||||||
|
case KeyCode.Subtract: "Subtract";
|
||||||
|
case KeyCode.Decimal: "Decimal";
|
||||||
|
case KeyCode.Divide: "Divide";
|
||||||
|
case KeyCode.F1: "F1";
|
||||||
|
case KeyCode.F2: "F2";
|
||||||
|
case KeyCode.F3: "F3";
|
||||||
|
case KeyCode.F4: "F4";
|
||||||
|
case KeyCode.F5: "F5";
|
||||||
|
case KeyCode.F6: "F6";
|
||||||
|
case KeyCode.F7: "F7";
|
||||||
|
case KeyCode.F8: "F8";
|
||||||
|
case KeyCode.F9: "F9";
|
||||||
|
case KeyCode.F10: "F10";
|
||||||
|
case KeyCode.F11: "F11";
|
||||||
|
case KeyCode.F12: "F12";
|
||||||
|
case KeyCode.F13: "F13";
|
||||||
|
case KeyCode.F14: "F14";
|
||||||
|
case KeyCode.F15: "F15";
|
||||||
|
case KeyCode.F16: "F16";
|
||||||
|
case KeyCode.F17: "F17";
|
||||||
|
case KeyCode.F18: "F18";
|
||||||
|
case KeyCode.F19: "F19";
|
||||||
|
case KeyCode.F20: "F20";
|
||||||
|
case KeyCode.F21: "F21";
|
||||||
|
case KeyCode.F22: "F22";
|
||||||
|
case KeyCode.F23: "F23";
|
||||||
|
case KeyCode.F24: "F24";
|
||||||
|
case KeyCode.NumLock: "NumLock";
|
||||||
|
case KeyCode.ScrollLock: "ScrollLock";
|
||||||
|
case KeyCode.WinOemFjJisho: "WinOemFjJisho";
|
||||||
|
case KeyCode.WinOemFjMasshou: "WinOemFjMasshou";
|
||||||
|
case KeyCode.WinOemFjTouroku: "WinOemFjTouroku";
|
||||||
|
case KeyCode.WinOemFjLoya: "WinOemFjLoya";
|
||||||
|
case KeyCode.WinOemFjRoya: "WinOemFjRoya";
|
||||||
|
case KeyCode.Circumflex: "Circumflex";
|
||||||
|
case KeyCode.Exclamation: "Exclamation";
|
||||||
|
case KeyCode.DoubleQuote: "DoubleQuote";
|
||||||
|
case KeyCode.Hash: "Hash";
|
||||||
|
case KeyCode.Dollar: "Dollar";
|
||||||
|
case KeyCode.Percent: "Percent";
|
||||||
|
case KeyCode.Ampersand: "Ampersand";
|
||||||
|
case KeyCode.Underscore: "Underscore";
|
||||||
|
case KeyCode.OpenParen: "OpenParen";
|
||||||
|
case KeyCode.CloseParen: "CloseParen";
|
||||||
|
case KeyCode.Asterisk: "Asterisk";
|
||||||
|
case KeyCode.Plus: "Plus";
|
||||||
|
case KeyCode.Pipe: "Pipe";
|
||||||
|
case KeyCode.HyphenMinus: "HyphenMinus";
|
||||||
|
case KeyCode.OpenCurlyBracket: "OpenCurlyBracket";
|
||||||
|
case KeyCode.CloseCurlyBracket: "CloseCurlyBracket";
|
||||||
|
case KeyCode.Tilde: "Tilde";
|
||||||
|
case KeyCode.VolumeMute: "VolumeMute";
|
||||||
|
case KeyCode.VolumeDown: "VolumeDown";
|
||||||
|
case KeyCode.VolumeUp: "VolumeUp";
|
||||||
|
case KeyCode.Comma: "Comma";
|
||||||
|
case KeyCode.Period: "Period";
|
||||||
|
case KeyCode.Slash: "Slash";
|
||||||
|
case KeyCode.BackQuote: "BackQuote";
|
||||||
|
case KeyCode.OpenBracket: "OpenBracket";
|
||||||
|
case KeyCode.BackSlash: "BackSlash";
|
||||||
|
case KeyCode.CloseBracket: "CloseBracket";
|
||||||
|
case KeyCode.Quote: "Quote";
|
||||||
|
case KeyCode.Meta: "Meta";
|
||||||
|
case KeyCode.AltGr: "AltGr";
|
||||||
|
case KeyCode.WinIcoHelp: "WinIcoHelp";
|
||||||
|
case KeyCode.WinIco00: "WinIco00";
|
||||||
|
case KeyCode.WinIcoClear: "WinIcoClear";
|
||||||
|
case KeyCode.WinOemReset: "WinOemReset";
|
||||||
|
case KeyCode.WinOemJump: "WinOemJump";
|
||||||
|
case KeyCode.WinOemPA1: "WinOemPA1";
|
||||||
|
case KeyCode.WinOemPA2: "WinOemPA2";
|
||||||
|
case KeyCode.WinOemPA3: "WinOemPA3";
|
||||||
|
case KeyCode.WinOemWSCTRL: "WinOemWSCTRL";
|
||||||
|
case KeyCode.WinOemCUSEL: "WinOemCUSEL";
|
||||||
|
case KeyCode.WinOemATTN: "WinOemATTN";
|
||||||
|
case KeyCode.WinOemFinish: "WinOemFinish";
|
||||||
|
case KeyCode.WinOemCopy: "WinOemCopy";
|
||||||
|
case KeyCode.WinOemAuto: "WinOemAuto";
|
||||||
|
case KeyCode.WinOemENLW: "WinOemENLW";
|
||||||
|
case KeyCode.WinOemBackTab: "WinOemBackTab";
|
||||||
|
case KeyCode.ATTN: "ATTN";
|
||||||
|
case KeyCode.CRSEL: "CRSEL";
|
||||||
|
case KeyCode.EXSEL: "EXSEL";
|
||||||
|
case KeyCode.EREOF: "EREOF";
|
||||||
|
case KeyCode.Play: "Play";
|
||||||
|
case KeyCode.Zoom: "Zoom";
|
||||||
|
case KeyCode.PA1: "PA1";
|
||||||
|
case KeyCode.WinOemClear: "WinOemClear";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
127
Sources/armory/ui/Popup.hx
Normal file
127
Sources/armory/ui/Popup.hx
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
package armory.ui;
|
||||||
|
|
||||||
|
import zui.Zui;
|
||||||
|
import kha.System;
|
||||||
|
|
||||||
|
@:access(zui.Zui)
|
||||||
|
class Popup {
|
||||||
|
public static var show = false;
|
||||||
|
|
||||||
|
static var ui: Zui = null;
|
||||||
|
static var hwnd = new Handle();
|
||||||
|
static var boxTitle = "";
|
||||||
|
static var boxText = "";
|
||||||
|
static var boxCommands: Zui->Void = null;
|
||||||
|
static var modalX = 0;
|
||||||
|
static var modalY = 0;
|
||||||
|
static var modalW = 400;
|
||||||
|
static var modalH = 160;
|
||||||
|
|
||||||
|
public static function render(g: kha.graphics2.Graphics) {
|
||||||
|
if (boxCommands == null) {
|
||||||
|
ui.begin(g);
|
||||||
|
if (ui.window(hwnd, modalX, modalY, modalW, modalH)) {
|
||||||
|
drawTitle(g);
|
||||||
|
|
||||||
|
for (line in boxText.split("\n")) {
|
||||||
|
ui.text(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
ui._y = ui._h - ui.t.BUTTON_H - 10;
|
||||||
|
ui.row([1/3, 1/3, 1/3]);
|
||||||
|
ui.endElement();
|
||||||
|
if (ui.button("OK")) {
|
||||||
|
show = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ui.end();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ui.begin(g);
|
||||||
|
if (ui.window(hwnd, modalX, modalY, modalW, modalH)) {
|
||||||
|
drawTitle(g);
|
||||||
|
|
||||||
|
ui._y += 10;
|
||||||
|
boxCommands(ui);
|
||||||
|
}
|
||||||
|
ui.end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function drawTitle(g: kha.graphics2.Graphics) {
|
||||||
|
if (boxTitle != "") {
|
||||||
|
g.color = ui.t.SEPARATOR_COL;
|
||||||
|
ui.drawRect(g, true, ui._x, ui._y, ui._w, ui.t.BUTTON_H);
|
||||||
|
|
||||||
|
g.color = ui.t.TEXT_COL;
|
||||||
|
ui.text(boxTitle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function update() {
|
||||||
|
var inUse = ui.comboSelectedHandle != null;
|
||||||
|
|
||||||
|
// Close popup
|
||||||
|
if (ui.inputStarted && !inUse) {
|
||||||
|
if (ui.inputX < modalX || ui.inputX > modalX + modalW || ui.inputY < modalY || ui.inputY > modalY + modalH) {
|
||||||
|
show = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Displays a message box with a title, a text body and a centered "OK" button.
|
||||||
|
@param ui the Zui instance for the popup
|
||||||
|
@param title the title to display
|
||||||
|
@param text the text to display
|
||||||
|
**/
|
||||||
|
public static function showMessage(ui: Zui, title: String, text: String) {
|
||||||
|
Popup.ui = ui;
|
||||||
|
init();
|
||||||
|
|
||||||
|
boxTitle = title;
|
||||||
|
boxText = text;
|
||||||
|
boxCommands = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Displays a popup box with custom drawing code.
|
||||||
|
@param ui the Zui instance for the popup
|
||||||
|
@param commands the function for drawing the popup's content
|
||||||
|
@param mx the x position of the popup. -1 = screen center (defaults to -1)
|
||||||
|
@param my the y position of the popup. -1 = screen center (defaults to -1)
|
||||||
|
@param mw the width of the popup (defaults to 400)
|
||||||
|
@param mh the height of the popup (defaults to 160)
|
||||||
|
**/
|
||||||
|
public static function showCustom(ui: Zui, commands: Zui->Void = null, mx = -1, my = -1, mw = 400, mh = 160) {
|
||||||
|
Popup.ui = ui;
|
||||||
|
init(mx, my, mw, mh);
|
||||||
|
|
||||||
|
boxTitle = "";
|
||||||
|
boxText = "";
|
||||||
|
boxCommands = commands;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function init(mx = -1, my = -1, mw = 400, mh = 160) {
|
||||||
|
var appW = System.windowWidth();
|
||||||
|
var appH = System.windowHeight();
|
||||||
|
|
||||||
|
modalX = mx;
|
||||||
|
modalY = my;
|
||||||
|
modalW = Std.int(mw * ui.SCALE());
|
||||||
|
modalH = Std.int(mh * ui.SCALE());
|
||||||
|
|
||||||
|
// Center popup window if no value is given
|
||||||
|
if (mx == -1) modalX = Std.int(appW / 2 - modalW / 2);
|
||||||
|
if (my == -1) modalY = Std.int(appH / 2 - modalH / 2);
|
||||||
|
|
||||||
|
// Limit popup position to screen
|
||||||
|
modalX = Std.int(Math.max(0, Math.min(modalX, appW - modalW)));
|
||||||
|
modalY = Std.int(Math.max(0, Math.min(modalY, appH - modalH)));
|
||||||
|
|
||||||
|
hwnd.dragX = 0;
|
||||||
|
hwnd.dragY = 0;
|
||||||
|
hwnd.scrollOffset = 0.0;
|
||||||
|
show = true;
|
||||||
|
}
|
||||||
|
}
|
42
Sources/armory/ui/Themes.hx
Normal file
42
Sources/armory/ui/Themes.hx
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
package armory.ui;
|
||||||
|
|
||||||
|
import zui.Themes;
|
||||||
|
|
||||||
|
class Themes {
|
||||||
|
|
||||||
|
// 2x scaled, for games
|
||||||
|
public static var light: TTheme = {
|
||||||
|
NAME: "Default Light",
|
||||||
|
WINDOW_BG_COL: 0xffefefef,
|
||||||
|
WINDOW_TINT_COL: 0xff222222,
|
||||||
|
ACCENT_COL: 0xffeeeeee,
|
||||||
|
ACCENT_HOVER_COL: 0xffbbbbbb,
|
||||||
|
ACCENT_SELECT_COL: 0xffaaaaaa,
|
||||||
|
BUTTON_COL: 0xffcccccc,
|
||||||
|
BUTTON_TEXT_COL: 0xff222222,
|
||||||
|
BUTTON_HOVER_COL: 0xffb3b3b3,
|
||||||
|
BUTTON_PRESSED_COL: 0xffb1b1b1,
|
||||||
|
TEXT_COL: 0xff999999,
|
||||||
|
LABEL_COL: 0xffaaaaaa,
|
||||||
|
SEPARATOR_COL: 0xff999999,
|
||||||
|
HIGHLIGHT_COL: 0xff205d9c,
|
||||||
|
CONTEXT_COL: 0xffaaaaaa,
|
||||||
|
PANEL_BG_COL: 0xffaaaaaa,
|
||||||
|
FONT_SIZE: 13 * 2,
|
||||||
|
ELEMENT_W: 100 * 2,
|
||||||
|
ELEMENT_H: 24 * 2,
|
||||||
|
ELEMENT_OFFSET: 4 * 2,
|
||||||
|
ARROW_SIZE: 5 * 2,
|
||||||
|
BUTTON_H: 22 * 2,
|
||||||
|
CHECK_SIZE: 15 * 2,
|
||||||
|
CHECK_SELECT_SIZE: 8 * 2,
|
||||||
|
SCROLL_W: 6 * 2,
|
||||||
|
TEXT_OFFSET: 8 * 2,
|
||||||
|
TAB_W: 12 * 2,
|
||||||
|
FILL_WINDOW_BG: false,
|
||||||
|
FILL_BUTTON_BG: true,
|
||||||
|
FILL_ACCENT_BG: false,
|
||||||
|
LINK_STYLE: Line,
|
||||||
|
FULL_TABS: false
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
import importlib
|
||||||
|
import sys
|
||||||
|
import types
|
||||||
|
|
||||||
|
# This gets cleared if this package/the __init__ module is reloaded
|
||||||
|
_module_cache: dict[str, types.ModuleType] = {}
|
||||||
|
|
||||||
|
|
||||||
|
def enable_reload(module_name: str):
|
||||||
|
"""Enable reloading for the next time the module with `module_name`
|
||||||
|
is executed.
|
||||||
|
"""
|
||||||
|
mod = sys.modules[module_name]
|
||||||
|
setattr(mod, module_name.replace('.', '_') + "_DO_RELOAD_MODULE", True)
|
||||||
|
|
||||||
|
|
||||||
|
def is_reload(module_name: str) -> bool:
|
||||||
|
"""True if the module given by `module_name` should reload the
|
||||||
|
modules it imports. This is the case if `enable_reload()` was called
|
||||||
|
for the module before.
|
||||||
|
"""
|
||||||
|
mod = sys.modules[module_name]
|
||||||
|
return hasattr(mod, module_name.replace('.', '_') + "_DO_RELOAD_MODULE")
|
||||||
|
|
||||||
|
|
||||||
|
def reload_module(module: types.ModuleType) -> types.ModuleType:
|
||||||
|
"""Wrapper around importlib.reload() to make sure no module is
|
||||||
|
reloaded twice.
|
||||||
|
|
||||||
|
Make sure to call this function in the same order in which the
|
||||||
|
modules are imported to make sure that the reloading respects the
|
||||||
|
module dependencies. Otherwise modules could depend on other modules
|
||||||
|
that are not yet reloaded.
|
||||||
|
|
||||||
|
If you import classes or functions from a module, make sure to
|
||||||
|
re-import them after the module is reloaded.
|
||||||
|
"""
|
||||||
|
mod = _module_cache.get(module.__name__, None)
|
||||||
|
|
||||||
|
if mod is None:
|
||||||
|
mod = importlib.reload(module)
|
||||||
|
_module_cache[module.__name__] = mod
|
||||||
|
|
||||||
|
return mod
|
|
@ -2,9 +2,16 @@ from typing import Callable, Dict, Optional
|
||||||
|
|
||||||
from bpy.types import Material, UILayout
|
from bpy.types import Material, UILayout
|
||||||
|
|
||||||
|
import arm
|
||||||
from arm.material.shader import ShaderContext
|
from arm.material.shader import ShaderContext
|
||||||
|
|
||||||
drivers: Dict[str, Dict] = dict()
|
if arm.is_reload(__name__):
|
||||||
|
arm.material.shader = arm.reload_module(arm.material.shader)
|
||||||
|
from arm.material.shader import ShaderContext
|
||||||
|
else:
|
||||||
|
drivers: Dict[str, Dict] = dict()
|
||||||
|
|
||||||
|
arm.enable_reload(__name__)
|
||||||
|
|
||||||
|
|
||||||
def add_driver(driver_name: str,
|
def add_driver(driver_name: str,
|
||||||
|
|
|
@ -6,6 +6,12 @@ import bpy
|
||||||
import arm.log as log
|
import arm.log as log
|
||||||
import arm.utils
|
import arm.utils
|
||||||
|
|
||||||
|
if arm.is_reload(__name__):
|
||||||
|
log = arm.reload_module(log)
|
||||||
|
arm.utils = arm.reload_module(arm.utils)
|
||||||
|
else:
|
||||||
|
arm.enable_reload(__name__)
|
||||||
|
|
||||||
assets = []
|
assets = []
|
||||||
reserved_names = ['return.']
|
reserved_names = ['return.']
|
||||||
khafile_params = []
|
khafile_params = []
|
||||||
|
|
|
@ -32,6 +32,19 @@ import arm.material.mat_batch as mat_batch
|
||||||
import arm.utils
|
import arm.utils
|
||||||
import arm.profiler
|
import arm.profiler
|
||||||
|
|
||||||
|
if arm.is_reload(__name__):
|
||||||
|
assets = arm.reload_module(assets)
|
||||||
|
exporter_opt = arm.reload_module(exporter_opt)
|
||||||
|
log = arm.reload_module(log)
|
||||||
|
make_renderpath = arm.reload_module(make_renderpath)
|
||||||
|
cycles = arm.reload_module(cycles)
|
||||||
|
make_material = arm.reload_module(make_material)
|
||||||
|
mat_batch = arm.reload_module(mat_batch)
|
||||||
|
arm.utils = arm.reload_module(arm.utils)
|
||||||
|
arm.profiler = arm.reload_module(arm.profiler)
|
||||||
|
else:
|
||||||
|
arm.enable_reload(__name__)
|
||||||
|
|
||||||
|
|
||||||
@unique
|
@unique
|
||||||
class NodeType(Enum):
|
class NodeType(Enum):
|
||||||
|
@ -278,6 +291,8 @@ class ArmoryExporter:
|
||||||
return out_track
|
return out_track
|
||||||
|
|
||||||
def export_object_transform(self, bobject: bpy.types.Object, o):
|
def export_object_transform(self, bobject: bpy.types.Object, o):
|
||||||
|
wrd = bpy.data.worlds['Arm']
|
||||||
|
|
||||||
# Static transform
|
# Static transform
|
||||||
o['transform'] = {'values': ArmoryExporter.write_matrix(bobject.matrix_local)}
|
o['transform'] = {'values': ArmoryExporter.write_matrix(bobject.matrix_local)}
|
||||||
|
|
||||||
|
@ -291,7 +306,7 @@ class ArmoryExporter:
|
||||||
fp = self.get_meshes_file_path('action_' + action_name, compressed=ArmoryExporter.compress_enabled)
|
fp = self.get_meshes_file_path('action_' + action_name, compressed=ArmoryExporter.compress_enabled)
|
||||||
assets.add(fp)
|
assets.add(fp)
|
||||||
ext = '.lz4' if ArmoryExporter.compress_enabled else ''
|
ext = '.lz4' if ArmoryExporter.compress_enabled else ''
|
||||||
if ext == '' and not bpy.data.worlds['Arm'].arm_minimize:
|
if ext == '' and not wrd.arm_minimize:
|
||||||
ext = '.json'
|
ext = '.json'
|
||||||
|
|
||||||
if 'object_actions' not in o:
|
if 'object_actions' not in o:
|
||||||
|
@ -307,6 +322,7 @@ class ArmoryExporter:
|
||||||
|
|
||||||
self.export_pose_markers(out_anim, action)
|
self.export_pose_markers(out_anim, action)
|
||||||
|
|
||||||
|
unresolved_data_paths = set()
|
||||||
for fcurve in action.fcurves:
|
for fcurve in action.fcurves:
|
||||||
data_path = fcurve.data_path
|
data_path = fcurve.data_path
|
||||||
|
|
||||||
|
@ -314,7 +330,11 @@ class ArmoryExporter:
|
||||||
out_track = self.export_animation_track(fcurve, frame_range, FCURVE_TARGET_NAMES[data_path][fcurve.array_index])
|
out_track = self.export_animation_track(fcurve, frame_range, FCURVE_TARGET_NAMES[data_path][fcurve.array_index])
|
||||||
except KeyError:
|
except KeyError:
|
||||||
if data_path not in FCURVE_TARGET_NAMES:
|
if data_path not in FCURVE_TARGET_NAMES:
|
||||||
log.warn(f"Action {action_name}: The data path '{data_path}' is not supported (yet)!")
|
# This can happen if the target is simply not
|
||||||
|
# supported or the action shares both bone
|
||||||
|
# and object transform data (FCURVE_TARGET_NAMES
|
||||||
|
# only contains object transform targets)
|
||||||
|
unresolved_data_paths.add(data_path)
|
||||||
continue
|
continue
|
||||||
# Missing target entry for array_index or something else
|
# Missing target entry for array_index or something else
|
||||||
else:
|
else:
|
||||||
|
@ -322,8 +342,20 @@ class ArmoryExporter:
|
||||||
|
|
||||||
out_anim['tracks'].append(out_track)
|
out_anim['tracks'].append(out_track)
|
||||||
|
|
||||||
|
if len(unresolved_data_paths) > 0:
|
||||||
|
warning = (
|
||||||
|
f'The action "{action_name}" has fcurve channels with data paths that could not be resolved.'
|
||||||
|
' This can be caused by the following things:\n'
|
||||||
|
' - The data paths are not supported.\n'
|
||||||
|
' - The action exists on both armature and non-armature objects or has both bone and object transform data.'
|
||||||
|
)
|
||||||
|
if wrd.arm_verbose_output:
|
||||||
|
warning += f'\n Unresolved data paths: {unresolved_data_paths}'
|
||||||
|
else:
|
||||||
|
warning += '\n To see the list of unresolved data paths please recompile with Armory Project > Verbose Output enabled.'
|
||||||
|
log.warn(warning)
|
||||||
|
|
||||||
if True: # not action.arm_cached or not os.path.exists(fp):
|
if True: # not action.arm_cached or not os.path.exists(fp):
|
||||||
wrd = bpy.data.worlds['Arm']
|
|
||||||
if wrd.arm_verbose_output:
|
if wrd.arm_verbose_output:
|
||||||
print('Exporting object action ' + action_name)
|
print('Exporting object action ' + action_name)
|
||||||
|
|
||||||
|
@ -1157,7 +1189,16 @@ class ArmoryExporter:
|
||||||
else:
|
else:
|
||||||
invscale_tex = 1 * 32767
|
invscale_tex = 1 * 32767
|
||||||
if has_tang:
|
if has_tang:
|
||||||
|
try:
|
||||||
exportMesh.calc_tangents(uvmap=lay0.name)
|
exportMesh.calc_tangents(uvmap=lay0.name)
|
||||||
|
except Exception as e:
|
||||||
|
if hasattr(e, 'message'):
|
||||||
|
log.error(e.message)
|
||||||
|
else:
|
||||||
|
# Assume it was caused because of encountering n-gons
|
||||||
|
log.error(f"""object {bobject.name} contains n-gons in its mesh, so it's impossible to compute tanget space for normal mapping.
|
||||||
|
Make sure the mesh only has tris/quads.""")
|
||||||
|
|
||||||
tangdata = np.empty(num_verts * 3, dtype='<f4')
|
tangdata = np.empty(num_verts * 3, dtype='<f4')
|
||||||
if has_col:
|
if has_col:
|
||||||
cdata = np.empty(num_verts * 3, dtype='<f4')
|
cdata = np.empty(num_verts * 3, dtype='<f4')
|
||||||
|
@ -1502,23 +1543,17 @@ class ArmoryExporter:
|
||||||
# Less bias for bigger maps
|
# Less bias for bigger maps
|
||||||
out_light['shadows_bias'] *= 1 / (out_light['shadowmap_size'] / 1024)
|
out_light['shadows_bias'] *= 1 / (out_light['shadowmap_size'] / 1024)
|
||||||
elif objtype == 'POINT':
|
elif objtype == 'POINT':
|
||||||
out_light['strength'] *= 2.6
|
|
||||||
if bpy.app.version >= (2, 80, 72):
|
|
||||||
out_light['strength'] *= 0.01
|
out_light['strength'] *= 0.01
|
||||||
out_light['fov'] = 1.5708 # pi/2
|
out_light['fov'] = 1.5708 # pi/2
|
||||||
out_light['shadowmap_cube'] = True
|
out_light['shadowmap_cube'] = True
|
||||||
if light_ref.shadow_soft_size > 0.1:
|
if light_ref.shadow_soft_size > 0.1:
|
||||||
out_light['light_size'] = light_ref.shadow_soft_size * 10
|
out_light['light_size'] = light_ref.shadow_soft_size * 10
|
||||||
elif objtype == 'SPOT':
|
elif objtype == 'SPOT':
|
||||||
out_light['strength'] *= 2.6
|
|
||||||
if bpy.app.version >= (2, 80, 72):
|
|
||||||
out_light['strength'] *= 0.01
|
out_light['strength'] *= 0.01
|
||||||
out_light['spot_size'] = math.cos(light_ref.spot_size / 2)
|
out_light['spot_size'] = math.cos(light_ref.spot_size / 2)
|
||||||
# Cycles defaults to 0.15
|
# Cycles defaults to 0.15
|
||||||
out_light['spot_blend'] = light_ref.spot_blend / 10
|
out_light['spot_blend'] = light_ref.spot_blend / 10
|
||||||
elif objtype == 'AREA':
|
elif objtype == 'AREA':
|
||||||
out_light['strength'] *= 80.0 / (light_ref.size * light_ref.size_y)
|
|
||||||
if bpy.app.version >= (2, 80, 72):
|
|
||||||
out_light['strength'] *= 0.01
|
out_light['strength'] *= 0.01
|
||||||
out_light['size'] = light_ref.size
|
out_light['size'] = light_ref.size
|
||||||
out_light['size_y'] = light_ref.size_y
|
out_light['size_y'] = light_ref.size_y
|
||||||
|
@ -1565,9 +1600,11 @@ class ArmoryExporter:
|
||||||
|
|
||||||
asset_name = arm.utils.asset_name(bobject)
|
asset_name = arm.utils.asset_name(bobject)
|
||||||
|
|
||||||
|
# Collection is in the same file
|
||||||
if collection.library is None:
|
if collection.library is None:
|
||||||
# collection is in the same file, but (likely) on another scene
|
# Only export linked objects (from other scenes for example),
|
||||||
if asset_name not in scene_objects:
|
# all other objects (in scene_objects) are already exported.
|
||||||
|
if bobject.name not in scene_objects:
|
||||||
self.process_bobject(bobject)
|
self.process_bobject(bobject)
|
||||||
self.export_object(bobject, self.scene)
|
self.export_object(bobject, self.scene)
|
||||||
else:
|
else:
|
||||||
|
@ -2308,6 +2345,10 @@ class ArmoryExporter:
|
||||||
|
|
||||||
return instanced_type, instanced_data
|
return instanced_type, instanced_data
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def rigid_body_static(rb):
|
||||||
|
return (not rb.enabled and not rb.kinematic) or (rb.type == 'PASSIVE' and not rb.kinematic)
|
||||||
|
|
||||||
def post_export_object(self, bobject: bpy.types.Object, o, type):
|
def post_export_object(self, bobject: bpy.types.Object, o, type):
|
||||||
# Export traits
|
# Export traits
|
||||||
self.export_traits(bobject, o)
|
self.export_traits(bobject, o)
|
||||||
|
@ -2334,7 +2375,7 @@ class ArmoryExporter:
|
||||||
elif rb.collision_shape == 'CAPSULE':
|
elif rb.collision_shape == 'CAPSULE':
|
||||||
shape = 6
|
shape = 6
|
||||||
body_mass = rb.mass
|
body_mass = rb.mass
|
||||||
is_static = (not rb.enabled and not rb.kinematic) or (rb.type == 'PASSIVE' and not rb.kinematic)
|
is_static = self.rigid_body_static(rb)
|
||||||
if is_static:
|
if is_static:
|
||||||
body_mass = 0
|
body_mass = 0
|
||||||
x = {}
|
x = {}
|
||||||
|
@ -2405,7 +2446,7 @@ class ArmoryExporter:
|
||||||
# Rigid body constraint
|
# Rigid body constraint
|
||||||
rbc = bobject.rigid_body_constraint
|
rbc = bobject.rigid_body_constraint
|
||||||
if rbc is not None and rbc.enabled:
|
if rbc is not None and rbc.enabled:
|
||||||
self.add_rigidbody_constraint(o, rbc)
|
self.add_rigidbody_constraint(o, bobject, rbc)
|
||||||
|
|
||||||
# Camera traits
|
# Camera traits
|
||||||
if type is NodeType.CAMERA:
|
if type is NodeType.CAMERA:
|
||||||
|
@ -2426,6 +2467,13 @@ class ArmoryExporter:
|
||||||
self.material_to_object_dict[mat] = [bobject]
|
self.material_to_object_dict[mat] = [bobject]
|
||||||
self.material_to_arm_object_dict[mat] = [o]
|
self.material_to_arm_object_dict[mat] = [o]
|
||||||
|
|
||||||
|
# Add UniformsManager trait
|
||||||
|
if type is NodeType.MESH:
|
||||||
|
uniformManager = {}
|
||||||
|
uniformManager['type'] = 'Script'
|
||||||
|
uniformManager['class_name'] = 'armory.trait.internal.UniformsManager'
|
||||||
|
o['traits'].append(uniformManager)
|
||||||
|
|
||||||
# Export constraints
|
# Export constraints
|
||||||
if len(bobject.constraints) > 0:
|
if len(bobject.constraints) > 0:
|
||||||
o['constraints'] = []
|
o['constraints'] = []
|
||||||
|
@ -2600,7 +2648,7 @@ class ArmoryExporter:
|
||||||
|
|
||||||
rbw = self.scene.rigidbody_world
|
rbw = self.scene.rigidbody_world
|
||||||
if rbw is not None and rbw.enabled:
|
if rbw is not None and rbw.enabled:
|
||||||
out_trait['parameters'] = [str(rbw.time_scale), str(1 / rbw.steps_per_second), str(rbw.solver_iterations)]
|
out_trait['parameters'] = [str(rbw.time_scale), str(rbw.substeps_per_frame), str(rbw.solver_iterations)]
|
||||||
|
|
||||||
self.output['traits'].append(out_trait)
|
self.output['traits'].append(out_trait)
|
||||||
|
|
||||||
|
@ -2636,7 +2684,7 @@ class ArmoryExporter:
|
||||||
}
|
}
|
||||||
self.output['traits'].append(out_trait)
|
self.output['traits'].append(out_trait)
|
||||||
|
|
||||||
if wrd.arm_live_patch:
|
if arm.utils.is_livepatch_enabled():
|
||||||
if 'traits' not in self.output:
|
if 'traits' not in self.output:
|
||||||
self.output['traits'] = []
|
self.output['traits'] = []
|
||||||
out_trait = {'type': 'Script', 'class_name': 'armory.trait.internal.LivePatch'}
|
out_trait = {'type': 'Script', 'class_name': 'armory.trait.internal.LivePatch'}
|
||||||
|
@ -2712,7 +2760,7 @@ class ArmoryExporter:
|
||||||
o['traits'].append(out_trait)
|
o['traits'].append(out_trait)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def add_rigidbody_constraint(o, rbc):
|
def add_rigidbody_constraint(o, bobject, rbc):
|
||||||
rb1 = rbc.object1
|
rb1 = rbc.object1
|
||||||
rb2 = rbc.object2
|
rb2 = rbc.object2
|
||||||
if rb1 is None or rb2 is None:
|
if rb1 is None or rb2 is None:
|
||||||
|
@ -2731,7 +2779,8 @@ class ArmoryExporter:
|
||||||
"'" + rb1.name + "'",
|
"'" + rb1.name + "'",
|
||||||
"'" + rb2.name + "'",
|
"'" + rb2.name + "'",
|
||||||
str(rbc.disable_collisions).lower(),
|
str(rbc.disable_collisions).lower(),
|
||||||
str(breaking_threshold)
|
str(breaking_threshold),
|
||||||
|
str(bobject.arm_relative_physics_constraint).lower()
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
if rbc.type == "FIXED":
|
if rbc.type == "FIXED":
|
||||||
|
@ -2847,6 +2896,7 @@ class ArmoryExporter:
|
||||||
out_world['sun_direction'] = list(world.arm_envtex_sun_direction)
|
out_world['sun_direction'] = list(world.arm_envtex_sun_direction)
|
||||||
out_world['turbidity'] = world.arm_envtex_turbidity
|
out_world['turbidity'] = world.arm_envtex_turbidity
|
||||||
out_world['ground_albedo'] = world.arm_envtex_ground_albedo
|
out_world['ground_albedo'] = world.arm_envtex_ground_albedo
|
||||||
|
out_world['nishita_density'] = list(world.arm_nishita_density)
|
||||||
|
|
||||||
disable_hdr = world.arm_envtex_name.endswith('.jpg')
|
disable_hdr = world.arm_envtex_name.endswith('.jpg')
|
||||||
|
|
||||||
|
@ -2861,16 +2911,9 @@ class ArmoryExporter:
|
||||||
rpdat = arm.utils.get_rp()
|
rpdat = arm.utils.get_rp()
|
||||||
solid_mat = rpdat.arm_material_model == 'Solid'
|
solid_mat = rpdat.arm_material_model == 'Solid'
|
||||||
arm_irradiance = rpdat.arm_irradiance and not solid_mat
|
arm_irradiance = rpdat.arm_irradiance and not solid_mat
|
||||||
arm_radiance = False
|
arm_radiance = rpdat.arm_radiance
|
||||||
radtex = world.arm_envtex_name.rsplit('.', 1)[0]
|
radtex = world.arm_envtex_name.rsplit('.', 1)[0] # Remove file extension
|
||||||
irrsharmonics = world.arm_envtex_irr_name
|
irrsharmonics = world.arm_envtex_irr_name
|
||||||
|
|
||||||
# Radiance
|
|
||||||
if '_EnvTex' in world.world_defs:
|
|
||||||
arm_radiance = rpdat.arm_radiance
|
|
||||||
elif '_EnvSky' in world.world_defs:
|
|
||||||
arm_radiance = rpdat.arm_radiance
|
|
||||||
radtex = 'hosek'
|
|
||||||
num_mips = world.arm_envtex_num_mips
|
num_mips = world.arm_envtex_num_mips
|
||||||
strength = world.arm_envtex_strength
|
strength = world.arm_envtex_strength
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,20 @@
|
||||||
|
"""
|
||||||
|
Exports smaller geometry but is slower.
|
||||||
|
To be replaced with https://github.com/zeux/meshoptimizer
|
||||||
|
"""
|
||||||
|
|
||||||
from mathutils import *
|
from mathutils import *
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
import arm.utils
|
|
||||||
import arm.log as log
|
import arm.log as log
|
||||||
|
import arm.utils
|
||||||
|
|
||||||
|
if arm.is_reload(__name__):
|
||||||
|
log = arm.reload_module(log)
|
||||||
|
arm.utils = arm.reload_module(arm.utils)
|
||||||
|
else:
|
||||||
|
arm.enable_reload(__name__)
|
||||||
|
|
||||||
# Exports smaller geometry but is slower
|
|
||||||
# To be replaced with https://github.com/zeux/meshoptimizer
|
|
||||||
|
|
||||||
class Vertex:
|
class Vertex:
|
||||||
__slots__ = ("co", "normal", "uvs", "col", "loop_indices", "index", "bone_weights", "bone_indices", "bone_count", "vertex_index")
|
__slots__ = ("co", "normal", "uvs", "col", "loop_indices", "index", "bone_weights", "bone_indices", "bone_count", "vertex_index")
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
import importlib
|
import importlib
|
||||||
import os
|
import os
|
||||||
|
import queue
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
from bpy.app.handlers import persistent
|
from bpy.app.handlers import persistent
|
||||||
|
|
||||||
|
import arm
|
||||||
import arm.api
|
import arm.api
|
||||||
|
import arm.live_patch as live_patch
|
||||||
import arm.logicnode.arm_nodes as arm_nodes
|
import arm.logicnode.arm_nodes as arm_nodes
|
||||||
import arm.nodes_logic
|
import arm.nodes_logic
|
||||||
import arm.make as make
|
import arm.make as make
|
||||||
|
@ -13,6 +16,19 @@ import arm.make_state as state
|
||||||
import arm.props as props
|
import arm.props as props
|
||||||
import arm.utils
|
import arm.utils
|
||||||
|
|
||||||
|
if arm.is_reload(__name__):
|
||||||
|
arm.api = arm.reload_module(arm.api)
|
||||||
|
live_patch = arm.reload_module(live_patch)
|
||||||
|
arm_nodes = arm.reload_module(arm_nodes)
|
||||||
|
arm.nodes_logic = arm.reload_module(arm.nodes_logic)
|
||||||
|
make = arm.reload_module(make)
|
||||||
|
state = arm.reload_module(state)
|
||||||
|
props = arm.reload_module(props)
|
||||||
|
arm.utils = arm.reload_module(arm.utils)
|
||||||
|
else:
|
||||||
|
arm.enable_reload(__name__)
|
||||||
|
|
||||||
|
|
||||||
@persistent
|
@persistent
|
||||||
def on_depsgraph_update_post(self):
|
def on_depsgraph_update_post(self):
|
||||||
if state.proc_build != None:
|
if state.proc_build != None:
|
||||||
|
@ -40,12 +56,10 @@ def on_depsgraph_update_post(self):
|
||||||
|
|
||||||
# Send last operator to Krom
|
# Send last operator to Krom
|
||||||
wrd = bpy.data.worlds['Arm']
|
wrd = bpy.data.worlds['Arm']
|
||||||
if state.proc_play != None and \
|
if state.proc_play is not None and state.target == 'krom' and wrd.arm_live_patch:
|
||||||
state.target == 'krom' and \
|
|
||||||
wrd.arm_live_patch:
|
|
||||||
ops = bpy.context.window_manager.operators
|
ops = bpy.context.window_manager.operators
|
||||||
if len(ops) > 0 and ops[-1] != None:
|
if len(ops) > 0 and ops[-1] is not None:
|
||||||
send_operator(ops[-1])
|
live_patch.on_operator(ops[-1].bl_idname)
|
||||||
|
|
||||||
# Hacky solution to update armory props after operator executions
|
# Hacky solution to update armory props after operator executions
|
||||||
last_operator = bpy.context.active_operator
|
last_operator = bpy.context.active_operator
|
||||||
|
@ -89,22 +103,51 @@ def send_operator(op):
|
||||||
else: # Rebuild
|
else: # Rebuild
|
||||||
make.patch()
|
make.patch()
|
||||||
|
|
||||||
def always():
|
|
||||||
|
def always() -> float:
|
||||||
# Force ui redraw
|
# Force ui redraw
|
||||||
if state.redraw_ui and context_screen != None:
|
if state.redraw_ui and context_screen is not None:
|
||||||
for area in context_screen.areas:
|
for area in context_screen.areas:
|
||||||
if area.type == 'VIEW_3D' or area.type == 'PROPERTIES':
|
if area.type == 'VIEW_3D' or area.type == 'PROPERTIES':
|
||||||
area.tag_redraw()
|
area.tag_redraw()
|
||||||
state.redraw_ui = False
|
state.redraw_ui = False
|
||||||
# TODO: depsgraph.updates only triggers material trees
|
# TODO: depsgraph.updates only triggers material trees
|
||||||
space = arm.utils.logic_editor_space(context_screen)
|
space = arm.utils.logic_editor_space(context_screen)
|
||||||
if space != None:
|
if space is not None:
|
||||||
space.node_tree.arm_cached = False
|
space.node_tree.arm_cached = False
|
||||||
return 0.5
|
return 0.5
|
||||||
|
|
||||||
|
|
||||||
|
def poll_threads() -> float:
|
||||||
|
"""Polls the thread callback queue and if a thread has finished, it
|
||||||
|
is joined with the main thread and the corresponding callback is
|
||||||
|
executed in the main thread.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
thread, callback = make.thread_callback_queue.get(block=False)
|
||||||
|
except queue.Empty:
|
||||||
|
return 0.25
|
||||||
|
|
||||||
|
thread.join()
|
||||||
|
|
||||||
|
try:
|
||||||
|
callback()
|
||||||
|
except Exception as e:
|
||||||
|
# If there is an exception, we can no longer return the time to
|
||||||
|
# the next call to this polling function, so to keep it running
|
||||||
|
# we re-register it and then raise the original exception.
|
||||||
|
bpy.app.timers.unregister(poll_threads)
|
||||||
|
bpy.app.timers.register(poll_threads, first_interval=0.01, persistent=True)
|
||||||
|
raise e
|
||||||
|
|
||||||
|
# Quickly check if another thread has finished
|
||||||
|
return 0.01
|
||||||
|
|
||||||
|
|
||||||
appended_py_paths = []
|
appended_py_paths = []
|
||||||
context_screen = None
|
context_screen = None
|
||||||
|
|
||||||
|
|
||||||
@persistent
|
@persistent
|
||||||
def on_load_post(context):
|
def on_load_post(context):
|
||||||
global appended_py_paths
|
global appended_py_paths
|
||||||
|
@ -181,7 +224,9 @@ def register():
|
||||||
bpy.app.handlers.load_post.append(on_load_post)
|
bpy.app.handlers.load_post.append(on_load_post)
|
||||||
bpy.app.handlers.depsgraph_update_post.append(on_depsgraph_update_post)
|
bpy.app.handlers.depsgraph_update_post.append(on_depsgraph_update_post)
|
||||||
# bpy.app.handlers.undo_post.append(on_undo_post)
|
# bpy.app.handlers.undo_post.append(on_undo_post)
|
||||||
|
|
||||||
bpy.app.timers.register(always, persistent=True)
|
bpy.app.timers.register(always, persistent=True)
|
||||||
|
bpy.app.timers.register(poll_threads, persistent=True)
|
||||||
|
|
||||||
if arm.utils.get_fp() != '':
|
if arm.utils.get_fp() != '':
|
||||||
appended_py_paths = []
|
appended_py_paths = []
|
||||||
|
@ -198,6 +243,9 @@ def register():
|
||||||
|
|
||||||
|
|
||||||
def unregister():
|
def unregister():
|
||||||
|
bpy.app.timers.unregister(poll_threads)
|
||||||
|
bpy.app.timers.unregister(always)
|
||||||
|
|
||||||
bpy.app.handlers.load_post.remove(on_load_post)
|
bpy.app.handlers.load_post.remove(on_load_post)
|
||||||
bpy.app.handlers.depsgraph_update_post.remove(on_depsgraph_update_post)
|
bpy.app.handlers.depsgraph_update_post.remove(on_depsgraph_update_post)
|
||||||
# bpy.app.handlers.undo_post.remove(on_undo_post)
|
# bpy.app.handlers.undo_post.remove(on_undo_post)
|
||||||
|
|
|
@ -1,8 +1,16 @@
|
||||||
import bpy
|
import bpy
|
||||||
|
|
||||||
|
import arm
|
||||||
import arm.props_ui as props_ui
|
import arm.props_ui as props_ui
|
||||||
|
|
||||||
|
if arm.is_reload(__name__):
|
||||||
|
props_ui = arm.reload_module(props_ui)
|
||||||
|
else:
|
||||||
|
arm.enable_reload(__name__)
|
||||||
|
|
||||||
arm_keymaps = []
|
arm_keymaps = []
|
||||||
|
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
wm = bpy.context.window_manager
|
wm = bpy.context.window_manager
|
||||||
addon_keyconfig = wm.keyconfigs.addon
|
addon_keyconfig = wm.keyconfigs.addon
|
||||||
|
@ -17,8 +25,11 @@ def register():
|
||||||
|
|
||||||
km = addon_keyconfig.keymaps.new(name='Window', space_type='EMPTY', region_type="WINDOW")
|
km = addon_keyconfig.keymaps.new(name='Window', space_type='EMPTY', region_type="WINDOW")
|
||||||
km.keymap_items.new(props_ui.ArmoryPlayButton.bl_idname, type='F5', value='PRESS')
|
km.keymap_items.new(props_ui.ArmoryPlayButton.bl_idname, type='F5', value='PRESS')
|
||||||
|
km.keymap_items.new("tlm.build_lightmaps", type='F6', value='PRESS')
|
||||||
|
km.keymap_items.new("tlm.clean_lightmaps", type='F7', value='PRESS')
|
||||||
arm_keymaps.append(km)
|
arm_keymaps.append(km)
|
||||||
|
|
||||||
|
|
||||||
def unregister():
|
def unregister():
|
||||||
wm = bpy.context.window_manager
|
wm = bpy.context.window_manager
|
||||||
for km in arm_keymaps:
|
for km in arm_keymaps:
|
||||||
|
|
|
@ -20,9 +20,10 @@
|
||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
#
|
#
|
||||||
import struct
|
|
||||||
import io
|
import io
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
import struct
|
||||||
|
|
||||||
|
|
||||||
def _pack_integer(obj, fp):
|
def _pack_integer(obj, fp):
|
||||||
if obj < 0:
|
if obj < 0:
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
|
import atexit
|
||||||
import http.server
|
import http.server
|
||||||
import socketserver
|
import socketserver
|
||||||
import subprocess
|
import subprocess
|
||||||
import atexit
|
|
||||||
|
|
||||||
haxe_server = None
|
haxe_server = None
|
||||||
|
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
__all__ = ('Operators', 'Properties', 'Utility', 'Keymap')
|
__all__ = ('Operators', 'Panels', 'Properties', 'Preferences', 'Utility', 'Keymap')
|
BIN
blender/arm/lightmapper/assets/TLM_Overlay.png
Normal file
BIN
blender/arm/lightmapper/assets/TLM_Overlay.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
Binary file not shown.
|
@ -1,7 +0,0 @@
|
||||||
from . import keymap
|
|
||||||
|
|
||||||
def register():
|
|
||||||
keymap.register()
|
|
||||||
|
|
||||||
def unregister():
|
|
||||||
keymap.unregister()
|
|
|
@ -9,6 +9,8 @@ def register():
|
||||||
winman = bpy.context.window_manager
|
winman = bpy.context.window_manager
|
||||||
keyman = winman.keyconfigs.addon.keymaps.new(name='Window', space_type='EMPTY', region_type="WINDOW")
|
keyman = winman.keyconfigs.addon.keymaps.new(name='Window', space_type='EMPTY', region_type="WINDOW")
|
||||||
|
|
||||||
|
|
||||||
|
#TODO - In Armory3D, merge with keymap.py
|
||||||
keyman.keymap_items.new('tlm.build_lightmaps', type='F6', value='PRESS')
|
keyman.keymap_items.new('tlm.build_lightmaps', type='F6', value='PRESS')
|
||||||
keyman.keymap_items.new('tlm.clean_lightmaps', type='F7', value='PRESS')
|
keyman.keymap_items.new('tlm.clean_lightmaps', type='F7', value='PRESS')
|
||||||
tlm_keymaps.append(keyman)
|
tlm_keymaps.append(keyman)
|
||||||
|
|
|
@ -6,10 +6,11 @@ classes = [
|
||||||
tlm.TLM_BuildLightmaps,
|
tlm.TLM_BuildLightmaps,
|
||||||
tlm.TLM_CleanLightmaps,
|
tlm.TLM_CleanLightmaps,
|
||||||
tlm.TLM_ExploreLightmaps,
|
tlm.TLM_ExploreLightmaps,
|
||||||
tlm.TLM_EnableSelection,
|
tlm.TLM_EnableSet,
|
||||||
tlm.TLM_DisableSelection,
|
tlm.TLM_DisableSelection,
|
||||||
tlm.TLM_RemoveLightmapUV,
|
tlm.TLM_RemoveLightmapUV,
|
||||||
tlm.TLM_SelectLightmapped,
|
tlm.TLM_SelectLightmapped,
|
||||||
|
tlm.TLM_ToggleTexelDensity,
|
||||||
installopencv.TLM_Install_OpenCV,
|
installopencv.TLM_Install_OpenCV,
|
||||||
tlm.TLM_AtlasListNewItem,
|
tlm.TLM_AtlasListNewItem,
|
||||||
tlm.TLM_AtlastListDeleteItem,
|
tlm.TLM_AtlastListDeleteItem,
|
||||||
|
@ -20,6 +21,8 @@ classes = [
|
||||||
tlm.TLM_StartServer,
|
tlm.TLM_StartServer,
|
||||||
tlm.TLM_BuildEnvironmentProbes,
|
tlm.TLM_BuildEnvironmentProbes,
|
||||||
tlm.TLM_CleanBuildEnvironmentProbes,
|
tlm.TLM_CleanBuildEnvironmentProbes,
|
||||||
|
tlm.TLM_PrepareUVMaps,
|
||||||
|
tlm.TLM_LoadLightmaps,
|
||||||
imagetools.TLM_ImageUpscale,
|
imagetools.TLM_ImageUpscale,
|
||||||
imagetools.TLM_ImageDownscale
|
imagetools.TLM_ImageDownscale
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import bpy, os, time
|
import bpy, os, time, importlib
|
||||||
|
|
||||||
class TLM_ImageUpscale(bpy.types.Operator):
|
class TLM_ImageUpscale(bpy.types.Operator):
|
||||||
bl_idname = "tlm.image_upscale"
|
bl_idname = "tlm.image_upscale"
|
||||||
|
@ -8,6 +8,69 @@ class TLM_ImageUpscale(bpy.types.Operator):
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
|
|
||||||
|
cv2 = importlib.util.find_spec("cv2")
|
||||||
|
|
||||||
|
if cv2 is None:
|
||||||
|
print("CV2 not found - Ignoring filtering")
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
cv2 = importlib.__import__("cv2")
|
||||||
|
|
||||||
|
for area in bpy.context.screen.areas:
|
||||||
|
if area.type == "IMAGE_EDITOR":
|
||||||
|
active_image = area.spaces.active.image
|
||||||
|
|
||||||
|
if active_image.source == "FILE":
|
||||||
|
img_path = active_image.filepath_raw
|
||||||
|
filename = os.path.basename(img_path)
|
||||||
|
|
||||||
|
basename = os.path.splitext(filename)[0]
|
||||||
|
extension = os.path.splitext(filename)[1]
|
||||||
|
|
||||||
|
size_x = active_image.size[0]
|
||||||
|
size_y = active_image.size[1]
|
||||||
|
|
||||||
|
dir_path = os.path.dirname(os.path.realpath(img_path))
|
||||||
|
|
||||||
|
#newfile = os.path.join(dir_path, basename + "_" + str(size_x) + "_" + str(size_y) + extension)
|
||||||
|
newfile = os.path.join(dir_path, basename + extension)
|
||||||
|
os.rename(img_path, newfile)
|
||||||
|
|
||||||
|
basefile = cv2.imread(newfile, cv2.IMREAD_UNCHANGED)
|
||||||
|
|
||||||
|
scale_percent = 200 # percent of original size
|
||||||
|
width = int(basefile.shape[1] * scale_percent / 100)
|
||||||
|
height = int(basefile.shape[0] * scale_percent / 100)
|
||||||
|
dim = (width, height)
|
||||||
|
|
||||||
|
if active_image.TLM_ImageProperties.tlm_image_scale_method == "Nearest":
|
||||||
|
interp = cv2.INTER_NEAREST
|
||||||
|
elif active_image.TLM_ImageProperties.tlm_image_scale_method == "Area":
|
||||||
|
interp = cv2.INTER_AREA
|
||||||
|
elif active_image.TLM_ImageProperties.tlm_image_scale_method == "Linear":
|
||||||
|
interp = cv2.INTER_LINEAR
|
||||||
|
elif active_image.TLM_ImageProperties.tlm_image_scale_method == "Cubic":
|
||||||
|
interp = cv2.INTER_CUBIC
|
||||||
|
elif active_image.TLM_ImageProperties.tlm_image_scale_method == "Lanczos":
|
||||||
|
interp = cv2.INTER_LANCZOS4
|
||||||
|
|
||||||
|
resized = cv2.resize(basefile, dim, interpolation = interp)
|
||||||
|
|
||||||
|
#resizedFile = os.path.join(dir_path, basename + "_" + str(width) + "_" + str(height) + extension)
|
||||||
|
resizedFile = os.path.join(dir_path, basename + extension)
|
||||||
|
|
||||||
|
cv2.imwrite(resizedFile, resized)
|
||||||
|
|
||||||
|
active_image.filepath_raw = resizedFile
|
||||||
|
bpy.ops.image.reload()
|
||||||
|
|
||||||
|
print(newfile)
|
||||||
|
print(img_path)
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
print("Please save image")
|
||||||
|
|
||||||
print("Upscale")
|
print("Upscale")
|
||||||
|
|
||||||
return {'RUNNING_MODAL'}
|
return {'RUNNING_MODAL'}
|
||||||
|
@ -20,6 +83,111 @@ class TLM_ImageDownscale(bpy.types.Operator):
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
|
|
||||||
print("Downscale")
|
cv2 = importlib.util.find_spec("cv2")
|
||||||
|
|
||||||
|
if cv2 is None:
|
||||||
|
print("CV2 not found - Ignoring filtering")
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
cv2 = importlib.__import__("cv2")
|
||||||
|
|
||||||
|
for area in bpy.context.screen.areas:
|
||||||
|
if area.type == "IMAGE_EDITOR":
|
||||||
|
active_image = area.spaces.active.image
|
||||||
|
|
||||||
|
if active_image.source == "FILE":
|
||||||
|
img_path = active_image.filepath_raw
|
||||||
|
filename = os.path.basename(img_path)
|
||||||
|
|
||||||
|
basename = os.path.splitext(filename)[0]
|
||||||
|
extension = os.path.splitext(filename)[1]
|
||||||
|
|
||||||
|
size_x = active_image.size[0]
|
||||||
|
size_y = active_image.size[1]
|
||||||
|
|
||||||
|
dir_path = os.path.dirname(os.path.realpath(img_path))
|
||||||
|
|
||||||
|
#newfile = os.path.join(dir_path, basename + "_" + str(size_x) + "_" + str(size_y) + extension)
|
||||||
|
newfile = os.path.join(dir_path, basename + extension)
|
||||||
|
os.rename(img_path, newfile)
|
||||||
|
|
||||||
|
basefile = cv2.imread(newfile, cv2.IMREAD_UNCHANGED)
|
||||||
|
|
||||||
|
scale_percent = 50 # percent of original size
|
||||||
|
width = int(basefile.shape[1] * scale_percent / 100)
|
||||||
|
height = int(basefile.shape[0] * scale_percent / 100)
|
||||||
|
dim = (width, height)
|
||||||
|
|
||||||
|
if dim[0] > 1 or dim[1] > 1:
|
||||||
|
|
||||||
|
if active_image.TLM_ImageProperties.tlm_image_scale_method == "Nearest":
|
||||||
|
interp = cv2.INTER_NEAREST
|
||||||
|
elif active_image.TLM_ImageProperties.tlm_image_scale_method == "Area":
|
||||||
|
interp = cv2.INTER_AREA
|
||||||
|
elif active_image.TLM_ImageProperties.tlm_image_scale_method == "Linear":
|
||||||
|
interp = cv2.INTER_LINEAR
|
||||||
|
elif active_image.TLM_ImageProperties.tlm_image_scale_method == "Cubic":
|
||||||
|
interp = cv2.INTER_CUBIC
|
||||||
|
elif active_image.TLM_ImageProperties.tlm_image_scale_method == "Lanczos":
|
||||||
|
interp = cv2.INTER_LANCZOS4
|
||||||
|
|
||||||
|
resized = cv2.resize(basefile, dim, interpolation = interp)
|
||||||
|
|
||||||
|
#resizedFile = os.path.join(dir_path, basename + "_" + str(width) + "_" + str(height) + extension)
|
||||||
|
resizedFile = os.path.join(dir_path, basename + extension)
|
||||||
|
|
||||||
|
cv2.imwrite(resizedFile, resized)
|
||||||
|
|
||||||
|
active_image.filepath_raw = resizedFile
|
||||||
|
bpy.ops.image.reload()
|
||||||
|
|
||||||
|
print(newfile)
|
||||||
|
print(img_path)
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
print("Please save image")
|
||||||
|
|
||||||
|
print("Upscale")
|
||||||
|
|
||||||
|
return {'RUNNING_MODAL'}
|
||||||
|
|
||||||
|
class TLM_ImageSwitchUp(bpy.types.Operator):
|
||||||
|
bl_idname = "tlm.image_switchup"
|
||||||
|
bl_label = "Quickswitch Up"
|
||||||
|
bl_description = "Switches to a cached upscaled image"
|
||||||
|
bl_options = {'REGISTER', 'UNDO'}
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
|
||||||
|
for area in bpy.context.screen.areas:
|
||||||
|
if area.type == "IMAGE_EDITOR":
|
||||||
|
active_image = area.spaces.active.image
|
||||||
|
|
||||||
|
if active_image.source == "FILE":
|
||||||
|
img_path = active_image.filepath_raw
|
||||||
|
filename = os.path.basename(img_path)
|
||||||
|
|
||||||
|
print("Switch up")
|
||||||
|
|
||||||
|
return {'RUNNING_MODAL'}
|
||||||
|
|
||||||
|
class TLM_ImageSwitchDown(bpy.types.Operator):
|
||||||
|
bl_idname = "tlm.image_switchdown"
|
||||||
|
bl_label = "Quickswitch Down"
|
||||||
|
bl_description = "Switches to a cached downscaled image"
|
||||||
|
bl_options = {'REGISTER', 'UNDO'}
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
|
||||||
|
for area in bpy.context.screen.areas:
|
||||||
|
if area.type == "IMAGE_EDITOR":
|
||||||
|
active_image = area.spaces.active.image
|
||||||
|
|
||||||
|
if active_image.source == "FILE":
|
||||||
|
img_path = active_image.filepath_raw
|
||||||
|
filename = os.path.basename(img_path)
|
||||||
|
|
||||||
|
print("Switch Down")
|
||||||
|
|
||||||
return {'RUNNING_MODAL'}
|
return {'RUNNING_MODAL'}
|
|
@ -21,7 +21,10 @@ class TLM_Install_OpenCV(bpy.types.Operator):
|
||||||
|
|
||||||
print("Module OpenCV")
|
print("Module OpenCV")
|
||||||
|
|
||||||
|
if (2, 91, 0) > bpy.app.version:
|
||||||
pythonbinpath = bpy.app.binary_path_python
|
pythonbinpath = bpy.app.binary_path_python
|
||||||
|
else:
|
||||||
|
pythonbinpath = sys.executable
|
||||||
|
|
||||||
if platform.system() == "Windows":
|
if platform.system() == "Windows":
|
||||||
pythonlibpath = os.path.join(os.path.dirname(os.path.dirname(pythonbinpath)), "lib")
|
pythonlibpath = os.path.join(os.path.dirname(os.path.dirname(pythonbinpath)), "lib")
|
||||||
|
|
|
@ -1,9 +1,36 @@
|
||||||
import bpy, os, time, blf, webbrowser, platform
|
import bpy, os, time, blf, webbrowser, platform, numpy, bmesh
|
||||||
import math, subprocess, multiprocessing
|
import math, subprocess, multiprocessing
|
||||||
|
from .. utility import utility
|
||||||
from .. utility import build
|
from .. utility import build
|
||||||
from .. utility.cycles import cache
|
from .. utility.cycles import cache
|
||||||
from .. network import server
|
from .. network import server
|
||||||
|
|
||||||
|
def setObjectLightmapByWeight(minimumRes, maximumRes, objWeight):
|
||||||
|
|
||||||
|
availableResolutions = [32,64,128,256,512,1024,2048,4096,8192]
|
||||||
|
|
||||||
|
minRes = minimumRes
|
||||||
|
minResIdx = availableResolutions.index(minRes)
|
||||||
|
maxRes = maximumRes
|
||||||
|
maxResIdx = availableResolutions.index(maxRes)
|
||||||
|
|
||||||
|
exampleWeight = objWeight
|
||||||
|
|
||||||
|
if minResIdx == maxResIdx:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
|
||||||
|
increment = 1.0/(maxResIdx-minResIdx)
|
||||||
|
|
||||||
|
assortedRange = []
|
||||||
|
|
||||||
|
for a in numpy.arange(0.0, 1.0, increment):
|
||||||
|
assortedRange.append(round(a, 2))
|
||||||
|
|
||||||
|
assortedRange.append(1.0)
|
||||||
|
nearestWeight = min(assortedRange, key=lambda x:abs(x - exampleWeight))
|
||||||
|
return (availableResolutions[assortedRange.index(nearestWeight) + minResIdx])
|
||||||
|
|
||||||
class TLM_BuildLightmaps(bpy.types.Operator):
|
class TLM_BuildLightmaps(bpy.types.Operator):
|
||||||
bl_idname = "tlm.build_lightmaps"
|
bl_idname = "tlm.build_lightmaps"
|
||||||
bl_label = "Build Lightmaps"
|
bl_label = "Build Lightmaps"
|
||||||
|
@ -52,13 +79,13 @@ class TLM_CleanLightmaps(bpy.types.Operator):
|
||||||
for file in os.listdir(dirpath):
|
for file in os.listdir(dirpath):
|
||||||
os.remove(os.path.join(dirpath + "/" + file))
|
os.remove(os.path.join(dirpath + "/" + file))
|
||||||
|
|
||||||
for obj in bpy.data.objects:
|
for obj in bpy.context.scene.objects:
|
||||||
if obj.type == "MESH":
|
if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects:
|
||||||
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
|
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
|
||||||
cache.backup_material_restore(obj)
|
cache.backup_material_restore(obj)
|
||||||
|
|
||||||
for obj in bpy.data.objects:
|
for obj in bpy.context.scene.objects:
|
||||||
if obj.type == "MESH":
|
if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects:
|
||||||
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
|
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
|
||||||
cache.backup_material_rename(obj)
|
cache.backup_material_rename(obj)
|
||||||
|
|
||||||
|
@ -75,8 +102,8 @@ class TLM_CleanLightmaps(bpy.types.Operator):
|
||||||
if image.name.endswith("_baked"):
|
if image.name.endswith("_baked"):
|
||||||
bpy.data.images.remove(image, do_unlink=True)
|
bpy.data.images.remove(image, do_unlink=True)
|
||||||
|
|
||||||
for obj in bpy.data.objects:
|
for obj in bpy.context.scene.objects:
|
||||||
if obj.type == "MESH":
|
if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects:
|
||||||
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
|
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
|
||||||
if obj.TLM_ObjectProperties.tlm_postpack_object:
|
if obj.TLM_ObjectProperties.tlm_postpack_object:
|
||||||
|
|
||||||
|
@ -92,14 +119,17 @@ class TLM_CleanLightmaps(bpy.types.Operator):
|
||||||
bpy.ops.object.select_all(action='DESELECT')
|
bpy.ops.object.select_all(action='DESELECT')
|
||||||
obj.select_set(True)
|
obj.select_set(True)
|
||||||
bpy.context.view_layer.objects.active = obj
|
bpy.context.view_layer.objects.active = obj
|
||||||
#print(x)
|
|
||||||
|
|
||||||
uv_layers = obj.data.uv_layers
|
uv_layers = obj.data.uv_layers
|
||||||
|
|
||||||
|
if not obj.TLM_ObjectProperties.tlm_use_default_channel:
|
||||||
|
uv_channel = obj.TLM_ObjectProperties.tlm_uv_channel
|
||||||
|
else:
|
||||||
|
uv_channel = "UVMap_Lightmap"
|
||||||
|
|
||||||
for i in range(0, len(uv_layers)):
|
for i in range(0, len(uv_layers)):
|
||||||
if uv_layers[i].name == 'UVMap_Lightmap':
|
if uv_layers[i].name == uv_channel:
|
||||||
uv_layers.active_index = i
|
uv_layers.active_index = i
|
||||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
|
||||||
print("Lightmap shift A")
|
|
||||||
break
|
break
|
||||||
|
|
||||||
bpy.ops.object.mode_set(mode='EDIT')
|
bpy.ops.object.mode_set(mode='EDIT')
|
||||||
|
@ -111,9 +141,11 @@ class TLM_CleanLightmaps(bpy.types.Operator):
|
||||||
bpy.ops.object.mode_set(mode='OBJECT')
|
bpy.ops.object.mode_set(mode='OBJECT')
|
||||||
|
|
||||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||||
#print(obj.name + ": Active UV: " + obj.data.uv_layers[obj.data.uv_layers.active_index].name)
|
|
||||||
print("Resized for obj: " + obj.name)
|
print("Resized for obj: " + obj.name)
|
||||||
|
|
||||||
|
if "Lightmap" in obj:
|
||||||
|
del obj["Lightmap"]
|
||||||
|
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
class TLM_ExploreLightmaps(bpy.types.Operator):
|
class TLM_ExploreLightmaps(bpy.types.Operator):
|
||||||
|
@ -153,62 +185,284 @@ class TLM_ExploreLightmaps(bpy.types.Operator):
|
||||||
|
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
class TLM_EnableSelection(bpy.types.Operator):
|
class TLM_EnableSet(bpy.types.Operator):
|
||||||
"""Enable for selection"""
|
"""Enable for set"""
|
||||||
bl_idname = "tlm.enable_selection"
|
bl_idname = "tlm.enable_set"
|
||||||
bl_label = "Enable for selection"
|
bl_label = "Enable for set"
|
||||||
bl_description = "Enable for selection"
|
bl_description = "Enable for set"
|
||||||
bl_options = {'REGISTER', 'UNDO'}
|
bl_options = {'REGISTER', 'UNDO'}
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
|
|
||||||
scene = context.scene
|
scene = context.scene
|
||||||
|
|
||||||
for obj in bpy.context.selected_objects:
|
weightList = {} #ObjName : [Dimension,Weight]
|
||||||
|
max = 0
|
||||||
|
|
||||||
|
if bpy.context.scene.TLM_SceneProperties.tlm_utility_set == "Scene":
|
||||||
|
for obj in bpy.context.scene.objects:
|
||||||
|
if obj.type == "MESH":
|
||||||
|
|
||||||
|
print("Enabling for scene: " + obj.name)
|
||||||
|
|
||||||
|
bpy.context.view_layer.objects.active = obj
|
||||||
|
obj.select_set(True)
|
||||||
|
bpy.ops.object.transform_apply(location=False, rotation=True, scale=True)
|
||||||
|
|
||||||
obj.TLM_ObjectProperties.tlm_mesh_lightmap_use = True
|
obj.TLM_ObjectProperties.tlm_mesh_lightmap_use = True
|
||||||
|
|
||||||
if scene.TLM_SceneProperties.tlm_override_object_settings:
|
obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode = bpy.context.scene.TLM_SceneProperties.tlm_mesh_lightmap_unwrap_mode
|
||||||
obj.TLM_ObjectProperties.tlm_mesh_lightmap_resolution = scene.TLM_SceneProperties.tlm_mesh_lightmap_resolution
|
obj.TLM_ObjectProperties.tlm_mesh_unwrap_margin = bpy.context.scene.TLM_SceneProperties.tlm_mesh_unwrap_margin
|
||||||
obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode = scene.TLM_SceneProperties.tlm_mesh_lightmap_unwrap_mode
|
obj.TLM_ObjectProperties.tlm_postpack_object = bpy.context.scene.TLM_SceneProperties.tlm_postpack_object
|
||||||
obj.TLM_ObjectProperties.tlm_mesh_unwrap_margin = scene.TLM_SceneProperties.tlm_mesh_unwrap_margin
|
|
||||||
obj.TLM_ObjectProperties.tlm_postpack_object = scene.TLM_SceneProperties.tlm_postpack_object
|
|
||||||
|
|
||||||
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA":
|
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA":
|
||||||
obj.TLM_ObjectProperties.tlm_atlas_pointer = scene.TLM_SceneProperties.tlm_atlas_pointer
|
obj.TLM_ObjectProperties.tlm_atlas_pointer = bpy.context.scene.TLM_SceneProperties.tlm_atlas_pointer
|
||||||
|
|
||||||
obj.TLM_ObjectProperties.tlm_postatlas_pointer = scene.TLM_SceneProperties.tlm_postatlas_pointer
|
obj.TLM_ObjectProperties.tlm_postatlas_pointer = bpy.context.scene.TLM_SceneProperties.tlm_postatlas_pointer
|
||||||
|
|
||||||
|
if bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight == "Single":
|
||||||
|
obj.TLM_ObjectProperties.tlm_mesh_lightmap_resolution = scene.TLM_SceneProperties.tlm_mesh_lightmap_resolution
|
||||||
|
elif bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight == "Dimension":
|
||||||
|
obj_dimensions = obj.dimensions.x * obj.dimensions.y * obj.dimensions.z
|
||||||
|
weightList[obj.name] = [obj_dimensions, 0]
|
||||||
|
if obj_dimensions > max:
|
||||||
|
max = obj_dimensions
|
||||||
|
elif bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight == "Surface":
|
||||||
|
bm = bmesh.new()
|
||||||
|
bm.from_mesh(obj.data)
|
||||||
|
area = sum(f.calc_area() for f in bm.faces)
|
||||||
|
weightList[obj.name] = [area, 0]
|
||||||
|
if area > max:
|
||||||
|
max = area
|
||||||
|
elif bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight == "Volume":
|
||||||
|
bm = bmesh.new()
|
||||||
|
bm.from_mesh(obj.data)
|
||||||
|
volume = float( bm.calc_volume())
|
||||||
|
weightList[obj.name] = [volume, 0]
|
||||||
|
if volume > max:
|
||||||
|
max = volume
|
||||||
|
|
||||||
|
elif bpy.context.scene.TLM_SceneProperties.tlm_utility_set == "Selection":
|
||||||
|
for obj in bpy.context.selected_objects:
|
||||||
|
if obj.type == "MESH":
|
||||||
|
|
||||||
|
print("Enabling for selection: " + obj.name)
|
||||||
|
|
||||||
|
bpy.context.view_layer.objects.active = obj
|
||||||
|
obj.select_set(True)
|
||||||
|
bpy.ops.object.transform_apply(location=False, rotation=True, scale=True)
|
||||||
|
|
||||||
|
obj.TLM_ObjectProperties.tlm_mesh_lightmap_use = True
|
||||||
|
|
||||||
|
obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode = bpy.context.scene.TLM_SceneProperties.tlm_mesh_lightmap_unwrap_mode
|
||||||
|
obj.TLM_ObjectProperties.tlm_mesh_unwrap_margin = bpy.context.scene.TLM_SceneProperties.tlm_mesh_unwrap_margin
|
||||||
|
obj.TLM_ObjectProperties.tlm_postpack_object = bpy.context.scene.TLM_SceneProperties.tlm_postpack_object
|
||||||
|
|
||||||
|
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA":
|
||||||
|
obj.TLM_ObjectProperties.tlm_atlas_pointer = bpy.context.scene.TLM_SceneProperties.tlm_atlas_pointer
|
||||||
|
|
||||||
|
obj.TLM_ObjectProperties.tlm_postatlas_pointer = bpy.context.scene.TLM_SceneProperties.tlm_postatlas_pointer
|
||||||
|
|
||||||
|
if bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight == "Single":
|
||||||
|
obj.TLM_ObjectProperties.tlm_mesh_lightmap_resolution = scene.TLM_SceneProperties.tlm_mesh_lightmap_resolution
|
||||||
|
elif bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight == "Dimension":
|
||||||
|
obj_dimensions = obj.dimensions.x * obj.dimensions.y * obj.dimensions.z
|
||||||
|
weightList[obj.name] = [obj_dimensions, 0]
|
||||||
|
if obj_dimensions > max:
|
||||||
|
max = obj_dimensions
|
||||||
|
elif bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight == "Surface":
|
||||||
|
bm = bmesh.new()
|
||||||
|
bm.from_mesh(obj.data)
|
||||||
|
area = sum(f.calc_area() for f in bm.faces)
|
||||||
|
weightList[obj.name] = [area, 0]
|
||||||
|
if area > max:
|
||||||
|
max = area
|
||||||
|
elif bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight == "Volume":
|
||||||
|
bm = bmesh.new()
|
||||||
|
bm.from_mesh(obj.data)
|
||||||
|
volume = float( bm.calc_volume())
|
||||||
|
weightList[obj.name] = [volume, 0]
|
||||||
|
if volume > max:
|
||||||
|
max = volume
|
||||||
|
|
||||||
|
else: #Enabled
|
||||||
|
for obj in bpy.context.scene.objects:
|
||||||
|
if obj.type == "MESH":
|
||||||
|
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
|
||||||
|
|
||||||
|
print("Enabling for designated: " + obj.name)
|
||||||
|
|
||||||
|
bpy.context.view_layer.objects.active = obj
|
||||||
|
obj.select_set(True)
|
||||||
|
bpy.ops.object.transform_apply(location=False, rotation=True, scale=True)
|
||||||
|
|
||||||
|
obj.TLM_ObjectProperties.tlm_mesh_lightmap_use = True
|
||||||
|
|
||||||
|
obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode = bpy.context.scene.TLM_SceneProperties.tlm_mesh_lightmap_unwrap_mode
|
||||||
|
obj.TLM_ObjectProperties.tlm_mesh_unwrap_margin = bpy.context.scene.TLM_SceneProperties.tlm_mesh_unwrap_margin
|
||||||
|
obj.TLM_ObjectProperties.tlm_postpack_object = bpy.context.scene.TLM_SceneProperties.tlm_postpack_object
|
||||||
|
|
||||||
|
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA":
|
||||||
|
obj.TLM_ObjectProperties.tlm_atlas_pointer = bpy.context.scene.TLM_SceneProperties.tlm_atlas_pointer
|
||||||
|
|
||||||
|
obj.TLM_ObjectProperties.tlm_postatlas_pointer = bpy.context.scene.TLM_SceneProperties.tlm_postatlas_pointer
|
||||||
|
|
||||||
|
if bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight == "Single":
|
||||||
|
obj.TLM_ObjectProperties.tlm_mesh_lightmap_resolution = scene.TLM_SceneProperties.tlm_mesh_lightmap_resolution
|
||||||
|
elif bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight == "Dimension":
|
||||||
|
obj_dimensions = obj.dimensions.x * obj.dimensions.y * obj.dimensions.z
|
||||||
|
weightList[obj.name] = [obj_dimensions, 0]
|
||||||
|
if obj_dimensions > max:
|
||||||
|
max = obj_dimensions
|
||||||
|
elif bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight == "Surface":
|
||||||
|
bm = bmesh.new()
|
||||||
|
bm.from_mesh(obj.data)
|
||||||
|
area = sum(f.calc_area() for f in bm.faces)
|
||||||
|
weightList[obj.name] = [area, 0]
|
||||||
|
if area > max:
|
||||||
|
max = area
|
||||||
|
elif bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight == "Volume":
|
||||||
|
bm = bmesh.new()
|
||||||
|
bm.from_mesh(obj.data)
|
||||||
|
volume = float( bm.calc_volume())
|
||||||
|
weightList[obj.name] = [volume, 0]
|
||||||
|
if volume > max:
|
||||||
|
max = volume
|
||||||
|
|
||||||
|
if bpy.context.scene.TLM_SceneProperties.tlm_utility_set == "Scene":
|
||||||
|
for obj in bpy.context.scene.objects:
|
||||||
|
if obj.type == "MESH":
|
||||||
|
|
||||||
|
if bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight != "Single":
|
||||||
|
for key in weightList:
|
||||||
|
weightList[obj.name][1] = weightList[obj.name][0] / max
|
||||||
|
a = setObjectLightmapByWeight(int(bpy.context.scene.TLM_SceneProperties.tlm_resolution_min), int(bpy.context.scene.TLM_SceneProperties.tlm_resolution_max), weightList[obj.name][1])
|
||||||
|
print(str(a) + "/" + str(weightList[obj.name][1]))
|
||||||
|
print("Scale: " + str(weightList[obj.name][0]))
|
||||||
|
print("Obj: " + obj.name)
|
||||||
|
obj.TLM_ObjectProperties.tlm_mesh_lightmap_resolution = str(a)
|
||||||
|
|
||||||
|
elif bpy.context.scene.TLM_SceneProperties.tlm_utility_set == "Selection":
|
||||||
|
for obj in bpy.context.selected_objects:
|
||||||
|
if obj.type == "MESH":
|
||||||
|
|
||||||
|
if bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight != "Single":
|
||||||
|
for key in weightList:
|
||||||
|
weightList[obj.name][1] = weightList[obj.name][0] / max
|
||||||
|
a = setObjectLightmapByWeight(int(bpy.context.scene.TLM_SceneProperties.tlm_resolution_min), int(bpy.context.scene.TLM_SceneProperties.tlm_resolution_max), weightList[obj.name][1])
|
||||||
|
print(str(a) + "/" + str(weightList[obj.name][1]))
|
||||||
|
print("Scale: " + str(weightList[obj.name][0]))
|
||||||
|
print("Obj: " + obj.name)
|
||||||
|
obj.TLM_ObjectProperties.tlm_mesh_lightmap_resolution = str(a)
|
||||||
|
|
||||||
|
|
||||||
|
else: #Enabled
|
||||||
|
for obj in bpy.context.scene.objects:
|
||||||
|
if obj.type == "MESH":
|
||||||
|
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
|
||||||
|
|
||||||
|
if bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight != "Single":
|
||||||
|
for key in weightList:
|
||||||
|
weightList[obj.name][1] = weightList[obj.name][0] / max
|
||||||
|
a = setObjectLightmapByWeight(int(bpy.context.scene.TLM_SceneProperties.tlm_resolution_min), int(bpy.context.scene.TLM_SceneProperties.tlm_resolution_max), weightList[obj.name][1])
|
||||||
|
print(str(a) + "/" + str(weightList[obj.name][1]))
|
||||||
|
print("Scale: " + str(weightList[obj.name][0]))
|
||||||
|
print("Obj: " + obj.name)
|
||||||
|
print("")
|
||||||
|
obj.TLM_ObjectProperties.tlm_mesh_lightmap_resolution = str(a)
|
||||||
|
|
||||||
return{'FINISHED'}
|
return{'FINISHED'}
|
||||||
|
|
||||||
class TLM_DisableSelection(bpy.types.Operator):
|
class TLM_DisableSelection(bpy.types.Operator):
|
||||||
"""Disable for selection"""
|
"""Disable for set"""
|
||||||
bl_idname = "tlm.disable_selection"
|
bl_idname = "tlm.disable_selection"
|
||||||
bl_label = "Disable for selection"
|
bl_label = "Disable for set"
|
||||||
bl_description = "Disable for selection"
|
bl_description = "Disable for selection"
|
||||||
bl_options = {'REGISTER', 'UNDO'}
|
bl_options = {'REGISTER', 'UNDO'}
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
|
|
||||||
for obj in bpy.context.selected_objects:
|
scene = context.scene
|
||||||
|
|
||||||
|
weightList = {} #ObjName : [Dimension,Weight]
|
||||||
|
max = 0
|
||||||
|
|
||||||
|
if bpy.context.scene.TLM_SceneProperties.tlm_utility_set == "Scene":
|
||||||
|
for obj in bpy.context.scene.objects:
|
||||||
|
if obj.type == "MESH":
|
||||||
|
|
||||||
obj.TLM_ObjectProperties.tlm_mesh_lightmap_use = False
|
obj.TLM_ObjectProperties.tlm_mesh_lightmap_use = False
|
||||||
|
|
||||||
|
elif bpy.context.scene.TLM_SceneProperties.tlm_utility_set == "Selection":
|
||||||
|
for obj in bpy.context.selected_objects:
|
||||||
|
if obj.type == "MESH":
|
||||||
|
|
||||||
|
obj.TLM_ObjectProperties.tlm_mesh_lightmap_use = False
|
||||||
|
|
||||||
|
|
||||||
|
else: #Enabled
|
||||||
|
for obj in bpy.context.scene.objects:
|
||||||
|
if obj.type == "MESH":
|
||||||
|
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
|
||||||
|
|
||||||
|
obj.TLM_ObjectProperties.tlm_mesh_lightmap_use = False
|
||||||
|
|
||||||
|
|
||||||
return{'FINISHED'}
|
return{'FINISHED'}
|
||||||
|
|
||||||
class TLM_RemoveLightmapUV(bpy.types.Operator):
|
class TLM_RemoveLightmapUV(bpy.types.Operator):
|
||||||
"""Remove Lightmap UV for selection"""
|
"""Remove Lightmap UV for set"""
|
||||||
bl_idname = "tlm.remove_uv_selection"
|
bl_idname = "tlm.remove_uv_selection"
|
||||||
bl_label = "Remove Lightmap UV"
|
bl_label = "Remove Lightmap UV"
|
||||||
bl_description = "Remove Lightmap UV for selection"
|
bl_description = "Remove Lightmap UV for set"
|
||||||
bl_options = {'REGISTER', 'UNDO'}
|
bl_options = {'REGISTER', 'UNDO'}
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
|
|
||||||
for obj in bpy.context.selected_objects:
|
if bpy.context.scene.TLM_SceneProperties.tlm_utility_set == "Scene":
|
||||||
|
for obj in bpy.context.scene.objects:
|
||||||
if obj.type == "MESH":
|
if obj.type == "MESH":
|
||||||
|
|
||||||
uv_layers = obj.data.uv_layers
|
uv_layers = obj.data.uv_layers
|
||||||
|
|
||||||
|
if not obj.TLM_ObjectProperties.tlm_use_default_channel:
|
||||||
|
uv_channel = obj.TLM_ObjectProperties.tlm_uv_channel
|
||||||
|
else:
|
||||||
|
uv_channel = "UVMap_Lightmap"
|
||||||
|
|
||||||
for uvlayer in uv_layers:
|
for uvlayer in uv_layers:
|
||||||
if uvlayer.name == "UVMap_Lightmap":
|
if uvlayer.name == uv_channel:
|
||||||
|
uv_layers.remove(uvlayer)
|
||||||
|
|
||||||
|
elif bpy.context.scene.TLM_SceneProperties.tlm_utility_set == "Selection":
|
||||||
|
for obj in bpy.context.selected_objects:
|
||||||
|
if obj.type == "MESH":
|
||||||
|
|
||||||
|
uv_layers = obj.data.uv_layers
|
||||||
|
|
||||||
|
if not obj.TLM_ObjectProperties.tlm_use_default_channel:
|
||||||
|
uv_channel = obj.TLM_ObjectProperties.tlm_uv_channel
|
||||||
|
else:
|
||||||
|
uv_channel = "UVMap_Lightmap"
|
||||||
|
|
||||||
|
for uvlayer in uv_layers:
|
||||||
|
if uvlayer.name == uv_channel:
|
||||||
|
uv_layers.remove(uvlayer)
|
||||||
|
|
||||||
|
else: #Enabled
|
||||||
|
for obj in bpy.context.scene.objects:
|
||||||
|
if obj.type == "MESH":
|
||||||
|
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
|
||||||
|
|
||||||
|
uv_layers = obj.data.uv_layers
|
||||||
|
|
||||||
|
if not obj.TLM_ObjectProperties.tlm_use_default_channel:
|
||||||
|
uv_channel = obj.TLM_ObjectProperties.tlm_uv_channel
|
||||||
|
else:
|
||||||
|
uv_channel = "UVMap_Lightmap"
|
||||||
|
|
||||||
|
for uvlayer in uv_layers:
|
||||||
|
if uvlayer.name == uv_channel:
|
||||||
uv_layers.remove(uvlayer)
|
uv_layers.remove(uvlayer)
|
||||||
|
|
||||||
return{'FINISHED'}
|
return{'FINISHED'}
|
||||||
|
@ -222,8 +476,8 @@ class TLM_SelectLightmapped(bpy.types.Operator):
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
|
|
||||||
for obj in bpy.data.objects:
|
for obj in bpy.context.scene.objects:
|
||||||
if obj.type == "MESH":
|
if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects:
|
||||||
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
|
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
|
||||||
|
|
||||||
obj.select_set(True)
|
obj.select_set(True)
|
||||||
|
@ -278,7 +532,7 @@ class TLM_AtlastListDeleteItem(bpy.types.Operator):
|
||||||
list = scene.TLM_AtlasList
|
list = scene.TLM_AtlasList
|
||||||
index = scene.TLM_AtlasListItem
|
index = scene.TLM_AtlasListItem
|
||||||
|
|
||||||
for obj in bpy.data.objects:
|
for obj in bpy.context.scene.objects:
|
||||||
|
|
||||||
atlasName = scene.TLM_AtlasList[index].name
|
atlasName = scene.TLM_AtlasList[index].name
|
||||||
|
|
||||||
|
@ -310,7 +564,7 @@ class TLM_PostAtlastListDeleteItem(bpy.types.Operator):
|
||||||
list = scene.TLM_PostAtlasList
|
list = scene.TLM_PostAtlasList
|
||||||
index = scene.TLM_PostAtlasListItem
|
index = scene.TLM_PostAtlasListItem
|
||||||
|
|
||||||
for obj in bpy.data.objects:
|
for obj in bpy.context.scene.objects:
|
||||||
|
|
||||||
atlasName = scene.TLM_PostAtlasList[index].name
|
atlasName = scene.TLM_PostAtlasList[index].name
|
||||||
|
|
||||||
|
@ -437,7 +691,7 @@ class TLM_BuildEnvironmentProbes(bpy.types.Operator):
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
|
|
||||||
for obj in bpy.data.objects:
|
for obj in bpy.context.scene.objects:
|
||||||
|
|
||||||
if obj.type == "LIGHT_PROBE":
|
if obj.type == "LIGHT_PROBE":
|
||||||
if obj.data.type == "CUBEMAP":
|
if obj.data.type == "CUBEMAP":
|
||||||
|
@ -500,7 +754,7 @@ class TLM_BuildEnvironmentProbes(bpy.types.Operator):
|
||||||
cam.rotation_euler = positions[val]
|
cam.rotation_euler = positions[val]
|
||||||
|
|
||||||
filename = os.path.join(directory, val) + "_" + camobj_name + ".hdr"
|
filename = os.path.join(directory, val) + "_" + camobj_name + ".hdr"
|
||||||
bpy.data.scenes['Scene'].render.filepath = filename
|
bpy.context.scene.render.filepath = filename
|
||||||
print("Writing out: " + val)
|
print("Writing out: " + val)
|
||||||
bpy.ops.render.render(write_still=True)
|
bpy.ops.render.render(write_still=True)
|
||||||
|
|
||||||
|
@ -642,7 +896,7 @@ class TLM_BuildEnvironmentProbes(bpy.types.Operator):
|
||||||
|
|
||||||
subprocess.call([envpipe3], shell=True)
|
subprocess.call([envpipe3], shell=True)
|
||||||
|
|
||||||
for obj in bpy.data.objects:
|
for obj in bpy.context.scene.objects:
|
||||||
obj.select_set(False)
|
obj.select_set(False)
|
||||||
|
|
||||||
cam_obj.select_set(True)
|
cam_obj.select_set(True)
|
||||||
|
@ -686,7 +940,92 @@ class TLM_MergeAdjacentActors(bpy.types.Operator):
|
||||||
|
|
||||||
scene = context.scene
|
scene = context.scene
|
||||||
|
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
class TLM_PrepareUVMaps(bpy.types.Operator):
|
||||||
|
bl_idname = "tlm.prepare_uvmaps"
|
||||||
|
bl_label = "Prepare UV maps"
|
||||||
|
bl_description = "Prepare UV lightmaps for selected objects"
|
||||||
|
bl_options = {'REGISTER', 'UNDO'}
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
|
||||||
|
scene = context.scene
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
class TLM_LoadLightmaps(bpy.types.Operator):
|
||||||
|
bl_idname = "tlm.load_lightmaps"
|
||||||
|
bl_label = "Load Lightmaps"
|
||||||
|
bl_description = "Load lightmaps from selected folder"
|
||||||
|
bl_options = {'REGISTER', 'UNDO'}
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
|
||||||
|
scene = context.scene
|
||||||
|
|
||||||
|
utility.transfer_load()
|
||||||
|
|
||||||
|
build.finish_assemble()
|
||||||
|
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
class TLM_ToggleTexelDensity(bpy.types.Operator):
|
||||||
|
bl_idname = "tlm.toggle_texel_density"
|
||||||
|
bl_label = "Toggle Texel Density"
|
||||||
|
bl_description = "Toggle visualize lightmap texel density for selected objects"
|
||||||
|
bl_options = {'REGISTER', 'UNDO'}
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
|
||||||
|
scene = context.scene
|
||||||
|
|
||||||
|
for obj in bpy.context.selected_objects:
|
||||||
|
if obj.type == "MESH":
|
||||||
|
uv_layers = obj.data.uv_layers
|
||||||
|
|
||||||
|
#if the object has a td_vis in the uv maps, toggle off
|
||||||
|
#else toggle on
|
||||||
|
|
||||||
|
if obj.TLM_ObjectProperties.tlm_use_default_channel:
|
||||||
|
|
||||||
|
for i in range(0, len(uv_layers)):
|
||||||
|
if uv_layers[i].name == 'UVMap_Lightmap':
|
||||||
|
uv_layers.active_index = i
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
|
||||||
|
for i in range(0, len(uv_layers)):
|
||||||
|
if uv_layers[i].name == obj.TLM_ObjectProperties.tlm_uv_channel:
|
||||||
|
uv_layers.active_index = i
|
||||||
|
break
|
||||||
|
|
||||||
|
#filepath = r"C:\path\to\image.png"
|
||||||
|
|
||||||
|
#img = bpy.data.images.load(filepath)
|
||||||
|
|
||||||
|
for area in bpy.context.screen.areas:
|
||||||
|
if area.type == 'VIEW_3D':
|
||||||
|
space_data = area.spaces.active
|
||||||
|
bpy.ops.screen.area_dupli('INVOKE_DEFAULT')
|
||||||
|
new_window = context.window_manager.windows[-1]
|
||||||
|
|
||||||
|
area = new_window.screen.areas[-1]
|
||||||
|
area.type = 'VIEW_3D'
|
||||||
|
#bg = space_data.background_images.new()
|
||||||
|
print(bpy.context.object)
|
||||||
|
bpy.ops.object.bake_td_uv_to_vc()
|
||||||
|
|
||||||
|
#bg.image = img
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
#set active uv_layer to
|
||||||
|
|
||||||
|
|
||||||
|
print("TLM_Viz_Toggle")
|
||||||
|
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
@ -699,6 +1038,3 @@ def TLM_HalfResolution():
|
||||||
|
|
||||||
def TLM_DivideLMGroups():
|
def TLM_DivideLMGroups():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def TLM_LoadFromFolder():
|
|
||||||
pass
|
|
0
blender/arm/lightmapper/panels/__init__.py
Normal file
0
blender/arm/lightmapper/panels/__init__.py
Normal file
66
blender/arm/lightmapper/panels/image.py
Normal file
66
blender/arm/lightmapper/panels/image.py
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
import bpy, os, math, importlib
|
||||||
|
|
||||||
|
from bpy.types import Menu, Operator, Panel, UIList
|
||||||
|
|
||||||
|
from bpy.props import (
|
||||||
|
StringProperty,
|
||||||
|
BoolProperty,
|
||||||
|
IntProperty,
|
||||||
|
FloatProperty,
|
||||||
|
FloatVectorProperty,
|
||||||
|
EnumProperty,
|
||||||
|
PointerProperty,
|
||||||
|
)
|
||||||
|
|
||||||
|
class TLM_PT_Imagetools(bpy.types.Panel):
|
||||||
|
bl_label = "TLM Imagetools"
|
||||||
|
bl_space_type = "IMAGE_EDITOR"
|
||||||
|
bl_region_type = 'UI'
|
||||||
|
bl_category = "TLM Imagetools"
|
||||||
|
|
||||||
|
def draw_header(self, _):
|
||||||
|
layout = self.layout
|
||||||
|
row = layout.row(align=True)
|
||||||
|
row.label(text ="Image Tools")
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
|
||||||
|
activeImg = None
|
||||||
|
|
||||||
|
for area in bpy.context.screen.areas:
|
||||||
|
if area.type == 'IMAGE_EDITOR':
|
||||||
|
activeImg = area.spaces.active.image
|
||||||
|
|
||||||
|
if activeImg is not None and activeImg.name != "Render Result" and activeImg.name != "Viewer Node":
|
||||||
|
|
||||||
|
cv2 = importlib.util.find_spec("cv2")
|
||||||
|
|
||||||
|
if cv2 is None:
|
||||||
|
row = layout.row(align=True)
|
||||||
|
row.label(text ="OpenCV not installed.")
|
||||||
|
else:
|
||||||
|
|
||||||
|
row = layout.row(align=True)
|
||||||
|
row.label(text ="Method")
|
||||||
|
row = layout.row(align=True)
|
||||||
|
row.prop(activeImg.TLM_ImageProperties, "tlm_image_scale_engine")
|
||||||
|
row = layout.row(align=True)
|
||||||
|
row.prop(activeImg.TLM_ImageProperties, "tlm_image_cache_switch")
|
||||||
|
row = layout.row(align=True)
|
||||||
|
row.operator("tlm.image_upscale")
|
||||||
|
if activeImg.TLM_ImageProperties.tlm_image_cache_switch:
|
||||||
|
row = layout.row(align=True)
|
||||||
|
row.label(text ="Switch up.")
|
||||||
|
row = layout.row(align=True)
|
||||||
|
row.operator("tlm.image_downscale")
|
||||||
|
if activeImg.TLM_ImageProperties.tlm_image_cache_switch:
|
||||||
|
row = layout.row(align=True)
|
||||||
|
row.label(text ="Switch down.")
|
||||||
|
if activeImg.TLM_ImageProperties.tlm_image_scale_engine == "OpenCV":
|
||||||
|
row = layout.row(align=True)
|
||||||
|
row.prop(activeImg.TLM_ImageProperties, "tlm_image_scale_method")
|
||||||
|
|
||||||
|
else:
|
||||||
|
row = layout.row(align=True)
|
||||||
|
row.label(text ="Select an image")
|
17
blender/arm/lightmapper/panels/light.py
Normal file
17
blender/arm/lightmapper/panels/light.py
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import bpy
|
||||||
|
from bpy.props import *
|
||||||
|
from bpy.types import Menu, Panel
|
||||||
|
|
||||||
|
class TLM_PT_LightMenu(bpy.types.Panel):
|
||||||
|
bl_label = "The Lightmapper"
|
||||||
|
bl_space_type = "PROPERTIES"
|
||||||
|
bl_region_type = "WINDOW"
|
||||||
|
bl_context = "light"
|
||||||
|
bl_options = {'DEFAULT_CLOSED'}
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
scene = context.scene
|
||||||
|
obj = bpy.context.object
|
||||||
|
layout.use_property_split = True
|
||||||
|
layout.use_property_decorate = False
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue