diff --git a/blight-assets/src/main/resources/MatDefs/BakedTerrain.j3md b/blight-assets/src/main/resources/MatDefs/BakedTerrain.j3md index 2023242..b179155 100644 --- a/blight-assets/src/main/resources/MatDefs/BakedTerrain.j3md +++ b/blight-assets/src/main/resources/MatDefs/BakedTerrain.j3md @@ -6,6 +6,8 @@ MaterialDef BakedTerrain { Texture2D AlphaMap Texture2D AlphaMap_1 Texture2D AlphaMap_2 + Boolean HasSteep + Int SteepIndex Vector3 LightDir : 0.6 -0.8 0.4 Vector3 SunColor : 0.68 0.65 0.60 Vector3 AmbientColor : 0.08 0.10 0.16 @@ -25,6 +27,7 @@ MaterialDef BakedTerrain { Defines { HAS_ALPHA_1 : AlphaMap_1 HAS_ALPHA_2 : AlphaMap_2 + HAS_STEEP : HasSteep DEBUG_NO_LIGHT : DebugNoLight } diff --git a/blight-assets/src/main/resources/Shaders/BakedTerrain.frag b/blight-assets/src/main/resources/Shaders/BakedTerrain.frag index 9e73eb8..3c1f520 100644 --- a/blight-assets/src/main/resources/Shaders/BakedTerrain.frag +++ b/blight-assets/src/main/resources/Shaders/BakedTerrain.frag @@ -9,6 +9,10 @@ uniform sampler2D m_AlphaMap_1; uniform sampler2D m_AlphaMap_2; #endif +#ifdef HAS_STEEP +uniform int m_SteepIndex; +#endif + uniform vec3 m_LightDir; uniform vec3 m_SunColor; uniform vec3 m_AmbientColor; @@ -19,6 +23,15 @@ in vec3 vNormal; out vec4 outColor; +#ifdef HAS_STEEP +// Triplanare Textur-Abfrage aus einem Sampler2DArray-Layer +vec4 triplanarArray(vec2 uvX, vec2 uvY, vec2 uvZ, vec3 bw, int idx) { + return texture(m_DiffuseArray, vec3(uvX, float(idx))) * bw.x + + texture(m_DiffuseArray, vec3(uvY, float(idx))) * bw.y + + texture(m_DiffuseArray, vec3(uvZ, float(idx))) * bw.z; +} +#endif + void main() { // World-space splatmap UV (identisch zur Painting-Logik in TerrainEditorState) vec2 splatUV = vec2( @@ -51,6 +64,19 @@ void main() { } } +#ifdef HAS_STEEP + // Klippen-Textur: Triplanar auf steilen Flächen (identisch zu Voxel.frag) + vec3 bw = pow(abs(vNormal), vec3(4.0)); + bw /= (bw.x + bw.y + bw.z + 0.001); + float steepScale = m_DiffuseScales[m_SteepIndex]; + vec2 uvX = vWorldPos.yz / steepScale; + vec2 uvZ = vWorldPos.xy / steepScale; + float flatBlend = smoothstep(0.70, 0.90, vNormal.y); + float steepBlend = 1.0 - flatBlend; + vec4 steepCol = triplanarArray(uvX, vWorldPos.xz / steepScale, uvZ, bw, m_SteepIndex); + col = col * flatBlend + steepCol * steepBlend; +#endif + vec3 N = normalize(vNormal); vec3 lightDir = normalize(m_LightDir); float diff = max(dot(N, lightDir), 0.0); diff --git a/blight-editor/src/main/java/de/blight/editor/state/SculptedMeshEditorState.java b/blight-editor/src/main/java/de/blight/editor/state/SculptedMeshEditorState.java index a6cea79..66e3d89 100644 --- a/blight-editor/src/main/java/de/blight/editor/state/SculptedMeshEditorState.java +++ b/blight-editor/src/main/java/de/blight/editor/state/SculptedMeshEditorState.java @@ -531,6 +531,12 @@ public class SculptedMeshEditorState extends BaseAppState { if (a2 != null) { mat.setTexture("AlphaMap_2", a2); } } + // Klippen-Textur + if (input.voxelSteepSlot >= 0) { + mat.setBoolean("HasSteep", true); + mat.setInt("SteepIndex", input.voxelSteepSlot); + } + mat.getAdditionalRenderState().setFaceCullMode(RenderState.FaceCullMode.Off); bakedMat = mat; normalMat = mat; diff --git a/blight-game/src/main/java/de/blight/game/scene/WorldScene.java b/blight-game/src/main/java/de/blight/game/scene/WorldScene.java index 1997b05..5bb00cf 100644 --- a/blight-game/src/main/java/de/blight/game/scene/WorldScene.java +++ b/blight-game/src/main/java/de/blight/game/scene/WorldScene.java @@ -938,7 +938,7 @@ public class WorldScene extends BaseAppState { // sofort die Physik für die Spawn-Umgebung aufbaut. terrainChunkState.setSpawnHint(spawnX, spawnZ); - app.getStateManager().attach(new SculptedMeshState(bulletAppState, loadedMapData)); + app.getStateManager().attach(new SculptedMeshState(bulletAppState, loadedMapData, terrainMaterial)); oceanSound = new de.blight.game.state.OceanSoundState(terrainChunkState); app.getStateManager().attach(oceanSound); diff --git a/blight-game/src/main/java/de/blight/game/state/SculptedMeshState.java b/blight-game/src/main/java/de/blight/game/state/SculptedMeshState.java index 8fbf55f..92c3212 100644 --- a/blight-game/src/main/java/de/blight/game/state/SculptedMeshState.java +++ b/blight-game/src/main/java/de/blight/game/state/SculptedMeshState.java @@ -16,13 +16,10 @@ import com.jme3.scene.Mesh; import com.jme3.scene.Node; import com.jme3.scene.VertexBuffer; import com.jme3.texture.Texture; -import com.jme3.texture.Texture2D; -import com.jme3.texture.image.ColorSpace; import de.blight.common.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.nio.ByteBuffer; import java.nio.FloatBuffer; import java.nio.file.Files; import java.nio.file.Path; @@ -40,13 +37,15 @@ public class SculptedMeshState extends BaseAppState { private final BulletAppState bulletState; private final MapData mapData; + private final Material terrainMaterial; private SimpleApplication app; private Node sculptRoot; private Material material; - public SculptedMeshState(BulletAppState bulletState, MapData mapData) { - this.bulletState = bulletState; - this.mapData = mapData; + public SculptedMeshState(BulletAppState bulletState, MapData mapData, Material terrainMaterial) { + this.bulletState = bulletState; + this.mapData = mapData; + this.terrainMaterial = terrainMaterial; } @Override @@ -235,75 +234,29 @@ public class SculptedMeshState extends BaseAppState { // ── Material ───────────────────────────────────────────────────────────── private Material buildMaterial() { - Material mat = new Material(app.getAssetManager(), "MatDefs/Voxel.j3md"); - mat.setFloat("TexScale", 8f); - int[] slots = { - mapData != null ? mapData.voxelFlatSlot : -1, - mapData != null ? mapData.voxelSteepSlot : -1, - }; - String[] colSlots = {"TexFlat", "TexSteep" }; - String[] normSlots = {"NormalMapFlat", "NormalMapSteep" }; - // Fallback-Farben wenn kein Slot konfiguriert: grünlich-grau (flach), felsgrau (steil) - int[][] fallbackRgb = { - {100, 130, 60}, - {110, 100, 90}, - }; - for (int i = 0; i < 2; i++) { - String tex = resolveSlotTex(slots[i]); - String norm = resolveSlotNorm(slots[i]); - if (!tex.isEmpty()) { - try { - Texture t = app.getAssetManager().loadTexture(tex); - t.getImage().setColorSpace(ColorSpace.Linear); - t.setWrap(Texture.WrapMode.Repeat); - mat.setTexture(colSlots[i], t); - } catch (Exception e) { - log.warn("Textur {} nicht ladbar: {}", tex, e.getMessage()); - mat.setTexture(colSlots[i], solidColorTexture(fallbackRgb[i])); - } - } else { - mat.setTexture(colSlots[i], solidColorTexture(fallbackRgb[i])); - } - if (!norm.isEmpty()) { - try { - Texture n = app.getAssetManager().loadTexture(norm); - n.setWrap(Texture.WrapMode.Repeat); - mat.setTexture(normSlots[i], n); - } catch (Exception e) { - mat.clearParam(normSlots[i]); - } - } else { - mat.clearParam(normSlots[i]); + Material mat = new Material(app.getAssetManager(), "MatDefs/BakedTerrain.j3md"); + + // DiffuseArray + DiffuseScales + AlphaMaps vom Terrain-Material übernehmen, + // damit die gemalten Splatmap-Texturen auch ingame sichtbar sind. + if (terrainMaterial != null) { + com.jme3.material.MatParam diffArr = terrainMaterial.getParam("DiffuseArray"); + if (diffArr != null) mat.setParam("DiffuseArray", diffArr.getVarType(), diffArr.getValue()); + + com.jme3.material.MatParam scales = terrainMaterial.getParam("DiffuseScales"); + if (scales != null) mat.setParam("DiffuseScales", scales.getVarType(), scales.getValue()); + + for (String key : new String[]{"AlphaMap", "AlphaMap_1", "AlphaMap_2"}) { + com.jme3.material.MatParam p = terrainMaterial.getParam(key); + if (p != null) mat.setTexture(key, (Texture) p.getValue()); } } + + // Klippen-Textur: steiler Slot aus MapData für Flat/Steep-Blending + if (mapData != null && mapData.voxelSteepSlot >= 0) { + mat.setBoolean("HasSteep", true); + mat.setInt("SteepIndex", mapData.voxelSteepSlot); + } + return mat; } - - private Texture solidColorTexture(int[] rgb) { - ByteBuffer buf = ByteBuffer.allocate(4); - buf.put((byte) rgb[0]).put((byte) rgb[1]).put((byte) rgb[2]).put((byte) 255); - buf.flip(); - Texture2D tex = new Texture2D( - new com.jme3.texture.Image(com.jme3.texture.Image.Format.RGBA8, 1, 1, buf, ColorSpace.Linear)); - tex.setWrap(Texture.WrapMode.Repeat); - return tex; - } - - private String resolveSlotTex(int slot) { - if (slot < 0 || mapData == null) return ""; - if (slot < 4) return s(mapData.terrainTextures[slot]); - if (slot < 8) return s(mapData.upperTextures[slot-4]); - if (slot < 12) return s(mapData.thirdTextures[slot-8]); - return ""; - } - - private String resolveSlotNorm(int slot) { - if (slot < 0 || mapData == null) return ""; - if (slot < 4) return s(mapData.terrainNormalMaps[slot]); - if (slot < 8) return s(mapData.upperNormalMaps[slot-4]); - if (slot < 12) return s(mapData.thirdNormalMaps[slot-8]); - return ""; - } - - private static String s(String v) { return v != null ? v : ""; } }