An der Texturierung der Welt gearbeitet

This commit is contained in:
2026-08-16 10:25:02 +02:00
parent e79f5b4755
commit 77cb58be6d
9 changed files with 184 additions and 47 deletions

View File

@@ -3833,6 +3833,25 @@ public class EditorApp extends Application {
toolTitle.setStyle("-fx-font-weight: bold; -fx-font-size: 13; -fx-text-fill: #111111;"); toolTitle.setStyle("-fx-font-weight: bold; -fx-font-size: 13; -fx-text-fill: #111111;");
panel.getChildren().addAll(toolTitle, new Separator()); panel.getChildren().addAll(toolTitle, new Separator());
// ── TextureTool: Radius/Stärke oben, dann 4×3-Grid + aktiver Slot ──────
if (tool instanceof de.blight.editor.tool.TextureTool tt) {
for (ToolParameter p : tt.getParameters()) {
Label name = new Label(p.getName());
name.setStyle("-fx-text-fill: #111111;");
Slider slider = new Slider(p.getMin(), p.getMax(), p.getValue());
slider.setShowTickMarks(true);
slider.setShowTickLabels(true);
slider.setMajorTickUnit((p.getMax() - p.getMin()) / 2);
slider.valueProperty().addListener((obs, oldV, newV) -> p.setValue(newV.doubleValue()));
panel.getChildren().add(new VBox(3, name, withField(slider, "%.3f")));
}
panel.getChildren().add(new Separator());
javafx.scene.Node texUI = buildTextureChoiceWithActiveSlotUI(tt.textureIndex);
VBox.setVgrow(texUI, Priority.ALWAYS);
panel.getChildren().add(texUI);
return;
}
for (ChoiceToolParameter param : tool.getChoiceParameters()) { for (ChoiceToolParameter param : tool.getChoiceParameters()) {
Label nameLabel = new Label(param.getName()); Label nameLabel = new Label(param.getName());
nameLabel.setStyle("-fx-text-fill: #111111;"); nameLabel.setStyle("-fx-text-fill: #111111;");
@@ -3948,46 +3967,6 @@ public class EditorApp extends Application {
} }
} }
// Textur-Slot-Konfigurator (nur beim TextureTool)
if (tool instanceof de.blight.editor.tool.TextureTool) {
// Globaler UV-Scale-Spinner (ändert alle 12 Slots gleichzeitig)
float curScale = (input.diffuseScales != null) ? input.diffuseScales[0] : 8f;
javafx.scene.control.Spinner<Double> globalScaleSpin =
new javafx.scene.control.Spinner<>(0.5, 1024.0, curScale, 0.5);
globalScaleSpin.setEditable(true);
globalScaleSpin.setMaxWidth(Double.MAX_VALUE);
globalScaleSpin.setTooltip(new Tooltip("UV-Scale in Metern — gilt für alle Slots gleichzeitig"));
globalScaleSpin.valueProperty().addListener((ob, ov, nv) -> {
if (nv == null) return;
float v = nv.floatValue();
java.util.Arrays.fill(input.diffuseScales, v);
input.diffuseScalesChanged = true;
});
Label scaleLbl = new Label("UV-Scale alle Slots (m):");
scaleLbl.setStyle("-fx-font-size: 11; -fx-text-fill: #333;");
// Slot-Gruppen in ScrollPane damit die Werkzeugleiste nicht überquillt
VBox slots = new VBox(5,
buildTextureSlotsUI(false),
new javafx.scene.control.Separator(),
buildTextureSlotsUI(true),
new javafx.scene.control.Separator(),
buildThirdTextureSlotsUI());
slots.setPadding(new javafx.geometry.Insets(4));
ScrollPane slotsScroll = new ScrollPane(slots);
slotsScroll.setFitToWidth(true);
slotsScroll.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
VBox.setVgrow(slotsScroll, Priority.ALWAYS);
panel.getChildren().addAll(
new javafx.scene.control.Separator(),
scaleLbl, globalScaleSpin,
new javafx.scene.control.Separator(),
slotsScroll,
new javafx.scene.control.Separator(),
buildVoxelSplatSectionUI());
}
// Textur-Picker (nur beim GrassTool) // Textur-Picker (nur beim GrassTool)
if (tool instanceof GrassTool) { if (tool instanceof GrassTool) {
javafx.scene.Node grassUI = buildGrassTextureUI(); javafx.scene.Node grassUI = buildGrassTextureUI();
@@ -4790,6 +4769,147 @@ public class EditorApp extends Application {
return box; return box;
} }
private String texPathForSlot(int globalIdx) {
if (globalIdx < 4) return input.terrainTexturePaths[globalIdx];
else if (globalIdx < 8) return input.upperTexturePaths[globalIdx - 4];
else return input.thirdTexturePaths[globalIdx - 8];
}
private void setTexPathForSlot(int globalIdx, String path) {
if (globalIdx < 4) {
input.terrainTexturePaths[globalIdx] = path;
input.terrainTexturesChanged = true;
} else if (globalIdx < 8) {
input.upperTexturePaths[globalIdx - 4] = path;
input.upperTexturesChanged = true;
} else {
input.thirdTexturePaths[globalIdx - 8] = path;
input.thirdTexturesChanged = true;
}
}
/** 4×3-Textur-Grid + darunter UV-Scale und Textur-Button für den aktiven Slot. */
private javafx.scene.Node buildTextureChoiceWithActiveSlotUI(ChoiceToolParameter param) {
VBox outer = new VBox(6);
// Aktiver-Slot-Bereich (unten) — wird bei Slot-Wechsel neu befüllt
VBox activeBox = new VBox(4);
activeBox.setPadding(new Insets(4, 0, 0, 0));
Runnable[] refresh = { null };
refresh[0] = () -> {
activeBox.getChildren().clear();
int sel = param.getSelectedIndex();
String path = texPathForSlot(sel);
boolean hasTexture = path != null && !path.isEmpty();
int group = sel < 4 ? 0 : sel < 8 ? 1 : 2;
int slotInGrp = sel % 4;
Label scaleLbl = new Label("UV-Scale Slot " + (sel + 1) + " (m):");
scaleLbl.setStyle("-fx-font-size: 11; -fx-text-fill: #333;");
float curScale = (input.diffuseScales != null && sel < input.diffuseScales.length)
? input.diffuseScales[sel] : 8f;
javafx.scene.control.Spinner<Double> scaleSpin =
new javafx.scene.control.Spinner<>(0.5, 1024.0, curScale, 0.5);
scaleSpin.setEditable(true);
scaleSpin.setMaxWidth(Double.MAX_VALUE);
scaleSpin.setDisable(!hasTexture);
scaleSpin.valueProperty().addListener((ob, ov, nv) -> {
if (nv == null) return;
input.diffuseScales[sel] = nv.floatValue();
input.diffuseScalesChanged = true;
});
Button chooseBtn = new Button("Textur ändern...");
chooseBtn.setMaxWidth(Double.MAX_VALUE);
chooseBtn.setDisable(!hasTexture);
chooseBtn.setOnAction(ev -> {
TextureChooser chooser = new TextureChooser(ASSET_ROOT, true);
if (primaryStage != null) chooser.initOwner(primaryStage);
chooser.showAndWait().ifPresent(p -> {
setTexPathForSlot(sel, p);
applyMetaToSlot(p, slotInGrp, group);
param.setSelectedIndex(sel);
showToolParameters(toolPanel, input.activeTool);
});
});
activeBox.getChildren().addAll(new Separator(), scaleLbl, scaleSpin, chooseBtn);
};
// 4×3 Grid (wie buildTextureChoiceUI, aber mit Refresh-Hook)
String[] defaultsTerrain = {
"Textures/Terrain/splat/grass.jpg", "Textures/Terrain/Rock2/rock.jpg",
"Textures/Terrain/splat/dirt.jpg", ""
};
String[] allPaths = new String[12];
String[] allLabels = new String[12];
for (int i = 0; i < 4; i++) {
String p = input.terrainTexturePaths[i];
allPaths[i] = (p != null && !p.isEmpty()) ? p : defaultsTerrain[i];
allLabels[i] = "S" + (i + 1) + " " + labelFromPath(allPaths[i]);
}
for (int i = 0; i < 4; i++) {
String p = input.upperTexturePaths[i];
allPaths[4 + i] = (p != null && !p.isEmpty()) ? p : "";
allLabels[4 + i] = "S" + (i + 5) + " " + labelFromPath(allPaths[4 + i]);
}
for (int i = 0; i < 4; i++) {
String p = input.thirdTexturePaths[i];
allPaths[8 + i] = (p != null && !p.isEmpty()) ? p : "";
allLabels[8 + i] = "S" + (i + 9) + " " + labelFromPath(allPaths[8 + i]);
}
ToggleGroup tg = new ToggleGroup();
javafx.scene.layout.TilePane tile = new javafx.scene.layout.TilePane();
tile.setHgap(4); tile.setVgap(4); tile.setPrefColumns(4);
for (int j = 0; j < 12; j++) {
final int idx = j;
String path = allPaths[j];
ToggleButton btn = new ToggleButton();
btn.setToggleGroup(tg);
btn.setPrefSize(56, 76);
btn.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
btn.setTooltip(new Tooltip("Slot " + (j + 1) + ": " + (path.isEmpty() ? "(leer)" : path)));
VBox content = new VBox(2);
content.setAlignment(Pos.CENTER);
if (!path.isEmpty()) {
String imgUrl = resolveImageUrl(path);
if (imgUrl != null) {
Image img = new Image(imgUrl, 44, 44, true, true);
ImageView iv = new ImageView(img.isError() ? null : img);
iv.setFitWidth(44); iv.setFitHeight(44);
content.getChildren().add(img.isError() ? emptySlotPlaceholder(44) : iv);
} else {
content.getChildren().add(emptySlotPlaceholder(44));
}
} else {
content.getChildren().add(emptySlotPlaceholder(44));
}
Label lbl = new Label(allLabels[j]);
lbl.setStyle("-fx-font-size: 9; -fx-text-fill: #222;");
lbl.setMaxWidth(54);
content.getChildren().add(lbl);
btn.setGraphic(content);
boolean initially = (j == param.getSelectedIndex());
btn.setSelected(initially);
applyModeButtonStyle(btn, initially);
btn.selectedProperty().addListener((obs, was, isNow) -> {
applyModeButtonStyle(btn, isNow);
if (isNow) { param.setSelectedIndex(idx); refresh[0].run(); }
});
tile.getChildren().add(btn);
}
tg.selectedToggleProperty().addListener((obs, oldT, newT) -> {
if (newT == null) tg.selectToggle(oldT);
});
refresh[0].run();
outer.getChildren().addAll(tile, activeBox);
return outer;
}
/** /**
* Normal-Map-Prioritätsliste: zeigt alle 12 Slots, lässt den Nutzer die Reihenfolge * Normal-Map-Prioritätsliste: zeigt alle 12 Slots, lässt den Nutzer die Reihenfolge
* per ↑/↓ ändern. Slots ohne Normal-Map sind ausgegraut; ein Budget-Hinweis zeigt, * per ↑/↓ ändern. Slots ohne Normal-Map sind ausgegraut; ein Budget-Hinweis zeigt,

View File

@@ -557,6 +557,12 @@ public class SculptedMeshEditorState extends BaseAppState {
bakedMat.setVector3("AmbientColor", ambientColor); bakedMat.setVector3("AmbientColor", ambientColor);
} }
public void applyDiffuseScales(float[] scales) {
if (bakedMat == null) return;
bakedMat.setParam("DiffuseScales",
com.jme3.shader.VarType.FloatArray, scales.clone());
}
// ── Normalen & Mesh-Update ──────────────────────────────────────────────── // ── Normalen & Mesh-Update ────────────────────────────────────────────────
private static void recomputeNormals(float[] positions, float[] normals, private static void recomputeNormals(float[] positions, float[] normals,

View File

@@ -1042,11 +1042,11 @@ public class TerrainEditorState extends BaseAppState {
// Terrain-Material neu aufbauen wenn Texturen (Slots 1-12), Normal-Maps // Terrain-Material neu aufbauen wenn Texturen (Slots 1-12), Normal-Maps
// oder UV-Scales geändert wurden // oder UV-Scales geändert wurden
if (input.terrainTexturesChanged || input.terrainNormalMapsChanged boolean needsFullRebuild = input.terrainTexturesChanged || input.terrainNormalMapsChanged
|| input.upperTexturesChanged || input.upperNormalMapsChanged || input.upperTexturesChanged || input.upperNormalMapsChanged
|| input.thirdTexturesChanged || input.thirdNormalMapsChanged || input.thirdTexturesChanged || input.thirdNormalMapsChanged
|| input.normalMapOrderChanged || input.normalMapOrderChanged;
|| input.diffuseScalesChanged) { if (needsFullRebuild || input.diffuseScalesChanged) {
input.terrainTexturesChanged = false; input.terrainTexturesChanged = false;
input.terrainNormalMapsChanged = false; input.terrainNormalMapsChanged = false;
input.upperTexturesChanged = false; input.upperTexturesChanged = false;
@@ -1055,7 +1055,17 @@ public class TerrainEditorState extends BaseAppState {
input.thirdNormalMapsChanged = false; input.thirdNormalMapsChanged = false;
input.normalMapOrderChanged = false; input.normalMapOrderChanged = false;
input.diffuseScalesChanged = false; input.diffuseScalesChanged = false;
terrain.setMaterial(terrainMat = buildTerrainMaterial()); if (needsFullRebuild) {
terrain.setMaterial(terrainMat = buildTerrainMaterial());
} else if (terrainMat != null) {
// Nur UV-Scales geändert → bestehendes Material direkt aktualisieren,
// kein teurer TextureArray-Rebuild nötig
terrainMat.setParam("DiffuseScales",
com.jme3.shader.VarType.FloatArray, input.diffuseScales.clone());
}
// BakedTerrain-Material (gebackene Voxel) ebenfalls aktualisieren
SculptedMeshEditorState smes = getStateManager().getState(SculptedMeshEditorState.class);
if (smes != null) smes.applyDiffuseScales(input.diffuseScales);
} }
if (input.reloadLightsEmitters) { if (input.reloadLightsEmitters) {

View File

@@ -1 +1,2 @@
# x y z r g b intensity radius # x y z r g b intensity radius
-12.11008 2.23313 -1313.13879 1.00000 1.00000 1.00000 1.00000 20.00000