Weiter am Wettersystem gearbeitet, ordentliche Wolken und Regen etc ergänzt
This commit is contained in:
23
blight-assets/src/main/resources/MatDefs/CloudDome.j3md
Normal file
23
blight-assets/src/main/resources/MatDefs/CloudDome.j3md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
MaterialDef CloudDome {
|
||||||
|
|
||||||
|
MaterialParameters {
|
||||||
|
Vector2 CloudOffset : 0.0 0.0
|
||||||
|
Float CloudCover : 0.0
|
||||||
|
Color CloudColor : 1.0 1.0 1.0 1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
Technique {
|
||||||
|
VertexShader GLSL150 : Shaders/skies/clouds/CloudDome.vert
|
||||||
|
FragmentShader GLSL150 : Shaders/skies/clouds/CloudDome.frag
|
||||||
|
|
||||||
|
WorldParameters {
|
||||||
|
WorldViewProjectionMatrix
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderState {
|
||||||
|
Blend Alpha
|
||||||
|
DepthWrite Off
|
||||||
|
FaceCull Front
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ MaterialDef Fern {
|
|||||||
Float WindStrength : 0.15
|
Float WindStrength : 0.15
|
||||||
Float WindSpeed : 0.6
|
Float WindSpeed : 0.6
|
||||||
Vector2 WindDir : 0.0 1.0
|
Vector2 WindDir : 0.0 1.0
|
||||||
|
Float Wetness : 0.0
|
||||||
Texture2D DiffuseMap
|
Texture2D DiffuseMap
|
||||||
Boolean HasDiffuseMap : false
|
Boolean HasDiffuseMap : false
|
||||||
Texture2D NormalMap -LINEAR
|
Texture2D NormalMap -LINEAR
|
||||||
|
|||||||
@@ -4,10 +4,12 @@ MaterialDef Grass {
|
|||||||
Color Color (Color) : 1.0 1.0 1.0 1.0
|
Color Color (Color) : 1.0 1.0 1.0 1.0
|
||||||
Texture2D ColorMap
|
Texture2D ColorMap
|
||||||
Texture2D NormalMap
|
Texture2D NormalMap
|
||||||
Float WindSpeed : 0.5
|
Float WindSpeed : 0.5
|
||||||
Float WindStrength : 0.12
|
Float WindStrength : 0.12
|
||||||
|
Vector2 WindDir : 0.0 1.0
|
||||||
Vector3 SunDir : 0.55 0.80 0.35
|
Vector3 SunDir : 0.55 0.80 0.35
|
||||||
Color SunColor : 1.0 1.0 0.95 1.0
|
Color SunColor : 1.0 1.0 0.95 1.0
|
||||||
|
Float Wetness : 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
Technique {
|
Technique {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ MaterialDef GrassSeed {
|
|||||||
Vector2 WindDir : 0.0 1.0
|
Vector2 WindDir : 0.0 1.0
|
||||||
Vector3 SunDir : 0.35 0.8 0.45
|
Vector3 SunDir : 0.35 0.8 0.45
|
||||||
Color SunColor : 0.95 0.90 0.75 1.0
|
Color SunColor : 0.95 0.90 0.75 1.0
|
||||||
|
Float Wetness : 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
Technique {
|
Technique {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ MaterialDef GrassVertex {
|
|||||||
Vector2 WindDir : 0.0 1.0
|
Vector2 WindDir : 0.0 1.0
|
||||||
Vector3 SunDir : 0.35 0.8 0.45
|
Vector3 SunDir : 0.35 0.8 0.45
|
||||||
Color SunColor : 0.95 0.90 0.75 1.0
|
Color SunColor : 0.95 0.90 0.75 1.0
|
||||||
|
Float Wetness : 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
Technique {
|
Technique {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ MaterialDef TerrainArray {
|
|||||||
Vector3 LightDir : 0.6 -0.8 0.4
|
Vector3 LightDir : 0.6 -0.8 0.4
|
||||||
Vector3 SunColor : 0.68 0.65 0.60
|
Vector3 SunColor : 0.68 0.65 0.60
|
||||||
Vector3 AmbientColor : 0.08 0.10 0.16
|
Vector3 AmbientColor : 0.08 0.10 0.16
|
||||||
|
Float Wetness : 0.0
|
||||||
Boolean DebugNoLight
|
Boolean DebugNoLight
|
||||||
Boolean DebugSlot0Only
|
Boolean DebugSlot0Only
|
||||||
Texture2D DebugDirectTex
|
Texture2D DebugDirectTex
|
||||||
@@ -24,6 +25,7 @@ MaterialDef TerrainArray {
|
|||||||
WorldViewProjectionMatrix
|
WorldViewProjectionMatrix
|
||||||
WorldMatrix
|
WorldMatrix
|
||||||
ViewProjectionMatrix
|
ViewProjectionMatrix
|
||||||
|
CameraPosition
|
||||||
}
|
}
|
||||||
|
|
||||||
Defines {
|
Defines {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ MaterialDef TreeLeaf {
|
|||||||
Float WindStrength : 0.30
|
Float WindStrength : 0.30
|
||||||
Float WindSpeed : 0.7
|
Float WindSpeed : 0.7
|
||||||
Vector2 WindDir : 0.0 1.0
|
Vector2 WindDir : 0.0 1.0
|
||||||
|
Float Wetness : 0.0
|
||||||
Texture2D LeafMap
|
Texture2D LeafMap
|
||||||
Boolean HasLeafMap : false
|
Boolean HasLeafMap : false
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ uniform sampler2D m_DiffuseMap;
|
|||||||
uniform bool m_HasDiffuseMap;
|
uniform bool m_HasDiffuseMap;
|
||||||
uniform sampler2D m_NormalMap;
|
uniform sampler2D m_NormalMap;
|
||||||
uniform bool m_HasNormalMap;
|
uniform bool m_HasNormalMap;
|
||||||
|
uniform float m_Wetness;
|
||||||
|
|
||||||
in vec2 texCoord;
|
in vec2 texCoord;
|
||||||
in vec3 vNormal;
|
in vec3 vNormal;
|
||||||
@@ -38,5 +39,7 @@ void main() {
|
|||||||
diff = max(diff, max(0.0, dot(-N, sun)) * 0.5);
|
diff = max(diff, max(0.0, dot(-N, sun)) * 0.5);
|
||||||
float lit = 0.45 + diff * 0.55;
|
float lit = 0.45 + diff * 0.55;
|
||||||
|
|
||||||
gl_FragColor = vec4(baseColor * lit, 1.0);
|
baseColor *= (1.0 - 0.18 * m_Wetness);
|
||||||
|
float shine = pow(diff, 12.0) * m_Wetness * 0.25;
|
||||||
|
gl_FragColor = vec4(baseColor * lit + vec3(shine), 1.0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
uniform vec4 m_Color;
|
uniform vec4 m_Color;
|
||||||
uniform vec3 m_SunDir;
|
uniform vec3 m_SunDir;
|
||||||
uniform vec4 m_SunColor;
|
uniform vec4 m_SunColor;
|
||||||
uniform vec4 g_AmbientLightColor;
|
uniform vec4 g_AmbientLightColor;
|
||||||
|
uniform float m_Wetness;
|
||||||
|
|
||||||
#ifdef HAS_COLORMAP
|
#ifdef HAS_COLORMAP
|
||||||
uniform sampler2D m_ColorMap;
|
uniform sampler2D m_ColorMap;
|
||||||
@@ -35,7 +36,8 @@ void main() {
|
|||||||
// Beleuchtung: Sonne (two-sided via abs für Grashalme) + Ambient
|
// Beleuchtung: Sonne (two-sided via abs für Grashalme) + Ambient
|
||||||
float diffuse = abs(dot(n, normalize(m_SunDir)));
|
float diffuse = abs(dot(n, normalize(m_SunDir)));
|
||||||
vec3 lit = g_AmbientLightColor.rgb + m_SunColor.rgb * diffuse;
|
vec3 lit = g_AmbientLightColor.rgb + m_SunColor.rgb * diffuse;
|
||||||
|
color.rgb *= (1.0 - 0.18 * m_Wetness);
|
||||||
color.rgb *= lit;
|
color.rgb *= lit;
|
||||||
|
float shine = pow(diffuse, 12.0) * m_Wetness * 0.25;
|
||||||
outFragColor = color;
|
outFragColor = vec4(color.rgb + vec3(shine), color.a);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ uniform float g_Time;
|
|||||||
|
|
||||||
uniform float m_WindSpeed;
|
uniform float m_WindSpeed;
|
||||||
uniform float m_WindStrength;
|
uniform float m_WindStrength;
|
||||||
|
uniform vec2 m_WindDir;
|
||||||
|
|
||||||
in vec3 inPosition;
|
in vec3 inPosition;
|
||||||
in vec2 inTexCoord;
|
in vec2 inTexCoord;
|
||||||
@@ -26,17 +27,20 @@ void main() {
|
|||||||
vec2 worldXZ = (g_WorldMatrix * pos).xz;
|
vec2 worldXZ = (g_WorldMatrix * pos).xz;
|
||||||
float t = g_Time * m_WindSpeed;
|
float t = g_Time * m_WindSpeed;
|
||||||
|
|
||||||
|
vec2 windN = (dot(m_WindDir, m_WindDir) > 0.001) ? normalize(m_WindDir) : vec2(0.0, 1.0);
|
||||||
|
float wavePhase = dot(worldXZ, windN);
|
||||||
|
|
||||||
// Zwei überlagerte Sinuswellen für organische Bewegung
|
// Zwei überlagerte Sinuswellen für organische Bewegung
|
||||||
float sway = sin(t * 2.1 + worldXZ.x * 0.08 + worldXZ.y * 0.06) * 0.6
|
float sway = sin(t * 2.1 + wavePhase * 0.08) * 0.6
|
||||||
+ sin(t * 1.4 - worldXZ.x * 0.05 + worldXZ.y * 0.09) * 0.4;
|
+ sin(t * 1.4 + wavePhase * 0.06) * 0.4;
|
||||||
|
|
||||||
// Mindest-Stärke damit bei wenig Wind noch Bewegung sichtbar ist
|
// Mindest-Stärke damit bei wenig Wind noch Bewegung sichtbar ist
|
||||||
float effectiveStrength = max(m_WindStrength, 0.05);
|
float effectiveStrength = max(m_WindStrength, 0.05);
|
||||||
|
|
||||||
// Quadratische Gewichtung: Spitze bewegt sich mehr als Basis
|
// Quadratische Gewichtung: Spitze bewegt sich mehr als Basis
|
||||||
float bend = sway * effectiveStrength * inTexCoord.y * inTexCoord.y;
|
float bend = sway * effectiveStrength * inTexCoord.y * inTexCoord.y;
|
||||||
pos.x += bend;
|
pos.x += windN.x * bend;
|
||||||
pos.z += bend * 0.3;
|
pos.z += windN.y * bend;
|
||||||
// Y-Kompression: Halm neigt sich statt zu strecken → verhindert visuelles Breiterwerden
|
// Y-Kompression: Halm neigt sich statt zu strecken → verhindert visuelles Breiterwerden
|
||||||
pos.y -= bend * bend * 0.5;
|
pos.y -= bend * bend * 0.5;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ uniform sampler2D m_ColorMap;
|
|||||||
uniform vec4 g_AmbientLightColor;
|
uniform vec4 g_AmbientLightColor;
|
||||||
uniform vec3 m_SunDir;
|
uniform vec3 m_SunDir;
|
||||||
uniform vec4 m_SunColor;
|
uniform vec4 m_SunColor;
|
||||||
|
uniform float m_Wetness;
|
||||||
|
|
||||||
in vec2 varUV;
|
in vec2 varUV;
|
||||||
|
|
||||||
@@ -14,6 +15,7 @@ void main() {
|
|||||||
// Wrapped diffuse – kein Normalvektor nötig für Billboard-Quads
|
// Wrapped diffuse – kein Normalvektor nötig für Billboard-Quads
|
||||||
float light = 0.5 + 0.5 * max(dot(m_SunDir, vec3(0.0, 1.0, 0.0)), 0.0);
|
float light = 0.5 + 0.5 * max(dot(m_SunDir, vec3(0.0, 1.0, 0.0)), 0.0);
|
||||||
vec3 ambient = g_AmbientLightColor.rgb * c.rgb;
|
vec3 ambient = g_AmbientLightColor.rgb * c.rgb;
|
||||||
vec3 diffuse = m_SunColor.rgb * c.rgb * light;
|
vec3 diffuse = m_SunColor.rgb * c.rgb * (1.0 - 0.18 * m_Wetness) * light;
|
||||||
outFragColor = vec4(min(ambient + diffuse, c.rgb * 1.5), c.a);
|
float shine = pow(light, 12.0) * m_Wetness * 0.25;
|
||||||
|
outFragColor = vec4(min(ambient + diffuse, c.rgb * 1.5) + vec3(shine), c.a);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
uniform vec4 g_AmbientLightColor;
|
uniform vec4 g_AmbientLightColor;
|
||||||
|
|
||||||
uniform vec3 m_SunDir; // Richtung von der Fläche zur Sonne (world-space, normiert)
|
uniform vec3 m_SunDir; // Richtung von der Fläche zur Sonne (world-space, normiert)
|
||||||
uniform vec4 m_SunColor; // Sonnenfarbe RGB
|
uniform vec4 m_SunColor; // Sonnenfarbe RGB
|
||||||
|
uniform float m_Wetness;
|
||||||
|
|
||||||
in vec4 varColor;
|
in vec4 varColor;
|
||||||
in vec3 varNormal;
|
in vec3 varNormal;
|
||||||
@@ -23,12 +24,13 @@ void main() {
|
|||||||
|
|
||||||
float light = nDotL + sss;
|
float light = nDotL + sss;
|
||||||
|
|
||||||
vec3 ambient = g_AmbientLightColor.rgb * varColor.rgb;
|
vec3 col = varColor.rgb * (1.0 - 0.18 * m_Wetness);
|
||||||
vec3 diffuse = m_SunColor.rgb * varColor.rgb * light;
|
vec3 ambient = g_AmbientLightColor.rgb * col;
|
||||||
|
vec3 diffuse = m_SunColor.rgb * col * light;
|
||||||
|
|
||||||
// Farbe nicht über die Vertex-Color-Helligkeit hinaus aufhellen
|
// Farbe nicht über die Vertex-Color-Helligkeit hinaus aufhellen
|
||||||
vec3 result = ambient + diffuse;
|
vec3 result = ambient + diffuse;
|
||||||
result = min(result, varColor.rgb * 1.5);
|
result = min(result, col * 1.5);
|
||||||
|
float shine = pow(max(nDotL, 0.0), 12.0) * m_Wetness * 0.25;
|
||||||
outFragColor = vec4(result, 1.0);
|
outFragColor = vec4(result + vec3(shine), 1.0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,9 +16,11 @@ uniform sampler2D m_AlphaMap_2;
|
|||||||
uniform sampler2DArray m_NormalArray;
|
uniform sampler2DArray m_NormalArray;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
uniform vec3 m_LightDir;
|
uniform vec3 m_LightDir;
|
||||||
uniform vec3 m_SunColor;
|
uniform vec3 m_SunColor;
|
||||||
uniform vec3 m_AmbientColor;
|
uniform vec3 m_AmbientColor;
|
||||||
|
uniform float m_Wetness;
|
||||||
|
uniform vec3 g_CameraPosition;
|
||||||
|
|
||||||
in vec2 vSplatUV;
|
in vec2 vSplatUV;
|
||||||
in vec3 vWorldPos;
|
in vec3 vWorldPos;
|
||||||
@@ -106,14 +108,20 @@ void main() {
|
|||||||
N = normalize(pertN);
|
N = normalize(pertN);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
vec3 lightDir = normalize(m_LightDir);
|
vec3 lightDir = normalize(m_LightDir);
|
||||||
float diff = max(dot(N, lightDir), 0.0);
|
float diff = max(dot(N, lightDir), 0.0);
|
||||||
vec3 light = m_AmbientColor + m_SunColor * diff;
|
vec3 light = m_AmbientColor + m_SunColor * diff;
|
||||||
|
|
||||||
|
// Nass-Effekt: Oberfläche abdunkeln + Blinn-Phong Specular Schimmer
|
||||||
|
col.rgb *= (1.0 - 0.20 * m_Wetness);
|
||||||
|
vec3 viewDir = normalize(g_CameraPosition - vWorldPos);
|
||||||
|
vec3 H = normalize(lightDir + viewDir);
|
||||||
|
float spec = pow(max(dot(N, H), 0.0), 80.0) * m_Wetness;
|
||||||
|
|
||||||
const float BRIGHTNESS = 0.80;
|
const float BRIGHTNESS = 0.80;
|
||||||
#ifdef DEBUG_NO_LIGHT
|
#ifdef DEBUG_NO_LIGHT
|
||||||
outColor = vec4(col.rgb * BRIGHTNESS, col.a);
|
outColor = vec4(col.rgb * BRIGHTNESS, col.a);
|
||||||
#else
|
#else
|
||||||
outColor = vec4(col.rgb * light * BRIGHTNESS, col.a);
|
outColor = vec4(col.rgb * light * BRIGHTNESS + vec3(spec * 0.35), col.a);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ uniform bool m_HasBarkMap;
|
|||||||
uniform vec3 m_LightDir;
|
uniform vec3 m_LightDir;
|
||||||
uniform vec3 m_SunColor;
|
uniform vec3 m_SunColor;
|
||||||
uniform vec3 m_AmbientColor;
|
uniform vec3 m_AmbientColor;
|
||||||
|
uniform float m_Wetness;
|
||||||
|
|
||||||
in vec2 texCoord;
|
in vec2 texCoord;
|
||||||
in vec3 worldNormal;
|
in vec3 worldNormal;
|
||||||
@@ -20,5 +21,7 @@ void main() {
|
|||||||
float diff = max(dot(n, normalize(m_LightDir)), 0.0);
|
float diff = max(dot(n, normalize(m_LightDir)), 0.0);
|
||||||
vec3 light = clamp(m_AmbientColor + m_SunColor * diff, 0.0, 1.0);
|
vec3 light = clamp(m_AmbientColor + m_SunColor * diff, 0.0, 1.0);
|
||||||
|
|
||||||
gl_FragColor = vec4(baseColor * light, m_Diffuse.a);
|
baseColor *= (1.0 - 0.18 * m_Wetness);
|
||||||
|
float shine = pow(diff, 12.0) * m_Wetness * 0.30;
|
||||||
|
gl_FragColor = vec4(baseColor * light + vec3(shine), m_Diffuse.a);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ uniform bool m_HasLeafMap;
|
|||||||
uniform vec3 m_LightDir;
|
uniform vec3 m_LightDir;
|
||||||
uniform vec3 m_SunColor;
|
uniform vec3 m_SunColor;
|
||||||
uniform vec3 m_AmbientColor;
|
uniform vec3 m_AmbientColor;
|
||||||
|
uniform float m_Wetness;
|
||||||
|
|
||||||
in vec2 texCoord;
|
in vec2 texCoord;
|
||||||
in vec3 worldNormal;
|
in vec3 worldNormal;
|
||||||
@@ -27,5 +28,7 @@ void main() {
|
|||||||
// Blätter transmittieren Licht — abs() für doppelseitige Beleuchtung
|
// Blätter transmittieren Licht — abs() für doppelseitige Beleuchtung
|
||||||
float diff = abs(dot(normalize(worldNormal), normalize(m_LightDir)));
|
float diff = abs(dot(normalize(worldNormal), normalize(m_LightDir)));
|
||||||
vec3 light = clamp(m_AmbientColor + m_SunColor * diff * 0.6, 0.0, 1.0);
|
vec3 light = clamp(m_AmbientColor + m_SunColor * diff * 0.6, 0.0, 1.0);
|
||||||
gl_FragColor = vec4(baseColor * light, 1.0);
|
baseColor *= (1.0 - 0.18 * m_Wetness);
|
||||||
|
float shine = pow(diff, 12.0) * m_Wetness * 0.25;
|
||||||
|
gl_FragColor = vec4(baseColor * light + vec3(shine), 1.0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
uniform vec2 m_CloudOffset;
|
||||||
|
uniform float m_CloudCover;
|
||||||
|
uniform vec4 m_CloudColor;
|
||||||
|
|
||||||
|
in vec3 vDir;
|
||||||
|
out vec4 outColor;
|
||||||
|
|
||||||
|
float hash(vec2 p) {
|
||||||
|
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123);
|
||||||
|
}
|
||||||
|
|
||||||
|
float vnoise(vec2 p) {
|
||||||
|
vec2 i = floor(p);
|
||||||
|
vec2 f = fract(p);
|
||||||
|
f = f * f * (3.0 - 2.0 * f);
|
||||||
|
return mix(mix(hash(i), hash(i + vec2(1.0, 0.0)), f.x),
|
||||||
|
mix(hash(i + vec2(0.0, 1.0)), hash(i + vec2(1.0, 1.0)), f.x), f.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
float fbm(vec2 p) {
|
||||||
|
float v = 0.0;
|
||||||
|
float a = 0.5;
|
||||||
|
for (int i = 0; i < 6; i++) {
|
||||||
|
v += a * vnoise(p);
|
||||||
|
p = p * 2.17 + vec2(0.631, 1.137);
|
||||||
|
a *= 0.5;
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec3 dir = normalize(vDir);
|
||||||
|
|
||||||
|
if (dir.y < -0.02) discard;
|
||||||
|
|
||||||
|
// Horizon fade — clouds become transparent near the horizon
|
||||||
|
float hFade = smoothstep(0.0, 0.15, dir.y);
|
||||||
|
|
||||||
|
// Stereographic projection: maps sphere direction to 2D without pole distortion
|
||||||
|
vec2 uv = dir.xz / (dir.y + 1.001);
|
||||||
|
uv = uv * 3.5 + m_CloudOffset;
|
||||||
|
|
||||||
|
float f = fbm(uv);
|
||||||
|
|
||||||
|
// m_CloudCover 0=clear (threshold high → few clouds), 1=overcast (threshold low → all clouds)
|
||||||
|
float threshold = mix(0.72, 0.22, m_CloudCover);
|
||||||
|
float softness = 0.09;
|
||||||
|
float cloud = smoothstep(threshold - softness, threshold + softness, f);
|
||||||
|
|
||||||
|
cloud *= hFade;
|
||||||
|
|
||||||
|
outColor = vec4(m_CloudColor.rgb, cloud * m_CloudColor.a);
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
uniform mat4 g_WorldViewProjectionMatrix;
|
||||||
|
|
||||||
|
in vec3 inPosition;
|
||||||
|
out vec3 vDir;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vDir = inPosition;
|
||||||
|
gl_Position = g_WorldViewProjectionMatrix * vec4(inPosition, 1.0);
|
||||||
|
}
|
||||||
@@ -0,0 +1,238 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2014-2022, Stephen Gold
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
* Neither the name of the copyright holder nor the names of its contributors
|
||||||
|
may be used to endorse or promote products derived from this software
|
||||||
|
without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* fragment shader used by dome66.j3md
|
||||||
|
*/
|
||||||
|
#import "Common/ShaderLib/GLSLCompat.glsllib"
|
||||||
|
uniform vec4 m_ClearColor;
|
||||||
|
varying vec2 skyTexCoord;
|
||||||
|
|
||||||
|
#ifdef HAS_STARS
|
||||||
|
uniform sampler2D m_StarsColorMap;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAS_OBJECT0
|
||||||
|
uniform vec4 m_Object0Color;
|
||||||
|
uniform sampler2D m_Object0ColorMap;
|
||||||
|
varying vec2 object0Coord;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAS_OBJECT1
|
||||||
|
uniform vec4 m_Object1Color;
|
||||||
|
uniform sampler2D m_Object1ColorMap;
|
||||||
|
varying vec2 object1Coord;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAS_OBJECT2
|
||||||
|
uniform vec4 m_Object2Color;
|
||||||
|
uniform sampler2D m_Object2ColorMap;
|
||||||
|
varying vec2 object2Coord;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAS_OBJECT3
|
||||||
|
uniform vec4 m_Object3Color;
|
||||||
|
uniform sampler2D m_Object3ColorMap;
|
||||||
|
varying vec2 object3Coord;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAS_OBJECT4
|
||||||
|
uniform vec4 m_Object4Color;
|
||||||
|
uniform sampler2D m_Object4ColorMap;
|
||||||
|
varying vec2 object4Coord;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAS_OBJECT5
|
||||||
|
uniform vec4 m_Object5Color;
|
||||||
|
uniform sampler2D m_Object5ColorMap;
|
||||||
|
varying vec2 object5Coord;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAS_HAZE
|
||||||
|
uniform sampler2D m_HazeAlphaMap;
|
||||||
|
uniform vec4 m_HazeColor;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAS_CLOUDS0
|
||||||
|
uniform sampler2D m_Clouds0AlphaMap;
|
||||||
|
uniform vec4 m_Clouds0Color;
|
||||||
|
varying vec2 clouds0Coord;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAS_CLOUDS1
|
||||||
|
uniform sampler2D m_Clouds1AlphaMap;
|
||||||
|
uniform vec4 m_Clouds1Color;
|
||||||
|
varying vec2 clouds1Coord;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAS_CLOUDS2
|
||||||
|
uniform sampler2D m_Clouds2AlphaMap;
|
||||||
|
uniform vec4 m_Clouds2Color;
|
||||||
|
varying vec2 clouds2Coord;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAS_CLOUDS3
|
||||||
|
uniform sampler2D m_Clouds3AlphaMap;
|
||||||
|
uniform vec4 m_Clouds3Color;
|
||||||
|
varying vec2 clouds3Coord;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAS_CLOUDS4
|
||||||
|
uniform sampler2D m_Clouds4AlphaMap;
|
||||||
|
uniform vec4 m_Clouds4Color;
|
||||||
|
varying vec2 clouds4Coord;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAS_CLOUDS5
|
||||||
|
uniform sampler2D m_Clouds5AlphaMap;
|
||||||
|
uniform vec4 m_Clouds5Color;
|
||||||
|
varying vec2 clouds5Coord;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
vec4 mixColors(vec4 color0, vec4 color1) {
|
||||||
|
vec4 result;
|
||||||
|
float a0 = color0.a * (1.0 - color1.a);
|
||||||
|
result.a = a0 + color1.a;
|
||||||
|
if (result.a > 0.0) {
|
||||||
|
result.rgb = (a0 * color0.rgb + color1.a * color1.rgb)/result.a;
|
||||||
|
} else {
|
||||||
|
result.rgb = vec3(0.0);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
#ifdef HAS_STARS
|
||||||
|
vec4 stars = texture2D(m_StarsColorMap, skyTexCoord);
|
||||||
|
#else
|
||||||
|
vec4 stars = vec4(0.0);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
vec4 objects = vec4(0.0);
|
||||||
|
|
||||||
|
#ifdef HAS_OBJECT0
|
||||||
|
if (floor(object0Coord.s) == 0.0 &&
|
||||||
|
floor(object0Coord.t) == 0.0) {
|
||||||
|
objects = m_Object0Color;
|
||||||
|
objects *= texture2D(m_Object0ColorMap, object0Coord);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAS_OBJECT1
|
||||||
|
if (floor(object1Coord.s) == 0.0 &&
|
||||||
|
floor(object1Coord.t) == 0.0) {
|
||||||
|
vec4 object1 = m_Object1Color;
|
||||||
|
object1 *= texture2D(m_Object1ColorMap, object1Coord);
|
||||||
|
objects = mixColors(objects, object1);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAS_OBJECT2
|
||||||
|
if (floor(object2Coord.s) == 0.0 &&
|
||||||
|
floor(object2Coord.t) == 0.0) {
|
||||||
|
vec4 object2 = m_Object2Color;
|
||||||
|
object2 *= texture2D(m_Object2ColorMap, object2Coord);
|
||||||
|
objects = mixColors(objects, object2);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAS_OBJECT3
|
||||||
|
if (floor(object3Coord.s) == 0.0 &&
|
||||||
|
floor(object3Coord.t) == 0.0) {
|
||||||
|
vec4 object3 = m_Object3Color;
|
||||||
|
object3 *= texture2D(m_Object3ColorMap, object3Coord);
|
||||||
|
objects = mixColors(objects, object3);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAS_OBJECT4
|
||||||
|
if (floor(object4Coord.s) == 0.0 &&
|
||||||
|
floor(object4Coord.t) == 0.0) {
|
||||||
|
vec4 object4 = m_Object4Color;
|
||||||
|
object4 *= texture2D(m_Object4ColorMap, object4Coord);
|
||||||
|
objects = mixColors(objects, object4);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAS_OBJECT5
|
||||||
|
if (floor(object5Coord.s) == 0.0 &&
|
||||||
|
floor(object5Coord.t) == 0.0) {
|
||||||
|
vec4 object5 = m_Object5Color;
|
||||||
|
object5 *= texture2D(m_Object5ColorMap, object5Coord);
|
||||||
|
objects = mixColors(objects, object5);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
vec4 color = mixColors(stars, objects);
|
||||||
|
|
||||||
|
vec4 clear = m_ClearColor;
|
||||||
|
#ifdef HAS_HAZE
|
||||||
|
vec4 haze = m_HazeColor;
|
||||||
|
haze.a *= texture2D(m_HazeAlphaMap, skyTexCoord).r;
|
||||||
|
clear = mixColors(clear, haze);
|
||||||
|
#endif
|
||||||
|
color = mixColors(color, clear);
|
||||||
|
// Bright parts of objects shine through the clear areas.
|
||||||
|
color.rgb += objects.rgb * objects.a * (1.0 - clear.rgb) * clear.a;
|
||||||
|
|
||||||
|
#ifdef HAS_CLOUDS0
|
||||||
|
vec4 clouds0 = m_Clouds0Color;
|
||||||
|
clouds0.a *= texture2D(m_Clouds0AlphaMap, clouds0Coord).r;
|
||||||
|
color = mixColors(color, clouds0);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAS_CLOUDS1
|
||||||
|
vec4 clouds1 = m_Clouds1Color;
|
||||||
|
clouds1.a *= texture2D(m_Clouds1AlphaMap, clouds1Coord).r;
|
||||||
|
color = mixColors(color, clouds1);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAS_CLOUDS2
|
||||||
|
vec4 clouds2 = m_Clouds2Color;
|
||||||
|
clouds2.a *= texture2D(m_Clouds2AlphaMap, clouds2Coord).r;
|
||||||
|
color = mixColors(color, clouds2);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAS_CLOUDS3
|
||||||
|
vec4 clouds3 = m_Clouds3Color;
|
||||||
|
clouds3.a *= texture2D(m_Clouds3AlphaMap, clouds3Coord).r;
|
||||||
|
color = mixColors(color, clouds3);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAS_CLOUDS4
|
||||||
|
vec4 clouds4 = m_Clouds4Color;
|
||||||
|
clouds4.a *= texture2D(m_Clouds4AlphaMap, clouds4Coord).r;
|
||||||
|
color = mixColors(color, clouds4);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAS_CLOUDS5
|
||||||
|
vec4 clouds5 = m_Clouds5Color;
|
||||||
|
clouds5.a *= texture2D(m_Clouds5AlphaMap, clouds5Coord).r;
|
||||||
|
color = mixColors(color, clouds5);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
gl_FragColor = color;
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
BIN
blight-assets/src/main/resources/Textures/skies/clouds/clear.png
Normal file
BIN
blight-assets/src/main/resources/Textures/skies/clouds/clear.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 84 B |
Binary file not shown.
|
After Width: | Height: | Size: 108 B |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -38,7 +38,7 @@ public final class ChunkTerrainIO {
|
|||||||
public static final int LOD0_RANGE = 1;
|
public static final int LOD0_RANGE = 1;
|
||||||
public static final int LOD1_RANGE = 3;
|
public static final int LOD1_RANGE = 3;
|
||||||
/** Chebyshev-Distanz ≤ PHYSICS_RANGE: Physik-Collider aktiv. */
|
/** Chebyshev-Distanz ≤ PHYSICS_RANGE: Physik-Collider aktiv. */
|
||||||
public static final int PHYSICS_RANGE = 1;
|
public static final int PHYSICS_RANGE = 2;
|
||||||
|
|
||||||
// ── Kanten-Konstanten ──────────────────────────────────────────────────────
|
// ── Kanten-Konstanten ──────────────────────────────────────────────────────
|
||||||
public static final int EDGE_NORTH = 0; // höchste Zeile (row = VERTS-1)
|
public static final int EDGE_NORTH = 0; // höchste Zeile (row = VERTS-1)
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ public class EditorApp extends Application {
|
|||||||
private Label camCoordsLabel;
|
private Label camCoordsLabel;
|
||||||
private boolean launchGameAfterSave = false;
|
private boolean launchGameAfterSave = false;
|
||||||
private Button gamePlayBtn; // Spielen-Button im Seitenpanel
|
private Button gamePlayBtn; // Spielen-Button im Seitenpanel
|
||||||
|
private CheckBox debugConsoleCB; // Debug-Konsole bei Spielstart anzeigen
|
||||||
private VBox toolPanel;
|
private VBox toolPanel;
|
||||||
private BorderPane root;
|
private BorderPane root;
|
||||||
private Stage gameConsoleStage;
|
private Stage gameConsoleStage;
|
||||||
@@ -621,16 +622,20 @@ public class EditorApp extends Application {
|
|||||||
drawCompass();
|
drawCompass();
|
||||||
|
|
||||||
// Spiel-Konsole: gepufferte Zeilen gebündelt ausgeben (max 200 auf einmal)
|
// Spiel-Konsole: gepufferte Zeilen gebündelt ausgeben (max 200 auf einmal)
|
||||||
if (!consoleBuffer.isEmpty() && gameConsoleArea != null) {
|
if (!consoleBuffer.isEmpty()) {
|
||||||
StringBuilder sb = new StringBuilder();
|
if (gameConsoleArea != null) {
|
||||||
int count = 0;
|
StringBuilder sb = new StringBuilder();
|
||||||
String ln;
|
int count = 0;
|
||||||
while (count < 200 && (ln = consoleBuffer.poll()) != null) {
|
String ln;
|
||||||
sb.append(ln).append('\n');
|
while (count < 200 && (ln = consoleBuffer.poll()) != null) {
|
||||||
count++;
|
sb.append(ln).append('\n');
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
gameConsoleArea.appendText(sb.toString());
|
||||||
|
gameConsoleArea.setScrollTop(Double.MAX_VALUE);
|
||||||
|
} else {
|
||||||
|
consoleBuffer.clear();
|
||||||
}
|
}
|
||||||
gameConsoleArea.appendText(sb.toString());
|
|
||||||
gameConsoleArea.setScrollTop(Double.MAX_VALUE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input.soundAreaSelectionChanged) {
|
if (input.soundAreaSelectionChanged) {
|
||||||
@@ -7666,7 +7671,7 @@ public class EditorApp extends Application {
|
|||||||
setStatus(isNewGame ? "Neues Spiel gestartet" : "Spiel gestartet");
|
setStatus(isNewGame ? "Neues Spiel gestartet" : "Spiel gestartet");
|
||||||
if (gamePlayBtn != null) gamePlayBtn.setText("🎮 Läuft…");
|
if (gamePlayBtn != null) gamePlayBtn.setText("🎮 Läuft…");
|
||||||
if (gameNewBtn != null) gameNewBtn.setText("🎮 Läuft…");
|
if (gameNewBtn != null) gameNewBtn.setText("🎮 Läuft…");
|
||||||
openGameConsole();
|
if (debugConsoleCB != null && debugConsoleCB.isSelected()) openGameConsole();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Stdout des Spiels in Puffer schreiben — Flush erfolgt gebündelt im UI-Timer
|
// Stdout des Spiels in Puffer schreiben — Flush erfolgt gebündelt im UI-Timer
|
||||||
@@ -8501,7 +8506,11 @@ public class EditorApp extends Application {
|
|||||||
"Startet ein neues Spiel am perm. Spawnpunkt mit Intro-Sequenz"));
|
"Startet ein neues Spiel am perm. Spawnpunkt mit Intro-Sequenz"));
|
||||||
newGameBtn.setOnAction(e -> launchNewGame());
|
newGameBtn.setOnAction(e -> launchNewGame());
|
||||||
|
|
||||||
inner.getChildren().addAll(playBtn, newGameBtn);
|
debugConsoleCB = new CheckBox("Debug-Konsole anzeigen");
|
||||||
|
debugConsoleCB.setTooltip(new javafx.scene.control.Tooltip(
|
||||||
|
"Öffnet bei Spielstart die Ausgabekonsole mit der Spielausgabe"));
|
||||||
|
|
||||||
|
inner.getChildren().addAll(playBtn, newGameBtn, debugConsoleCB);
|
||||||
|
|
||||||
ScrollPane scroll = new ScrollPane(inner);
|
ScrollPane scroll = new ScrollPane(inner);
|
||||||
scroll.setFitToWidth(true);
|
scroll.setFitToWidth(true);
|
||||||
|
|||||||
@@ -0,0 +1,139 @@
|
|||||||
|
package de.blight.game;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.WindowAdapter;
|
||||||
|
import java.awt.event.WindowEvent;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Globaler Exception-Handler.
|
||||||
|
*
|
||||||
|
* Kritisch (JME-Render-Thread via BlightGame.handleError):
|
||||||
|
* GLFW-Cursor wird vor dem Dialog freigegeben. Blockierender Dialog → OK → System.exit(1).
|
||||||
|
* Das JME-Fenster bleibt als eingefrorenem Hintergrund sichtbar, bis der Prozess endet.
|
||||||
|
*
|
||||||
|
* Nicht kritisch (Hintergrundthreads via UncaughtExceptionHandler):
|
||||||
|
* Cursor wird über registrierte Callbacks temporär eingeblendet, nach OK wieder versteckt.
|
||||||
|
* Das Spiel läuft während des Dialogs weiter.
|
||||||
|
*/
|
||||||
|
public final class BlightExceptionHandler implements Thread.UncaughtExceptionHandler {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(BlightExceptionHandler.class);
|
||||||
|
|
||||||
|
/** Zeigt den Mauszeiger (auf dem JME-Thread via enqueue auszuführen). */
|
||||||
|
private static Runnable showCursorCb;
|
||||||
|
/** Versteckt den Mauszeiger wieder (auf dem JME-Thread via enqueue auszuführen). */
|
||||||
|
private static Runnable hideCursorCb;
|
||||||
|
|
||||||
|
private BlightExceptionHandler() {}
|
||||||
|
|
||||||
|
public static void install() {
|
||||||
|
Thread.setDefaultUncaughtExceptionHandler(new BlightExceptionHandler());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cursor-Callbacks registrieren. Wird von BlightGame.simpleInitApp() aufgerufen,
|
||||||
|
* sobald GLFW und InputManager bereit sind.
|
||||||
|
*/
|
||||||
|
public static void registerCursorCallbacks(Runnable showCursor, Runnable hideCursor) {
|
||||||
|
showCursorCb = showCursor;
|
||||||
|
hideCursorCb = hideCursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Hintergrundthread-Fehler (nicht kritisch) ─────────────────────────────
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void uncaughtException(Thread t, Throwable e) {
|
||||||
|
log.error("[Exception] Uncaught in '{}': {}", t.getName(), e.getMessage(), e);
|
||||||
|
// Cursor einblenden, bevor der Dialog auf dem EDT erscheint
|
||||||
|
if (showCursorCb != null) showCursorCb.run();
|
||||||
|
EventQueue.invokeLater(() -> {
|
||||||
|
showDialog(t.getName(), e, false);
|
||||||
|
// Nach Schließen des Dialogs Cursor wieder einfangen
|
||||||
|
if (hideCursorCb != null) hideCursorCb.run();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── JME-Render-Thread-Fehler (kritisch) ──────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zeigt einen blockierenden Dialog und beendet die Anwendung nach OK.
|
||||||
|
* Muss erst NACH dem GLFW-Cursor-Release aufgerufen werden.
|
||||||
|
*/
|
||||||
|
public static void showCritical(String threadName, Throwable e) {
|
||||||
|
log.error("[Exception] Kritisch in '{}': {}", threadName, e.getMessage(), e);
|
||||||
|
try {
|
||||||
|
if (EventQueue.isDispatchThread()) {
|
||||||
|
showDialog(threadName, e, true);
|
||||||
|
} else {
|
||||||
|
EventQueue.invokeAndWait(() -> showDialog(threadName, e, true));
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.error("[Exception] Fehlerdialog konnte nicht angezeigt werden", ex);
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Dialog ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private static void showDialog(String threadName, Throwable e, boolean critical) {
|
||||||
|
StringWriter sw = new StringWriter();
|
||||||
|
e.printStackTrace(new PrintWriter(sw));
|
||||||
|
|
||||||
|
JDialog dialog = new JDialog((Frame) null,
|
||||||
|
critical ? "Kritischer Fehler" : "Fehler", true);
|
||||||
|
dialog.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
|
||||||
|
dialog.setLayout(new BorderLayout(8, 8));
|
||||||
|
|
||||||
|
JLabel titleLbl = new JLabel(critical
|
||||||
|
? "Kritischer Fehler – Anwendung wird beendet"
|
||||||
|
: "Fehler in Hintergrundthread – Spiel läuft weiter");
|
||||||
|
titleLbl.setFont(titleLbl.getFont().deriveFont(Font.BOLD, 14f));
|
||||||
|
titleLbl.setForeground(critical ? new Color(0xcc2200) : new Color(0x884400));
|
||||||
|
titleLbl.setBorder(BorderFactory.createEmptyBorder(10, 14, 4, 14));
|
||||||
|
|
||||||
|
String msg = e.getClass().getName()
|
||||||
|
+ (e.getMessage() != null ? ": " + e.getMessage() : "")
|
||||||
|
+ " [Thread: " + threadName + "]";
|
||||||
|
JLabel infoLbl = new JLabel(msg);
|
||||||
|
infoLbl.setFont(infoLbl.getFont().deriveFont(Font.PLAIN, 12f));
|
||||||
|
infoLbl.setBorder(BorderFactory.createEmptyBorder(0, 14, 10, 14));
|
||||||
|
|
||||||
|
JPanel north = new JPanel(new GridLayout(2, 1));
|
||||||
|
north.add(titleLbl);
|
||||||
|
north.add(infoLbl);
|
||||||
|
|
||||||
|
JTextArea ta = new JTextArea(sw.toString(), 22, 90);
|
||||||
|
ta.setEditable(false);
|
||||||
|
ta.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 11));
|
||||||
|
JScrollPane scroll = new JScrollPane(ta);
|
||||||
|
scroll.setBorder(BorderFactory.createEmptyBorder(0, 8, 0, 8));
|
||||||
|
|
||||||
|
JButton btn = new JButton(critical ? "Anwendung beenden" : "OK");
|
||||||
|
btn.setFont(btn.getFont().deriveFont(Font.BOLD, 13f));
|
||||||
|
JPanel south = new JPanel(new FlowLayout(FlowLayout.CENTER));
|
||||||
|
south.add(btn);
|
||||||
|
|
||||||
|
dialog.add(north, BorderLayout.NORTH);
|
||||||
|
dialog.add(scroll, BorderLayout.CENTER);
|
||||||
|
dialog.add(south, BorderLayout.SOUTH);
|
||||||
|
dialog.setSize(920, 560);
|
||||||
|
dialog.setLocationRelativeTo(null);
|
||||||
|
|
||||||
|
Runnable close = () -> {
|
||||||
|
dialog.dispose();
|
||||||
|
if (critical) System.exit(1);
|
||||||
|
};
|
||||||
|
btn.addActionListener(ev -> close.run());
|
||||||
|
dialog.addWindowListener(new WindowAdapter() {
|
||||||
|
@Override public void windowClosing(WindowEvent ev) { close.run(); }
|
||||||
|
});
|
||||||
|
|
||||||
|
dialog.setVisible(true); // blockiert (modal) bis der Dialog geschlossen wird
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -77,6 +77,7 @@ public class BlightGame extends SimpleApplication {
|
|||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
SLF4JBridgeHandler.removeHandlersForRootLogger();
|
SLF4JBridgeHandler.removeHandlersForRootLogger();
|
||||||
SLF4JBridgeHandler.install();
|
SLF4JBridgeHandler.install();
|
||||||
|
BlightExceptionHandler.install();
|
||||||
|
|
||||||
BlightGame app = new BlightGame();
|
BlightGame app = new BlightGame();
|
||||||
app.splashWindow = showSplash();
|
app.splashWindow = showSplash();
|
||||||
@@ -203,6 +204,17 @@ public class BlightGame extends SimpleApplication {
|
|||||||
|
|
||||||
startTimeMs = System.currentTimeMillis();
|
startTimeMs = System.currentTimeMillis();
|
||||||
initialFixDone = false;
|
initialFixDone = false;
|
||||||
|
|
||||||
|
// Cursor-Callbacks für nicht-kritische Exception-Dialoge aus Hintergrundthreads
|
||||||
|
BlightExceptionHandler.registerCursorCallbacks(
|
||||||
|
() -> enqueue(() -> {
|
||||||
|
if (context instanceof LwjglWindow) {
|
||||||
|
long win = ((LwjglWindow) context).getWindowHandle();
|
||||||
|
if (win != 0L) GLFW.glfwSetInputMode(win, GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_NORMAL);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
() -> enqueue(() -> inputManager.setCursorVisible(false))
|
||||||
|
);
|
||||||
if (graphicsSettings.fullscreen) {
|
if (graphicsSettings.fullscreen) {
|
||||||
log.info("[Grafik] Vollbild-Start: cam={}×{} fullscreen={} gewünschte Aufl.={}×{}",
|
log.info("[Grafik] Vollbild-Start: cam={}×{} fullscreen={} gewünschte Aufl.={}×{}",
|
||||||
cam.getWidth(), cam.getHeight(), graphicsSettings.fullscreen,
|
cam.getWidth(), cam.getHeight(), graphicsSettings.fullscreen,
|
||||||
@@ -375,6 +387,8 @@ public class BlightGame extends SimpleApplication {
|
|||||||
worldScene.setPaused(true);
|
worldScene.setPaused(true);
|
||||||
}, "ToggleMenu");
|
}, "ToggleMenu");
|
||||||
|
|
||||||
|
stateManager.attach(new de.blight.game.state.CursorState());
|
||||||
|
|
||||||
// ── Startentscheidung: Hauptmenü oder direkt ins Spiel (Editor-Start) ─
|
// ── Startentscheidung: Hauptmenü oder direkt ins Spiel (Editor-Start) ─
|
||||||
boolean autostart = Boolean.getBoolean("blight.autostart");
|
boolean autostart = Boolean.getBoolean("blight.autostart");
|
||||||
if (autostart) {
|
if (autostart) {
|
||||||
@@ -384,6 +398,24 @@ public class BlightGame extends SimpleApplication {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleError(String errMsg, Throwable t) {
|
||||||
|
// GLFW-Cursor freigeben, damit der Dialog bedienbar ist.
|
||||||
|
// Das JME-Fenster bleibt als eingefrorener Hintergrund sichtbar bis System.exit().
|
||||||
|
try {
|
||||||
|
if (context instanceof LwjglWindow) {
|
||||||
|
long win = ((LwjglWindow) context).getWindowHandle();
|
||||||
|
if (win != 0L) {
|
||||||
|
GLFW.glfwSetInputMode(win, GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_NORMAL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
|
||||||
|
Throwable ex = t != null ? t : new RuntimeException(errMsg);
|
||||||
|
BlightExceptionHandler.showCritical(Thread.currentThread().getName(), ex);
|
||||||
|
stop(); // Fallback: wird nur erreicht wenn der Dialog nicht angezeigt werden konnte
|
||||||
|
}
|
||||||
|
|
||||||
// ── Start-Flows ──────────────────────────────────────────────────────────
|
// ── Start-Flows ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
private void showMainMenu() {
|
private void showMainMenu() {
|
||||||
@@ -568,14 +600,44 @@ public class BlightGame extends SimpleApplication {
|
|||||||
if (ws == null) return "Wettersystem nicht aktiv";
|
if (ws == null) return "Wettersystem nicht aktiv";
|
||||||
if (args.length < 2)
|
if (args.length < 2)
|
||||||
return "Aktuell: " + ws.getActiveWeather()
|
return "Aktuell: " + ws.getActiveWeather()
|
||||||
+ " | Syntax: weather <sunny|cloudy|overcast|storm>";
|
+ " | Syntax: weather current | weather next | weather <"
|
||||||
|
+ java.util.Arrays.stream(de.blight.game.state.WeatherState.Weather.values())
|
||||||
|
.map(w -> w.name().toLowerCase())
|
||||||
|
.collect(java.util.stream.Collectors.joining("|"))
|
||||||
|
+ ">";
|
||||||
|
if ("current".equalsIgnoreCase(args[1])) {
|
||||||
|
float windDeg = (float) Math.toDegrees(ws.getWindAngle());
|
||||||
|
if (windDeg < 0) windDeg += 360f;
|
||||||
|
String[] compass = {"N","NO","O","SO","S","SW","W","NW"};
|
||||||
|
String dir = compass[Math.round(windDeg / 45f) % 8];
|
||||||
|
return String.format(
|
||||||
|
"Wetter: %s%n" +
|
||||||
|
"Wind: %.1f km/h aus %s (%.0f°)%n" +
|
||||||
|
"Regen: %.0f%%%n" +
|
||||||
|
"Wolken: %.0f%%%n" +
|
||||||
|
"Nebel: %.3f%n" +
|
||||||
|
"Nächste Ä.: in %.0f s",
|
||||||
|
ws.getActiveWeather(),
|
||||||
|
ws.getWindSpeed(), dir, windDeg,
|
||||||
|
ws.getRainIntensity() * 100f,
|
||||||
|
ws.getCloudCover() * 100f,
|
||||||
|
ws.getFogDensity(),
|
||||||
|
ws.getChangeTimer());
|
||||||
|
}
|
||||||
|
if ("next".equalsIgnoreCase(args[1])) {
|
||||||
|
de.blight.game.state.WeatherState.Weather next = ws.triggerNext();
|
||||||
|
return "Wetter → " + next;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
de.blight.game.state.WeatherState.Weather w =
|
de.blight.game.state.WeatherState.Weather w =
|
||||||
de.blight.game.state.WeatherState.Weather.valueOf(args[1].toUpperCase());
|
de.blight.game.state.WeatherState.Weather.valueOf(args[1].toUpperCase());
|
||||||
ws.forceWeather(w);
|
ws.forceWeather(w);
|
||||||
return "Wetter gesetzt: " + w;
|
return "Wetter gesetzt: " + w;
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
return "Unbekanntes Wetter. Erlaubt: sunny, cloudy, overcast, storm";
|
return "Unbekanntes Wetter. Erlaubt: next, "
|
||||||
|
+ java.util.Arrays.stream(de.blight.game.state.WeatherState.Weather.values())
|
||||||
|
.map(w -> w.name().toLowerCase())
|
||||||
|
.collect(java.util.stream.Collectors.joining(", "));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -438,6 +438,8 @@ public class WorldScene extends BaseAppState {
|
|||||||
ColorRGBA ac = dayNight.getCustomAmbient();
|
ColorRGBA ac = dayNight.getCustomAmbient();
|
||||||
terrainMaterial.setVector3("SunColor", new Vector3f(sc.r, sc.g, sc.b));
|
terrainMaterial.setVector3("SunColor", new Vector3f(sc.r, sc.g, sc.b));
|
||||||
terrainMaterial.setVector3("AmbientColor", new Vector3f(ac.r, ac.g, ac.b));
|
terrainMaterial.setVector3("AmbientColor", new Vector3f(ac.r, ac.g, ac.b));
|
||||||
|
de.blight.game.state.RainState rs = app.getStateManager().getState(de.blight.game.state.RainState.class);
|
||||||
|
if (rs != null) terrainMaterial.setFloat("Wetness", rs.getWetness());
|
||||||
// Charakter-PBR: Emissive = customAmbient (IBL-Fallback ohne fertige LightProbe)
|
// Charakter-PBR: Emissive = customAmbient (IBL-Fallback ohne fertige LightProbe)
|
||||||
for (Material mat : characterPbrMaterials) {
|
for (Material mat : characterPbrMaterials) {
|
||||||
mat.setColor("Emissive", new ColorRGBA(ac.r, ac.g, ac.b, 1f));
|
mat.setColor("Emissive", new ColorRGBA(ac.r, ac.g, ac.b, 1f));
|
||||||
@@ -808,7 +810,9 @@ public class WorldScene extends BaseAppState {
|
|||||||
weather.setFogFilter(fogFilter);
|
weather.setFogFilter(fogFilter);
|
||||||
weather.setViewDistanceFactor(graphicsSettings.viewDistance.fogFactor);
|
weather.setViewDistanceFactor(graphicsSettings.viewDistance.fogFactor);
|
||||||
weather.setSkyControl(dayNight.getSkyControl());
|
weather.setSkyControl(dayNight.getSkyControl());
|
||||||
|
weather.setDayTime(dayNight.getDayTime());
|
||||||
app.getStateManager().attach(weather);
|
app.getStateManager().attach(weather);
|
||||||
|
app.getStateManager().attach(new de.blight.game.state.RainState());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("[WorldScene] Post-Processing nicht verfügbar: {}", e.getMessage());
|
log.warn("[WorldScene] Post-Processing nicht verfügbar: {}", e.getMessage());
|
||||||
}
|
}
|
||||||
@@ -828,11 +832,13 @@ public class WorldScene extends BaseAppState {
|
|||||||
// Initiale Sonnenposition: von Kamera aus weit entfernt entgegen Lichtrichtung
|
// Initiale Sonnenposition: von Kamera aus weit entfernt entgegen Lichtrichtung
|
||||||
Vector3f initSunPos = app.getCamera().getLocation().add(sunDir.negate().mult(10000f));
|
Vector3f initSunPos = app.getCamera().getLocation().add(sunDir.negate().mult(10000f));
|
||||||
lightScatterFilter = new com.jme3.post.filters.LightScatteringFilter(initSunPos);
|
lightScatterFilter = new com.jme3.post.filters.LightScatteringFilter(initSunPos);
|
||||||
lightScatterFilter.setLightDensity(0.8f);
|
lightScatterFilter.setLightDensity(0.08f);
|
||||||
lightScatterFilter.setBlurStart(0.3f);
|
lightScatterFilter.setBlurStart(0.3f);
|
||||||
lightScatterFilter.setBlurWidth(0.4f);
|
lightScatterFilter.setBlurWidth(0.4f);
|
||||||
lightScatterFilter.setNbSamples(150);
|
lightScatterFilter.setNbSamples(150);
|
||||||
fpp.addFilter(lightScatterFilter);
|
fpp.addFilter(lightScatterFilter);
|
||||||
|
WeatherState ws = app.getStateManager().getState(WeatherState.class);
|
||||||
|
if (ws != null) ws.setLightScatterFilter(lightScatterFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pfx.toneMap) {
|
if (pfx.toneMap) {
|
||||||
|
|||||||
126
blight-game/src/main/java/de/blight/game/state/CursorState.java
Normal file
126
blight-game/src/main/java/de/blight/game/state/CursorState.java
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
package de.blight.game.state;
|
||||||
|
|
||||||
|
import com.jme3.app.Application;
|
||||||
|
import com.jme3.app.state.BaseAppState;
|
||||||
|
import com.jme3.input.RawInputListener;
|
||||||
|
import com.jme3.input.event.*;
|
||||||
|
import com.jme3.system.lwjgl.LwjglWindow;
|
||||||
|
import org.lwjgl.glfw.GLFW;
|
||||||
|
import org.lwjgl.glfw.GLFWImage;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import java.awt.RenderingHints;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ersetzt den Betriebssystem-Mauszeiger durch benutzerdefinierte GLFW-Cursor-Images.
|
||||||
|
* Tauscht automatisch zwischen Normalzustand und gedrücktem Zustand (LMB) aus.
|
||||||
|
*/
|
||||||
|
public class CursorState extends BaseAppState implements RawInputListener {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(CursorState.class);
|
||||||
|
|
||||||
|
private long windowHandle;
|
||||||
|
private long normalCursor;
|
||||||
|
private long pressedCursor;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void initialize(Application app) {
|
||||||
|
if (!(app.getContext() instanceof LwjglWindow)) {
|
||||||
|
log.warn("[Cursor] Kein LWJGL-Fenster – Custom-Cursor nicht verfügbar.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
windowHandle = ((LwjglWindow) app.getContext()).getWindowHandle();
|
||||||
|
if (windowHandle == 0L) return;
|
||||||
|
|
||||||
|
normalCursor = loadCursor(app, "Textures/internal/cursor/cursor.png");
|
||||||
|
pressedCursor = loadCursor(app, "Textures/internal/cursor/cursor_pressed.png");
|
||||||
|
|
||||||
|
if (normalCursor != 0L) {
|
||||||
|
GLFW.glfwSetCursor(windowHandle, normalCursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
app.getInputManager().addRawInputListener(this);
|
||||||
|
log.info("[Cursor] Custom-Cursor geladen.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void cleanup(Application app) {
|
||||||
|
app.getInputManager().removeRawInputListener(this);
|
||||||
|
if (windowHandle != 0L) {
|
||||||
|
GLFW.glfwSetCursor(windowHandle, 0L); // Standard-Cursor wiederherstellen
|
||||||
|
}
|
||||||
|
if (normalCursor != 0L) { GLFW.glfwDestroyCursor(normalCursor); normalCursor = 0L; }
|
||||||
|
if (pressedCursor != 0L) { GLFW.glfwDestroyCursor(pressedCursor); pressedCursor = 0L; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override protected void onEnable() {}
|
||||||
|
@Override protected void onDisable() {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMouseButtonEvent(MouseButtonEvent evt) {
|
||||||
|
if (windowHandle == 0L) return;
|
||||||
|
if (evt.getButtonIndex() == 0) {
|
||||||
|
long c = evt.isPressed() ? pressedCursor : normalCursor;
|
||||||
|
if (c != 0L) GLFW.glfwSetCursor(windowHandle, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── RawInputListener-Stubs ────────────────────────────────────────────────
|
||||||
|
@Override public void beginInput() {}
|
||||||
|
@Override public void endInput() {}
|
||||||
|
@Override public void onJoyAxisEvent(JoyAxisEvent evt) {}
|
||||||
|
@Override public void onJoyButtonEvent(JoyButtonEvent evt) {}
|
||||||
|
@Override public void onMouseMotionEvent(MouseMotionEvent evt) {}
|
||||||
|
@Override public void onKeyEvent(KeyInputEvent evt) {}
|
||||||
|
@Override public void onTouchEvent(TouchEvent evt) {}
|
||||||
|
|
||||||
|
// ── GLFW-Cursor aus Classpath-PNG laden ────────────────────────────────────
|
||||||
|
|
||||||
|
private static long loadCursor(Application app, String assetPath) {
|
||||||
|
try (InputStream is = app.getAssetManager()
|
||||||
|
.locateAsset(new com.jme3.asset.TextureKey(assetPath))
|
||||||
|
.openStream()) {
|
||||||
|
|
||||||
|
BufferedImage src = ImageIO.read(is);
|
||||||
|
if (src == null) throw new IllegalStateException("ImageIO konnte das Bild nicht lesen");
|
||||||
|
|
||||||
|
// Auf 32×32 skalieren – GLFW rendert Cursor pixelgenau
|
||||||
|
int w = 32, h = 32;
|
||||||
|
BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
|
||||||
|
java.awt.Graphics2D g2 = img.createGraphics();
|
||||||
|
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
|
||||||
|
g2.drawImage(src, 0, 0, w, h, null);
|
||||||
|
g2.dispose();
|
||||||
|
|
||||||
|
ByteBuffer pixels = ByteBuffer.allocateDirect(w * h * 4);
|
||||||
|
|
||||||
|
for (int y = 0; y < h; y++) {
|
||||||
|
for (int x = 0; x < w; x++) {
|
||||||
|
int argb = img.getRGB(x, y);
|
||||||
|
pixels.put((byte) ((argb >> 16) & 0xFF)); // R
|
||||||
|
pixels.put((byte) ((argb >> 8) & 0xFF)); // G
|
||||||
|
pixels.put((byte) ( argb & 0xFF)); // B
|
||||||
|
pixels.put((byte) ((argb >> 24) & 0xFF)); // A
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pixels.flip();
|
||||||
|
|
||||||
|
long cursor;
|
||||||
|
try (GLFWImage glfwImg = GLFWImage.malloc()) {
|
||||||
|
glfwImg.set(w, h, pixels);
|
||||||
|
cursor = GLFW.glfwCreateCursor(glfwImg, 0, 0);
|
||||||
|
}
|
||||||
|
if (cursor == 0L) throw new IllegalStateException("glfwCreateCursor schlug fehl");
|
||||||
|
return cursor;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("[Cursor] Cursor nicht ladbar '{}': {}", assetPath, e.getMessage());
|
||||||
|
return 0L;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,13 +6,17 @@ import com.jme3.app.state.BaseAppState;
|
|||||||
import com.jme3.bullet.BulletAppState;
|
import com.jme3.bullet.BulletAppState;
|
||||||
import com.jme3.light.AmbientLight;
|
import com.jme3.light.AmbientLight;
|
||||||
import com.jme3.light.DirectionalLight;
|
import com.jme3.light.DirectionalLight;
|
||||||
|
import com.jme3.material.Material;
|
||||||
import com.jme3.math.*;
|
import com.jme3.math.*;
|
||||||
|
import com.jme3.renderer.queue.RenderQueue;
|
||||||
|
import com.jme3.scene.Geometry;
|
||||||
import com.jme3.scene.Node;
|
import com.jme3.scene.Node;
|
||||||
|
import com.jme3.scene.shape.Sphere;
|
||||||
import com.jme3.shadow.DirectionalLightShadowFilter;
|
import com.jme3.shadow.DirectionalLightShadowFilter;
|
||||||
import com.jme3.shadow.EdgeFilteringMode;
|
import com.jme3.shadow.EdgeFilteringMode;
|
||||||
import de.blight.common.time.DayTime;
|
import de.blight.common.time.DayTime;
|
||||||
import de.blight.common.time.TimeListener;
|
import de.blight.common.time.TimeListener;
|
||||||
import jme3utilities.sky.CloudLayer;
|
import jme3utilities.sky.LunarPhase;
|
||||||
import jme3utilities.sky.SkyControl;
|
import jme3utilities.sky.SkyControl;
|
||||||
import jme3utilities.sky.StarsOption;
|
import jme3utilities.sky.StarsOption;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -42,6 +46,17 @@ public class DayNightState extends BaseAppState implements TimeListener {
|
|||||||
private DirectionalLightShadowFilter shadowFilter;
|
private DirectionalLightShadowFilter shadowFilter;
|
||||||
private Node skyNode;
|
private Node skyNode;
|
||||||
private SkyControl skyControl;
|
private SkyControl skyControl;
|
||||||
|
private Node cloudDomeNode;
|
||||||
|
private Material cloudMat;
|
||||||
|
private float cloudOffsetX = 0f;
|
||||||
|
private float cloudOffsetZ = 0f;
|
||||||
|
|
||||||
|
// ── Mond-Phase ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/** 0 = Neumond, 0.5 = Vollmond, 1 = Neumond. Zyklus: 7 Spieltage = 6300 s Echtzeit. */
|
||||||
|
private float moonAge = 0.375f; // Start: zunehmend Gibbous
|
||||||
|
private LunarPhase currentMoonPhase = null; // zuletzt gesetzte Phase — vermeidet redundante Aufrufe
|
||||||
|
private static final float LUNAR_CYCLE = 6300f;
|
||||||
|
|
||||||
// ── Sonnenrichtungs-Drosselung ────────────────────────────────────────────
|
// ── Sonnenrichtungs-Drosselung ────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -56,6 +71,10 @@ public class DayNightState extends BaseAppState implements TimeListener {
|
|||||||
private float caveCheckTimer = 0f;
|
private float caveCheckTimer = 0f;
|
||||||
private ColorRGBA sunBaseColor = new ColorRGBA(1, 1, 1, 1);
|
private ColorRGBA sunBaseColor = new ColorRGBA(1, 1, 1, 1);
|
||||||
private float shadowBaseIntensity = 0f;
|
private float shadowBaseIntensity = 0f;
|
||||||
|
/** Letzter berechneter Wert von sunOcclusionByCloud — für updateCaveLighting(). */
|
||||||
|
private float lastSunOcc = 0f;
|
||||||
|
/** 1.0 = klar, sinkt mit zunehmender Wolkendecke (min 0.20). */
|
||||||
|
private float cloudDimFactor = 1f;
|
||||||
private ColorRGBA customAmbient = new ColorRGBA();
|
private ColorRGBA customAmbient = new ColorRGBA();
|
||||||
/** true = Spiel-Beleuchtungswerte, false = Editor-Werte. Standardmäßig = withShadows. */
|
/** true = Spiel-Beleuchtungswerte, false = Editor-Werte. Standardmäßig = withShadows. */
|
||||||
private boolean gameMode;
|
private boolean gameMode;
|
||||||
@@ -144,17 +163,31 @@ public class DayNightState extends BaseAppState implements TimeListener {
|
|||||||
rootNode.attachChild(skyNode);
|
rootNode.attachChild(skyNode);
|
||||||
|
|
||||||
SkyControl sc = new SkyControl(app.getAssetManager(), app.getCamera(),
|
SkyControl sc = new SkyControl(app.getAssetManager(), app.getCamera(),
|
||||||
0.9f, StarsOption.Cube, true);
|
0f, StarsOption.Cube, true);
|
||||||
// SkyControl default: North=+X, but JME3 world: North=-Z, East=+X
|
// SkyControl default: North=+X, but JME3 world: North=-Z, East=+X
|
||||||
sc.getSunAndStars().setAxes(new Vector3f(0f, 0f, -1f), Vector3f.UNIT_Y);
|
sc.getSunAndStars().setAxes(new Vector3f(0f, 0f, -1f), Vector3f.UNIT_Y);
|
||||||
// Deckel-Kuppel leicht unter den Horizont verlängern → kein sichtbarer Saum beim Blick nach unten
|
// Deckel-Kuppel leicht unter den Horizont verlängern → kein sichtbarer Saum beim Blick nach unten
|
||||||
sc.setTopVerticalAngle(FastMath.HALF_PI + 0.12f);
|
sc.setTopVerticalAngle(FastMath.HALF_PI + 0.12f);
|
||||||
skyNode.addControl(sc);
|
skyNode.addControl(sc);
|
||||||
|
|
||||||
CloudLayer clouds = sc.getCloudLayer(0);
|
// SkyControl-Wolkenlayer alle deaktivieren — CloudDome-Shader übernimmt
|
||||||
clouds.setMotion(0.37f, 0f, 0.2f, 0.001f);
|
sc.setCloudiness(1.0f);
|
||||||
clouds.setTexture("Textures/skies/clouds/fbm.png", 0.3f);
|
for (int li = 0; li < 6; li++) {
|
||||||
clouds.setOpacity(0f);
|
sc.getCloudLayer(li).setTexture("Textures/skies/clouds/clear.png", 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prozedurale Wolkenkuppel (FBM-Shader, kein Sphere-UV-Drift)
|
||||||
|
Sphere cloudSphere = new Sphere(32, 64, 900f);
|
||||||
|
Geometry cloudGeo = new Geometry("cloudDome", cloudSphere);
|
||||||
|
cloudGeo.setQueueBucket(RenderQueue.Bucket.Sky);
|
||||||
|
cloudMat = new Material(app.getAssetManager(), "MatDefs/CloudDome.j3md");
|
||||||
|
cloudMat.setVector2("CloudOffset", new Vector2f(0f, 0f));
|
||||||
|
cloudMat.setFloat("CloudCover", 0f);
|
||||||
|
cloudMat.setColor("CloudColor", new ColorRGBA(1f, 1f, 1f, 1f));
|
||||||
|
cloudGeo.setMaterial(cloudMat);
|
||||||
|
cloudDomeNode = new Node("cloudDomeNode");
|
||||||
|
cloudDomeNode.attachChild(cloudGeo);
|
||||||
|
rootNode.attachChild(cloudDomeNode);
|
||||||
|
|
||||||
// Updater nur für Viewport-Hintergrundfarbe nutzen (Licht wird selbst gesteuert)
|
// Updater nur für Viewport-Hintergrundfarbe nutzen (Licht wird selbst gesteuert)
|
||||||
sc.getUpdater().addViewPort(this.app.getViewPort());
|
sc.getUpdater().addViewPort(this.app.getViewPort());
|
||||||
@@ -185,6 +218,11 @@ public class DayNightState extends BaseAppState implements TimeListener {
|
|||||||
rootNode.detachChild(skyNode);
|
rootNode.detachChild(skyNode);
|
||||||
}
|
}
|
||||||
skyNode = null;
|
skyNode = null;
|
||||||
|
if (cloudDomeNode != null && cloudDomeNode.getParent() != null) {
|
||||||
|
rootNode.detachChild(cloudDomeNode);
|
||||||
|
}
|
||||||
|
cloudDomeNode = null;
|
||||||
|
cloudMat = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override protected void onEnable() {}
|
@Override protected void onEnable() {}
|
||||||
@@ -199,6 +237,60 @@ public class DayNightState extends BaseAppState implements TimeListener {
|
|||||||
if (skyNode != null && skyNode.getParent() == null) {
|
if (skyNode != null && skyNode.getParent() == null) {
|
||||||
rootNode.attachChild(skyNode);
|
rootNode.attachChild(skyNode);
|
||||||
}
|
}
|
||||||
|
if (cloudDomeNode != null && cloudDomeNode.getParent() == null) {
|
||||||
|
rootNode.attachChild(cloudDomeNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mond-Phase: 7 Spieltage pro Zyklus; 5 Phasen-Texturen über LunarPhase-Enum
|
||||||
|
moonAge += tpf / LUNAR_CYCLE;
|
||||||
|
if (moonAge >= 1f) moonAge -= 1f;
|
||||||
|
if (skyControl != null) {
|
||||||
|
LunarPhase phase = lunarPhaseFor(moonAge);
|
||||||
|
if (phase != currentMoonPhase) {
|
||||||
|
currentMoonPhase = phase;
|
||||||
|
skyControl.setPhase(phase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wolkenkuppel: Kamera folgen, Wind-Scroll und Cover-Uniform setzen
|
||||||
|
WeatherState ws = app.getStateManager().getState(WeatherState.class);
|
||||||
|
float cloudCover = 0f;
|
||||||
|
if (cloudMat != null) {
|
||||||
|
Vector3f cam = app.getCamera().getLocation();
|
||||||
|
cloudDomeNode.setLocalTranslation(cam.x, cam.y, cam.z);
|
||||||
|
|
||||||
|
if (ws != null) {
|
||||||
|
Vector3f wind = ws.getWindDirection();
|
||||||
|
float scrollRate = ws.getWindSpeed() * 0.0002f * tpf;
|
||||||
|
cloudOffsetX += wind.x * scrollRate;
|
||||||
|
cloudOffsetZ += wind.z * scrollRate;
|
||||||
|
cloudCover = ws.getCloudCover();
|
||||||
|
cloudMat.setVector2("CloudOffset", new Vector2f(cloudOffsetX, cloudOffsetZ));
|
||||||
|
cloudMat.setFloat("CloudCover", cloudCover);
|
||||||
|
}
|
||||||
|
// Wolkenfarbe aus sunBaseColor; Minimum-Helligkeit für bewölkte Nächte
|
||||||
|
ColorRGBA cc = sunBaseColor.clone();
|
||||||
|
float lum = Math.max(cc.r, Math.max(cc.g, cc.b));
|
||||||
|
if (lum < 0.04f) {
|
||||||
|
cc.r = Math.max(cc.r, 0.04f);
|
||||||
|
cc.g = Math.max(cc.g, 0.04f);
|
||||||
|
cc.b = Math.max(cc.b, 0.05f);
|
||||||
|
}
|
||||||
|
cc.a = 1f;
|
||||||
|
cloudMat.setColor("CloudColor", cc);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sonnenlicht + Schatten: Okklusion durch CloudDome bestimmt beides
|
||||||
|
if (skyControl != null) {
|
||||||
|
Vector3f sunDir = skyControl.getSunAndStars().sunDirection(null);
|
||||||
|
lastSunOcc = sunOcclusionByCloud(sunDir, cloudCover);
|
||||||
|
// Direktlicht: bis 92% gedimmt wenn Sonne verdeckt; min 8% bleibt (diffuses Streulicht)
|
||||||
|
float sunDirect = FastMath.clamp(1f - lastSunOcc * 0.92f, 0.08f, 1f);
|
||||||
|
sun.setColor(sunBaseColor.mult(sunDirect * (1f - caveFactor)));
|
||||||
|
if (shadowFilter != null) {
|
||||||
|
shadowFilter.setShadowIntensity(shadowBaseIntensity * (1f - lastSunOcc) * (1f - caveFactor));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
updateCaveLighting(tpf, app.getCamera().getLocation());
|
updateCaveLighting(tpf, app.getCamera().getLocation());
|
||||||
}
|
}
|
||||||
@@ -224,14 +316,69 @@ public class DayNightState extends BaseAppState implements TimeListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (caveFactor != prev) {
|
if (caveFactor != prev) {
|
||||||
float scale = 1f - caveFactor;
|
float sunDirect = FastMath.clamp(1f - lastSunOcc * 0.92f, 0.08f, 1f);
|
||||||
sun.setColor(sunBaseColor.mult(scale));
|
sun.setColor(sunBaseColor.mult(sunDirect * (1f - caveFactor)));
|
||||||
if (shadowFilter != null) {
|
|
||||||
shadowFilter.setShadowIntensity(shadowBaseIntensity * scale);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Mond-Phasen-Mapping ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bildet moonAge (0=Neumond, 0.5=Vollmond) auf die fünf LunarPhase-Texturen ab.
|
||||||
|
* setPhase(LunarPhase) setzt gleichzeitig den Längenunterschied zur Sonne,
|
||||||
|
* sodass der Mond sich korrekt relativ zur Sonne positioniert.
|
||||||
|
*/
|
||||||
|
private static LunarPhase lunarPhaseFor(float age) {
|
||||||
|
if (age < 0.20f) return LunarPhase.WAXING_CRESCENT;
|
||||||
|
if (age < 0.40f) return LunarPhase.WAXING_GIBBOUS;
|
||||||
|
if (age < 0.60f) return LunarPhase.FULL;
|
||||||
|
if (age < 0.80f) return LunarPhase.WANING_GIBBOUS;
|
||||||
|
return LunarPhase.WANING_CRESCENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Cloud-Okklusion (CPU-Replikation des CloudDome-Shaders) ──────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt zurück, wie stark die Sonne durch den CloudDome verdeckt ist (0=frei, 1=verdeckt).
|
||||||
|
* Gleiche FBM-Formel wie CloudDome.frag, ausgewertet für die Sonnenrichtung.
|
||||||
|
*/
|
||||||
|
private float sunOcclusionByCloud(Vector3f sunDir, float cloudCover) {
|
||||||
|
if (sunDir.y <= 0.02f) return 1f; // Sonne unter Horizont → keine Schatten sowieso
|
||||||
|
float denom = sunDir.y + 1.001f;
|
||||||
|
float u = sunDir.x / denom * 3.5f + cloudOffsetX;
|
||||||
|
float v = sunDir.z / denom * 3.5f + cloudOffsetZ;
|
||||||
|
float f = fbmCpu(u, v);
|
||||||
|
float threshold = 0.72f - 0.5f * cloudCover; // mix(0.72, 0.22, cloudCover)
|
||||||
|
float t = FastMath.clamp((f - (threshold - 0.09f)) / 0.18f, 0f, 1f);
|
||||||
|
return t * t * (3f - 2f * t); // smoothstep
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float fbmCpu(float px, float py) {
|
||||||
|
float v = 0f, a = 0.5f;
|
||||||
|
for (int i = 0; i < 6; i++) {
|
||||||
|
v += a * vnoiseCpu(px, py);
|
||||||
|
px = px * 2.17f + 0.631f;
|
||||||
|
py = py * 2.17f + 1.137f;
|
||||||
|
a *= 0.5f;
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float vnoiseCpu(float px, float py) {
|
||||||
|
float ix = (float) Math.floor(px), iy = (float) Math.floor(py);
|
||||||
|
float fx = px - ix, fy = py - iy;
|
||||||
|
fx = fx * fx * (3f - 2f * fx);
|
||||||
|
fy = fy * fy * (3f - 2f * fy);
|
||||||
|
float a = hashCpu(ix, iy) + fx * (hashCpu(ix + 1f, iy) - hashCpu(ix, iy));
|
||||||
|
float b = hashCpu(ix, iy + 1f) + fx * (hashCpu(ix + 1f, iy + 1f) - hashCpu(ix, iy + 1f));
|
||||||
|
return a + fy * (b - a);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float hashCpu(float x, float y) {
|
||||||
|
double s = Math.sin(x * 127.1 + y * 311.7) * 43758.5453;
|
||||||
|
return (float) (s - Math.floor(s));
|
||||||
|
}
|
||||||
|
|
||||||
// ── Zeit-Callback ─────────────────────────────────────────────────────────
|
// ── Zeit-Callback ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -251,12 +398,17 @@ public class DayNightState extends BaseAppState implements TimeListener {
|
|||||||
ColorRGBA custNight = gameMode ? LightingConfig.CUSTOM_AMB_NIGHT : LightingConfig.EDITOR_CUSTOM_AMB_NIGHT;
|
ColorRGBA custNight = gameMode ? LightingConfig.CUSTOM_AMB_NIGHT : LightingConfig.EDITOR_CUSTOM_AMB_NIGHT;
|
||||||
float sunIntensity = gameMode ? LightingConfig.SUN_INTENSITY : LightingConfig.EDITOR_SUN_INTENSITY;
|
float sunIntensity = gameMode ? LightingConfig.SUN_INTENSITY : LightingConfig.EDITOR_SUN_INTENSITY;
|
||||||
|
|
||||||
|
// ── Wolkendecke dämpft Sonnenlicht und Schatten ──────────────────────────
|
||||||
|
WeatherState ws = app.getStateManager().getState(WeatherState.class);
|
||||||
|
float cloudCover = ws != null ? ws.getCloudCover() : 0f;
|
||||||
|
cloudDimFactor = Math.max(0.20f, 1f - cloudCover * 0.80f);
|
||||||
|
|
||||||
// ── Sonnenfarbe: Dämmerung (orange) → Tag (warm-weiß) — jedes Frame ──
|
// ── Sonnenfarbe: Dämmerung (orange) → Tag (warm-weiß) — jedes Frame ──
|
||||||
float dawnFactor = 1f - FastMath.clamp(elevation * LightingConfig.SUN_DAWN_SLOPE, 0f, 1f);
|
float dawnFactor = 1f - FastMath.clamp(elevation * LightingConfig.SUN_DAWN_SLOPE, 0f, 1f);
|
||||||
ColorRGBA sunTint = LightingConfig.SUN_COLOR_DAWN.clone()
|
ColorRGBA sunTint = LightingConfig.SUN_COLOR_DAWN.clone()
|
||||||
.interpolateLocal(LightingConfig.SUN_COLOR_DAY, 1f - dawnFactor);
|
.interpolateLocal(LightingConfig.SUN_COLOR_DAY, 1f - dawnFactor);
|
||||||
sunBaseColor = sunTint.mult(elevC * sunIntensity);
|
sunBaseColor = sunTint.mult(elevC * sunIntensity * cloudDimFactor);
|
||||||
sun.setColor(sunBaseColor.mult(1f - caveFactor));
|
// sun.setColor() wird jedes Frame in update() mit sunOcc-Faktor gesetzt
|
||||||
|
|
||||||
// ── Ambient: Nacht (blau) → Tag (Sonnenfarbe-tinted) — jedes Frame ──
|
// ── Ambient: Nacht (blau) → Tag (Sonnenfarbe-tinted) — jedes Frame ──
|
||||||
// Tages-Ambient bekommt den Farbton der Sonne: orange bei Dämmerung,
|
// Tages-Ambient bekommt den Farbton der Sonne: orange bei Dämmerung,
|
||||||
@@ -268,16 +420,15 @@ public class DayNightState extends BaseAppState implements TimeListener {
|
|||||||
ambient.setColor(ambNight.clone().interpolateLocal(ambDayTinted, ambFactor));
|
ambient.setColor(ambNight.clone().interpolateLocal(ambDayTinted, ambFactor));
|
||||||
customAmbient.set(custNight.clone().interpolateLocal(custDayTinted, ambFactor));
|
customAmbient.set(custNight.clone().interpolateLocal(custDayTinted, ambFactor));
|
||||||
|
|
||||||
|
// Basis-Schattenintensität: nur von Sonnenhöhe abhängig.
|
||||||
|
// Wolken-Okklusion wird jedes Frame separat in update() berechnet.
|
||||||
shadowBaseIntensity = FastMath.clamp(
|
shadowBaseIntensity = FastMath.clamp(
|
||||||
elevation * LightingConfig.SHADOW_SLOPE, 0f, LightingConfig.SHADOW_MAX);
|
elevation * LightingConfig.SHADOW_SLOPE, 0f, LightingConfig.SHADOW_MAX);
|
||||||
|
|
||||||
// Lichtrichtung und Schatten nur 4×/Sek aktualisieren → kein Shadow-Map-Flackern
|
// Lichtrichtung nur alle 2 s aktualisieren → kein Shadow-Map-Flackern
|
||||||
if (sunDirTimer >= SUN_DIR_INTERVAL) {
|
if (sunDirTimer >= SUN_DIR_INTERVAL) {
|
||||||
sunDirTimer = 0f;
|
sunDirTimer = 0f;
|
||||||
sun.setDirection(toSun.negateLocal());
|
sun.setDirection(toSun.negateLocal());
|
||||||
if (shadowFilter != null) {
|
|
||||||
shadowFilter.setShadowIntensity(shadowBaseIntensity * (1f - caveFactor));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,6 +112,28 @@ public class GrassState extends BaseAppState
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WeatherState ws = getApplication().getStateManager().getState(WeatherState.class);
|
||||||
|
if (ws != null) {
|
||||||
|
Vector3f wd3 = ws.getWindDirection();
|
||||||
|
Vector2f wDir = new Vector2f(wd3.x, wd3.z);
|
||||||
|
float speed = Math.max(0.05f, ws.getWindSpeed() * 0.04f);
|
||||||
|
float strength = FastMath.clamp(ws.getWindSpeed() * 0.009f, 0.01f, 0.45f);
|
||||||
|
for (Material mat : slotMaterials.values()) {
|
||||||
|
mat.setVector2("WindDir", wDir);
|
||||||
|
mat.setFloat("WindSpeed", speed);
|
||||||
|
mat.setFloat("WindStrength", strength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RainState rs = getApplication().getStateManager().getState(RainState.class);
|
||||||
|
if (rs != null) {
|
||||||
|
float w = rs.getWetness();
|
||||||
|
for (Material mat : slotMaterials.values()) {
|
||||||
|
if (mat.getMaterialDef().getMaterialParam("Wetness") != null)
|
||||||
|
mat.setFloat("Wetness", w);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Material ──────────────────────────────────────────────────────────────
|
// ── Material ──────────────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -193,6 +193,20 @@ public class GrassVertexRenderState extends BaseAppState
|
|||||||
sm.setFloat("WindStrength", strength);
|
sm.setFloat("WindStrength", strength);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RainState rs = getApplication().getStateManager().getState(RainState.class);
|
||||||
|
if (rs != null) {
|
||||||
|
float w = rs.getWetness();
|
||||||
|
setWetness(material, w);
|
||||||
|
setWetness(seedStalkMaterial, w);
|
||||||
|
for (Material sm : seedMaterials) setWetness(sm, w);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void setWetness(Material mat, float w) {
|
||||||
|
if (mat != null && mat.getMaterialDef().getMaterialParam("Wetness") != null) {
|
||||||
|
mat.setFloat("Wetness", w);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Material ──────────────────────────────────────────────────────────────
|
// ── Material ──────────────────────────────────────────────────────────────
|
||||||
|
|||||||
310
blight-game/src/main/java/de/blight/game/state/RainState.java
Normal file
310
blight-game/src/main/java/de/blight/game/state/RainState.java
Normal file
@@ -0,0 +1,310 @@
|
|||||||
|
package de.blight.game.state;
|
||||||
|
|
||||||
|
import com.jme3.app.Application;
|
||||||
|
import com.jme3.app.SimpleApplication;
|
||||||
|
import com.jme3.app.state.BaseAppState;
|
||||||
|
import com.jme3.asset.AssetManager;
|
||||||
|
import com.jme3.audio.AudioData;
|
||||||
|
import com.jme3.audio.AudioNode;
|
||||||
|
import com.jme3.effect.ParticleEmitter;
|
||||||
|
import com.jme3.effect.ParticleMesh;
|
||||||
|
import com.jme3.effect.shapes.EmitterBoxShape;
|
||||||
|
import com.jme3.light.AmbientLight;
|
||||||
|
import com.jme3.math.ColorRGBA;
|
||||||
|
import com.jme3.math.FastMath;
|
||||||
|
import com.jme3.math.Vector3f;
|
||||||
|
import com.jme3.scene.Node;
|
||||||
|
import com.jme3.texture.Image;
|
||||||
|
import com.jme3.texture.Texture2D;
|
||||||
|
import com.jme3.texture.image.ColorSpace;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.io.FileWriter;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Regen-Partikel, Nass-Effekt, Gewitter-Blitz und Donner-Audio.
|
||||||
|
* Wetness (0..1) wird von anderen States abgefragt und als Shader-Uniform gesetzt.
|
||||||
|
*/
|
||||||
|
public class RainState extends BaseAppState {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(RainState.class);
|
||||||
|
|
||||||
|
// ── Wetness ───────────────────────────────────────────────────────────────
|
||||||
|
private static final float WET_RATE_UP = 0.020f; // pro Sekunde
|
||||||
|
private static final float WET_RATE_DOWN = 0.005f; // langsames Abtrocknen
|
||||||
|
private float wetness = 0f;
|
||||||
|
|
||||||
|
// ── Regen-Partikel ────────────────────────────────────────────────────────
|
||||||
|
private static final float EMITTER_HEIGHT = 26f;
|
||||||
|
private static final float EMITTER_RADIUS = 45f;
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
private ParticleEmitter rainEmitter;
|
||||||
|
private Node rainNode;
|
||||||
|
|
||||||
|
// ── Audio ─────────────────────────────────────────────────────────────────
|
||||||
|
private AudioNode lightRainAudio;
|
||||||
|
private AudioNode heavyRainAudio;
|
||||||
|
private final AudioNode[] thunderAudio = new AudioNode[3];
|
||||||
|
private float lightRainVol = 0f;
|
||||||
|
private float heavyRainVol = 0f;
|
||||||
|
|
||||||
|
// ── Blitz / Donner ────────────────────────────────────────────────────────
|
||||||
|
private AmbientLight lightningLight;
|
||||||
|
private float nextLightningTimer = 18f;
|
||||||
|
private float flashTimer = 0f;
|
||||||
|
private static final float FLASH_DURATION = 0.13f;
|
||||||
|
private boolean thunderPending = false;
|
||||||
|
private float thunderTimer = 0f;
|
||||||
|
private int thunderIdx = 0;
|
||||||
|
|
||||||
|
// ── Persistenz ────────────────────────────────────────────────────────────
|
||||||
|
private float saveTimer = 60f;
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/** Nässe-Faktor 0..1 — wird von Terrain/Objekt-States als Shader-Uniform gesetzt. */
|
||||||
|
public float getWetness() { return wetness; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
protected void initialize(Application app) {
|
||||||
|
SimpleApplication sa = (SimpleApplication) app;
|
||||||
|
Node root = sa.getRootNode();
|
||||||
|
AssetManager assets = app.getAssetManager();
|
||||||
|
|
||||||
|
// Blitz-Licht (startet bei Intensität 0)
|
||||||
|
lightningLight = new AmbientLight(new ColorRGBA(0, 0, 0, 1));
|
||||||
|
root.addLight(lightningLight);
|
||||||
|
|
||||||
|
// Regen-Partikel
|
||||||
|
rainEmitter = new ParticleEmitter("rain", ParticleMesh.Type.Triangle, 5000);
|
||||||
|
rainEmitter.setShape(new EmitterBoxShape(
|
||||||
|
new Vector3f(-EMITTER_RADIUS, -1f, -EMITTER_RADIUS),
|
||||||
|
new Vector3f( EMITTER_RADIUS, 1f, EMITTER_RADIUS)));
|
||||||
|
rainEmitter.setGravity(0, 0, 0);
|
||||||
|
rainEmitter.setParticlesPerSec(0);
|
||||||
|
rainEmitter.setLowLife(0.9f);
|
||||||
|
rainEmitter.setHighLife(1.2f);
|
||||||
|
rainEmitter.setStartSize(0.15f);
|
||||||
|
rainEmitter.setEndSize(0.06f);
|
||||||
|
rainEmitter.setStartColor(new ColorRGBA(0.82f, 0.90f, 1.00f, 0.80f));
|
||||||
|
rainEmitter.setEndColor( new ColorRGBA(0.82f, 0.90f, 1.00f, 0.00f));
|
||||||
|
rainEmitter.setInitialVelocity(new Vector3f(0f, -26f, 0f));
|
||||||
|
rainEmitter.setVelocityVariation(0.06f);
|
||||||
|
com.jme3.material.Material pm =
|
||||||
|
new com.jme3.material.Material(assets, "Common/MatDefs/Misc/Particle.j3md");
|
||||||
|
pm.setTexture("Texture", createRainTexture());
|
||||||
|
pm.getAdditionalRenderState()
|
||||||
|
.setBlendMode(com.jme3.material.RenderState.BlendMode.Alpha);
|
||||||
|
pm.getAdditionalRenderState().setDepthWrite(false);
|
||||||
|
rainEmitter.setMaterial(pm);
|
||||||
|
|
||||||
|
rainNode = new Node("rainNode");
|
||||||
|
rainNode.attachChild(rainEmitter);
|
||||||
|
root.attachChild(rainNode);
|
||||||
|
|
||||||
|
// Regen-Audio (gestreamt, da lange Schleifen)
|
||||||
|
lightRainAudio = streamAudio(assets, "audio/ambient/weather/leichter_regen.ogg");
|
||||||
|
heavyRainAudio = streamAudio(assets, "audio/ambient/weather/starker_regen.ogg");
|
||||||
|
root.attachChild(lightRainAudio);
|
||||||
|
root.attachChild(heavyRainAudio);
|
||||||
|
lightRainAudio.play();
|
||||||
|
heavyRainAudio.play();
|
||||||
|
|
||||||
|
// Donner (gepuffert, kurze Clips)
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
thunderAudio[i] = bufferAudio(assets, "audio/ambient/weather/donner" + (i + 1) + ".ogg");
|
||||||
|
root.attachChild(thunderAudio[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadWetness();
|
||||||
|
log.info("[Rain] initialisiert.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
public void update(float tpf) {
|
||||||
|
WeatherState ws = getApplication().getStateManager().getState(WeatherState.class);
|
||||||
|
if (ws == null) return;
|
||||||
|
|
||||||
|
float intensity = ws.getRainIntensity();
|
||||||
|
WeatherState.Weather weather = ws.getActiveWeather();
|
||||||
|
boolean stormy = weather == WeatherState.Weather.THUNDERSTORM;
|
||||||
|
|
||||||
|
saveTimer -= tpf;
|
||||||
|
if (saveTimer <= 0f) { saveTimer = 60f; saveWetness(); }
|
||||||
|
|
||||||
|
// ── Wetness ───────────────────────────────────────────────────────────
|
||||||
|
float wetnessTarget = intensity > 0.01f ? FastMath.clamp(intensity + 0.25f, 0f, 1f) : 0f;
|
||||||
|
if (wetness < wetnessTarget) {
|
||||||
|
wetness = Math.min(wetnessTarget, wetness + WET_RATE_UP * tpf);
|
||||||
|
} else {
|
||||||
|
wetness = Math.max(wetnessTarget, wetness - WET_RATE_DOWN * tpf);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Partikel ──────────────────────────────────────────────────────────
|
||||||
|
Vector3f cam = getApplication().getCamera().getLocation();
|
||||||
|
rainNode.setLocalTranslation(cam.x, cam.y + EMITTER_HEIGHT, cam.z);
|
||||||
|
|
||||||
|
Vector3f wind = ws.getWindDirection();
|
||||||
|
float ws2 = ws.getWindSpeed() * 0.07f;
|
||||||
|
rainEmitter.getParticleInfluencer()
|
||||||
|
.setInitialVelocity(new Vector3f(wind.x * ws2, -26f, wind.z * ws2));
|
||||||
|
|
||||||
|
rainEmitter.setParticlesPerSec(intensity * 2500f);
|
||||||
|
|
||||||
|
// ── Audio ─────────────────────────────────────────────────────────────
|
||||||
|
float tgtLight = intensity > 0.01f ? FastMath.clamp(1f - intensity * 1.2f, 0f, 0.225f) : 0f;
|
||||||
|
float tgtHeavy = FastMath.clamp((intensity - 0.20f) * 1.35f, 0f, 0.25f);
|
||||||
|
|
||||||
|
AudioSettingsState audioState = getApplication().getStateManager()
|
||||||
|
.getState(AudioSettingsState.class);
|
||||||
|
float ambVol = audioState != null ? audioState.effectiveAmbient() : 1f;
|
||||||
|
|
||||||
|
float alpha = FastMath.clamp(tpf * 2.5f, 0f, 1f);
|
||||||
|
lightRainVol += (tgtLight - lightRainVol) * alpha;
|
||||||
|
heavyRainVol += (tgtHeavy - heavyRainVol) * alpha;
|
||||||
|
lightRainAudio.setVolume(lightRainVol * ambVol);
|
||||||
|
heavyRainAudio.setVolume(heavyRainVol * ambVol);
|
||||||
|
|
||||||
|
// ── Blitz-Flash ───────────────────────────────────────────────────────
|
||||||
|
if (flashTimer > 0f) {
|
||||||
|
flashTimer = Math.max(0f, flashTimer - tpf);
|
||||||
|
float phase = 1f - flashTimer / FLASH_DURATION;
|
||||||
|
float fi = (float) Math.sin(phase * Math.PI);
|
||||||
|
lightningLight.setColor(new ColorRGBA(fi, fi * 0.95f, fi * 0.85f, 1f));
|
||||||
|
} else {
|
||||||
|
lightningLight.setColor(new ColorRGBA(0f, 0f, 0f, 1f));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Donner-Verzögerung ────────────────────────────────────────────────
|
||||||
|
if (thunderPending) {
|
||||||
|
thunderTimer -= tpf;
|
||||||
|
if (thunderTimer <= 0f) {
|
||||||
|
thunderPending = false;
|
||||||
|
float vol = FastMath.clamp(1f - (2.5f + thunderTimer) / 2.5f, 0.15f, 1f);
|
||||||
|
thunderAudio[thunderIdx].setVolume(vol);
|
||||||
|
thunderAudio[thunderIdx].playInstance();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Nächster Blitz ────────────────────────────────────────────────────
|
||||||
|
nextLightningTimer -= tpf;
|
||||||
|
if (nextLightningTimer <= 0f) {
|
||||||
|
if (stormy) {
|
||||||
|
// Gewitter: Blitz + Donner alle 8-28s
|
||||||
|
triggerLightning(true);
|
||||||
|
nextLightningTimer = 8f + FastMath.nextRandomFloat() * 20f;
|
||||||
|
} else if (weather == WeatherState.Weather.RAIN_HEAVY) {
|
||||||
|
// Starker Regen: ferner Donner ohne Blitz alle 40-80s
|
||||||
|
triggerLightning(false);
|
||||||
|
nextLightningTimer = 40f + FastMath.nextRandomFloat() * 40f;
|
||||||
|
} else {
|
||||||
|
nextLightningTimer = 30f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void triggerLightning(boolean withFlash) {
|
||||||
|
if (withFlash) flashTimer = FLASH_DURATION;
|
||||||
|
thunderIdx = (int)(FastMath.nextRandomFloat() * 3);
|
||||||
|
float delay = FastMath.nextRandomFloat() * 2.5f + 0.3f;
|
||||||
|
thunderPending = true;
|
||||||
|
thunderTimer = delay;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDisable() {
|
||||||
|
if (rainEmitter != null) rainEmitter.setParticlesPerSec(0);
|
||||||
|
if (lightRainAudio != null) lightRainAudio.setVolume(0);
|
||||||
|
if (heavyRainAudio != null) heavyRainAudio.setVolume(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override protected void onEnable() {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void cleanup(Application app) {
|
||||||
|
saveWetness();
|
||||||
|
Node root = ((SimpleApplication) app).getRootNode();
|
||||||
|
if (lightningLight != null) root.removeLight(lightningLight);
|
||||||
|
if (rainNode != null) root.detachChild(rainNode);
|
||||||
|
if (lightRainAudio != null) { lightRainAudio.stop(); root.detachChild(lightRainAudio); }
|
||||||
|
if (heavyRainAudio != null) { heavyRainAudio.stop(); root.detachChild(heavyRainAudio); }
|
||||||
|
for (AudioNode tn : thunderAudio) { if (tn != null) root.detachChild(tn); }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Persistenz ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private static File wetFile() {
|
||||||
|
File dir = new File(System.getProperty("user.home"), ".blight");
|
||||||
|
dir.mkdirs();
|
||||||
|
return new File(dir, "rain.properties");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveWetness() {
|
||||||
|
Properties p = new Properties();
|
||||||
|
p.setProperty("wetness", String.valueOf(wetness));
|
||||||
|
try (FileWriter fw = new FileWriter(wetFile())) {
|
||||||
|
p.store(fw, null);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("[Rain] Speichern fehlgeschlagen: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadWetness() {
|
||||||
|
File f = wetFile();
|
||||||
|
if (!f.exists()) return;
|
||||||
|
Properties p = new Properties();
|
||||||
|
try (FileReader fr = new FileReader(f)) {
|
||||||
|
p.load(fr);
|
||||||
|
wetness = Float.parseFloat(p.getProperty("wetness", "0"));
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("[Rain] Laden fehlgeschlagen: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Helpers ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private static Texture2D createRainTexture() {
|
||||||
|
int w = 4, h = 32;
|
||||||
|
ByteBuffer buf = ByteBuffer.allocateDirect(w * h * 4);
|
||||||
|
for (int y = 0; y < h; y++) {
|
||||||
|
float fy = (float) y / (h - 1);
|
||||||
|
// Oben ausgeblendet (Tropfen erscheint), unten voll sichtbar → Strich-Silhouette
|
||||||
|
float ay = FastMath.clamp(fy * 5f, 0f, 1f) * FastMath.clamp((1f - fy) * 2f, 0f, 1f);
|
||||||
|
for (int x = 0; x < w; x++) {
|
||||||
|
float fx = Math.abs((x - (w - 1) * 0.5f) / ((w - 1) * 0.5f));
|
||||||
|
float ax = FastMath.clamp(1f - fx * 1.5f, 0f, 1f);
|
||||||
|
float alpha = ay * ax;
|
||||||
|
buf.put((byte) 210);
|
||||||
|
buf.put((byte) 228);
|
||||||
|
buf.put((byte) 255);
|
||||||
|
buf.put((byte) (int) (alpha * 230));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf.flip();
|
||||||
|
return new Texture2D(new Image(Image.Format.RGBA8, w, h, buf, ColorSpace.Linear));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AudioNode streamAudio(AssetManager assets, String path) {
|
||||||
|
AudioNode n = new AudioNode(assets, path, AudioData.DataType.Stream);
|
||||||
|
n.setPositional(false);
|
||||||
|
n.setLooping(true);
|
||||||
|
n.setVolume(0f);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AudioNode bufferAudio(AssetManager assets, String path) {
|
||||||
|
AudioNode n = new AudioNode(assets, path, AudioData.DataType.Buffer);
|
||||||
|
n.setPositional(false);
|
||||||
|
n.setLooping(false);
|
||||||
|
n.setVolume(1f);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,6 +30,7 @@ public class StoneWorldState extends BaseAppState {
|
|||||||
private BulletAppState bulletAppState;
|
private BulletAppState bulletAppState;
|
||||||
private TerrainChunkState terrainChunkState;
|
private TerrainChunkState terrainChunkState;
|
||||||
private Node stoneRoot;
|
private Node stoneRoot;
|
||||||
|
private final List<Material> stoneMaterials = new ArrayList<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void initialize(Application app) {
|
protected void initialize(Application app) {
|
||||||
@@ -56,6 +57,7 @@ public class StoneWorldState extends BaseAppState {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stoneMaterials.clear();
|
||||||
Material[] slotMat = buildMaterials(data.slotPaths());
|
Material[] slotMat = buildMaterials(data.slotPaths());
|
||||||
Material defMat = buildDefaultMat();
|
Material defMat = buildDefaultMat();
|
||||||
|
|
||||||
@@ -86,12 +88,27 @@ public class StoneWorldState extends BaseAppState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stoneMaterials.add(mat);
|
||||||
stoneRoot.attachChild(geo);
|
stoneRoot.attachChild(geo);
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
log.info("[StoneWorld] {} Steine geladen.", count);
|
log.info("[StoneWorld] {} Steine geladen.", count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(float tpf) {
|
||||||
|
if (stoneMaterials.isEmpty()) return;
|
||||||
|
RainState rs = getApplication().getStateManager().getState(RainState.class);
|
||||||
|
if (rs == null) return;
|
||||||
|
float w = rs.getWetness();
|
||||||
|
float roughness = 0.85f * (1f - w * 0.80f);
|
||||||
|
float metallic = w * 0.10f;
|
||||||
|
for (Material mat : stoneMaterials) {
|
||||||
|
mat.setFloat("Roughness", roughness);
|
||||||
|
mat.setFloat("Metallic", metallic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void cleanup(Application app) {
|
protected void cleanup(Application app) {
|
||||||
this.app.getRootNode().detachChild(stoneRoot);
|
this.app.getRootNode().detachChild(stoneRoot);
|
||||||
|
|||||||
@@ -219,6 +219,7 @@ public class TerrainChunkState extends BaseAppState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Loop 1: Meshes für schmutzige Chunks + Nachbarn neu aufbauen
|
||||||
for (int cz = 0; cz < N; cz++) {
|
for (int cz = 0; cz < N; cz++) {
|
||||||
for (int cx = 0; cx < N; cx++) {
|
for (int cx = 0; cx < N; cx++) {
|
||||||
int ci = ChunkTerrainIO.chunkIndex(cx, cz);
|
int ci = ChunkTerrainIO.chunkIndex(cx, cz);
|
||||||
@@ -229,12 +230,6 @@ public class TerrainChunkState extends BaseAppState {
|
|||||||
|
|
||||||
rebuildChunkMesh(cx, cz, newLod, targetLod);
|
rebuildChunkMesh(cx, cz, newLod, targetLod);
|
||||||
|
|
||||||
// Physik: nur für nahe Chunks halten
|
|
||||||
int dist = ChunkTerrainIO.chebyshev(cx, cz, pcx, pcz);
|
|
||||||
boolean wantsPhysics = dist <= ChunkTerrainIO.PHYSICS_RANGE;
|
|
||||||
if (wantsPhysics && physics[ci] == null) addPhysics(ci);
|
|
||||||
if (!wantsPhysics && physics[ci] != null) removePhysics(ci);
|
|
||||||
|
|
||||||
// Listener benachrichtigen
|
// Listener benachrichtigen
|
||||||
if (oldLod < 0) {
|
if (oldLod < 0) {
|
||||||
notifyVisible(cx, cz, newLod);
|
notifyVisible(cx, cz, newLod);
|
||||||
@@ -243,6 +238,18 @@ public class TerrainChunkState extends BaseAppState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Loop 2: Physik für alle Chunks aktualisieren – nach dem Mesh-Rebuild,
|
||||||
|
// damit chunkNodes bereits existieren wenn addPhysics aufgerufen wird.
|
||||||
|
for (int cz = 0; cz < N; cz++) {
|
||||||
|
for (int cx = 0; cx < N; cx++) {
|
||||||
|
int ci = ChunkTerrainIO.chunkIndex(cx, cz);
|
||||||
|
int dist = ChunkTerrainIO.chebyshev(cx, cz, pcx, pcz);
|
||||||
|
boolean wantsPhysics = dist <= ChunkTerrainIO.PHYSICS_RANGE;
|
||||||
|
if (wantsPhysics && physics[ci] == null) addPhysics(ci);
|
||||||
|
if (!wantsPhysics && physics[ci] != null) removePhysics(ci);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Öffentliche API ───────────────────────────────────────────────────────
|
// ── Öffentliche API ───────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -6,136 +6,324 @@ import com.jme3.math.ColorRGBA;
|
|||||||
import com.jme3.math.FastMath;
|
import com.jme3.math.FastMath;
|
||||||
import com.jme3.math.Vector2f;
|
import com.jme3.math.Vector2f;
|
||||||
import com.jme3.math.Vector3f;
|
import com.jme3.math.Vector3f;
|
||||||
|
import de.blight.common.time.DayTime;
|
||||||
import de.blight.game.post.BlightFogFilter;
|
import de.blight.game.post.BlightFogFilter;
|
||||||
import com.jme3.water.WaterFilter;
|
import com.jme3.water.WaterFilter;
|
||||||
import jme3utilities.sky.SkyControl;
|
import jme3utilities.sky.SkyControl;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.io.FileWriter;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
public class WeatherState extends BaseAppState {
|
public class WeatherState extends BaseAppState {
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(WeatherState.class);
|
private static final Logger log = LoggerFactory.getLogger(WeatherState.class);
|
||||||
|
|
||||||
public enum Weather { SUNNY, CLOUDY, OVERCAST, STORM, FOG }
|
/**
|
||||||
|
* Wetterübergänge folgen meteorologischer Logik: Regen eskaliert stufenweise,
|
||||||
|
* ein Gewitter setzt immer starken Regen voraus. CALIMA (Sahara-Staub) ist
|
||||||
|
* kanaren-typisch.
|
||||||
|
*
|
||||||
|
* Ordinal-Reihenfolge darf nicht verändert werden — RAIN_INTENSITY und alle
|
||||||
|
* anderen Arrays sind darauf indiziert.
|
||||||
|
*/
|
||||||
|
public enum Weather {
|
||||||
|
SUNNY, // 0 — strahlend blauer Himmel
|
||||||
|
PARTLY_CLOUDY, // 1 — einzelne Cumulus-Wolken
|
||||||
|
CLOUDY, // 2 — bedeckt, aber trocken
|
||||||
|
OVERCAST, // 3 — dunkle Wolkendecke
|
||||||
|
FOG, // 4 — Passatwind-Nebel (Bruma)
|
||||||
|
DRIZZLE, // 5 — leichter Nieselregen
|
||||||
|
RAIN_LIGHT, // 6 — leichter Regen
|
||||||
|
RAIN_MEDIUM, // 7 — mäßiger Regen
|
||||||
|
RAIN_HEAVY, // 8 — starker Regen
|
||||||
|
THUNDERSTORM, // 9 — Gewitter mit Blitz & Donner
|
||||||
|
CALIMA, // 10 — Sahara-Staub, gelblicher Dunst
|
||||||
|
WIND_STORM // 11 — Sturm ohne Regen (Tramontana)
|
||||||
|
}
|
||||||
|
|
||||||
// ── Per-weather targets (Reihenfolge: SUNNY, CLOUDY, OVERCAST, STORM, FOG) ─
|
// ── Per-Zustand-Zielwerte (Reihenfolge = Weather.ordinal()) ──────────────
|
||||||
|
|
||||||
private static final float[] FOG_DENSITY = { 0.40f, 0.55f, 0.75f, 0.90f, 0.88f };
|
private static final float[] FOG_DENSITY = {
|
||||||
private static final float[] FOG_DISTANCE = { 600f, 350f, 140f, 50f, 80f };
|
0.10f, 0.18f, 0.35f, 0.65f, 0.88f,
|
||||||
private static final float[] WIND_SPEED = { 4f, 14f, 26f, 55f, 3f };
|
0.55f, 0.50f, 0.72f, 0.85f, 0.92f,
|
||||||
private static final float[] WAVE_SPEED = { 0.5f, 1.0f, 1.5f, 3.2f, 0.4f };
|
0.70f, 0.22f
|
||||||
private static final float[] WAVE_AMP = { 0.3f, 0.5f, 0.8f, 1.8f, 0.2f };
|
};
|
||||||
private static final float[] WAVE_SCALE = { 0.008f, 0.007f, 0.006f, 0.005f, 0.009f};
|
private static final float[] FOG_DISTANCE = {
|
||||||
private static final float[] WATER_TRANS = { 0.15f, 0.10f, 0.07f, 0.02f, 0.12f };
|
800f, 600f, 400f, 180f, 80f,
|
||||||
private static final float[] FOAM_INTENSITY= { 0.0f, 0.20f, 0.45f, 0.90f, 0.0f };
|
220f, 280f, 130f, 70f, 50f,
|
||||||
private static final float[] CLOUD_OPACITY = { 0.0f, 0.40f, 0.80f, 1.00f, 0.95f };
|
120f, 500f
|
||||||
|
};
|
||||||
|
private static final float[] WIND_SPEED = {
|
||||||
|
3f, 8f, 16f, 22f, 2f,
|
||||||
|
10f, 14f, 28f, 42f, 62f,
|
||||||
|
20f, 72f
|
||||||
|
};
|
||||||
|
private static final float[] WAVE_SPEED = {
|
||||||
|
0.4f, 0.6f, 0.9f, 1.3f, 0.3f,
|
||||||
|
0.7f, 1.0f, 1.8f, 2.5f, 3.5f,
|
||||||
|
0.8f, 3.8f
|
||||||
|
};
|
||||||
|
private static final float[] WAVE_AMP = {
|
||||||
|
0.20f, 0.30f, 0.50f, 0.75f, 0.15f,
|
||||||
|
0.40f, 0.50f, 1.00f, 1.40f, 2.00f,
|
||||||
|
0.35f, 2.20f
|
||||||
|
};
|
||||||
|
private static final float[] WAVE_SCALE = {
|
||||||
|
0.009f, 0.008f, 0.007f, 0.006f, 0.010f,
|
||||||
|
0.007f, 0.007f, 0.006f, 0.005f, 0.005f,
|
||||||
|
0.008f, 0.005f
|
||||||
|
};
|
||||||
|
private static final float[] WATER_TRANS = {
|
||||||
|
0.18f, 0.14f, 0.10f, 0.07f, 0.13f,
|
||||||
|
0.09f, 0.09f, 0.04f, 0.02f, 0.01f,
|
||||||
|
0.12f, 0.06f
|
||||||
|
};
|
||||||
|
private static final float[] FOAM_INTENSITY = {
|
||||||
|
0.00f, 0.05f, 0.20f, 0.40f, 0.00f,
|
||||||
|
0.15f, 0.20f, 0.55f, 0.75f, 0.95f,
|
||||||
|
0.10f, 0.80f
|
||||||
|
};
|
||||||
|
private static final float[] CLOUD_OPACITY = {
|
||||||
|
0.00f, 0.30f, 0.65f, 0.90f, 0.95f,
|
||||||
|
0.75f, 0.85f, 0.95f, 1.00f, 1.00f,
|
||||||
|
0.60f, 0.50f
|
||||||
|
};
|
||||||
|
/** Opazität der gleichmäßigen Überdeckungs-Wolkenschicht (Layer 1). */
|
||||||
|
private static final float[] OVERCAST_OPACITY = {
|
||||||
|
0.00f, 0.08f, 0.35f, 0.60f, 0.50f,
|
||||||
|
0.42f, 0.52f, 0.68f, 0.82f, 0.90f,
|
||||||
|
0.12f, 0.28f
|
||||||
|
};
|
||||||
|
/** 0 = kein Regen, 1 = Gewitter. */
|
||||||
|
private static final float[] RAIN_INTENSITY = {
|
||||||
|
0f, 0f, 0f, 0f, 0f,
|
||||||
|
0.12f, 0.33f, 0.67f, 0.88f, 1.00f,
|
||||||
|
0f, 0f
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Minimale Dauer des Zustands in Sekunden. */
|
||||||
|
private static final float[] DURATION_MIN = {
|
||||||
|
180f, 90f, 60f, 60f, 120f,
|
||||||
|
45f, 60f, 45f, 30f, 20f,
|
||||||
|
180f, 90f
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Maximale Dauer des Zustands in Sekunden. */
|
||||||
|
private static final float[] DURATION_MAX = {
|
||||||
|
600f, 300f, 240f, 180f, 420f,
|
||||||
|
150f, 210f, 150f, 90f, 60f,
|
||||||
|
480f, 270f
|
||||||
|
};
|
||||||
|
|
||||||
private static final ColorRGBA[] FOG_COLOR = {
|
private static final ColorRGBA[] FOG_COLOR = {
|
||||||
new ColorRGBA(0.75f, 0.80f, 0.88f, 1f),
|
new ColorRGBA(0.78f, 0.85f, 0.95f, 1f), // SUNNY
|
||||||
new ColorRGBA(0.62f, 0.65f, 0.70f, 1f),
|
new ColorRGBA(0.75f, 0.80f, 0.90f, 1f), // PARTLY_CLOUDY
|
||||||
new ColorRGBA(0.42f, 0.43f, 0.46f, 1f),
|
new ColorRGBA(0.62f, 0.65f, 0.72f, 1f), // CLOUDY
|
||||||
new ColorRGBA(0.18f, 0.19f, 0.21f, 1f),
|
new ColorRGBA(0.42f, 0.44f, 0.48f, 1f), // OVERCAST
|
||||||
new ColorRGBA(0.70f, 0.72f, 0.75f, 1f),
|
new ColorRGBA(0.70f, 0.72f, 0.75f, 1f), // FOG
|
||||||
|
new ColorRGBA(0.50f, 0.52f, 0.58f, 1f), // DRIZZLE
|
||||||
|
new ColorRGBA(0.45f, 0.48f, 0.55f, 1f), // RAIN_LIGHT
|
||||||
|
new ColorRGBA(0.28f, 0.30f, 0.35f, 1f), // RAIN_MEDIUM
|
||||||
|
new ColorRGBA(0.18f, 0.19f, 0.22f, 1f), // RAIN_HEAVY
|
||||||
|
new ColorRGBA(0.12f, 0.13f, 0.16f, 1f), // THUNDERSTORM
|
||||||
|
new ColorRGBA(0.82f, 0.74f, 0.52f, 1f), // CALIMA — Sahara-Gelbton
|
||||||
|
new ColorRGBA(0.60f, 0.62f, 0.68f, 1f), // WIND_STORM
|
||||||
};
|
};
|
||||||
private static final ColorRGBA[] WATER_COLOR = {
|
private static final ColorRGBA[] WATER_COLOR = {
|
||||||
new ColorRGBA(0.05f, 0.25f, 0.55f, 1f),
|
new ColorRGBA(0.05f, 0.25f, 0.55f, 1f),
|
||||||
|
new ColorRGBA(0.05f, 0.22f, 0.50f, 1f),
|
||||||
new ColorRGBA(0.04f, 0.18f, 0.42f, 1f),
|
new ColorRGBA(0.04f, 0.18f, 0.42f, 1f),
|
||||||
new ColorRGBA(0.03f, 0.12f, 0.28f, 1f),
|
new ColorRGBA(0.03f, 0.12f, 0.28f, 1f),
|
||||||
new ColorRGBA(0.02f, 0.06f, 0.14f, 1f),
|
|
||||||
new ColorRGBA(0.04f, 0.20f, 0.45f, 1f),
|
new ColorRGBA(0.04f, 0.20f, 0.45f, 1f),
|
||||||
|
new ColorRGBA(0.03f, 0.15f, 0.36f, 1f),
|
||||||
|
new ColorRGBA(0.03f, 0.15f, 0.38f, 1f),
|
||||||
|
new ColorRGBA(0.02f, 0.09f, 0.22f, 1f),
|
||||||
|
new ColorRGBA(0.01f, 0.05f, 0.14f, 1f),
|
||||||
|
new ColorRGBA(0.01f, 0.03f, 0.09f, 1f),
|
||||||
|
new ColorRGBA(0.06f, 0.22f, 0.42f, 1f), // CALIMA — leicht trüb
|
||||||
|
new ColorRGBA(0.02f, 0.10f, 0.30f, 1f),
|
||||||
};
|
};
|
||||||
private static final ColorRGBA[] DEEP_WATER_COLOR = {
|
private static final ColorRGBA[] DEEP_WATER_COLOR = {
|
||||||
new ColorRGBA(0.02f, 0.12f, 0.30f, 1f),
|
new ColorRGBA(0.02f, 0.12f, 0.30f, 1f),
|
||||||
|
new ColorRGBA(0.02f, 0.10f, 0.26f, 1f),
|
||||||
new ColorRGBA(0.01f, 0.08f, 0.20f, 1f),
|
new ColorRGBA(0.01f, 0.08f, 0.20f, 1f),
|
||||||
new ColorRGBA(0.01f, 0.04f, 0.12f, 1f),
|
new ColorRGBA(0.01f, 0.04f, 0.12f, 1f),
|
||||||
new ColorRGBA(0.00f, 0.02f, 0.06f, 1f),
|
|
||||||
new ColorRGBA(0.01f, 0.09f, 0.23f, 1f),
|
new ColorRGBA(0.01f, 0.09f, 0.23f, 1f),
|
||||||
|
new ColorRGBA(0.01f, 0.07f, 0.18f, 1f),
|
||||||
|
new ColorRGBA(0.01f, 0.06f, 0.18f, 1f),
|
||||||
|
new ColorRGBA(0.00f, 0.03f, 0.10f, 1f),
|
||||||
|
new ColorRGBA(0.00f, 0.02f, 0.06f, 1f),
|
||||||
|
new ColorRGBA(0.00f, 0.01f, 0.04f, 1f),
|
||||||
|
new ColorRGBA(0.02f, 0.09f, 0.20f, 1f),
|
||||||
|
new ColorRGBA(0.01f, 0.04f, 0.14f, 1f),
|
||||||
};
|
};
|
||||||
|
|
||||||
// ── State ────────────────────────────────────────────────────────────────
|
/**
|
||||||
|
* Übergangsmatrix — Wiederholungen erhöhen die Wahrscheinlichkeit.
|
||||||
|
* Regen darf nur stufenweise eskalieren; direkter Sprung von SUNNY zu
|
||||||
|
* THUNDERSTORM ist nicht möglich.
|
||||||
|
*/
|
||||||
|
private static final Weather[][] TRANSITIONS = {
|
||||||
|
// SUNNY: fast immer erst PARTLY_CLOUDY, selten Calima oder Sturm
|
||||||
|
{ Weather.PARTLY_CLOUDY, Weather.PARTLY_CLOUDY, Weather.PARTLY_CLOUDY,
|
||||||
|
Weather.PARTLY_CLOUDY, Weather.PARTLY_CLOUDY,
|
||||||
|
Weather.CALIMA, Weather.CALIMA, Weather.WIND_STORM },
|
||||||
|
// PARTLY_CLOUDY: häufig Besserung, manchmal Eintrübung
|
||||||
|
{ Weather.SUNNY, Weather.SUNNY, Weather.SUNNY,
|
||||||
|
Weather.CLOUDY, Weather.CLOUDY, Weather.WIND_STORM },
|
||||||
|
// CLOUDY: kann zu Niesel, Überbedeckung oder Besserung wechseln
|
||||||
|
{ Weather.PARTLY_CLOUDY, Weather.PARTLY_CLOUDY,
|
||||||
|
Weather.OVERCAST, Weather.OVERCAST, Weather.DRIZZLE, Weather.WIND_STORM },
|
||||||
|
// OVERCAST: Nebel, Niesel oder leichter Regen, selten Besserung
|
||||||
|
{ Weather.CLOUDY, Weather.CLOUDY,
|
||||||
|
Weather.FOG, Weather.DRIZZLE, Weather.DRIZZLE, Weather.RAIN_LIGHT },
|
||||||
|
// FOG: löst sich langsam auf oder geht in Niesel über
|
||||||
|
{ Weather.OVERCAST, Weather.OVERCAST, Weather.CLOUDY, Weather.DRIZZLE },
|
||||||
|
// DRIZZLE: kann intensivieren oder aufhören
|
||||||
|
{ Weather.OVERCAST, Weather.OVERCAST, Weather.RAIN_LIGHT, Weather.CLOUDY },
|
||||||
|
// RAIN_LIGHT: eskaliert oder beruhigt sich zu Niesel
|
||||||
|
{ Weather.DRIZZLE, Weather.DRIZZLE,
|
||||||
|
Weather.RAIN_MEDIUM, Weather.OVERCAST, Weather.OVERCAST },
|
||||||
|
// RAIN_MEDIUM: eskaliert zu RAIN_HEAVY oder lässt nach
|
||||||
|
{ Weather.RAIN_LIGHT, Weather.RAIN_LIGHT, Weather.RAIN_HEAVY, Weather.OVERCAST },
|
||||||
|
// RAIN_HEAVY: kann zu Gewitter eskalieren oder nachlassen
|
||||||
|
{ Weather.RAIN_MEDIUM, Weather.RAIN_MEDIUM, Weather.THUNDERSTORM, Weather.OVERCAST },
|
||||||
|
// THUNDERSTORM: beruhigt sich immer stufenweise
|
||||||
|
{ Weather.RAIN_HEAVY, Weather.RAIN_HEAVY, Weather.RAIN_MEDIUM, Weather.OVERCAST },
|
||||||
|
// CALIMA: meist Aufklärung, selten Überbedeckung
|
||||||
|
{ Weather.SUNNY, Weather.SUNNY, Weather.PARTLY_CLOUDY, Weather.OVERCAST },
|
||||||
|
// WIND_STORM: klingt über Bewölkung oder Überbedeckung ab
|
||||||
|
{ Weather.CLOUDY, Weather.PARTLY_CLOUDY, Weather.PARTLY_CLOUDY, Weather.OVERCAST },
|
||||||
|
};
|
||||||
|
|
||||||
|
// ── Laufzeit-State ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
private Weather active = Weather.SUNNY;
|
private Weather active = Weather.SUNNY;
|
||||||
private float changeTimer = 120f;
|
private float changeTimer = 180f;
|
||||||
|
private float saveTimer = 60f;
|
||||||
|
|
||||||
private float fogDensity = 0.40f; // Startwert = SUNNY-Ziel, kein langsames Fade-in
|
private float fogDensity = FOG_DENSITY[0];
|
||||||
private float fogDistance = 600f;
|
private float fogDistance = FOG_DISTANCE[0];
|
||||||
private float windSpeed = 4f;
|
private float windSpeed = WIND_SPEED[0];
|
||||||
private float waveSpeed = 0.5f;
|
private float waveSpeed = WAVE_SPEED[0];
|
||||||
private float waveAmp = 0.3f;
|
private float waveAmp = WAVE_AMP[0];
|
||||||
private float waveScale = 0.008f;
|
private float waveScale = WAVE_SCALE[0];
|
||||||
private float waterTrans = 0.15f;
|
private float waterTrans = WATER_TRANS[0];
|
||||||
private float foamIntensity = 0f;
|
private float foamIntensity = FOAM_INTENSITY[0];
|
||||||
private float cloudOpacity = 0f;
|
private float cloudOpacity = CLOUD_OPACITY[0];
|
||||||
private float windAngle = 0f;
|
private float cloudOvercastOpacity = OVERCAST_OPACITY[0];
|
||||||
private float windAngleTgt = 0.4f;
|
private float cloudOffsetU = 0f;
|
||||||
private final ColorRGBA fogColor = new ColorRGBA(0.75f, 0.80f, 0.88f, 1f);
|
private float cloudOffsetV = 0f;
|
||||||
private final ColorRGBA waterColor = new ColorRGBA(0.05f, 0.25f, 0.55f, 1f);
|
private float windAngle = 0f;
|
||||||
private final ColorRGBA deepWaterColor= new ColorRGBA(0.02f, 0.12f, 0.30f, 1f);
|
private float windAngleTgt = 0.4f;
|
||||||
|
|
||||||
// ── Sichtweiten-Faktor (aus Grafikeinstellungen) ──────────────────────────
|
private final ColorRGBA fogColor = FOG_COLOR[0].clone();
|
||||||
|
private final ColorRGBA waterColor = WATER_COLOR[0].clone();
|
||||||
|
private final ColorRGBA deepWaterColor = DEEP_WATER_COLOR[0].clone();
|
||||||
|
|
||||||
private float viewDistanceFactor = 1.0f;
|
private float viewDistanceFactor = 1.0f;
|
||||||
|
|
||||||
public void setViewDistanceFactor(float f) { this.viewDistanceFactor = f; }
|
|
||||||
|
|
||||||
// ── Externe Referenzen ────────────────────────────────────────────────────
|
// ── Externe Referenzen ────────────────────────────────────────────────────
|
||||||
|
|
||||||
private BlightFogFilter fogFilter;
|
private BlightFogFilter fogFilter;
|
||||||
private WaterFilter waterFilter;
|
private WaterFilter waterFilter;
|
||||||
private SkyControl skyControl;
|
private SkyControl skyControl;
|
||||||
|
private com.jme3.post.filters.LightScatteringFilter lightScatterFilter;
|
||||||
|
private DayTime dayTime;
|
||||||
|
|
||||||
|
public void setViewDistanceFactor(float f) { this.viewDistanceFactor = f; }
|
||||||
public void setFogFilter(BlightFogFilter f) { this.fogFilter = f; }
|
public void setFogFilter(BlightFogFilter f) { this.fogFilter = f; }
|
||||||
public void setWaterFilter(WaterFilter f) { this.waterFilter = f; }
|
public void setWaterFilter(WaterFilter f) { this.waterFilter = f; }
|
||||||
public void setSkyControl(SkyControl sc) { this.skyControl = sc; }
|
public void setSkyControl(SkyControl sc) { this.skyControl = sc; }
|
||||||
|
public void setLightScatterFilter(com.jme3.post.filters.LightScatteringFilter f) { this.lightScatterFilter = f; }
|
||||||
|
public void setDayTime(DayTime dt) { this.dayTime = dt; }
|
||||||
|
|
||||||
public Weather getActiveWeather() { return active; }
|
public Weather getActiveWeather() { return active; }
|
||||||
public float getWindSpeed() { return windSpeed; }
|
public float getWindSpeed() { return windSpeed; }
|
||||||
|
public float getWindAngle() { return windAngle; }
|
||||||
/**
|
public float getFogDensity() { return fogDensity; }
|
||||||
* Setzt das Wetter sofort; Werte interpolieren sanft zum neuen Ziel.
|
public float getChangeTimer() { return changeTimer; }
|
||||||
* Der automatische Wechsel-Timer wird zurückgesetzt.
|
public float getRainIntensity() { return RAIN_INTENSITY[active.ordinal()]; }
|
||||||
*/
|
/** Normierte Wolkenbedeckung 0..1. */
|
||||||
public void forceWeather(Weather w) {
|
public float getCloudCover() { return FastMath.clamp((cloudOpacity + cloudOvercastOpacity) * 0.5f, 0f, 1f); }
|
||||||
active = w;
|
|
||||||
changeTimer = 90f + FastMath.nextRandomFloat() * 150f;
|
|
||||||
windAngleTgt = FastMath.nextRandomFloat() * FastMath.TWO_PI;
|
|
||||||
log.info("[Weather] forceWeather → {}", w);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Vector3f getWindDirection() {
|
public Vector3f getWindDirection() {
|
||||||
return new Vector3f(FastMath.sin(windAngle), 0f, FastMath.cos(windAngle));
|
return new Vector3f(FastMath.sin(windAngle), 0f, FastMath.cos(windAngle));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Nächsten Wetterzustand sofort auslösen (wie automatischer Übergang).
|
||||||
|
* Gibt den neuen Zustand zurück.
|
||||||
|
*/
|
||||||
|
public Weather triggerNext() {
|
||||||
|
pickNextWeather();
|
||||||
|
return active;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wetter sofort setzen; Werte interpolieren sanft zum neuen Ziel.
|
||||||
|
* Automatischer Wechsel-Timer wird zurückgesetzt.
|
||||||
|
*/
|
||||||
|
public void forceWeather(Weather w) {
|
||||||
|
active = w;
|
||||||
|
changeTimer = randomDuration(w);
|
||||||
|
windAngleTgt = FastMath.nextRandomFloat() * FastMath.TWO_PI;
|
||||||
|
log.info("[Weather] forceWeather → {}", w);
|
||||||
|
}
|
||||||
|
|
||||||
// ── Lifecycle ─────────────────────────────────────────────────────────────
|
// ── Lifecycle ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@Override protected void initialize(Application app) {}
|
@Override
|
||||||
@Override protected void cleanup(Application app) {}
|
protected void initialize(Application app) {
|
||||||
@Override protected void onEnable() {}
|
load();
|
||||||
@Override protected void onDisable() {}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void cleanup(Application app) {
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override protected void onEnable() {}
|
||||||
|
@Override protected void onDisable() {}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void update(float tpf) {
|
public void update(float tpf) {
|
||||||
changeTimer -= tpf;
|
changeTimer -= tpf;
|
||||||
if (changeTimer <= 0f) {
|
if (changeTimer <= 0f) {
|
||||||
changeTimer = 90f + FastMath.nextRandomFloat() * 150f;
|
|
||||||
pickNextWeather();
|
pickNextWeather();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
saveTimer -= tpf;
|
||||||
|
if (saveTimer <= 0f) {
|
||||||
|
saveTimer = 60f;
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
|
||||||
int i = active.ordinal();
|
int i = active.ordinal();
|
||||||
|
|
||||||
fogDensity = approach(fogDensity, FOG_DENSITY[i], tpf * 0.025f);
|
fogDensity = approach(fogDensity, FOG_DENSITY[i], tpf * 0.025f);
|
||||||
fogDistance = approach(fogDistance, FOG_DISTANCE[i] * viewDistanceFactor, tpf * 0.025f);
|
fogDistance = approach(fogDistance, FOG_DISTANCE[i] * viewDistanceFactor, tpf * 0.025f);
|
||||||
windSpeed = approach(windSpeed, WIND_SPEED[i], tpf * 0.040f);
|
windSpeed = approach(windSpeed, WIND_SPEED[i], tpf * 0.040f);
|
||||||
waveSpeed = approach(waveSpeed, WAVE_SPEED[i], tpf * 0.030f);
|
waveSpeed = approach(waveSpeed, WAVE_SPEED[i], tpf * 0.030f);
|
||||||
waveAmp = approach(waveAmp, WAVE_AMP[i], tpf * 0.025f);
|
waveAmp = approach(waveAmp, WAVE_AMP[i], tpf * 0.025f);
|
||||||
waveScale = approach(waveScale, WAVE_SCALE[i], tpf * 0.020f);
|
waveScale = approach(waveScale, WAVE_SCALE[i], tpf * 0.020f);
|
||||||
waterTrans = approach(waterTrans, WATER_TRANS[i], tpf * 0.025f);
|
waterTrans = approach(waterTrans, WATER_TRANS[i], tpf * 0.025f);
|
||||||
foamIntensity = approach(foamIntensity, FOAM_INTENSITY[i], tpf * 0.020f);
|
foamIntensity = approach(foamIntensity, FOAM_INTENSITY[i],tpf * 0.020f);
|
||||||
cloudOpacity = approach(cloudOpacity, CLOUD_OPACITY[i], tpf * 0.025f);
|
cloudOpacity = approach(cloudOpacity, CLOUD_OPACITY[i], tpf * 0.025f);
|
||||||
windAngle = approachAngle(windAngle, windAngleTgt, tpf * 0.012f);
|
cloudOvercastOpacity = approach(cloudOvercastOpacity, OVERCAST_OPACITY[i], tpf * 0.025f);
|
||||||
|
windAngle = approachAngle(windAngle, windAngleTgt, tpf * 0.012f);
|
||||||
|
|
||||||
fogColor.interpolateLocal(FOG_COLOR[i], tpf * 0.025f);
|
// UV-Offset in Windrichtung akkumulieren
|
||||||
waterColor.interpolateLocal(WATER_COLOR[i], tpf * 0.020f);
|
float scrollRate = windSpeed * 0.0001f;
|
||||||
|
cloudOffsetU += FastMath.sin(windAngle) * scrollRate * tpf;
|
||||||
|
cloudOffsetV += FastMath.cos(windAngle) * scrollRate * tpf;
|
||||||
|
|
||||||
|
fogColor.interpolateLocal(FOG_COLOR[i], tpf * 0.025f);
|
||||||
|
waterColor.interpolateLocal(WATER_COLOR[i], tpf * 0.020f);
|
||||||
deepWaterColor.interpolateLocal(DEEP_WATER_COLOR[i], tpf * 0.020f);
|
deepWaterColor.interpolateLocal(DEEP_WATER_COLOR[i], tpf * 0.020f);
|
||||||
|
|
||||||
if (fogFilter != null) {
|
if (fogFilter != null) {
|
||||||
@@ -143,7 +331,6 @@ public class WeatherState extends BaseAppState {
|
|||||||
fogFilter.setFogDistance(fogDistance);
|
fogFilter.setFogDistance(fogDistance);
|
||||||
fogFilter.setFogColor(fogColor.clone());
|
fogFilter.setFogColor(fogColor.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (waterFilter != null) {
|
if (waterFilter != null) {
|
||||||
waterFilter.setSpeed(waveSpeed);
|
waterFilter.setSpeed(waveSpeed);
|
||||||
waterFilter.setMaxAmplitude(waveAmp);
|
waterFilter.setMaxAmplitude(waveAmp);
|
||||||
@@ -153,30 +340,117 @@ public class WeatherState extends BaseAppState {
|
|||||||
waterFilter.setWaterColor(waterColor.clone());
|
waterFilter.setWaterColor(waterColor.clone());
|
||||||
waterFilter.setDeepWaterColor(deepWaterColor.clone());
|
waterFilter.setDeepWaterColor(deepWaterColor.clone());
|
||||||
waterFilter.setWindDirection(
|
waterFilter.setWindDirection(
|
||||||
new Vector2f(FastMath.sin(windAngle), FastMath.cos(windAngle)));
|
new Vector2f(FastMath.sin(windAngle), FastMath.cos(windAngle)));
|
||||||
}
|
}
|
||||||
|
if (lightScatterFilter != null) {
|
||||||
|
// Max 0.08 um Gras-Streifen zu vermeiden; Nebel/Wolken reduzieren weiter
|
||||||
|
float ld = Math.max(0f, 0.08f - fogDensity * 0.3f);
|
||||||
|
lightScatterFilter.setLightDensity(ld);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (skyControl != null) {
|
// ── Übergänge ─────────────────────────────────────────────────────────────
|
||||||
skyControl.getCloudLayer(0).setOpacity(cloudOpacity);
|
|
||||||
|
private void pickNextWeather() {
|
||||||
|
Weather[] opts = TRANSITIONS[active.ordinal()];
|
||||||
|
Weather next = opts[(int) (FastMath.nextRandomFloat() * opts.length)];
|
||||||
|
windAngleTgt = FastMath.nextRandomFloat() * FastMath.TWO_PI;
|
||||||
|
active = next;
|
||||||
|
changeTimer = randomDuration(next);
|
||||||
|
log.info("[Weather] → {} ({}s)", active, (int) changeTimer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float randomDuration(Weather w) {
|
||||||
|
int i = w.ordinal();
|
||||||
|
return DURATION_MIN[i] + FastMath.nextRandomFloat() * (DURATION_MAX[i] - DURATION_MIN[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Persistenz ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private static File saveFile() {
|
||||||
|
File dir = new File(System.getProperty("user.home"), ".blight");
|
||||||
|
dir.mkdirs();
|
||||||
|
return new File(dir, "weather.properties");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void save() {
|
||||||
|
Properties p = new Properties();
|
||||||
|
p.setProperty("weather", active.name());
|
||||||
|
if (dayTime != null) p.setProperty("timeOfDay", String.valueOf(dayTime.getTimeOfDay()));
|
||||||
|
p.setProperty("changeTimer", String.valueOf(changeTimer));
|
||||||
|
p.setProperty("windAngle", String.valueOf(windAngle));
|
||||||
|
p.setProperty("windAngleTgt", String.valueOf(windAngleTgt));
|
||||||
|
p.setProperty("fogDensity", String.valueOf(fogDensity));
|
||||||
|
p.setProperty("fogDistance", String.valueOf(fogDistance));
|
||||||
|
p.setProperty("windSpeed", String.valueOf(windSpeed));
|
||||||
|
p.setProperty("waveSpeed", String.valueOf(waveSpeed));
|
||||||
|
p.setProperty("waveAmp", String.valueOf(waveAmp));
|
||||||
|
p.setProperty("waveScale", String.valueOf(waveScale));
|
||||||
|
p.setProperty("waterTrans", String.valueOf(waterTrans));
|
||||||
|
p.setProperty("foamIntensity", String.valueOf(foamIntensity));
|
||||||
|
p.setProperty("cloudOpacity", String.valueOf(cloudOpacity));
|
||||||
|
p.setProperty("fogColorR", String.valueOf(fogColor.r));
|
||||||
|
p.setProperty("fogColorG", String.valueOf(fogColor.g));
|
||||||
|
p.setProperty("fogColorB", String.valueOf(fogColor.b));
|
||||||
|
p.setProperty("waterColorR", String.valueOf(waterColor.r));
|
||||||
|
p.setProperty("waterColorG", String.valueOf(waterColor.g));
|
||||||
|
p.setProperty("waterColorB", String.valueOf(waterColor.b));
|
||||||
|
p.setProperty("deepWaterR", String.valueOf(deepWaterColor.r));
|
||||||
|
p.setProperty("deepWaterG", String.valueOf(deepWaterColor.g));
|
||||||
|
p.setProperty("deepWaterB", String.valueOf(deepWaterColor.b));
|
||||||
|
try (FileWriter fw = new FileWriter(saveFile())) {
|
||||||
|
p.store(fw, "Blight weather state");
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("[Weather] Speichern fehlgeschlagen: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void load() {
|
||||||
|
File f = saveFile();
|
||||||
|
if (!f.exists()) return;
|
||||||
|
Properties p = new Properties();
|
||||||
|
try (FileReader fr = new FileReader(f)) {
|
||||||
|
p.load(fr);
|
||||||
|
active = Weather.valueOf(p.getProperty("weather", "SUNNY"));
|
||||||
|
changeTimer = Float.parseFloat(p.getProperty("changeTimer", "180"));
|
||||||
|
windAngle = Float.parseFloat(p.getProperty("windAngle", "0"));
|
||||||
|
windAngleTgt = Float.parseFloat(p.getProperty("windAngleTgt", "0.4"));
|
||||||
|
fogDensity = Float.parseFloat(p.getProperty("fogDensity", String.valueOf(FOG_DENSITY[0])));
|
||||||
|
fogDistance = Float.parseFloat(p.getProperty("fogDistance", String.valueOf(FOG_DISTANCE[0])));
|
||||||
|
windSpeed = Float.parseFloat(p.getProperty("windSpeed", String.valueOf(WIND_SPEED[0])));
|
||||||
|
waveSpeed = Float.parseFloat(p.getProperty("waveSpeed", String.valueOf(WAVE_SPEED[0])));
|
||||||
|
waveAmp = Float.parseFloat(p.getProperty("waveAmp", String.valueOf(WAVE_AMP[0])));
|
||||||
|
waveScale = Float.parseFloat(p.getProperty("waveScale", String.valueOf(WAVE_SCALE[0])));
|
||||||
|
waterTrans = Float.parseFloat(p.getProperty("waterTrans", String.valueOf(WATER_TRANS[0])));
|
||||||
|
foamIntensity = Float.parseFloat(p.getProperty("foamIntensity", String.valueOf(FOAM_INTENSITY[0])));
|
||||||
|
cloudOpacity = Float.parseFloat(p.getProperty("cloudOpacity", String.valueOf(CLOUD_OPACITY[0])));
|
||||||
|
fogColor.set(
|
||||||
|
Float.parseFloat(p.getProperty("fogColorR", String.valueOf(FOG_COLOR[0].r))),
|
||||||
|
Float.parseFloat(p.getProperty("fogColorG", String.valueOf(FOG_COLOR[0].g))),
|
||||||
|
Float.parseFloat(p.getProperty("fogColorB", String.valueOf(FOG_COLOR[0].b))),
|
||||||
|
1f);
|
||||||
|
waterColor.set(
|
||||||
|
Float.parseFloat(p.getProperty("waterColorR", String.valueOf(WATER_COLOR[0].r))),
|
||||||
|
Float.parseFloat(p.getProperty("waterColorG", String.valueOf(WATER_COLOR[0].g))),
|
||||||
|
Float.parseFloat(p.getProperty("waterColorB", String.valueOf(WATER_COLOR[0].b))),
|
||||||
|
1f);
|
||||||
|
deepWaterColor.set(
|
||||||
|
Float.parseFloat(p.getProperty("deepWaterR", String.valueOf(DEEP_WATER_COLOR[0].r))),
|
||||||
|
Float.parseFloat(p.getProperty("deepWaterG", String.valueOf(DEEP_WATER_COLOR[0].g))),
|
||||||
|
Float.parseFloat(p.getProperty("deepWaterB", String.valueOf(DEEP_WATER_COLOR[0].b))),
|
||||||
|
1f);
|
||||||
|
String savedTime = p.getProperty("timeOfDay");
|
||||||
|
if (savedTime != null && dayTime != null) {
|
||||||
|
dayTime.setTimeOfDay(Float.parseFloat(savedTime));
|
||||||
|
}
|
||||||
|
log.info("[Weather] Geladen: {} (noch {}s)", active, (int) changeTimer);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("[Weather] Laden fehlgeschlagen: {}", e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Helpers ───────────────────────────────────────────────────────────────
|
// ── Helpers ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
private void pickNextWeather() {
|
|
||||||
float r = FastMath.nextRandomFloat();
|
|
||||||
Weather next = r < 0.38f ? Weather.SUNNY
|
|
||||||
: r < 0.65f ? Weather.CLOUDY
|
|
||||||
: r < 0.82f ? Weather.OVERCAST
|
|
||||||
: r < 0.92f ? Weather.STORM
|
|
||||||
: Weather.FOG;
|
|
||||||
windAngleTgt = FastMath.nextRandomFloat() * FastMath.TWO_PI;
|
|
||||||
if (next != active) {
|
|
||||||
active = next;
|
|
||||||
log.info("[Weather] transitioning to {}", active);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static float approach(float cur, float tgt, float alpha) {
|
private static float approach(float cur, float tgt, float alpha) {
|
||||||
return cur + (tgt - cur) * FastMath.clamp(alpha, 0f, 1f);
|
return cur + (tgt - cur) * FastMath.clamp(alpha, 0f, 1f);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -224,6 +224,13 @@ public class WorldObjectsState extends BaseAppState {
|
|||||||
mat.setFloat("WindStrength", strength);
|
mat.setFloat("WindStrength", strength);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RainState rs = getApplication().getStateManager().getState(RainState.class);
|
||||||
|
if (rs != null) {
|
||||||
|
float w = rs.getWetness();
|
||||||
|
for (Material mat : sceneLitMaterials) setWetnessIfSupported(mat, w);
|
||||||
|
for (Material mat : windMaterials) setWetnessIfSupported(mat, w);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Spatial buildSpatial(PlacedModel m) {
|
private Spatial buildSpatial(PlacedModel m) {
|
||||||
@@ -536,4 +543,10 @@ public class WorldObjectsState extends BaseAppState {
|
|||||||
// PhysicsSpace kann beim App-Shutdown bereits zerstört sein
|
// PhysicsSpace kann beim App-Shutdown bereits zerstört sein
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void setWetnessIfSupported(Material mat, float w) {
|
||||||
|
if (mat != null && mat.getMaterialDef().getMaterialParam("Wetness") != null) {
|
||||||
|
mat.setFloat("Wetness", w);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
blight-map/src/main/map/chunks/sculpt_14_0_05.blsm
Normal file
BIN
blight-map/src/main/map/chunks/sculpt_14_0_05.blsm
Normal file
Binary file not shown.
BIN
blight-map/src/main/map/chunks/sculpt_14_m1_05.blsm
Normal file
BIN
blight-map/src/main/map/chunks/sculpt_14_m1_05.blsm
Normal file
Binary file not shown.
@@ -1,420 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009-2025 jMonkeyEngine
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are
|
|
||||||
* met:
|
|
||||||
*
|
|
||||||
* * Redistributions of source code must retain the above copyright
|
|
||||||
* notice, this list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* * Redistributions in binary form must reproduce the above copyright
|
|
||||||
* notice, this list of conditions and the following disclaimer in the
|
|
||||||
* documentation and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
|
|
||||||
* may be used to endorse or promote products derived from this software
|
|
||||||
* without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
|
||||||
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
||||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
|
||||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
||||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
||||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
||||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
||||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
||||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
package com.jme3.app;
|
|
||||||
|
|
||||||
import com.jme3.app.state.AppState;
|
|
||||||
import com.jme3.app.state.ConstantVerifierState;
|
|
||||||
import com.jme3.audio.AudioListenerState;
|
|
||||||
import com.jme3.font.BitmapFont;
|
|
||||||
import com.jme3.font.BitmapText;
|
|
||||||
import com.jme3.input.FlyByCamera;
|
|
||||||
import com.jme3.input.KeyInput;
|
|
||||||
import com.jme3.input.controls.ActionListener;
|
|
||||||
import com.jme3.input.controls.KeyTrigger;
|
|
||||||
import com.jme3.profile.AppStep;
|
|
||||||
import com.jme3.renderer.RenderManager;
|
|
||||||
import com.jme3.renderer.queue.RenderQueue.Bucket;
|
|
||||||
import com.jme3.scene.Node;
|
|
||||||
import com.jme3.scene.Spatial.CullHint;
|
|
||||||
import com.jme3.scene.threadwarden.SceneGraphThreadWarden;
|
|
||||||
import com.jme3.system.AppSettings;
|
|
||||||
import com.jme3.system.JmeContext.Type;
|
|
||||||
import com.jme3.system.JmeSystem;
|
|
||||||
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* `SimpleApplication` is the foundational base class for all jMonkeyEngine 3 (jME3) applications.
|
|
||||||
* It provides a streamlined setup for common game development tasks, including scene management,
|
|
||||||
* camera controls, and performance monitoring.
|
|
||||||
*
|
|
||||||
* <p>By default, `SimpleApplication` attaches several essential {@link com.jme3.app.state.AppState} instances:
|
|
||||||
* <ul>
|
|
||||||
* <li>{@link com.jme3.app.StatsAppState}: Displays real-time frames-per-second (FPS) and
|
|
||||||
* detailed performance statistics on-screen.</li>
|
|
||||||
* <li>{@link com.jme3.app.FlyCamAppState}: Provides a convenient first-person fly-by camera
|
|
||||||
* controller, allowing easy navigation within the scene.</li>
|
|
||||||
* <li>{@link com.jme3.audio.AudioListenerState}: Manages the audio listener, essential for 3D sound.</li>
|
|
||||||
* <li>{@link com.jme3.app.DebugKeysAppState}: Enables debug functionalities like displaying
|
|
||||||
* camera position and memory usage in the console.</li>
|
|
||||||
* <li>{@link com.jme3.app.state.ConstantVerifierState}: A utility state for verifying constant
|
|
||||||
* values, primarily for internal engine debugging.</li>
|
|
||||||
* </ul>
|
|
||||||
*
|
|
||||||
* <p><b>Default Key Bindings:</b></p>
|
|
||||||
* <ul>
|
|
||||||
* <li><b>Esc:</b> Closes and exits the application.</li>
|
|
||||||
* <li><b>F5:</b> Toggles the visibility of the statistics view (FPS and debug stats).</li>
|
|
||||||
* <li><b>C:</b> Prints the current camera position and rotation to the console.</li>
|
|
||||||
* <li><b>M:</b> Prints memory usage statistics to the console.</li>
|
|
||||||
* </ul>
|
|
||||||
*
|
|
||||||
* <p>Applications extending `SimpleApplication` should implement the
|
|
||||||
* {@link #simpleInitApp()} method to set up their initial scene and game logic.
|
|
||||||
*/
|
|
||||||
public abstract class SimpleApplication extends LegacyApplication {
|
|
||||||
|
|
||||||
protected static final Logger logger = Logger.getLogger(SimpleApplication.class.getName());
|
|
||||||
|
|
||||||
public static final String INPUT_MAPPING_EXIT = "SIMPLEAPP_Exit";
|
|
||||||
public static final String INPUT_MAPPING_CAMERA_POS = DebugKeysAppState.INPUT_MAPPING_CAMERA_POS;
|
|
||||||
public static final String INPUT_MAPPING_MEMORY = DebugKeysAppState.INPUT_MAPPING_MEMORY;
|
|
||||||
public static final String INPUT_MAPPING_HIDE_STATS = "SIMPLEAPP_HideStats";
|
|
||||||
|
|
||||||
protected Node rootNode = new Node("Root Node");
|
|
||||||
protected Node guiNode = new Node("Gui Node");
|
|
||||||
protected BitmapText fpsText;
|
|
||||||
protected BitmapFont guiFont;
|
|
||||||
protected FlyByCamera flyCam;
|
|
||||||
protected boolean showSettings = true;
|
|
||||||
private final AppActionListener actionListener = new AppActionListener();
|
|
||||||
|
|
||||||
private class AppActionListener implements ActionListener {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAction(String name, boolean isPressed, float tpf) {
|
|
||||||
if (!isPressed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name.equals(INPUT_MAPPING_EXIT)) {
|
|
||||||
stop();
|
|
||||||
} else if (name.equals(INPUT_MAPPING_HIDE_STATS)) {
|
|
||||||
StatsAppState statsState = stateManager.getState(StatsAppState.class);
|
|
||||||
if (statsState != null) {
|
|
||||||
statsState.toggleStats();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a `SimpleApplication` with a predefined set of default
|
|
||||||
* {@link com.jme3.app.state.AppState} instances.
|
|
||||||
* These states provide common functionalities like statistics display,
|
|
||||||
* fly camera control, audio listener, debug keys, and constant verification.
|
|
||||||
*/
|
|
||||||
public SimpleApplication() {
|
|
||||||
this(new StatsAppState(),
|
|
||||||
new FlyCamAppState(),
|
|
||||||
new AudioListenerState(),
|
|
||||||
new DebugKeysAppState(),
|
|
||||||
new ConstantVerifierState());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a `SimpleApplication` with a custom array of initial
|
|
||||||
* {@link com.jme3.app.state.AppState} instances.
|
|
||||||
*
|
|
||||||
* @param initialStates An array of `AppState` instances to be attached
|
|
||||||
* to the `stateManager` upon initialization.
|
|
||||||
*/
|
|
||||||
public SimpleApplication(AppState... initialStates) {
|
|
||||||
super(initialStates);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void start() {
|
|
||||||
// set some default settings in-case
|
|
||||||
// settings dialog is not shown
|
|
||||||
boolean loadSettings = false;
|
|
||||||
if (settings == null) {
|
|
||||||
logger.log(Level.INFO, "AppSettings not set, creating default settings.");
|
|
||||||
setSettings(new AppSettings(true));
|
|
||||||
loadSettings = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// show settings dialog
|
|
||||||
if (showSettings) {
|
|
||||||
if (!JmeSystem.showSettingsDialog(settings, loadSettings)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//re-setting settings they can have been merged from the registry.
|
|
||||||
setSettings(settings);
|
|
||||||
super.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the current speed multiplier of the application.
|
|
||||||
* This value affects how quickly the game world updates relative to real time.
|
|
||||||
* A value of 1.0f means normal speed, 0.5f means half speed, 2.0f means double speed.
|
|
||||||
*
|
|
||||||
* @return The current speed of the application.
|
|
||||||
*/
|
|
||||||
public float getSpeed() {
|
|
||||||
return speed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Changes the application's speed multiplier.
|
|
||||||
* A `speed` of 0.0f effectively pauses the application's update cycle.
|
|
||||||
*
|
|
||||||
* @param speed The desired speed multiplier. A value of 1.0f is normal speed.
|
|
||||||
* Must be non-negative.
|
|
||||||
*/
|
|
||||||
public void setSpeed(float speed) {
|
|
||||||
this.speed = speed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the `FlyByCamera` instance associated with this application.
|
|
||||||
* This camera allows free-form navigation within the 3D scene.
|
|
||||||
*
|
|
||||||
* @return The `FlyByCamera` object, or `null` if `FlyCamAppState` is not attached
|
|
||||||
* or has not yet initialized the camera.
|
|
||||||
*/
|
|
||||||
public FlyByCamera getFlyByCamera() {
|
|
||||||
return flyCam;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the `Node` dedicated to 2D graphical user interface (GUI) elements.
|
|
||||||
* Objects attached to this node are rendered on top of the 3D scene,
|
|
||||||
* typically without perspective effects, suitable for HUDs and UI.
|
|
||||||
*
|
|
||||||
* @return The `Node` object representing the GUI root.
|
|
||||||
*/
|
|
||||||
public Node getGuiNode() {
|
|
||||||
return guiNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the root `Node` of the 3D scene graph.
|
|
||||||
* All main 3D spatial objects and models should be attached to this node
|
|
||||||
* to be part of the rendered scene.
|
|
||||||
*
|
|
||||||
* @return The `Node` object representing the 3D scene root.
|
|
||||||
*/
|
|
||||||
public Node getRootNode() {
|
|
||||||
return rootNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks whether the settings dialog is configured to be shown at application startup.
|
|
||||||
*
|
|
||||||
* @return `true` if the settings dialog will be displayed, `false` otherwise.
|
|
||||||
*/
|
|
||||||
public boolean isShowSettings() {
|
|
||||||
return showSettings;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets whether the jME3 settings dialog should be displayed before the application starts.
|
|
||||||
*
|
|
||||||
* @param showSettings `true` to show the settings dialog, `false` to suppress it.
|
|
||||||
*/
|
|
||||||
public void setShowSettings(boolean showSettings) {
|
|
||||||
this.showSettings = showSettings;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates the font that will be set to the guiFont field
|
|
||||||
* and subsequently set as the font for the stats text.
|
|
||||||
*
|
|
||||||
* @return the loaded BitmapFont
|
|
||||||
*/
|
|
||||||
protected BitmapFont loadGuiFont() {
|
|
||||||
return assetManager.loadFont("Interface/Fonts/Default.fnt");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void initialize() {
|
|
||||||
super.initialize();
|
|
||||||
|
|
||||||
//noinspection AssertWithSideEffects
|
|
||||||
assert SceneGraphThreadWarden.setup(rootNode);
|
|
||||||
//noinspection AssertWithSideEffects
|
|
||||||
assert SceneGraphThreadWarden.setup(guiNode);
|
|
||||||
|
|
||||||
// Several things rely on having this
|
|
||||||
guiFont = loadGuiFont();
|
|
||||||
|
|
||||||
guiNode.setQueueBucket(Bucket.Gui);
|
|
||||||
guiNode.setCullHint(CullHint.Never);
|
|
||||||
|
|
||||||
viewPort.attachScene(rootNode);
|
|
||||||
guiViewPort.attachScene(guiNode);
|
|
||||||
|
|
||||||
if (inputManager != null) {
|
|
||||||
// Special handling for FlyCamAppState:
|
|
||||||
// Although FlyCamAppState manages the FlyByCamera, SimpleApplication
|
|
||||||
// historically initializes and configures a default FlyByCamera instance
|
|
||||||
// and sets its initial speed. This allows subclasses to directly access
|
|
||||||
// 'flyCam' early in simpleInitApp().
|
|
||||||
|
|
||||||
FlyCamAppState flyCamState = stateManager.getState(FlyCamAppState.class);
|
|
||||||
if (flyCamState != null) {
|
|
||||||
flyCam = new FlyByCamera(cam);
|
|
||||||
flyCam.setMoveSpeed(1f); // Set a default movement speed for the camera
|
|
||||||
flyCamState.setCamera(flyCam); // Link the FlyCamAppState to this camera instance
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register the "Exit" input mapping for the Escape key, but only for Display contexts.
|
|
||||||
if (context.getType() == Type.Display) {
|
|
||||||
inputManager.addMapping(INPUT_MAPPING_EXIT, new KeyTrigger(KeyInput.KEY_ESCAPE));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register the "Hide Stats" input mapping for the F5 key, if StatsAppState is active.
|
|
||||||
StatsAppState statsState = stateManager.getState(StatsAppState.class);
|
|
||||||
if (statsState != null) {
|
|
||||||
inputManager.addMapping(INPUT_MAPPING_HIDE_STATS, new KeyTrigger(KeyInput.KEY_F5));
|
|
||||||
inputManager.addListener(actionListener, INPUT_MAPPING_HIDE_STATS);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attach the action listener to the "Exit" mapping.
|
|
||||||
inputManager.addListener(actionListener, INPUT_MAPPING_EXIT);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configure the StatsAppState if it exists.
|
|
||||||
StatsAppState statsState = stateManager.getState(StatsAppState.class);
|
|
||||||
if (statsState != null) {
|
|
||||||
statsState.setFont(guiFont);
|
|
||||||
fpsText = statsState.getFpsText();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call the user's application initialization code.
|
|
||||||
simpleInitApp();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void stop(boolean waitFor) {
|
|
||||||
//noinspection AssertWithSideEffects
|
|
||||||
assert SceneGraphThreadWarden.reset();
|
|
||||||
super.stop(waitFor);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void update() {
|
|
||||||
if (prof != null) {
|
|
||||||
prof.appStep(AppStep.BeginFrame);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Executes AppTasks from the main thread
|
|
||||||
super.update();
|
|
||||||
|
|
||||||
// Skip updates if paused or speed is zero
|
|
||||||
if (speed == 0 || paused) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
float tpf = timer.getTimePerFrame() * speed;
|
|
||||||
|
|
||||||
// Update AppStates
|
|
||||||
if (prof != null) {
|
|
||||||
prof.appStep(AppStep.StateManagerUpdate);
|
|
||||||
}
|
|
||||||
stateManager.update(tpf);
|
|
||||||
|
|
||||||
// Call user's per-frame update method
|
|
||||||
simpleUpdate(tpf);
|
|
||||||
|
|
||||||
// Update scene graph nodes (logical and geometric states)
|
|
||||||
if (prof != null) {
|
|
||||||
prof.appStep(AppStep.SpatialUpdate);
|
|
||||||
}
|
|
||||||
rootNode.updateLogicalState(tpf);
|
|
||||||
guiNode.updateLogicalState(tpf);
|
|
||||||
|
|
||||||
rootNode.updateGeometricState();
|
|
||||||
guiNode.updateGeometricState();
|
|
||||||
|
|
||||||
// Render AppStates and the scene
|
|
||||||
if (prof != null) {
|
|
||||||
prof.appStep(AppStep.StateManagerRender);
|
|
||||||
}
|
|
||||||
stateManager.render(renderManager);
|
|
||||||
|
|
||||||
if (prof != null) {
|
|
||||||
prof.appStep(AppStep.RenderFrame);
|
|
||||||
}
|
|
||||||
renderManager.render(tpf, context.isRenderable());
|
|
||||||
// Call user's custom render method
|
|
||||||
simpleRender(renderManager);
|
|
||||||
stateManager.postRender();
|
|
||||||
|
|
||||||
if (prof != null) {
|
|
||||||
prof.appStep(AppStep.EndFrame);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Controls the visibility of the frames-per-second (FPS) display on the screen.
|
|
||||||
*
|
|
||||||
* @param show `true` to display the FPS, `false` to hide it.
|
|
||||||
*/
|
|
||||||
public void setDisplayFps(boolean show) {
|
|
||||||
StatsAppState statsState = stateManager.getState(StatsAppState.class);
|
|
||||||
if (statsState != null) {
|
|
||||||
statsState.setDisplayFps(show);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Controls the visibility of the comprehensive statistics view on the screen.
|
|
||||||
* This view typically includes details about memory, triangles, and other performance metrics.
|
|
||||||
*
|
|
||||||
* @param show `true` to display the statistics view, `false` to hide it.
|
|
||||||
*/
|
|
||||||
public void setDisplayStatView(boolean show) {
|
|
||||||
StatsAppState statsState = stateManager.getState(StatsAppState.class);
|
|
||||||
if (statsState != null) {
|
|
||||||
statsState.setDisplayStatView(show);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract void simpleInitApp();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An optional method that can be overridden by subclasses for per-frame update logic.
|
|
||||||
* This method is called during the application's update loop, after AppStates are updated
|
|
||||||
* and before the scene graph's logical state is updated.
|
|
||||||
*
|
|
||||||
* @param tpf The time per frame (in seconds), adjusted by the application's speed.
|
|
||||||
*/
|
|
||||||
public void simpleUpdate(float tpf) {
|
|
||||||
// Default empty implementation; subclasses can override
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An optional method that can be overridden by subclasses for custom rendering logic.
|
|
||||||
* This method is called during the application's render loop, after the main scene
|
|
||||||
* has been rendered and before post-rendering for states.
|
|
||||||
* Useful for drawing overlays or specific rendering tasks outside the main scene graph.
|
|
||||||
*
|
|
||||||
* @param rm The `RenderManager` instance, which provides access to rendering functionalities.
|
|
||||||
*/
|
|
||||||
public void simpleRender(RenderManager rm) {
|
|
||||||
// Default empty implementation; subclasses can override
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,339 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009-2025 jMonkeyEngine
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are
|
|
||||||
* met:
|
|
||||||
*
|
|
||||||
* * Redistributions of source code must retain the above copyright
|
|
||||||
* notice, this list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* * Redistributions in binary form must reproduce the above copyright
|
|
||||||
* notice, this list of conditions and the following disclaimer in the
|
|
||||||
* documentation and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
|
|
||||||
* may be used to endorse or promote products derived from this software
|
|
||||||
* without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
|
||||||
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
||||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
|
||||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
||||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
||||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
||||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
||||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
||||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
package com.jme3.shadow;
|
|
||||||
|
|
||||||
import com.jme3.asset.AssetManager;
|
|
||||||
import com.jme3.material.Material;
|
|
||||||
import com.jme3.material.RenderState;
|
|
||||||
import com.jme3.math.Matrix4f;
|
|
||||||
import com.jme3.math.Vector4f;
|
|
||||||
import com.jme3.post.Filter;
|
|
||||||
import com.jme3.renderer.RenderManager;
|
|
||||||
import com.jme3.renderer.ViewPort;
|
|
||||||
import com.jme3.renderer.queue.RenderQueue;
|
|
||||||
import com.jme3.texture.FrameBuffer;
|
|
||||||
import com.jme3.util.TempVars;
|
|
||||||
import com.jme3.util.clone.Cloner;
|
|
||||||
import com.jme3.util.clone.JmeCloneable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generic abstract filter that holds common implementations for the different
|
|
||||||
* shadow filters
|
|
||||||
*
|
|
||||||
* @author Rémy Bouquet aka Nehon
|
|
||||||
*/
|
|
||||||
public abstract class AbstractShadowFilter<T extends AbstractShadowRenderer> extends Filter implements JmeCloneable {
|
|
||||||
|
|
||||||
protected T shadowRenderer;
|
|
||||||
protected ViewPort viewPort;
|
|
||||||
|
|
||||||
private final Vector4f tempVec4 = new Vector4f();
|
|
||||||
private final Matrix4f tempMat4 = new Matrix4f();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For serialization only. Do not use.
|
|
||||||
*/
|
|
||||||
protected AbstractShadowFilter() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an AbstractShadowFilter. Subclasses invoke this constructor.
|
|
||||||
*
|
|
||||||
* @param assetManager The application's asset manager.
|
|
||||||
* @param shadowMapSize The size of the rendered shadow maps (e.g., 512, 1024, 2048).
|
|
||||||
* @param shadowRenderer The shadowRenderer to use for this Filter
|
|
||||||
*/
|
|
||||||
protected AbstractShadowFilter(AssetManager assetManager, int shadowMapSize, T shadowRenderer) {
|
|
||||||
super("Post Shadow");
|
|
||||||
this.shadowRenderer = shadowRenderer;
|
|
||||||
// this is legacy setting for shadows with backface shadows
|
|
||||||
this.shadowRenderer.setRenderBackFacesShadows(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Material getMaterial() {
|
|
||||||
return material;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean isRequiresDepthTexture() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Use {@link #getMaterial()} instead.
|
|
||||||
* @return The Material used by this filter.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
public Material getShadowMaterial() {
|
|
||||||
return material;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void preFrame(float tpf) {
|
|
||||||
shadowRenderer.preFrame(tpf);
|
|
||||||
Matrix4f m = viewPort.getCamera().getViewProjectionMatrix();
|
|
||||||
material.setMatrix4("ViewProjectionMatrixInverse", tempMat4.set(m).invertLocal());
|
|
||||||
material.setVector4("ViewProjectionMatrixRow2", tempVec4.set(m.m20, m.m21, m.m22, m.m23));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void postQueue(RenderQueue queue) {
|
|
||||||
shadowRenderer.postQueue(queue);
|
|
||||||
if (shadowRenderer.skipPostPass) {
|
|
||||||
// removing the shadow map so that the post pass is skipped
|
|
||||||
material.setTexture("ShadowMap0", null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void postFrame(RenderManager renderManager, ViewPort viewPort, FrameBuffer prevFilterBuffer, FrameBuffer sceneBuffer) {
|
|
||||||
if (!shadowRenderer.skipPostPass) {
|
|
||||||
shadowRenderer.setPostShadowParams();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) {
|
|
||||||
shadowRenderer.needsfallBackMaterial = true;
|
|
||||||
material = new Material(manager, "Common/MatDefs/Shadow/PostShadowFilter.j3md");
|
|
||||||
shadowRenderer.setPostShadowMaterial(material);
|
|
||||||
shadowRenderer.initialize(renderManager, vp);
|
|
||||||
this.viewPort = vp;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* How far the shadows are rendered in the view
|
|
||||||
*
|
|
||||||
* @return shadowZExtend
|
|
||||||
* @see #setShadowZExtend(float zFar)
|
|
||||||
*/
|
|
||||||
public float getShadowZExtend() {
|
|
||||||
return shadowRenderer.getShadowZExtend();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the distance from the eye where the shadows will be rendered default
|
|
||||||
* value is dynamically computed to the shadow casters/receivers union bound
|
|
||||||
* zFar, capped to view frustum far value.
|
|
||||||
*
|
|
||||||
* @param zFar the zFar values that override the computed one
|
|
||||||
*/
|
|
||||||
public void setShadowZExtend(float zFar) {
|
|
||||||
shadowRenderer.setShadowZExtend(zFar);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Define the length over which the shadow will fade out when using a
|
|
||||||
* shadowZextend
|
|
||||||
*
|
|
||||||
* @param length the fade length in world units
|
|
||||||
*/
|
|
||||||
public void setShadowZFadeLength(float length) {
|
|
||||||
shadowRenderer.setShadowZFadeLength(length);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* get the length over which the shadow will fade out when using a
|
|
||||||
* shadowZextend
|
|
||||||
*
|
|
||||||
* @return the fade length in world units
|
|
||||||
*/
|
|
||||||
public float getShadowZFadeLength() {
|
|
||||||
return shadowRenderer.getShadowZFadeLength();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* returns the shadow intensity
|
|
||||||
*
|
|
||||||
* @see #setShadowIntensity(float shadowIntensity)
|
|
||||||
* @return shadowIntensity
|
|
||||||
*/
|
|
||||||
public float getShadowIntensity() {
|
|
||||||
return shadowRenderer.getShadowIntensity();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the shadowIntensity, the value should be between 0 and 1, a 0 value
|
|
||||||
* gives a bright and invisible shadow, a 1 value gives a pitch black
|
|
||||||
* shadow, default is 0.7
|
|
||||||
*
|
|
||||||
* @param shadowIntensity the darkness of the shadow
|
|
||||||
*/
|
|
||||||
final public void setShadowIntensity(float shadowIntensity) {
|
|
||||||
shadowRenderer.setShadowIntensity(shadowIntensity);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* returns the edges thickness <br>
|
|
||||||
*
|
|
||||||
* @see #setEdgesThickness(int edgesThickness)
|
|
||||||
* @return edgesThickness
|
|
||||||
*/
|
|
||||||
public int getEdgesThickness() {
|
|
||||||
return shadowRenderer.getEdgesThickness();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the shadow edges thickness. Default is 10. Setting it to lower values
|
|
||||||
* can help reduce the jagged effect of shadow edges.
|
|
||||||
*
|
|
||||||
* @param edgesThickness the desired thickness (in tenths of a pixel, default=10)
|
|
||||||
*/
|
|
||||||
public void setEdgesThickness(int edgesThickness) {
|
|
||||||
shadowRenderer.setEdgesThickness(edgesThickness);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* isFlushQueues does nothing and is kept only for backward compatibility
|
|
||||||
*
|
|
||||||
* @return false
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
public boolean isFlushQueues() {
|
|
||||||
return shadowRenderer.isFlushQueues();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* sets the shadow compare mode see {@link CompareMode} for more info
|
|
||||||
*
|
|
||||||
* @param compareMode the desired mode
|
|
||||||
*/
|
|
||||||
final public void setShadowCompareMode(CompareMode compareMode) {
|
|
||||||
shadowRenderer.setShadowCompareMode(compareMode);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* returns the shadow compare mode
|
|
||||||
*
|
|
||||||
* @see CompareMode
|
|
||||||
* @return the shadowCompareMode
|
|
||||||
*/
|
|
||||||
public CompareMode getShadowCompareMode() {
|
|
||||||
return shadowRenderer.getShadowCompareMode();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the filtering mode for shadow edges see {@link EdgeFilteringMode}
|
|
||||||
* for more info
|
|
||||||
*
|
|
||||||
* @param filterMode the desired mode
|
|
||||||
*/
|
|
||||||
final public void setEdgeFilteringMode(EdgeFilteringMode filterMode) {
|
|
||||||
shadowRenderer.setEdgeFilteringMode(filterMode);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* !! WARNING !! this parameter is defaulted to true for the ShadowFilter.
|
|
||||||
* Setting it to true, may produce edges artifacts on shadows.
|
|
||||||
*
|
|
||||||
* Set to true if you want back faces shadows on geometries.
|
|
||||||
* Note that back faces shadows will be blended over dark lighten areas and may produce overly dark lighting.
|
|
||||||
*
|
|
||||||
* Setting this parameter will override this parameter for ALL materials in the scene.
|
|
||||||
* This also will automatically adjust the faceCullMode and the PolyOffset of the pre shadow pass.
|
|
||||||
* You can modify them by using {@link #getPreShadowForcedRenderState()}
|
|
||||||
*
|
|
||||||
* If you want to set it differently for each material in the scene you have to use the ShadowRenderer instead
|
|
||||||
* of the shadow filter.
|
|
||||||
*
|
|
||||||
* @param renderBackFacesShadows true or false.
|
|
||||||
*/
|
|
||||||
public void setRenderBackFacesShadows(boolean renderBackFacesShadows) {
|
|
||||||
shadowRenderer.setRenderBackFacesShadows(renderBackFacesShadows);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* if this filter renders back faces shadows
|
|
||||||
* @return true if this filter renders back faces shadows
|
|
||||||
*/
|
|
||||||
public boolean isRenderBackFacesShadows() {
|
|
||||||
return shadowRenderer.isRenderBackFacesShadows();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* returns the pre shadows pass render state.
|
|
||||||
* use it to adjust the RenderState parameters of the pre shadow pass.
|
|
||||||
* Note that this will be overridden if the preShadow technique in the material has a ForcedRenderState
|
|
||||||
* @return the pre shadow render state.
|
|
||||||
*/
|
|
||||||
public RenderState getPreShadowForcedRenderState() {
|
|
||||||
return shadowRenderer.getPreShadowForcedRenderState();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* returns the edge filtering mode
|
|
||||||
*
|
|
||||||
* @see EdgeFilteringMode
|
|
||||||
* @return the enum value
|
|
||||||
*/
|
|
||||||
public EdgeFilteringMode getEdgeFilteringMode() {
|
|
||||||
return shadowRenderer.getEdgeFilteringMode();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read the number of shadow maps rendered by this filter.
|
|
||||||
*
|
|
||||||
* @return count
|
|
||||||
*/
|
|
||||||
public int getNumShadowMaps() {
|
|
||||||
return shadowRenderer.getNumShadowMaps();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read the size of each shadow map rendered by this filter.
|
|
||||||
*
|
|
||||||
* @return a map's height (which is also its width, in pixels)
|
|
||||||
*/
|
|
||||||
public int getShadowMapSize() {
|
|
||||||
return shadowRenderer.getShadowMapSize();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public AbstractShadowFilter<T> jmeClone() {
|
|
||||||
try {
|
|
||||||
return (AbstractShadowFilter<T>) super.clone();
|
|
||||||
} catch (final CloneNotSupportedException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void cloneFields(final Cloner cloner, final Object original) {
|
|
||||||
material = cloner.clone(material);
|
|
||||||
shadowRenderer = cloner.clone(shadowRenderer);
|
|
||||||
shadowRenderer.setPostShadowMaterial(material);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,157 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009-2024 jMonkeyEngine
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are
|
|
||||||
* met:
|
|
||||||
*
|
|
||||||
* * Redistributions of source code must retain the above copyright
|
|
||||||
* notice, this list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* * Redistributions in binary form must reproduce the above copyright
|
|
||||||
* notice, this list of conditions and the following disclaimer in the
|
|
||||||
* documentation and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
|
|
||||||
* may be used to endorse or promote products derived from this software
|
|
||||||
* without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
|
||||||
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
||||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
|
||||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
||||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
||||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
||||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
||||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
||||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
package com.jme3.shadow;
|
|
||||||
|
|
||||||
import com.jme3.asset.AssetManager;
|
|
||||||
import com.jme3.export.InputCapsule;
|
|
||||||
import com.jme3.export.JmeExporter;
|
|
||||||
import com.jme3.export.JmeImporter;
|
|
||||||
import com.jme3.export.OutputCapsule;
|
|
||||||
import com.jme3.light.DirectionalLight;
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This Filter does basically the same as a DirectionalLightShadowRenderer
|
|
||||||
* except it renders the post shadow pass as a fullscreen quad pass instead of a
|
|
||||||
* geometry pass. It's mostly faster than PssmShadowRenderer as long as you have
|
|
||||||
* more than about ten shadow receiving objects. The expense is the drawback
|
|
||||||
* that the shadow Receive mode set on spatial is ignored. So basically all and
|
|
||||||
* only objects that render depth in the scene receive shadows.
|
|
||||||
*
|
|
||||||
* API is basically the same as the PssmShadowRenderer.
|
|
||||||
*
|
|
||||||
* @author Rémy Bouquet aka Nehon
|
|
||||||
*/
|
|
||||||
public class DirectionalLightShadowFilter extends AbstractShadowFilter<DirectionalLightShadowRenderer> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For serialization only. Do not use.
|
|
||||||
*
|
|
||||||
* @see #DirectionalLightShadowFilter(AssetManager assetManager, int shadowMapSize, int nbSplits)
|
|
||||||
*/
|
|
||||||
public DirectionalLightShadowFilter() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a DirectionalLightShadowFilter.
|
|
||||||
*
|
|
||||||
* @param assetManager the application's asset manager
|
|
||||||
* @param shadowMapSize the size of the rendered shadow maps (512, 1024, 2048, etc...)
|
|
||||||
* @param nbSplits the number of shadow maps rendered (more shadow maps = better quality, but slower)
|
|
||||||
*
|
|
||||||
* @throws IllegalArgumentException if the provided 'nbSplits' is not within the valid range of 1 to 4.
|
|
||||||
*/
|
|
||||||
public DirectionalLightShadowFilter(AssetManager assetManager, int shadowMapSize, int nbSplits) {
|
|
||||||
super(assetManager, shadowMapSize, new DirectionalLightShadowRenderer(assetManager, shadowMapSize, nbSplits));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the light used to cast shadows.
|
|
||||||
*
|
|
||||||
* @return the DirectionalLight
|
|
||||||
*/
|
|
||||||
public DirectionalLight getLight() {
|
|
||||||
return shadowRenderer.getLight();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the light to use to cast shadows.
|
|
||||||
*
|
|
||||||
* @param light a DirectionalLight
|
|
||||||
*/
|
|
||||||
public void setLight(DirectionalLight light) {
|
|
||||||
shadowRenderer.setLight(light);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the lambda parameter.
|
|
||||||
*
|
|
||||||
* @see #setLambda(float lambda)
|
|
||||||
* @return lambda
|
|
||||||
*/
|
|
||||||
public float getLambda() {
|
|
||||||
return shadowRenderer.getLambda();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adjusts the partition of the shadow extend into shadow maps. Lambda is
|
|
||||||
* usually between 0 and 1.
|
|
||||||
* <p>
|
|
||||||
* A low value gives a more linear partition, resulting in consistent shadow
|
|
||||||
* quality over the extend, but near shadows could look very jagged. A high
|
|
||||||
* value gives a more logarithmic partition, resulting in high quality for near
|
|
||||||
* shadows, but quality decreases rapidly with distance.
|
|
||||||
* <p>
|
|
||||||
* The default value is 0.65 (the theoretical optimum).
|
|
||||||
*
|
|
||||||
* @param lambda the lambda value
|
|
||||||
*/
|
|
||||||
public void setLambda(float lambda) {
|
|
||||||
shadowRenderer.setLambda(lambda);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if stabilization is enabled.
|
|
||||||
*
|
|
||||||
* @return true if stabilization is enabled
|
|
||||||
*/
|
|
||||||
public boolean isEnabledStabilization() {
|
|
||||||
return shadowRenderer.isEnabledStabilization();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enables the stabilization of the shadow's edges. (default is true)
|
|
||||||
* This prevents shadow edges from flickering when the camera moves.
|
|
||||||
* However, it can lead to some loss of shadow quality in particular scenes.
|
|
||||||
*
|
|
||||||
* @param stabilize true to stabilize, false to disable stabilization
|
|
||||||
*/
|
|
||||||
public void setEnabledStabilization(boolean stabilize) {
|
|
||||||
shadowRenderer.setEnabledStabilization(stabilize);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(JmeExporter ex) throws IOException {
|
|
||||||
super.write(ex);
|
|
||||||
OutputCapsule oc = ex.getCapsule(this);
|
|
||||||
oc.write(shadowRenderer, "shadowRenderer", null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void read(JmeImporter im) throws IOException {
|
|
||||||
super.read(im);
|
|
||||||
InputCapsule ic = im.getCapsule(this);
|
|
||||||
shadowRenderer = (DirectionalLightShadowRenderer) ic.readSavable("shadowRenderer", null);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user