diff --git a/blight-assets/src/main/resources/MatDefs/BakedTerrain.j3md b/blight-assets/src/main/resources/MatDefs/BakedTerrain.j3md new file mode 100644 index 0000000..2023242 --- /dev/null +++ b/blight-assets/src/main/resources/MatDefs/BakedTerrain.j3md @@ -0,0 +1,35 @@ +MaterialDef BakedTerrain { + + MaterialParameters { + TextureArray DiffuseArray + FloatArray DiffuseScales + Texture2D AlphaMap + Texture2D AlphaMap_1 + Texture2D AlphaMap_2 + Vector3 LightDir : 0.6 -0.8 0.4 + Vector3 SunColor : 0.68 0.65 0.60 + Vector3 AmbientColor : 0.08 0.10 0.16 + Boolean DebugNoLight + } + + Technique { + VertexShader GLSL150 : Shaders/BakedTerrain.vert + FragmentShader GLSL150 : Shaders/BakedTerrain.frag + + WorldParameters { + WorldViewProjectionMatrix + WorldMatrix + CameraPosition + } + + Defines { + HAS_ALPHA_1 : AlphaMap_1 + HAS_ALPHA_2 : AlphaMap_2 + DEBUG_NO_LIGHT : DebugNoLight + } + + RenderState { + FaceCull Off + } + } +} diff --git a/blight-assets/src/main/resources/Shaders/BakedTerrain.frag b/blight-assets/src/main/resources/Shaders/BakedTerrain.frag new file mode 100644 index 0000000..9e73eb8 --- /dev/null +++ b/blight-assets/src/main/resources/Shaders/BakedTerrain.frag @@ -0,0 +1,65 @@ +uniform sampler2DArray m_DiffuseArray; +uniform float m_DiffuseScales[12]; +uniform sampler2D m_AlphaMap; + +#ifdef HAS_ALPHA_1 +uniform sampler2D m_AlphaMap_1; +#endif +#ifdef HAS_ALPHA_2 +uniform sampler2D m_AlphaMap_2; +#endif + +uniform vec3 m_LightDir; +uniform vec3 m_SunColor; +uniform vec3 m_AmbientColor; +uniform vec3 g_CameraPosition; + +in vec3 vWorldPos; +in vec3 vNormal; + +out vec4 outColor; + +void main() { + // World-space splatmap UV (identisch zur Painting-Logik in TerrainEditorState) + vec2 splatUV = vec2( + (vWorldPos.x + 2048.0) / 4096.0, + 1.0 - (vWorldPos.z + 2048.0) / 4096.0 + ); + + vec4 a0 = texture(m_AlphaMap, splatUV); + vec4 a1 = vec4(0.0); + vec4 a2 = vec4(0.0); +#ifdef HAS_ALPHA_1 + a1 = texture(m_AlphaMap_1, splatUV); +#endif +#ifdef HAS_ALPHA_2 + a2 = texture(m_AlphaMap_2, splatUV); +#endif + + float overlays[11] = float[](a0.g, a0.b, a0.a, + a1.r, a1.g, a1.b, a1.a, + a2.r, a2.g, a2.b, a2.a); + + // Basis (Slot 1) + vec4 col = texture(m_DiffuseArray, vec3(vWorldPos.xz / m_DiffuseScales[0], 0.0)); + + // Overlays (Slots 2-12) sequentiell darüber mischen + for (int i = 0; i < 11; i++) { + if (overlays[i] > 0.001) { + vec2 uv = vWorldPos.xz / m_DiffuseScales[i + 1]; + col = mix(col, texture(m_DiffuseArray, vec3(uv, float(i + 1))), overlays[i]); + } + } + + vec3 N = normalize(vNormal); + vec3 lightDir = normalize(m_LightDir); + float diff = max(dot(N, lightDir), 0.0); + vec3 light = m_AmbientColor + m_SunColor * diff; + + const float BRIGHTNESS = 0.80; +#ifdef DEBUG_NO_LIGHT + outColor = vec4(col.rgb * BRIGHTNESS, col.a); +#else + outColor = vec4(col.rgb * light * BRIGHTNESS, col.a); +#endif +} diff --git a/blight-assets/src/main/resources/Shaders/BakedTerrain.vert b/blight-assets/src/main/resources/Shaders/BakedTerrain.vert new file mode 100644 index 0000000..d5da7f1 --- /dev/null +++ b/blight-assets/src/main/resources/Shaders/BakedTerrain.vert @@ -0,0 +1,16 @@ +uniform mat4 g_WorldViewProjectionMatrix; +uniform mat4 g_WorldMatrix; + +in vec3 inPosition; +in vec3 inNormal; + +out vec3 vWorldPos; +out vec3 vNormal; + +void main() { + vec4 worldPos4 = g_WorldMatrix * vec4(inPosition, 1.0); + mat3 worldMat3 = mat3(g_WorldMatrix); + vWorldPos = worldPos4.xyz; + vNormal = normalize(worldMat3 * inNormal); + gl_Position = g_WorldViewProjectionMatrix * vec4(inPosition, 1.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 16eb567..a6cea79 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 @@ -68,6 +68,7 @@ public class SculptedMeshEditorState extends BaseAppState { private long selectedKey = -1L; private Material normalMat = null; + private Material bakedMat = null; private Material highlightMat = null; private Material selectionWireframeMat = null; private final Set appliedSelectionKeys = new HashSet<>(); @@ -345,14 +346,11 @@ public class SculptedMeshEditorState extends BaseAppState { if (m.getBuffer(VertexBuffer.Type.Normal) != null) m.getBuffer(VertexBuffer.Type.Normal).setUsage(VertexBuffer.Usage.Dynamic); - // Material von VoxelEditorState wiederverwenden - VoxelEditorState ves = app.getStateManager().getState(VoxelEditorState.class); - Material mat = (ves != null) ? ves.getVoxelMaterial() : null; - if (mat == null) mat = new Material(app.getAssetManager(), "MatDefs/Voxel.j3md"); - if (normalMat == null) normalMat = mat; + if (bakedMat == null) buildBakedMat(); + if (normalMat == null) normalMat = bakedMat; Geometry geo = new Geometry("sculpt_" + cx + "_" + cy + "_" + cz, m); - geo.setMaterial(mat); + geo.setMaterial(bakedMat); float ox = cx * VoxelChunk.CELLS - 2048f; float oy = cy * (float) VoxelChunk.CELLS; @@ -505,6 +503,46 @@ public class SculptedMeshEditorState extends BaseAppState { return changed; } + // ── BakedTerrain-Material ───────────────────────────────────────────────── + + private void buildBakedMat() { + Material mat = new Material(app.getAssetManager(), "MatDefs/BakedTerrain.j3md"); + + TerrainEditorState tes = app.getStateManager().getState(TerrainEditorState.class); + if (tes != null) { + com.jme3.material.Material terrMat = tes.getTerrainMaterial(); + if (terrMat != null) { + com.jme3.material.MatParam diffArr = terrMat.getParam("DiffuseArray"); + if (diffArr != null) { + mat.setParam("DiffuseArray", diffArr.getVarType(), diffArr.getValue()); + } + + com.jme3.material.MatParam scales = terrMat.getParam("DiffuseScales"); + if (scales != null) { + mat.setParam("DiffuseScales", scales.getVarType(), scales.getValue()); + } + } + + com.jme3.texture.Texture2D a0 = tes.getSplatTex(); + com.jme3.texture.Texture2D a1 = tes.getUpperSplatTex(); + com.jme3.texture.Texture2D a2 = tes.getThirdSplatTex(); + if (a0 != null) { mat.setTexture("AlphaMap", a0); } + if (a1 != null) { mat.setTexture("AlphaMap_1", a1); } + if (a2 != null) { mat.setTexture("AlphaMap_2", a2); } + } + + mat.getAdditionalRenderState().setFaceCullMode(RenderState.FaceCullMode.Off); + bakedMat = mat; + normalMat = mat; + } + + public void updateBakedMatLighting(Vector3f lightDir, Vector3f sunColor, Vector3f ambientColor) { + if (bakedMat == null) return; + bakedMat.setVector3("LightDir", lightDir); + bakedMat.setVector3("SunColor", sunColor); + bakedMat.setVector3("AmbientColor", ambientColor); + } + // ── Normalen & Mesh-Update ──────────────────────────────────────────────── private static void recomputeNormals(float[] positions, float[] normals, diff --git a/blight-editor/src/main/java/de/blight/editor/state/TerrainEditorState.java b/blight-editor/src/main/java/de/blight/editor/state/TerrainEditorState.java index f5d739b..a83798e 100644 --- a/blight-editor/src/main/java/de/blight/editor/state/TerrainEditorState.java +++ b/blight-editor/src/main/java/de/blight/editor/state/TerrainEditorState.java @@ -769,12 +769,13 @@ public class TerrainEditorState extends BaseAppState { * @param textureIndex 0=Slot1/Reset, 1=Slot2(G→1), 2=Slot3(B→1), 3=Slot4(A→1) */ private void applyTexturePaint(Vector3f contact, float strength, int textureIndex) { - float radius = (float) input.textureTool.brushRadius.getValue(); + float radius = (float) input.textureTool.brushRadius.getValue(); + float softEdge = radius + Math.min(radius * 0.5f, 2f * SPLAT_WE_PER_PX); int centerPX = Math.round((contact.x + WORLD_HALF) / SPLAT_WE_PER_PX); // JME3-Terrain UV: V=0 → worldZ=+2048, V=1 → worldZ=-2048 (Z-Achse gespiegelt) int centerPZ = (SPLAT_SIZE - 1) - Math.round((contact.z + WORLD_HALF) / SPLAT_WE_PER_PX); - int pixR = (int) Math.ceil(radius / SPLAT_WE_PER_PX); + int pixR = (int) Math.ceil(softEdge / SPLAT_WE_PER_PX); boolean changed = false; for (int dz = -pixR; dz <= pixR; dz++) { @@ -785,9 +786,9 @@ public class TerrainEditorState extends BaseAppState { if (px < 0 || px >= SPLAT_SIZE) continue; float distWE = FastMath.sqrt(dx * dx + dz * dz) * SPLAT_WE_PER_PX; - if (distWE >= radius) continue; + if (distWE >= softEdge) continue; - float t = distWE / radius; + float t = distWE / softEdge; float falloff = (1f + FastMath.cos(FastMath.PI * t)) * 0.5f; // Slot 1 (textureIndex==0) = Zurücksetzen: volle Stärke damit ein Strich reicht float blend = (textureIndex == 0) ? falloff : strength * falloff; @@ -826,11 +827,12 @@ public class TerrainEditorState extends BaseAppState { * @param textureIndex 0=Slot5(R), 1=Slot6(G), 2=Slot7(B), 3=Slot8(A), -1=Löschen(alle→0) */ private void applyUpperTexturePaint(Vector3f contact, float strength, int textureIndex) { - float radius = (float) input.textureTool.brushRadius.getValue(); + float radius = (float) input.textureTool.brushRadius.getValue(); + float softEdge = radius + Math.min(radius * 0.5f, 2f * SPLAT_WE_PER_PX); int centerPX = Math.round((contact.x + WORLD_HALF) / SPLAT_WE_PER_PX); int centerPZ = (SPLAT_SIZE - 1) - Math.round((contact.z + WORLD_HALF) / SPLAT_WE_PER_PX); - int pixR = (int) Math.ceil(radius / SPLAT_WE_PER_PX); + int pixR = (int) Math.ceil(softEdge / SPLAT_WE_PER_PX); boolean changed = false; for (int dz = -pixR; dz <= pixR; dz++) { @@ -841,9 +843,9 @@ public class TerrainEditorState extends BaseAppState { if (px < 0 || px >= SPLAT_SIZE) continue; float distWE = FastMath.sqrt(dx * dx + dz * dz) * SPLAT_WE_PER_PX; - if (distWE >= radius) continue; + if (distWE >= softEdge) continue; - float t = distWE / radius; + float t = distWE / softEdge; float falloff = (1f + FastMath.cos(FastMath.PI * t)) * 0.5f; int idx = pz * SPLAT_SIZE + px; @@ -893,11 +895,12 @@ public class TerrainEditorState extends BaseAppState { * @param textureIndex 0=Slot9(R), 1=Slot10(G), 2=Slot11(B), 3=Slot12(A), -1=Löschen(alle→0) */ private void applyThirdTexturePaint(Vector3f contact, float strength, int textureIndex) { - float radius = (float) input.textureTool.brushRadius.getValue(); + float radius = (float) input.textureTool.brushRadius.getValue(); + float softEdge = radius + Math.min(radius * 0.5f, 2f * SPLAT_WE_PER_PX); int centerPX = Math.round((contact.x + WORLD_HALF) / SPLAT_WE_PER_PX); int centerPZ = (SPLAT_SIZE - 1) - Math.round((contact.z + WORLD_HALF) / SPLAT_WE_PER_PX); - int pixR = (int) Math.ceil(radius / SPLAT_WE_PER_PX); + int pixR = (int) Math.ceil(softEdge / SPLAT_WE_PER_PX); boolean changed = false; for (int dz = -pixR; dz <= pixR; dz++) { @@ -908,9 +911,9 @@ public class TerrainEditorState extends BaseAppState { if (px < 0 || px >= SPLAT_SIZE) continue; float distWE = FastMath.sqrt(dx * dx + dz * dz) * SPLAT_WE_PER_PX; - if (distWE >= radius) continue; + if (distWE >= softEdge) continue; - float t = distWE / radius; + float t = distWE / softEdge; float falloff = (1f + FastMath.cos(FastMath.PI * t)) * 0.5f; int idx = pz * SPLAT_SIZE + px; float blend = strength * falloff; @@ -974,11 +977,16 @@ public class TerrainEditorState extends BaseAppState { // Terrain-Shader mit DayNightState synchronisieren (reagiert auf Zeit-Menü-Änderungen) if (terrainMat != null && dayNightState != null && dayNightState.getSunLight() != null) { - terrainMat.setVector3("LightDir", dayNightState.getSunDirection().negate()); + com.jme3.math.Vector3f lightDir = dayNightState.getSunDirection().negate(); com.jme3.math.ColorRGBA sc = dayNightState.getSunLight().getColor(); com.jme3.math.ColorRGBA ac = dayNightState.getCustomAmbient(); - terrainMat.setVector3("SunColor", new com.jme3.math.Vector3f(sc.r, sc.g, sc.b)); - terrainMat.setVector3("AmbientColor", new com.jme3.math.Vector3f(ac.r, ac.g, ac.b)); + com.jme3.math.Vector3f sunColor = new com.jme3.math.Vector3f(sc.r, sc.g, sc.b); + com.jme3.math.Vector3f ambColor = new com.jme3.math.Vector3f(ac.r, ac.g, ac.b); + terrainMat.setVector3("LightDir", lightDir); + terrainMat.setVector3("SunColor", sunColor); + terrainMat.setVector3("AmbientColor", ambColor); + SculptedMeshEditorState smes = getStateManager().getState(SculptedMeshEditorState.class); + if (smes != null) smes.updateBakedMatLighting(lightDir, sunColor, ambColor); } float pgx = input.pendingGotoX; @@ -1113,6 +1121,7 @@ public class TerrainEditorState extends BaseAppState { private void processTextureEdits() { SharedInput.TextureEdit edit; int processed = 0; + VoxelEditorState ves = getStateManager().getState(VoxelEditorState.class); while ((edit = input.textureEditQueue.poll()) != null && processed < MAX_EDITS_PER_FRAME) { processed++; float jmeX = (float)(edit.screenX() * input.viewportScaleX); @@ -1122,10 +1131,9 @@ public class TerrainEditorState extends BaseAppState { Vector3f far = cam.getWorldCoordinates(new Vector2f(jmeX, jmeY), 1f); com.jme3.math.Ray ray = new com.jme3.math.Ray(near, far.subtract(near).normalizeLocal()); - CollisionResults texHits = new CollisionResults(); - terrain.collideWith(ray, texHits); - if (texHits.size() == 0) continue; - Vector3f contact = texHits.getClosestCollision().getContactPoint(); + Vector3f contact = raycastSurface(ray); + if (contact == null) continue; + if (ves != null && ves.terrainTypeAt(contact.x, contact.z) == VoxelEditorState.TerrainType.VOXEL_UNBAKED) continue; int selIdx = input.textureTool.textureIndex.getSelectedIndex(); float str = (float) input.textureTool.brushStrength.getValue(); @@ -1151,6 +1159,13 @@ public class TerrainEditorState extends BaseAppState { /** Gibt den TerrainQuad-Node zurück (z.B. für Voxel-Raycasts). */ public TerrainQuad getTerrainNode() { return terrain; } + /** Gibt das Terrain-Material zurück (für BakedTerrain-Material-Aufbau in anderen States). */ + public com.jme3.material.Material getTerrainMaterial() { return terrainMat; } + + public com.jme3.texture.Texture2D getSplatTex() { return splatTex; } + public com.jme3.texture.Texture2D getUpperSplatTex() { return upperSplatTex; } + public com.jme3.texture.Texture2D getThirdSplatTex() { return thirdSplatTex; } + /** * Raycast gegen Basis-Terrain, ungebackenes Voxel-Terrain und gebackene Sculpted-Meshes. * Gibt den zur Kamera nächstgelegenen Treffer zurück, oder null wenn keines getroffen wurde. @@ -1387,6 +1402,10 @@ public class TerrainEditorState extends BaseAppState { brushRadius = (layer == 0) ? (float) input.heightTool.brushRadius.getValue() : (float) input.textureTool.brushRadius.getValue(); + VoxelEditorState ves = getStateManager().getState(VoxelEditorState.class); + if (ves != null && ves.terrainTypeAt(contactPoint.x, contactPoint.z) == VoxelEditorState.TerrainType.VOXEL_UNBAKED) { + contactPoint = null; + } } } else if (layer == 3) { contactPoint = raycastSurface(ray); diff --git a/blight-editor/src/main/java/de/blight/editor/state/VoxelEditorState.java b/blight-editor/src/main/java/de/blight/editor/state/VoxelEditorState.java index 05ebf19..f023bdf 100644 --- a/blight-editor/src/main/java/de/blight/editor/state/VoxelEditorState.java +++ b/blight-editor/src/main/java/de/blight/editor/state/VoxelEditorState.java @@ -3283,15 +3283,12 @@ public class VoxelEditorState extends BaseAppState { private void updateBrushIndicator() { if (brushIndicator == null) return; - boolean isVoxelOrTex = input.activeLayer == SharedInput.LAYER_VOXEL - || input.activeLayer == 4; - if (!isVoxelOrTex) { + if (input.activeLayer != SharedInput.LAYER_VOXEL) { brushIndicator.setCullHint(Spatial.CullHint.Always); return; } // Im Selektierungs-Modus nur Mauszeiger, kein Brush-Indikator - if (input.activeLayer == SharedInput.LAYER_VOXEL - && input.voxelTool.mode.getSelectedIndex() == de.blight.editor.tool.VoxelTool.MODE_DELETE_BAKED) { + if (input.voxelTool.mode.getSelectedIndex() == de.blight.editor.tool.VoxelTool.MODE_DELETE_BAKED) { brushIndicator.setCullHint(Spatial.CullHint.Always); return; } @@ -3305,9 +3302,7 @@ public class VoxelEditorState extends BaseAppState { float jmeY = cam.getHeight() - my * (float) input.viewportScaleY; Hit hit = raycastHit(jmeX, jmeY); if (hit != null) { - float r = (input.activeLayer == 4) - ? (float) input.textureTool.brushRadius.getValue() - : (float) input.voxelTool.brushRadius.getValue(); + float r = (float) input.voxelTool.brushRadius.getValue(); // Leicht entlang der Flächen-Normalen versetzt, um Z-Fighting zu vermeiden brushIndicator.setLocalTranslation(hit.pos.add(hit.normal().mult(0.05f))); diff --git a/blight-editor/src/main/java/de/blight/editor/tool/TextureTool.java b/blight-editor/src/main/java/de/blight/editor/tool/TextureTool.java index 3a3798c..949fd9d 100644 --- a/blight-editor/src/main/java/de/blight/editor/tool/TextureTool.java +++ b/blight-editor/src/main/java/de/blight/editor/tool/TextureTool.java @@ -16,8 +16,8 @@ public class TextureTool extends EditorTool { "Textur", TEXTURE_NAMES, 0 ); - public final ToolParameter brushRadius = new ToolParameter("Pinselradius", 50.0, 1.0, 500.0); - public final ToolParameter brushStrength = new ToolParameter("Pinselstärke", 0.05, 0.005, 0.5); + public final ToolParameter brushRadius = new ToolParameter("Pinselradius", 5.0, 1.0, 500.0); + public final ToolParameter brushStrength = new ToolParameter("Pinselstärke", 0.25, 0.005, 0.5); @Override public String getName() { return "Textur"; } diff --git a/blight-map/src/main/map/blight_map.blm b/blight-map/src/main/map/blight_map.blm index 76466b9..127e81d 100644 Binary files a/blight-map/src/main/map/blight_map.blm and b/blight-map/src/main/map/blight_map.blm differ diff --git a/blight-map/src/main/map/chunks/voxel_16_0_07_baked_lod0.j3o b/blight-map/src/main/map/chunks/voxel_16_0_07_baked_lod0.j3o index 2dd0594..264e293 100644 Binary files a/blight-map/src/main/map/chunks/voxel_16_0_07_baked_lod0.j3o and b/blight-map/src/main/map/chunks/voxel_16_0_07_baked_lod0.j3o differ diff --git a/blight-map/src/main/map/chunks/voxel_16_0_07_baked_lod1.j3o b/blight-map/src/main/map/chunks/voxel_16_0_07_baked_lod1.j3o index abd53cd..706ebdc 100644 Binary files a/blight-map/src/main/map/chunks/voxel_16_0_07_baked_lod1.j3o and b/blight-map/src/main/map/chunks/voxel_16_0_07_baked_lod1.j3o differ