Texturierung von Voxel Popoxel ermöglicht

This commit is contained in:
2026-08-10 09:50:47 +02:00
parent accdd2fa75
commit 21036a579c
10 changed files with 203 additions and 35 deletions

View File

@@ -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<Long> 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,

View File

@@ -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);

View File

@@ -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)));

View File

@@ -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"; }