An der Texturierung der Welt gearbeitet
This commit is contained in:
@@ -3833,6 +3833,25 @@ public class EditorApp extends Application {
|
||||
toolTitle.setStyle("-fx-font-weight: bold; -fx-font-size: 13; -fx-text-fill: #111111;");
|
||||
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()) {
|
||||
Label nameLabel = new Label(param.getName());
|
||||
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)
|
||||
if (tool instanceof GrassTool) {
|
||||
javafx.scene.Node grassUI = buildGrassTextureUI();
|
||||
@@ -4790,6 +4769,147 @@ public class EditorApp extends Application {
|
||||
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
|
||||
* per ↑/↓ ändern. Slots ohne Normal-Map sind ausgegraut; ein Budget-Hinweis zeigt,
|
||||
|
||||
@@ -557,6 +557,12 @@ public class SculptedMeshEditorState extends BaseAppState {
|
||||
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 ────────────────────────────────────────────────
|
||||
|
||||
private static void recomputeNormals(float[] positions, float[] normals,
|
||||
|
||||
@@ -1042,11 +1042,11 @@ public class TerrainEditorState extends BaseAppState {
|
||||
|
||||
// Terrain-Material neu aufbauen wenn Texturen (Slots 1-12), Normal-Maps
|
||||
// oder UV-Scales geändert wurden
|
||||
if (input.terrainTexturesChanged || input.terrainNormalMapsChanged
|
||||
boolean needsFullRebuild = input.terrainTexturesChanged || input.terrainNormalMapsChanged
|
||||
|| input.upperTexturesChanged || input.upperNormalMapsChanged
|
||||
|| input.thirdTexturesChanged || input.thirdNormalMapsChanged
|
||||
|| input.normalMapOrderChanged
|
||||
|| input.diffuseScalesChanged) {
|
||||
|| input.normalMapOrderChanged;
|
||||
if (needsFullRebuild || input.diffuseScalesChanged) {
|
||||
input.terrainTexturesChanged = false;
|
||||
input.terrainNormalMapsChanged = false;
|
||||
input.upperTexturesChanged = false;
|
||||
@@ -1055,7 +1055,17 @@ public class TerrainEditorState extends BaseAppState {
|
||||
input.thirdNormalMapsChanged = false;
|
||||
input.normalMapOrderChanged = 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) {
|
||||
|
||||
Reference in New Issue
Block a user