Weitere Bugfixes und kleine Feature Erweiterungen
This commit is contained in:
@@ -0,0 +1,307 @@
|
|||||||
|
// Blight-Override von JME3's Shadows.glsllib
|
||||||
|
// Änderung: SHADOW_BIAS wird einmal in getDirectionalLightShadows() von
|
||||||
|
// projCoord.z abgezogen (Empfänger-seitiger Depth-Bias).
|
||||||
|
// Das behebt Self-Shadowing-Acne bei hohem Sonnenstand, ohne den
|
||||||
|
// Pre-Shadow-PolyOffset zu erhöhen.
|
||||||
|
// Wert anpassen: bei verbleibendem Acne erhöhen, bei "Peter Panning" verkleinern.
|
||||||
|
#define SHADOW_BIAS 0.0002
|
||||||
|
|
||||||
|
#if __VERSION__ >= 130
|
||||||
|
#ifdef GL_ES
|
||||||
|
#extension GL_OES_gpu_shader5 : enable
|
||||||
|
#else
|
||||||
|
#extension GL_ARB_gpu_shader5 : enable
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define IVEC2 ivec2
|
||||||
|
#ifdef HARDWARE_SHADOWS
|
||||||
|
#define SHADOWMAP sampler2DShadow
|
||||||
|
#define SHADOWCOMPAREOFFSET(tex,coord,offset) textureProjOffset(tex, coord, offset)
|
||||||
|
#define SHADOWCOMPARE(tex,coord) textureProj(tex, coord)
|
||||||
|
#if defined GL_ES && __VERSION__ <= 300
|
||||||
|
#define SHADOWGATHER(tex,coord) step(coord.z, textureGather(tex, coord.xy))
|
||||||
|
#else
|
||||||
|
#define SHADOWGATHER(tex,coord) textureGather(tex, coord.xy, coord.z)
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
#define SHADOWMAP sampler2D
|
||||||
|
#define SHADOWCOMPAREOFFSET(tex,coord,offset) step(coord.z, textureProjOffset(tex, coord, offset).r)
|
||||||
|
#define SHADOWCOMPARE(tex,coord) step(coord.z, textureProj(tex, coord).r)
|
||||||
|
#define SHADOWGATHER(tex,coord) step(coord.z, textureGather(tex, coord.xy))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if FILTER_MODE == 10
|
||||||
|
#define GETSHADOW Shadow_Nearest
|
||||||
|
#define KERNEL 1.0
|
||||||
|
#elif FILTER_MODE == 1
|
||||||
|
#ifdef HARDWARE_SHADOWS
|
||||||
|
#define GETSHADOW Shadow_Nearest
|
||||||
|
#else
|
||||||
|
#define GETSHADOW Shadow_DoBilinear_2x2
|
||||||
|
#endif
|
||||||
|
#define KERNEL 1.0
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
#define IVEC2 vec2
|
||||||
|
#if defined GL_ES
|
||||||
|
#define SHADOWMAP sampler2D
|
||||||
|
#define SHADOWCOMPARE(tex,coord) step(coord.z, texture2DProj(tex, coord).r)
|
||||||
|
#elif defined HARDWARE_SHADOWS
|
||||||
|
#define SHADOWMAP sampler2DShadow
|
||||||
|
#define SHADOWCOMPARE(tex,coord) shadow2DProj(tex, coord).r
|
||||||
|
#else
|
||||||
|
#define SHADOWMAP sampler2D
|
||||||
|
#define SHADOWCOMPARE(tex,coord) step(coord.z, texture2DProj(tex, coord).r)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if FILTER_MODE == 10
|
||||||
|
#define GETSHADOW Shadow_DoShadowCompare
|
||||||
|
#define KERNEL 1.0
|
||||||
|
#elif FILTER_MODE == 1
|
||||||
|
#ifdef HARDWARE_SHADOWS
|
||||||
|
#define GETSHADOW Shadow_DoShadowCompare
|
||||||
|
#else
|
||||||
|
#define GETSHADOW Shadow_DoBilinear_2x2
|
||||||
|
#endif
|
||||||
|
#define KERNEL 1.0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if (FILTER_MODE == 2)
|
||||||
|
#define GETSHADOW Shadow_DoDither_2x2
|
||||||
|
#define KERNEL 1.0
|
||||||
|
#elif FILTER_MODE == 3
|
||||||
|
#define GETSHADOW Shadow_DoPCF
|
||||||
|
#define KERNEL 4.0
|
||||||
|
#elif FILTER_MODE == 4
|
||||||
|
#define GETSHADOW Shadow_DoPCFPoisson
|
||||||
|
#define KERNEL 4.0
|
||||||
|
#elif FILTER_MODE == 5
|
||||||
|
#define GETSHADOW Shadow_DoPCF
|
||||||
|
#define KERNEL 8.0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
uniform SHADOWMAP m_ShadowMap0;
|
||||||
|
uniform SHADOWMAP m_ShadowMap1;
|
||||||
|
uniform SHADOWMAP m_ShadowMap2;
|
||||||
|
uniform SHADOWMAP m_ShadowMap3;
|
||||||
|
#ifdef POINTLIGHT
|
||||||
|
uniform SHADOWMAP m_ShadowMap4;
|
||||||
|
uniform SHADOWMAP m_ShadowMap5;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef PSSM
|
||||||
|
uniform vec4 m_Splits;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
uniform float m_ShadowIntensity;
|
||||||
|
|
||||||
|
const vec2 pixSize2 = vec2(1.0 / SHADOWMAP_SIZE);
|
||||||
|
float shadowBorderScale = 1.0;
|
||||||
|
|
||||||
|
float Shadow_DoShadowCompare(in SHADOWMAP tex,in vec4 projCoord){
|
||||||
|
return SHADOWCOMPARE(tex, projCoord);
|
||||||
|
}
|
||||||
|
|
||||||
|
float Shadow_BorderCheck(in vec2 coord){
|
||||||
|
#ifdef GL_ES
|
||||||
|
return 0.0;
|
||||||
|
#else
|
||||||
|
vec4 t = vec4(coord.xy, 0.0, 1.0);
|
||||||
|
t = step(t.wwxy, t.xyzz);
|
||||||
|
return dot(t,t);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
float Shadow_Nearest(in SHADOWMAP tex,in vec4 projCoord){
|
||||||
|
float border = Shadow_BorderCheck(projCoord.xy);
|
||||||
|
if (border > 0.0){
|
||||||
|
return 1.0;
|
||||||
|
}
|
||||||
|
return SHADOWCOMPARE(tex, projCoord);
|
||||||
|
}
|
||||||
|
|
||||||
|
float Shadow_DoShadowCompareOffset(in SHADOWMAP tex,in vec4 projCoord,in vec2 offset){
|
||||||
|
vec4 coord = vec4(projCoord.xy + offset.xy * pixSize2 * shadowBorderScale, projCoord.zw);
|
||||||
|
return SHADOWCOMPARE(tex, coord);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
float Shadow_DoDither_2x2(in SHADOWMAP tex, in vec4 projCoord){
|
||||||
|
float border = Shadow_BorderCheck(projCoord.xy);
|
||||||
|
if (border > 0.0)
|
||||||
|
return 1.0;
|
||||||
|
|
||||||
|
float shadow = 0.0;
|
||||||
|
vec2 o = vec2(IVEC2(mod(floor(gl_FragCoord.xy), 2.0)));
|
||||||
|
shadow += Shadow_DoShadowCompareOffset(tex, projCoord, (vec2(-1.5, 1.5)+o));
|
||||||
|
shadow += Shadow_DoShadowCompareOffset(tex, projCoord, (vec2( 0.5, 1.5)+o));
|
||||||
|
shadow += Shadow_DoShadowCompareOffset(tex, projCoord, (vec2(-1.5, -0.5)+o));
|
||||||
|
shadow += Shadow_DoShadowCompareOffset(tex, projCoord, (vec2( 0.5, -0.5)+o));
|
||||||
|
shadow *= 0.25;
|
||||||
|
return shadow;
|
||||||
|
}
|
||||||
|
|
||||||
|
float Shadow_DoBilinear_2x2(in SHADOWMAP tex, in vec4 projCoord){
|
||||||
|
float border = Shadow_BorderCheck(projCoord.xy);
|
||||||
|
if (border > 0.0){
|
||||||
|
return 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 gather = vec4(0.0);
|
||||||
|
#if __VERSION__ >= 130
|
||||||
|
#if defined GL_ARB_gpu_shader5 || defined GL_OES_gpu_shader5
|
||||||
|
vec4 coord = vec4(projCoord.xyz / projCoord.www,0.0);
|
||||||
|
gather = SHADOWGATHER(tex, coord);
|
||||||
|
#else
|
||||||
|
gather.x = SHADOWCOMPAREOFFSET(tex, projCoord, ivec2(0, 1));
|
||||||
|
gather.y = SHADOWCOMPAREOFFSET(tex, projCoord, ivec2(1, 1));
|
||||||
|
gather.z = SHADOWCOMPAREOFFSET(tex, projCoord, ivec2(1, 0));
|
||||||
|
gather.w = SHADOWCOMPAREOFFSET(tex, projCoord, ivec2(0, 0));
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
gather.x = Shadow_DoShadowCompareOffset(tex, projCoord, vec2(0.0, 0.0));
|
||||||
|
gather.y = Shadow_DoShadowCompareOffset(tex, projCoord, vec2(1.0, 0.0));
|
||||||
|
gather.z = Shadow_DoShadowCompareOffset(tex, projCoord, vec2(0.0, 1.0));
|
||||||
|
gather.w = Shadow_DoShadowCompareOffset(tex, projCoord, vec2(1.0, 1.0));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
vec2 f = fract( projCoord.xy * SHADOWMAP_SIZE );
|
||||||
|
vec2 mx = mix( gather.wx, gather.zy, f.x );
|
||||||
|
return mix( mx.x, mx.y, f.y );
|
||||||
|
}
|
||||||
|
|
||||||
|
float Shadow_DoPCF(in SHADOWMAP tex,in vec4 projCoord){
|
||||||
|
|
||||||
|
float shadow = 0.0;
|
||||||
|
float border = Shadow_BorderCheck(projCoord.xy);
|
||||||
|
if (border > 0.0)
|
||||||
|
return 1.0;
|
||||||
|
|
||||||
|
const float bound = (KERNEL * 0.5 - 0.5 ) * PCFEDGE;
|
||||||
|
for (float y = -bound; y <= bound; y += PCFEDGE){
|
||||||
|
for (float x = -bound; x <= bound; x += PCFEDGE){
|
||||||
|
#if __VERSION__ < 130
|
||||||
|
shadow += clamp(Shadow_DoShadowCompareOffset(tex,projCoord,vec2(x,y)) + border, 0.0, 1.0);
|
||||||
|
#else
|
||||||
|
shadow += Shadow_DoShadowCompareOffset(tex, projCoord, vec2(x,y));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
shadow = shadow / (KERNEL * KERNEL);
|
||||||
|
return shadow;
|
||||||
|
}
|
||||||
|
|
||||||
|
//12 tap poisson disk
|
||||||
|
const vec2 poissonDisk0 = vec2(-0.1711046, -0.425016);
|
||||||
|
const vec2 poissonDisk1 = vec2(-0.7829809, 0.2162201);
|
||||||
|
const vec2 poissonDisk2 = vec2(-0.2380269, -0.8835521);
|
||||||
|
const vec2 poissonDisk3 = vec2(0.4198045, 0.1687819);
|
||||||
|
const vec2 poissonDisk4 = vec2(-0.684418, -0.3186957);
|
||||||
|
const vec2 poissonDisk5 = vec2(0.6026866, -0.2587841);
|
||||||
|
const vec2 poissonDisk6 = vec2(-0.2412762, 0.3913516);
|
||||||
|
const vec2 poissonDisk7 = vec2(0.4720655, -0.7664126);
|
||||||
|
const vec2 poissonDisk8 = vec2(0.9571564, 0.2680693);
|
||||||
|
const vec2 poissonDisk9 = vec2(-0.5238616, 0.802707);
|
||||||
|
const vec2 poissonDisk10 = vec2(0.5653144, 0.60262);
|
||||||
|
const vec2 poissonDisk11 = vec2(0.0123658, 0.8627419);
|
||||||
|
|
||||||
|
|
||||||
|
float Shadow_DoPCFPoisson(in SHADOWMAP tex, in vec4 projCoord){
|
||||||
|
float shadow = 0.0;
|
||||||
|
float border = Shadow_BorderCheck(projCoord.xy);
|
||||||
|
if (border > 0.0){
|
||||||
|
return 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec2 texelSize = pixSize2 * 4.0 * PCFEDGE * shadowBorderScale;
|
||||||
|
|
||||||
|
shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk0 * texelSize, projCoord.zw));
|
||||||
|
shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk1 * texelSize, projCoord.zw));
|
||||||
|
shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk2 * texelSize, projCoord.zw));
|
||||||
|
shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk3 * texelSize, projCoord.zw));
|
||||||
|
shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk4 * texelSize, projCoord.zw));
|
||||||
|
shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk5 * texelSize, projCoord.zw));
|
||||||
|
shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk6 * texelSize, projCoord.zw));
|
||||||
|
shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk7 * texelSize, projCoord.zw));
|
||||||
|
shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk8 * texelSize, projCoord.zw));
|
||||||
|
shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk9 * texelSize, projCoord.zw));
|
||||||
|
shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk10 * texelSize, projCoord.zw));
|
||||||
|
shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk11 * texelSize, projCoord.zw));
|
||||||
|
|
||||||
|
//this is divided by 12
|
||||||
|
return shadow * 0.08333333333;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef POINTLIGHT
|
||||||
|
float getPointLightShadows(in vec4 worldPos,in vec3 lightPos,
|
||||||
|
in SHADOWMAP shadowMap0,in SHADOWMAP shadowMap1,in SHADOWMAP shadowMap2,in SHADOWMAP shadowMap3,in SHADOWMAP shadowMap4,in SHADOWMAP shadowMap5,
|
||||||
|
in vec4 projCoord0,in vec4 projCoord1,in vec4 projCoord2,in vec4 projCoord3,in vec4 projCoord4,in vec4 projCoord5){
|
||||||
|
float shadow = 1.0;
|
||||||
|
vec3 vect = worldPos.xyz - lightPos;
|
||||||
|
vec3 absv= abs(vect);
|
||||||
|
float maxComp = max(absv.x,max(absv.y,absv.z));
|
||||||
|
if(maxComp == absv.y){
|
||||||
|
if(vect.y < 0.0){
|
||||||
|
shadow = GETSHADOW(shadowMap0, projCoord0 / projCoord0.w);
|
||||||
|
}else{
|
||||||
|
shadow = GETSHADOW(shadowMap1, projCoord1 / projCoord1.w);
|
||||||
|
}
|
||||||
|
}else if(maxComp == absv.z){
|
||||||
|
if(vect.z < 0.0){
|
||||||
|
shadow = GETSHADOW(shadowMap2, projCoord2 / projCoord2.w);
|
||||||
|
}else{
|
||||||
|
shadow = GETSHADOW(shadowMap3, projCoord3 / projCoord3.w);
|
||||||
|
}
|
||||||
|
}else if(maxComp == absv.x){
|
||||||
|
if(vect.x < 0.0){
|
||||||
|
shadow = GETSHADOW(shadowMap4, projCoord4 / projCoord4.w);
|
||||||
|
}else{
|
||||||
|
shadow = GETSHADOW(shadowMap5, projCoord5 / projCoord5.w);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return shadow;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
#ifdef PSSM
|
||||||
|
float getDirectionalLightShadows(in vec4 splits,in float shadowPosition,
|
||||||
|
in SHADOWMAP shadowMap0,in SHADOWMAP shadowMap1,in SHADOWMAP shadowMap2,in SHADOWMAP shadowMap3,
|
||||||
|
in vec4 projCoord0,in vec4 projCoord1,in vec4 projCoord2,in vec4 projCoord3){
|
||||||
|
// Receiver-seitiger Depth-Bias: verhindert Self-Shadowing-Acne bei
|
||||||
|
// flachen Oberflächen und hohem Sonnenstand (PSSM nutzt orthografische
|
||||||
|
// Projektion → w=1, daher direkt von z abziehen).
|
||||||
|
vec4 pc0 = vec4(projCoord0.xy, projCoord0.z - SHADOW_BIAS, projCoord0.w);
|
||||||
|
vec4 pc1 = vec4(projCoord1.xy, projCoord1.z - SHADOW_BIAS, projCoord1.w);
|
||||||
|
vec4 pc2 = vec4(projCoord2.xy, projCoord2.z - SHADOW_BIAS, projCoord2.w);
|
||||||
|
vec4 pc3 = vec4(projCoord3.xy, projCoord3.z - SHADOW_BIAS, projCoord3.w);
|
||||||
|
|
||||||
|
float shadow = 1.0;
|
||||||
|
if(shadowPosition < splits.x){
|
||||||
|
shadow = GETSHADOW(shadowMap0, pc0);
|
||||||
|
}else if( shadowPosition < splits.y){
|
||||||
|
shadowBorderScale = 0.5;
|
||||||
|
shadow = GETSHADOW(shadowMap1, pc1);
|
||||||
|
}else if( shadowPosition < splits.z){
|
||||||
|
shadowBorderScale = 0.25;
|
||||||
|
shadow = GETSHADOW(shadowMap2, pc2);
|
||||||
|
}else if( shadowPosition < splits.w){
|
||||||
|
shadowBorderScale = 0.125;
|
||||||
|
shadow = GETSHADOW(shadowMap3, pc3);
|
||||||
|
}
|
||||||
|
return shadow;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
float getSpotLightShadows(in SHADOWMAP shadowMap,in vec4 projCoord){
|
||||||
|
float shadow = 1.0;
|
||||||
|
projCoord /= projCoord.w;
|
||||||
|
shadow = GETSHADOW(shadowMap,projCoord);
|
||||||
|
|
||||||
|
projCoord = projCoord * 2.0 - 1.0;
|
||||||
|
float fallOff = ( length(projCoord.xy) - 0.9 ) / 0.1;
|
||||||
|
return mix(shadow,1.0,clamp(fallOff,0.0,1.0));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
@@ -12,6 +12,7 @@ MaterialDef BakedTerrain {
|
|||||||
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
|
||||||
Boolean DebugNoLight
|
Boolean DebugNoLight
|
||||||
|
Float Wetness
|
||||||
}
|
}
|
||||||
|
|
||||||
Technique {
|
Technique {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ uniform vec3 m_LightDir;
|
|||||||
uniform vec3 m_SunColor;
|
uniform vec3 m_SunColor;
|
||||||
uniform vec3 m_AmbientColor;
|
uniform vec3 m_AmbientColor;
|
||||||
uniform vec3 g_CameraPosition;
|
uniform vec3 g_CameraPosition;
|
||||||
|
uniform float m_Wetness;
|
||||||
|
|
||||||
in vec3 vWorldPos;
|
in vec3 vWorldPos;
|
||||||
in vec3 vNormal;
|
in vec3 vNormal;
|
||||||
@@ -82,10 +83,17 @@ void main() {
|
|||||||
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
|
||||||
|
float wet = clamp(m_Wetness, 0.0, 1.0);
|
||||||
|
col.rgb *= (1.0 - 0.20 * wet);
|
||||||
|
vec3 viewDir = normalize(g_CameraPosition - vWorldPos);
|
||||||
|
vec3 H = normalize(lightDir + viewDir);
|
||||||
|
float spec = pow(max(dot(N, H), 0.0), 80.0) * wet;
|
||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,13 +29,15 @@ void main() {
|
|||||||
|
|
||||||
vec2 windN = (dot(m_WindDir, m_WindDir) > 0.001) ? normalize(m_WindDir) : vec2(0.0, 1.0);
|
vec2 windN = (dot(m_WindDir, m_WindDir) > 0.001) ? normalize(m_WindDir) : vec2(0.0, 1.0);
|
||||||
float wavePhase = dot(worldXZ, windN);
|
float wavePhase = dot(worldXZ, windN);
|
||||||
|
// Pro-Halm-Zufallsphase: floor() macht sie für alle Vertices desselben Halms gleich
|
||||||
|
float randPhase = fract(sin(dot(floor(worldXZ), vec2(127.1, 311.7))) * 43758.5453) * 6.2832;
|
||||||
|
|
||||||
// Zwei überlagerte Sinuswellen für organische Bewegung
|
// Zwei überlagerte Sinuswellen + Zufallsphase → kein synchroner Gleichtakt
|
||||||
float sway = sin(t * 2.1 + wavePhase * 0.08) * 0.6
|
float sway = sin(t * 2.1 + wavePhase * 0.08 + randPhase) * 0.6
|
||||||
+ sin(t * 1.4 + wavePhase * 0.06) * 0.4;
|
+ sin(t * 1.4 + wavePhase * 0.06 + randPhase * 0.73) * 0.4;
|
||||||
|
|
||||||
// Mindest-Stärke damit bei wenig Wind noch Bewegung sichtbar ist
|
// Halbierte Windstärke für Textur-Gras; Mindest-Stärke für minimale Bewegung
|
||||||
float effectiveStrength = max(m_WindStrength, 0.05);
|
float effectiveStrength = max(m_WindStrength, 0.05) * 0.5;
|
||||||
|
|
||||||
// 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;
|
||||||
|
|||||||
BIN
blight-assets/src/main/resources/Textures/hud/compass_needle.png
Normal file
BIN
blight-assets/src/main/resources/Textures/hud/compass_needle.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.1 KiB |
BIN
blight-assets/src/main/resources/Textures/hud/compass_rose.png
Normal file
BIN
blight-assets/src/main/resources/Textures/hud/compass_rose.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
@@ -290,7 +290,8 @@ public class PlacedObjectState extends BaseAppState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void paintGrass(float cx, float cz, float radius) {
|
private void paintGrass(float cx, float cz, float radius) {
|
||||||
int n = Math.max(1, (int) input.grassTool.density.getValue());
|
int n = (int) Math.round(input.grassTool.density.getValue() * radius * 2.0);
|
||||||
|
if (n <= 0) return;
|
||||||
float baseH = (float) input.grassTool.grassHeight.getValue();
|
float baseH = (float) input.grassTool.grassHeight.getValue();
|
||||||
int slot = input.grassActiveSlot;
|
int slot = input.grassActiveSlot;
|
||||||
Random rng = new Random();
|
Random rng = new Random();
|
||||||
@@ -423,13 +424,13 @@ public class PlacedObjectState extends BaseAppState {
|
|||||||
pos.put(x+w).put(y ).put(z); uv.put(1).put(0);
|
pos.put(x+w).put(y ).put(z); uv.put(1).put(0);
|
||||||
pos.put(x+w).put(y+h).put(z); uv.put(1).put(1);
|
pos.put(x+w).put(y+h).put(z); uv.put(1).put(1);
|
||||||
pos.put(x-w).put(y+h).put(z); uv.put(0).put(1);
|
pos.put(x-w).put(y+h).put(z); uv.put(0).put(1);
|
||||||
for (int i = 0; i < 4; i++) { nor.put(0).put(0).put(1); tan.put(1).put(0).put(0).put(1); }
|
for (int i = 0; i < 4; i++) { nor.put(0).put(1).put(0); tan.put(1).put(0).put(0).put(1); }
|
||||||
// Quad 2: liegt in ZY-Ebene (konstantes x) – Normal zeigt -X, Tangent zeigt +Z
|
// Quad 2: liegt in ZY-Ebene (konstantes x) – Normal zeigt +Y, Tangent zeigt +Z
|
||||||
pos.put(x).put(y ).put(z-w); uv.put(0).put(0);
|
pos.put(x).put(y ).put(z-w); uv.put(0).put(0);
|
||||||
pos.put(x).put(y ).put(z+w); uv.put(1).put(0);
|
pos.put(x).put(y ).put(z+w); uv.put(1).put(0);
|
||||||
pos.put(x).put(y+h).put(z+w); uv.put(1).put(1);
|
pos.put(x).put(y+h).put(z+w); uv.put(1).put(1);
|
||||||
pos.put(x).put(y+h).put(z-w); uv.put(0).put(1);
|
pos.put(x).put(y+h).put(z-w); uv.put(0).put(1);
|
||||||
for (int i = 0; i < 4; i++) { nor.put(-1).put(0).put(0); tan.put(0).put(0).put(1).put(1); }
|
for (int i = 0; i < 4; i++) { nor.put(0).put(1).put(0); tan.put(0).put(0).put(1).put(1); }
|
||||||
idx.put(vi).put(vi+1).put(vi+2);
|
idx.put(vi).put(vi+1).put(vi+2);
|
||||||
idx.put(vi).put(vi+2).put(vi+3);
|
idx.put(vi).put(vi+2).put(vi+3);
|
||||||
idx.put(vi+4).put(vi+5).put(vi+6);
|
idx.put(vi+4).put(vi+5).put(vi+6);
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public class GrassTool extends EditorTool {
|
public class GrassTool extends EditorTool {
|
||||||
|
|
||||||
public final ToolParameter brushRadius = new ToolParameter("Pinselradius", 40.0, 1.0, 500.0);
|
public final ToolParameter brushRadius = new ToolParameter("Pinselradius", 5.0, 1.0, 50.0);
|
||||||
public final ToolParameter grassHeight = new ToolParameter("Grashöhe", 1.5, 0.1, 10.0);
|
public final ToolParameter grassHeight = new ToolParameter("Grashöhe", 0.8, 0.0, 2.0);
|
||||||
public final ToolParameter density = new ToolParameter("Dichte", 8.0, 1.0, 200.0);
|
public final ToolParameter density = new ToolParameter("Dichte", 0.5, 0.0, 2.0);
|
||||||
|
|
||||||
@Override public String getName() { return "Gras (Textur)"; }
|
@Override public String getName() { return "Gras (Textur)"; }
|
||||||
|
|
||||||
|
|||||||
@@ -340,11 +340,12 @@ public class BlightGame extends SimpleApplication {
|
|||||||
setDisplayFps(false);
|
setDisplayFps(false);
|
||||||
setDisplayStatView(false);
|
setDisplayStatView(false);
|
||||||
int h = a.getCamera().getHeight();
|
int h = a.getCamera().getHeight();
|
||||||
if (fpsText != null) fpsText.setLocalTranslation(0, h, 0);
|
|
||||||
if (statsView != null) {
|
|
||||||
float fpsH = fpsText != null ? fpsText.getLineHeight() : 0f;
|
float fpsH = fpsText != null ? fpsText.getLineHeight() : 0f;
|
||||||
statsView.setLocalTranslation(0, h - fpsH, 0);
|
float statsH = statsView != null ? statsView.getHeight() : 0f;
|
||||||
}
|
if (fpsText != null) fpsText.setLocalTranslation(0, h, 0);
|
||||||
|
if (statsView != null) statsView.setLocalTranslation(0, h - fpsH - statsH, 0);
|
||||||
|
if (darkenFps != null) darkenFps.setLocalTranslation(0, h - fpsH, -1);
|
||||||
|
if (darkenStats != null) darkenStats.setLocalTranslation(0, h - fpsH - statsH, -1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
stateManager.attach(statsState);
|
stateManager.attach(statsState);
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package de.blight.game;
|
||||||
|
|
||||||
|
import com.jme3.asset.AssetInfo;
|
||||||
|
import com.jme3.asset.AssetKey;
|
||||||
|
import com.jme3.asset.AssetLocator;
|
||||||
|
import com.jme3.asset.AssetManager;
|
||||||
|
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Leitet veraltete Textur-Pfade auf die aktuellen Speicherorte um.
|
||||||
|
* Behebt Laden von j3o-Dateien, die noch den alten Textur-Pfad
|
||||||
|
* (z. B. Textures/internal/leaves/) statt des neuen (internal/foliage/) enthalten.
|
||||||
|
*
|
||||||
|
* Registriert mit root = blight-assets/src/main/resources.
|
||||||
|
*/
|
||||||
|
public class LegacyAssetRedirectLocator implements AssetLocator {
|
||||||
|
|
||||||
|
private static final Map<String, String> PREFIXES = Map.of(
|
||||||
|
"Textures/bark/", "Textures/internal/bark/",
|
||||||
|
"Textures/leaves/", "Textures/internal/foliage/",
|
||||||
|
"Textures/internal/leaves/", "Textures/internal/foliage/",
|
||||||
|
"Textures/fern/", "Textures/internal/fern/",
|
||||||
|
"Textures/water/", "Textures/internal/water/",
|
||||||
|
"Textures/Water/", "Textures/internal/water/"
|
||||||
|
);
|
||||||
|
|
||||||
|
private Path root;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRootPath(String rootPath) {
|
||||||
|
root = Paths.get(rootPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
public AssetInfo locate(AssetManager manager, AssetKey key) {
|
||||||
|
String name = key.getName();
|
||||||
|
for (var entry : PREFIXES.entrySet()) {
|
||||||
|
if (name.startsWith(entry.getKey())) {
|
||||||
|
String remapped = entry.getValue() + name.substring(entry.getKey().length());
|
||||||
|
AssetInfo info = tryFile(manager, key, remapped);
|
||||||
|
if (info != null) return info;
|
||||||
|
if (remapped.toLowerCase().endsWith(".tga")) {
|
||||||
|
info = tryFile(manager, key, remapped.substring(0, remapped.length() - 4) + ".png");
|
||||||
|
if (info != null) return info;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
private AssetInfo tryFile(AssetManager manager, AssetKey key, String rel) {
|
||||||
|
Path p = root.resolve(rel);
|
||||||
|
if (!Files.exists(p)) return null;
|
||||||
|
return new AssetInfo(manager, key) {
|
||||||
|
@Override
|
||||||
|
public InputStream openStream() {
|
||||||
|
try { return new FileInputStream(p.toFile()); }
|
||||||
|
catch (FileNotFoundException ex) { throw new RuntimeException(ex); }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -46,6 +46,10 @@ public abstract class MenuScreen extends BaseAppState {
|
|||||||
public static final float CONT_W = MenuCanvas.CONT_W;
|
public static final float CONT_W = MenuCanvas.CONT_W;
|
||||||
public static final float CONT_H = MenuCanvas.CONT_H;
|
public static final float CONT_H = MenuCanvas.CONT_H;
|
||||||
|
|
||||||
|
private static int openCount = 0;
|
||||||
|
|
||||||
|
public static boolean isAnyOpen() { return openCount > 0; }
|
||||||
|
|
||||||
public enum BtnStyle { DEFAULT, SAVE, QUIT, DISABLED, ARROW }
|
public enum BtnStyle { DEFAULT, SAVE, QUIT, DISABLED, ARROW }
|
||||||
|
|
||||||
protected SimpleApplication app;
|
protected SimpleApplication app;
|
||||||
@@ -77,6 +81,7 @@ public abstract class MenuScreen extends BaseAppState {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onEnable() {
|
protected void onEnable() {
|
||||||
|
openCount++;
|
||||||
uiScale = MenuCanvas.getScale(app.getCamera());
|
uiScale = MenuCanvas.getScale(app.getCamera());
|
||||||
bgLayer = buildBgLayer();
|
bgLayer = buildBgLayer();
|
||||||
canvasNode = buildCanvas();
|
canvasNode = buildCanvas();
|
||||||
@@ -98,6 +103,7 @@ public abstract class MenuScreen extends BaseAppState {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onDisable() {
|
protected void onDisable() {
|
||||||
|
if (openCount > 0) { openCount--; }
|
||||||
onDisableExtras();
|
onDisableExtras();
|
||||||
app.getInputManager().removeListener(clickListener);
|
app.getInputManager().removeListener(clickListener);
|
||||||
if (app.getInputManager().hasMapping(MAP_CLICK)) {
|
if (app.getInputManager().hasMapping(MAP_CLICK)) {
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ public abstract class OverlayState extends BaseAppState {
|
|||||||
|
|
||||||
private static OverlayState activeOverlay;
|
private static OverlayState activeOverlay;
|
||||||
|
|
||||||
|
public static boolean isAnyOpen() { return activeOverlay != null; }
|
||||||
|
|
||||||
// ── Panel-Dimensionen (90% des Referenzraums) ─────────────────────────────
|
// ── Panel-Dimensionen (90% des Referenzraums) ─────────────────────────────
|
||||||
|
|
||||||
public static final float PW = MenuCanvas.CONT_W;
|
public static final float PW = MenuCanvas.CONT_W;
|
||||||
|
|||||||
@@ -232,9 +232,9 @@ public class WorldScene extends BaseAppState {
|
|||||||
@Override
|
@Override
|
||||||
protected void onEnable() {
|
protected void onEnable() {
|
||||||
try {
|
try {
|
||||||
assetManager.registerLocator(
|
String assetRootStr = AnimationLibrary.findAssetRoot().toAbsolutePath().toString();
|
||||||
AnimationLibrary.findAssetRoot().toAbsolutePath().toString(),
|
assetManager.registerLocator(assetRootStr, com.jme3.asset.plugins.FileLocator.class);
|
||||||
com.jme3.asset.plugins.FileLocator.class);
|
assetManager.registerLocator(assetRootStr, de.blight.game.LegacyAssetRedirectLocator.class);
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {}
|
||||||
|
|
||||||
app.getCamera().setFrustumPerspective(45f,
|
app.getCamera().setFrustumPerspective(45f,
|
||||||
@@ -271,7 +271,7 @@ public class WorldScene extends BaseAppState {
|
|||||||
// Bullet-Charakter: Kapsel 0.4 Radius, 1.0 Höhe, Y-Achse (1)
|
// Bullet-Charakter: Kapsel 0.4 Radius, 1.0 Höhe, Y-Achse (1)
|
||||||
CapsuleCollisionShape capsule = new CapsuleCollisionShape(0.4f, 1.0f, 1);
|
CapsuleCollisionShape capsule = new CapsuleCollisionShape(0.4f, 1.0f, 1);
|
||||||
physicsChar = new CharacterControl(capsule, 0.05f);
|
physicsChar = new CharacterControl(capsule, 0.05f);
|
||||||
physicsChar.setJumpSpeed(12f);
|
physicsChar.setJumpSpeed(7f);
|
||||||
physicsChar.setFallSpeed(35f);
|
physicsChar.setFallSpeed(35f);
|
||||||
physicsChar.setGravity(35f);
|
physicsChar.setGravity(35f);
|
||||||
character.addControl(physicsChar);
|
character.addControl(physicsChar);
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ import com.jme3.app.Application;
|
|||||||
import com.jme3.app.SimpleApplication;
|
import com.jme3.app.SimpleApplication;
|
||||||
import com.jme3.app.state.BaseAppState;
|
import com.jme3.app.state.BaseAppState;
|
||||||
import com.jme3.asset.AssetManager;
|
import com.jme3.asset.AssetManager;
|
||||||
import com.jme3.font.BitmapFont;
|
|
||||||
import com.jme3.font.BitmapText;
|
|
||||||
import com.jme3.material.Material;
|
import com.jme3.material.Material;
|
||||||
import com.jme3.material.RenderState;
|
import com.jme3.material.RenderState;
|
||||||
import com.jme3.math.ColorRGBA;
|
import com.jme3.math.ColorRGBA;
|
||||||
@@ -15,51 +13,48 @@ import com.jme3.math.Vector3f;
|
|||||||
import com.jme3.renderer.Camera;
|
import com.jme3.renderer.Camera;
|
||||||
import com.jme3.renderer.queue.RenderQueue;
|
import com.jme3.renderer.queue.RenderQueue;
|
||||||
import com.jme3.scene.Geometry;
|
import com.jme3.scene.Geometry;
|
||||||
import com.jme3.scene.Mesh;
|
|
||||||
import com.jme3.scene.Node;
|
import com.jme3.scene.Node;
|
||||||
import com.jme3.scene.VertexBuffer;
|
import com.jme3.scene.Spatial;
|
||||||
import com.jme3.util.BufferUtils;
|
import com.jme3.scene.shape.Quad;
|
||||||
|
import com.jme3.texture.Texture;
|
||||||
|
import de.blight.game.animation.AnimationLibrary;
|
||||||
|
import de.blight.game.config.MenuScreen;
|
||||||
|
import de.blight.game.config.OverlayState;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Kompass unten links – runde rotierende Rose, identisch mit dem Editor-Kompass.
|
* Textur-basierter Kompass unten links.
|
||||||
*
|
*
|
||||||
* Die Rose dreht sich mit dem Kamera-Yaw; die Buchstaben konter-rotieren,
|
* compass_rose.png (100×100) – dreht sich mit dem Kamera-Yaw.
|
||||||
* damit sie immer aufrecht bleiben. Ein festes goldenes Dreieck oben zeigt
|
* compass_needle.png (100×100) – fix, zeigt immer nach oben (= aktuelle Blickrichtung).
|
||||||
* die aktuelle Blickrichtung an.
|
*
|
||||||
|
* Beide Dateien werden beim ersten Start als Platzhalter erzeugt
|
||||||
|
* und können durch eigene Grafiken ersetzt werden.
|
||||||
*/
|
*/
|
||||||
public class CompassHudState extends BaseAppState {
|
public class CompassHudState extends BaseAppState {
|
||||||
|
|
||||||
// ── Layout ────────────────────────────────────────────────────────────────
|
private static final Logger log = LoggerFactory.getLogger(CompassHudState.class);
|
||||||
private static final float SIZE = 100f; // Canvas-Größe (px)
|
|
||||||
private static final float RADIUS = 47f; // Kreis-Außenradius
|
|
||||||
private static final float LABEL_R = 30f; // Beschriftungsradius vom Mittelpunkt
|
|
||||||
private static final float MARGIN = 16f; // Abstand zum Bildschirmrand
|
|
||||||
|
|
||||||
// ── Farben (matching EditorApp.drawCompass) ───────────────────────────────
|
private static final float SIZE = 100f;
|
||||||
private static final ColorRGBA COL_BG = new ColorRGBA(0.047f, 0.047f, 0.094f, 0.82f);
|
private static final float MARGIN = 16f;
|
||||||
private static final ColorRGBA COL_BORDER = new ColorRGBA(0.392f, 0.392f, 0.627f, 0.85f);
|
|
||||||
private static final ColorRGBA COL_TICK_MJ = new ColorRGBA(0.627f, 0.627f, 0.784f, 0.65f);
|
|
||||||
private static final ColorRGBA COL_TICK_MN = new ColorRGBA(0.627f, 0.627f, 0.784f, 0.45f);
|
|
||||||
private static final ColorRGBA COL_N = new ColorRGBA(1.00f, 0.314f, 0.314f, 1.00f);
|
|
||||||
private static final ColorRGBA COL_CARD = new ColorRGBA(0.824f, 0.824f, 0.902f, 1.00f);
|
|
||||||
private static final ColorRGBA COL_MARKER = new ColorRGBA(1.00f, 0.863f, 0.235f, 0.95f);
|
|
||||||
private static final ColorRGBA COL_CENTER = new ColorRGBA(0.784f, 0.784f, 0.941f, 0.85f);
|
|
||||||
|
|
||||||
// Kompasswinkel (Grad von Nord CW): N=0, O=90, S=180, W=270
|
|
||||||
private static final String[] LABELS = {"N", "O", "S", "W"};
|
|
||||||
private static final float[] LABEL_DEG = {0f, 90f, 180f, 270f};
|
|
||||||
|
|
||||||
private SimpleApplication app;
|
private SimpleApplication app;
|
||||||
private Camera cam;
|
private Camera cam;
|
||||||
private AssetManager assets;
|
private AssetManager assets;
|
||||||
private BitmapFont font;
|
|
||||||
|
|
||||||
private Node compassNode;
|
private Node compassNode;
|
||||||
private Node roseNode;
|
private Node roseNode;
|
||||||
|
|
||||||
private final Node[] labelNodes = new Node[4];
|
|
||||||
private final Quaternion roseRot = new Quaternion();
|
private final Quaternion roseRot = new Quaternion();
|
||||||
private final Quaternion labelRot = new Quaternion();
|
private boolean lastMenuOpen = false;
|
||||||
|
|
||||||
// ── Lifecycle ─────────────────────────────────────────────────────────────
|
// ── Lifecycle ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -68,7 +63,7 @@ public class CompassHudState extends BaseAppState {
|
|||||||
app = (SimpleApplication) application;
|
app = (SimpleApplication) application;
|
||||||
cam = app.getCamera();
|
cam = app.getCamera();
|
||||||
assets = app.getAssetManager();
|
assets = app.getAssetManager();
|
||||||
font = assets.loadFont("Interface/Fonts/Default.fnt");
|
ensureAssets();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -94,169 +89,160 @@ public class CompassHudState extends BaseAppState {
|
|||||||
public void update(float tpf) {
|
public void update(float tpf) {
|
||||||
if (compassNode == null) { return; }
|
if (compassNode == null) { return; }
|
||||||
|
|
||||||
Vector3f dir = cam.getDirection();
|
boolean menuOpen = OverlayState.isAnyOpen() || MenuScreen.isAnyOpen();
|
||||||
// yaw=0 = Blick nach -Z (Nord); JME3-Kamera blickt in -Z bei Norden
|
if (menuOpen != lastMenuOpen) {
|
||||||
float yaw = FastMath.atan2(dir.x, -dir.z);
|
lastMenuOpen = menuOpen;
|
||||||
|
compassNode.setCullHint(menuOpen ? Spatial.CullHint.Always : Spatial.CullHint.Inherit);
|
||||||
|
}
|
||||||
|
if (menuOpen) { return; }
|
||||||
|
|
||||||
// Rose dreht sich so, dass Nord immer in der echten Nord-Richtung liegt
|
Vector3f dir = cam.getDirection();
|
||||||
|
float yaw = FastMath.atan2(dir.x, -dir.z);
|
||||||
roseRot.fromAngleAxis(-yaw, Vector3f.UNIT_Z);
|
roseRot.fromAngleAxis(-yaw, Vector3f.UNIT_Z);
|
||||||
roseNode.setLocalRotation(roseRot);
|
roseNode.setLocalRotation(roseRot);
|
||||||
|
|
||||||
// Buchstaben konter-rotieren → bleiben immer aufrecht
|
|
||||||
labelRot.fromAngleAxis(yaw, Vector3f.UNIT_Z);
|
|
||||||
for (Node ln : labelNodes) {
|
|
||||||
ln.setLocalRotation(labelRot);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Aufbau ────────────────────────────────────────────────────────────────
|
// ── Aufbau ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
private void buildCompass() {
|
private void buildCompass() {
|
||||||
float oy = HotbarState.MARGIN_BOT + HotbarState.SLOT_SIZE + 8f;
|
// Textur-Größen auslesen → echte Pixelgröße bestimmt Layout und Quad-Größe
|
||||||
float cx = MARGIN + SIZE / 2f;
|
float roseW = SIZE, roseH = SIZE;
|
||||||
float cy = oy + SIZE / 2f;
|
try {
|
||||||
|
com.jme3.texture.Texture roseTex = assets.loadTexture("Textures/hud/compass_rose.png");
|
||||||
|
roseW = roseTex.getImage().getWidth();
|
||||||
|
roseH = roseTex.getImage().getHeight();
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
|
||||||
|
// compassNode-Mittelpunkt so setzen, dass die Rose mit MARGIN Abstand zur Ecke endet
|
||||||
|
float cx = MARGIN + roseW / 2f;
|
||||||
|
float cy = MARGIN + roseH / 2f;
|
||||||
|
|
||||||
compassNode = new Node("compass");
|
compassNode = new Node("compass");
|
||||||
compassNode.setLocalTranslation(cx, cy, 0f);
|
compassNode.setLocalTranslation(cx, cy, 0f);
|
||||||
|
|
||||||
// Rahmen (etwas größerer Kreis) + Hintergrund
|
|
||||||
compassNode.attachChild(makeCircle(RADIUS + 1.5f, 64, COL_BORDER, 1f));
|
|
||||||
compassNode.attachChild(makeCircle(RADIUS, 64, COL_BG, 2f));
|
|
||||||
|
|
||||||
// Rotierende Rose
|
|
||||||
roseNode = new Node("rose");
|
roseNode = new Node("rose");
|
||||||
compassNode.attachChild(roseNode);
|
compassNode.attachChild(roseNode);
|
||||||
|
roseNode.attachChild(makeTexQuad("Textures/hud/compass_rose.png", 2f));
|
||||||
|
|
||||||
// 8 Tick-Striche (major = Hauptrichtungen, minor = Zwischenrichtungen)
|
// Nadel zentriert auf dem Kompass-Mittelpunkt, auf natürlicher Textur-Größe
|
||||||
for (int i = 0; i < 8; i++) {
|
compassNode.attachChild(makeTexQuad("Textures/hud/compass_needle.png", 5f));
|
||||||
float deg = i * 45f;
|
|
||||||
boolean maj = (i % 2 == 0);
|
|
||||||
float innerR = RADIUS - (maj ? 9f : 5f);
|
|
||||||
roseNode.attachChild(makeTickQuad(deg, innerR, maj ? COL_TICK_MJ : COL_TICK_MN, 3f));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Himmelsrichtungs-Beschriftungen
|
|
||||||
for (int i = 0; i < 4; i++) {
|
|
||||||
float ca = LABEL_DEG[i] * FastMath.DEG_TO_RAD;
|
|
||||||
float lx = FastMath.sin(ca) * LABEL_R;
|
|
||||||
float ly = FastMath.cos(ca) * LABEL_R;
|
|
||||||
|
|
||||||
BitmapText lbl = makeBitmapText(LABELS[i], i == 0 ? 14 : 12, i == 0 ? COL_N : COL_CARD);
|
|
||||||
// Buchstabe am Label-Ursprung zentrieren
|
|
||||||
lbl.setLocalTranslation(-lbl.getLineWidth() * 0.5f, lbl.getLineHeight() * 0.4f, 0f);
|
|
||||||
|
|
||||||
Node ln = new Node("lbl_" + LABELS[i]);
|
|
||||||
ln.setLocalTranslation(lx, ly, 4f);
|
|
||||||
ln.attachChild(lbl);
|
|
||||||
labelNodes[i] = ln;
|
|
||||||
roseNode.attachChild(ln);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fixer Richtungs-Zeiger: gelbes Dreieck oben (nicht in roseNode)
|
|
||||||
compassNode.attachChild(makeTriangle(COL_MARKER, 5f));
|
|
||||||
|
|
||||||
// Mittelpunkt-Punkt
|
|
||||||
compassNode.attachChild(makeCircle(2.5f, 16, COL_CENTER, 6f));
|
|
||||||
|
|
||||||
app.getGuiNode().attachChild(compassNode);
|
app.getGuiNode().attachChild(compassNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Geometry-Helfer ───────────────────────────────────────────────────────
|
/** Lädt Textur, erzeugt Quad in natürlicher Textur-Größe, zentriert bei (0,0). */
|
||||||
|
private Geometry makeTexQuad(String assetPath, float z) {
|
||||||
/** Gefüllter Kreis (Triangle-Fan) zentriert bei (0,0). */
|
Material mat = new Material(assets, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||||
private Geometry makeCircle(float r, int segs, ColorRGBA col, float z) {
|
mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
|
||||||
float[] verts = new float[(segs + 2) * 3];
|
float w = SIZE, h = SIZE;
|
||||||
// Mittelpunkt
|
try {
|
||||||
verts[0] = 0f; verts[1] = 0f; verts[2] = 0f;
|
Texture tex = assets.loadTexture(assetPath);
|
||||||
for (int i = 0; i <= segs; i++) {
|
w = tex.getImage().getWidth();
|
||||||
float a = FastMath.TWO_PI * i / segs;
|
h = tex.getImage().getHeight();
|
||||||
int base = (i + 1) * 3;
|
mat.setTexture("ColorMap", tex);
|
||||||
verts[base] = FastMath.cos(a) * r;
|
} catch (Exception e) {
|
||||||
verts[base + 1] = FastMath.sin(a) * r;
|
mat.setColor("Color", new ColorRGBA(1f, 1f, 1f, 0.5f));
|
||||||
verts[base + 2] = 0f;
|
|
||||||
}
|
}
|
||||||
int[] idx = new int[segs * 3];
|
String name = "cmp_" + assetPath.substring(assetPath.lastIndexOf('/') + 1);
|
||||||
for (int i = 0; i < segs; i++) {
|
Geometry g = new Geometry(name, new Quad(w, h));
|
||||||
idx[i * 3] = 0;
|
g.setLocalTranslation(-w / 2f, -h / 2f, z);
|
||||||
idx[i * 3 + 1] = i + 1;
|
g.setMaterial(mat);
|
||||||
idx[i * 3 + 2] = i + 2;
|
g.setQueueBucket(RenderQueue.Bucket.Gui);
|
||||||
}
|
|
||||||
Geometry g = new Geometry("circle", buildMesh(verts, idx));
|
|
||||||
g.setMaterial(makeMat(col));
|
|
||||||
g.setLocalTranslation(0f, 0f, z);
|
|
||||||
g.setQueueBucket(col.a < 1f ? RenderQueue.Bucket.Transparent : RenderQueue.Bucket.Gui);
|
|
||||||
return g;
|
return g;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// ── Placeholder-PNGs ──────────────────────────────────────────────────────
|
||||||
* Tick-Strich als dünnes Quad entlang der radialen Richtung.
|
|
||||||
* ca_deg: Kompasswinkel (0=Nord, 90=Ost, CW).
|
|
||||||
*/
|
|
||||||
private Geometry makeTickQuad(float ca_deg, float innerR, ColorRGBA col, float z) {
|
|
||||||
float ca = ca_deg * FastMath.DEG_TO_RAD;
|
|
||||||
float sx = FastMath.sin(ca); // Richtung entlang des Radius
|
|
||||||
float cy_ = FastMath.cos(ca);
|
|
||||||
float px = -cy_ * 0.75f; // Halbe Breite (senkrecht zur Radiale)
|
|
||||||
float py = sx * 0.75f;
|
|
||||||
|
|
||||||
float[] v = {
|
private void ensureAssets() {
|
||||||
sx * innerR + px, cy_ * innerR + py, z, // v0 links innen
|
Path root = AnimationLibrary.findAssetRoot();
|
||||||
sx * innerR - px, cy_ * innerR - py, z, // v1 rechts innen
|
Path dir = root.resolve("Textures").resolve("hud");
|
||||||
sx * RADIUS + px, cy_ * RADIUS + py, z, // v2 links außen
|
try {
|
||||||
sx * RADIUS - px, cy_ * RADIUS - py, z // v3 rechts außen
|
Files.createDirectories(dir);
|
||||||
};
|
} catch (IOException e) {
|
||||||
int[] idx = {0, 1, 2, 1, 3, 2};
|
log.warn("[Compass] Verzeichnis nicht erstellbar: {}", dir);
|
||||||
|
return;
|
||||||
Geometry g = new Geometry("tick", buildMesh(v, idx));
|
}
|
||||||
g.setMaterial(makeMat(col));
|
ensureRose(dir.resolve("compass_rose.png"));
|
||||||
g.setQueueBucket(RenderQueue.Bucket.Transparent);
|
ensureNeedle(dir.resolve("compass_needle.png"));
|
||||||
return g;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Erzeugt Platzhalter-Rose: dunkler Kreis + 8 Striche + N/E/S/W. */
|
||||||
* Festes gelbes Dreieck – zeigt immer nach oben (Blickrichtung).
|
private static void ensureRose(Path path) {
|
||||||
* Spitze bei (0, RADIUS−5), Basis bei (±5, RADIUS−16).
|
if (Files.exists(path)) { return; }
|
||||||
*/
|
try {
|
||||||
private Geometry makeTriangle(ColorRGBA col, float z) {
|
int cx = 50, cy = 50, r = 47;
|
||||||
float tipY = RADIUS - 5f;
|
BufferedImage img = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
|
||||||
float baseY = RADIUS - 16f;
|
Graphics2D g = img.createGraphics();
|
||||||
float[] v = {
|
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||||
0f, tipY, 0f, // Spitze
|
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
|
||||||
-5f, baseY, 0f, // Basis links
|
|
||||||
5f, baseY, 0f // Basis rechts
|
// Hintergrund-Kreis
|
||||||
};
|
g.setColor(new Color(12, 12, 24, 209));
|
||||||
// CCW von +Z-Seite: (0,high)→(-5,low)→(5,low) ✓
|
g.fillOval(cx - r, cy - r, r * 2, r * 2);
|
||||||
int[] idx = {0, 1, 2};
|
|
||||||
Geometry g = new Geometry("triangle", buildMesh(v, idx));
|
// Rand-Ring
|
||||||
g.setMaterial(makeMat(col));
|
g.setColor(new Color(100, 100, 160, 216));
|
||||||
g.setLocalTranslation(0f, 0f, z);
|
g.setStroke(new BasicStroke(1.5f));
|
||||||
g.setQueueBucket(RenderQueue.Bucket.Transparent);
|
g.drawOval(cx - r, cy - r, r * 2, r * 2);
|
||||||
return g;
|
|
||||||
|
// 8 Tick-Striche; angle −90° → 0° zeigt nach oben (= Norden in BufferedImage-Y)
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
double angle = Math.toRadians(i * 45.0 - 90.0);
|
||||||
|
boolean major = (i % 2 == 0);
|
||||||
|
int innerR = r - (major ? 9 : 5);
|
||||||
|
int x1 = (int)(cx + Math.cos(angle) * innerR);
|
||||||
|
int y1 = (int)(cy + Math.sin(angle) * innerR);
|
||||||
|
int x2 = (int)(cx + Math.cos(angle) * (r - 1));
|
||||||
|
int y2 = (int)(cy + Math.sin(angle) * (r - 1));
|
||||||
|
g.setColor(new Color(160, 160, 200, 166));
|
||||||
|
g.setStroke(new BasicStroke(major ? 1.5f : 1.0f));
|
||||||
|
g.drawLine(x1, y1, x2, y2);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Mesh buildMesh(float[] verts, int[] idx) {
|
// Himmelsrichtungs-Buchstaben
|
||||||
Mesh m = new Mesh();
|
int labelR = 31;
|
||||||
m.setBuffer(VertexBuffer.Type.Position, 3, BufferUtils.createFloatBuffer(verts));
|
|
||||||
m.setBuffer(VertexBuffer.Type.Index, 3, BufferUtils.createIntBuffer(idx));
|
g.setFont(new Font("SansSerif", Font.BOLD, 13));
|
||||||
m.updateBound();
|
FontMetrics fm = g.getFontMetrics();
|
||||||
return m;
|
g.setColor(new Color(255, 80, 80, 255));
|
||||||
|
drawCentered(g, fm, "N", cx, cy - labelR); // oben in BufferedImage = oben auf dem Bildschirm
|
||||||
|
|
||||||
|
g.setFont(new Font("SansSerif", Font.BOLD, 11));
|
||||||
|
fm = g.getFontMetrics();
|
||||||
|
g.setColor(new Color(210, 210, 230, 255));
|
||||||
|
drawCentered(g, fm, "E", cx + labelR, cy);
|
||||||
|
drawCentered(g, fm, "S", cx, cy + labelR);
|
||||||
|
drawCentered(g, fm, "W", cx - labelR, cy);
|
||||||
|
|
||||||
|
g.dispose();
|
||||||
|
ImageIO.write(img, "PNG", path.toFile());
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("[Compass] compass_rose.png nicht erstellbar: {}", e.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Material makeMat(ColorRGBA col) {
|
/** Erzeugt Platzhalter-Nadel: gelbes Dreieck, Spitze oben. */
|
||||||
Material m = new Material(assets, "Common/MatDefs/Misc/Unshaded.j3md");
|
private static void ensureNeedle(Path path) {
|
||||||
m.setColor("Color", col.clone());
|
if (Files.exists(path)) { return; }
|
||||||
if (col.a < 1f) {
|
try {
|
||||||
m.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
|
BufferedImage img = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
|
||||||
|
Graphics2D g = img.createGraphics();
|
||||||
|
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||||
|
g.setColor(new Color(255, 220, 60, 242));
|
||||||
|
// Spitze bei y=8, Basis bei y=21; kleines y in BufferedImage = oben auf dem Bildschirm
|
||||||
|
int[] xp = {50, 43, 57};
|
||||||
|
int[] yp = { 8, 21, 21};
|
||||||
|
g.fillPolygon(xp, yp, 3);
|
||||||
|
g.dispose();
|
||||||
|
ImageIO.write(img, "PNG", path.toFile());
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("[Compass] compass_needle.png nicht erstellbar: {}", e.getMessage());
|
||||||
}
|
}
|
||||||
return m;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private BitmapText makeBitmapText(String text, int size, ColorRGBA col) {
|
private static void drawCentered(Graphics2D g, FontMetrics fm, String text, int cx, int cy) {
|
||||||
BitmapText t = new BitmapText(font);
|
int x = cx - fm.stringWidth(text) / 2;
|
||||||
t.setSize(size);
|
int y = cy + (fm.getAscent() - fm.getDescent()) / 2;
|
||||||
t.setColor(col.clone());
|
g.drawString(text, x, y);
|
||||||
t.setText(text);
|
|
||||||
t.setQueueBucket(RenderQueue.Bucket.Gui);
|
|
||||||
return t;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import com.jme3.app.Application;
|
|||||||
import com.jme3.app.SimpleApplication;
|
import com.jme3.app.SimpleApplication;
|
||||||
import com.jme3.app.state.BaseAppState;
|
import com.jme3.app.state.BaseAppState;
|
||||||
import com.jme3.asset.AssetManager;
|
import com.jme3.asset.AssetManager;
|
||||||
|
import com.jme3.bullet.BulletAppState;
|
||||||
|
import com.jme3.bullet.collision.PhysicsRayTestResult;
|
||||||
import com.jme3.material.Material;
|
import com.jme3.material.Material;
|
||||||
import com.jme3.material.RenderState;
|
import com.jme3.material.RenderState;
|
||||||
import com.jme3.math.*;
|
import com.jme3.math.*;
|
||||||
@@ -201,6 +203,8 @@ public class GrassState extends BaseAppState
|
|||||||
List<GrassTuft> tufts = chunkTufts[idx];
|
List<GrassTuft> tufts = chunkTufts[idx];
|
||||||
if (tufts.isEmpty()) return;
|
if (tufts.isEmpty()) return;
|
||||||
|
|
||||||
|
BulletAppState bullet = getApplication().getStateManager().getState(BulletAppState.class);
|
||||||
|
|
||||||
Map<Integer, List<float[]>> bySlot = new LinkedHashMap<>();
|
Map<Integer, List<float[]>> bySlot = new LinkedHashMap<>();
|
||||||
for (GrassTuft t : tufts) {
|
for (GrassTuft t : tufts) {
|
||||||
long seed = (long) Float.floatToRawIntBits(t.x()) * 0x9E3779B9L
|
long seed = (long) Float.floatToRawIntBits(t.x()) * 0x9E3779B9L
|
||||||
@@ -210,7 +214,7 @@ public class GrassState extends BaseAppState
|
|||||||
for (int b = 0; b < BLADES_PER_TUFT; b++) {
|
for (int b = 0; b < BLADES_PER_TUFT; b++) {
|
||||||
float bx = t.x() + (rng.nextFloat() - 0.5f) * TUFT_SPREAD * 2f;
|
float bx = t.x() + (rng.nextFloat() - 0.5f) * TUFT_SPREAD * 2f;
|
||||||
float bz = t.z() + (rng.nextFloat() - 0.5f) * TUFT_SPREAD * 2f;
|
float bz = t.z() + (rng.nextFloat() - 0.5f) * TUFT_SPREAD * 2f;
|
||||||
float th = terrainChunkState.getHeightAt(bx, bz);
|
float th = surfaceHeight(bullet, bx, bz);
|
||||||
if (Float.isNaN(th)) continue;
|
if (Float.isNaN(th)) continue;
|
||||||
float h = t.height() * (0.7f + rng.nextFloat() * 0.6f);
|
float h = t.height() * (0.7f + rng.nextFloat() * 0.6f);
|
||||||
blades.add(new float[]{bx, th, bz, h});
|
blades.add(new float[]{bx, th, bz, h});
|
||||||
@@ -259,12 +263,36 @@ public class GrassState extends BaseAppState
|
|||||||
chunkNodes[ci].setCullHint(visible ? Spatial.CullHint.Inherit : Spatial.CullHint.Always);
|
chunkNodes[ci].setCullHint(visible ? Spatial.CullHint.Inherit : Spatial.CullHint.Always);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Oberflächen-Höhe via Physik-Raycast ──────────────────────────────────
|
||||||
|
|
||||||
|
private float surfaceHeight(BulletAppState bullet, float wx, float wz) {
|
||||||
|
float base = terrainChunkState.getHeightAt(wx, wz);
|
||||||
|
if (bullet == null || bullet.getPhysicsSpace() == null) {
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
float refY = Float.isNaN(base) ? 0f : base;
|
||||||
|
Vector3f from = new Vector3f(wx, refY + 200f, wz);
|
||||||
|
Vector3f to = new Vector3f(wx, refY - 5f, wz);
|
||||||
|
List<PhysicsRayTestResult> hits = bullet.getPhysicsSpace().rayTest(from, to);
|
||||||
|
if (hits.isEmpty()) {
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
float bestFrac = Float.MAX_VALUE;
|
||||||
|
for (PhysicsRayTestResult hit : hits) {
|
||||||
|
if (hit.getHitFraction() < bestFrac) {
|
||||||
|
bestFrac = hit.getHitFraction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return from.y + (to.y - from.y) * bestFrac;
|
||||||
|
}
|
||||||
|
|
||||||
// ── Mesh: Kreuz-Quad mit UV ───────────────────────────────────────────────
|
// ── Mesh: Kreuz-Quad mit UV ───────────────────────────────────────────────
|
||||||
|
|
||||||
private static Mesh buildGrassMesh(List<float[]> blades) {
|
private static Mesh buildGrassMesh(List<float[]> blades) {
|
||||||
int n = blades.size();
|
int n = blades.size();
|
||||||
FloatBuffer pos = BufferUtils.createFloatBuffer(n * 8 * 3);
|
FloatBuffer pos = BufferUtils.createFloatBuffer(n * 8 * 3);
|
||||||
FloatBuffer uv = BufferUtils.createFloatBuffer(n * 8 * 2);
|
FloatBuffer uv = BufferUtils.createFloatBuffer(n * 8 * 2);
|
||||||
|
FloatBuffer nrm = BufferUtils.createFloatBuffer(n * 8 * 3);
|
||||||
IntBuffer idx = BufferUtils.createIntBuffer(n * 12);
|
IntBuffer idx = BufferUtils.createIntBuffer(n * 12);
|
||||||
|
|
||||||
int vi = 0;
|
int vi = 0;
|
||||||
@@ -272,15 +300,15 @@ public class GrassState extends BaseAppState
|
|||||||
float x = blade[0], y = blade[1], z = blade[2], h = blade[3];
|
float x = blade[0], y = blade[1], z = blade[2], h = blade[3];
|
||||||
float w = Math.max(0.05f, h * BLADE_WIDTH);
|
float w = Math.max(0.05f, h * BLADE_WIDTH);
|
||||||
|
|
||||||
pos.put(x-w).put(y ).put(z); uv.put(0).put(0);
|
pos.put(x-w).put(y ).put(z); uv.put(0).put(0); nrm.put(0).put(1).put(0);
|
||||||
pos.put(x+w).put(y ).put(z); uv.put(1).put(0);
|
pos.put(x+w).put(y ).put(z); uv.put(1).put(0); nrm.put(0).put(1).put(0);
|
||||||
pos.put(x+w).put(y+h).put(z); uv.put(1).put(1);
|
pos.put(x+w).put(y+h).put(z); uv.put(1).put(1); nrm.put(0).put(1).put(0);
|
||||||
pos.put(x-w).put(y+h).put(z); uv.put(0).put(1);
|
pos.put(x-w).put(y+h).put(z); uv.put(0).put(1); nrm.put(0).put(1).put(0);
|
||||||
|
|
||||||
pos.put(x).put(y ).put(z-w); uv.put(0).put(0);
|
pos.put(x).put(y ).put(z-w); uv.put(0).put(0); nrm.put(0).put(1).put(0);
|
||||||
pos.put(x).put(y ).put(z+w); uv.put(1).put(0);
|
pos.put(x).put(y ).put(z+w); uv.put(1).put(0); nrm.put(0).put(1).put(0);
|
||||||
pos.put(x).put(y+h).put(z+w); uv.put(1).put(1);
|
pos.put(x).put(y+h).put(z+w); uv.put(1).put(1); nrm.put(0).put(1).put(0);
|
||||||
pos.put(x).put(y+h).put(z-w); uv.put(0).put(1);
|
pos.put(x).put(y+h).put(z-w); uv.put(0).put(1); nrm.put(0).put(1).put(0);
|
||||||
|
|
||||||
idx.put(vi ).put(vi+1).put(vi+2);
|
idx.put(vi ).put(vi+1).put(vi+2);
|
||||||
idx.put(vi ).put(vi+2).put(vi+3);
|
idx.put(vi ).put(vi+2).put(vi+3);
|
||||||
@@ -292,6 +320,7 @@ public class GrassState extends BaseAppState
|
|||||||
Mesh mesh = new Mesh();
|
Mesh mesh = new Mesh();
|
||||||
mesh.setBuffer(VertexBuffer.Type.Position, 3, pos);
|
mesh.setBuffer(VertexBuffer.Type.Position, 3, pos);
|
||||||
mesh.setBuffer(VertexBuffer.Type.TexCoord, 2, uv);
|
mesh.setBuffer(VertexBuffer.Type.TexCoord, 2, uv);
|
||||||
|
mesh.setBuffer(VertexBuffer.Type.Normal, 3, nrm);
|
||||||
mesh.setBuffer(VertexBuffer.Type.Index, 3, idx);
|
mesh.setBuffer(VertexBuffer.Type.Index, 3, idx);
|
||||||
mesh.updateBound();
|
mesh.updateBound();
|
||||||
return mesh;
|
return mesh;
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ import com.jme3.scene.shape.Quad;
|
|||||||
import com.jme3.texture.Texture;
|
import com.jme3.texture.Texture;
|
||||||
import de.blight.common.model.Item;
|
import de.blight.common.model.Item;
|
||||||
import de.blight.common.model.MainCharacter;
|
import de.blight.common.model.MainCharacter;
|
||||||
|
import de.blight.game.config.MenuScreen;
|
||||||
|
import de.blight.game.config.OverlayState;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Immer sichtbare Schnellzugriffsleiste (Hotbar) mit 10 Slots (Tasten 1–9, 0).
|
* Immer sichtbare Schnellzugriffsleiste (Hotbar) mit 10 Slots (Tasten 1–9, 0).
|
||||||
@@ -62,6 +64,7 @@ public class HotbarState extends BaseAppState {
|
|||||||
|
|
||||||
final Item[] slots = new Item[SLOT_COUNT];
|
final Item[] slots = new Item[SLOT_COUNT];
|
||||||
private int activeSlot = 0;
|
private int activeSlot = 0;
|
||||||
|
private boolean lastMenuOpen = false;
|
||||||
|
|
||||||
private final Geometry[] borderGeoms = new Geometry[SLOT_COUNT];
|
private final Geometry[] borderGeoms = new Geometry[SLOT_COUNT];
|
||||||
private final Node[] thumbNodes = new Node[SLOT_COUNT];
|
private final Node[] thumbNodes = new Node[SLOT_COUNT];
|
||||||
@@ -98,6 +101,17 @@ public class HotbarState extends BaseAppState {
|
|||||||
@Override
|
@Override
|
||||||
protected void cleanup(Application application) {}
|
protected void cleanup(Application application) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(float tpf) {
|
||||||
|
boolean menuOpen = OverlayState.isAnyOpen() || MenuScreen.isAnyOpen();
|
||||||
|
if (menuOpen != lastMenuOpen) {
|
||||||
|
lastMenuOpen = menuOpen;
|
||||||
|
if (hotbarNode != null) {
|
||||||
|
hotbarNode.setCullHint(menuOpen ? Spatial.CullHint.Always : Spatial.CullHint.Inherit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ── Aufbau ───────────────────────────────────────────────────────────────
|
// ── Aufbau ───────────────────────────────────────────────────────────────
|
||||||
private void buildHotbar() {
|
private void buildHotbar() {
|
||||||
float sw = app.getCamera().getWidth();
|
float sw = app.getCamera().getWidth();
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ import com.jme3.ui.Picture;
|
|||||||
import com.jme3.util.BufferUtils;
|
import com.jme3.util.BufferUtils;
|
||||||
import de.blight.common.model.MainCharacter;
|
import de.blight.common.model.MainCharacter;
|
||||||
import de.blight.game.animation.AnimationLibrary;
|
import de.blight.game.animation.AnimationLibrary;
|
||||||
|
import de.blight.game.config.MenuScreen;
|
||||||
|
import de.blight.game.config.OverlayState;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -63,6 +65,7 @@ public class HudState extends BaseAppState {
|
|||||||
private Node hudNode;
|
private Node hudNode;
|
||||||
private final Geometry[] fills = new Geometry[3];
|
private final Geometry[] fills = new Geometry[3];
|
||||||
private final float[] lastRatios = { -1f, -1f, -1f };
|
private final float[] lastRatios = { -1f, -1f, -1f };
|
||||||
|
private boolean lastMenuOpen = false;
|
||||||
|
|
||||||
public HudState(MainCharacter mc) {
|
public HudState(MainCharacter mc) {
|
||||||
this.mc = mc;
|
this.mc = mc;
|
||||||
@@ -96,6 +99,13 @@ public class HudState extends BaseAppState {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void update(float tpf) {
|
public void update(float tpf) {
|
||||||
|
boolean menuOpen = OverlayState.isAnyOpen() || MenuScreen.isAnyOpen();
|
||||||
|
if (menuOpen != lastMenuOpen) {
|
||||||
|
lastMenuOpen = menuOpen;
|
||||||
|
if (hudNode != null) {
|
||||||
|
hudNode.setCullHint(menuOpen ? Spatial.CullHint.Always : Spatial.CullHint.Inherit);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (mc == null) return;
|
if (mc == null) return;
|
||||||
int maxHp = mc.getMaxHp();
|
int maxHp = mc.getMaxHp();
|
||||||
int maxSt = mc.getMaxStamina();
|
int maxSt = mc.getMaxStamina();
|
||||||
|
|||||||
@@ -74,6 +74,8 @@ public class SculptedMeshState extends BaseAppState {
|
|||||||
ColorRGBA ac = dns.getCustomAmbient();
|
ColorRGBA ac = dns.getCustomAmbient();
|
||||||
material.setVector3("SunColor", new Vector3f(sc.r, sc.g, sc.b));
|
material.setVector3("SunColor", new Vector3f(sc.r, sc.g, sc.b));
|
||||||
material.setVector3("AmbientColor", new Vector3f(ac.r, ac.g, ac.b));
|
material.setVector3("AmbientColor", new Vector3f(ac.r, ac.g, ac.b));
|
||||||
|
RainState rs = app.getStateManager().getState(RainState.class);
|
||||||
|
if (rs != null) material.setFloat("Wetness", rs.getWetness());
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Laden ────────────────────────────────────────────────────────────────
|
// ── Laden ────────────────────────────────────────────────────────────────
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user