Texturierung von Voxel Popoxel ermöglicht
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)));
|
||||
|
||||
@@ -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"; }
|
||||
|
||||
Reference in New Issue
Block a user