Weiter das Palmen Tool verfeinert
4
.gitignore
vendored
@@ -1,6 +1,10 @@
|
||||
/.metadata/
|
||||
/.gradle/
|
||||
|
||||
# Gradle build output
|
||||
**/build/
|
||||
**/config/
|
||||
|
||||
# Eclipse project files
|
||||
.project
|
||||
.classpath
|
||||
|
||||
BIN
blight-assets/src/main/resources/Textures/bark/Bark_Palm.png
Normal file
|
After Width: | Height: | Size: 158 KiB |
BIN
blight-assets/src/main/resources/Textures/bark/Bark_Palm2.png
Normal file
|
After Width: | Height: | Size: 138 KiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 799 KiB After Width: | Height: | Size: 799 KiB |
BIN
blight-assets/src/main/resources/Textures/leaves/palmcrown.png
Normal file
|
After Width: | Height: | Size: 120 KiB |
BIN
blight-editor/editor-assets/textures/impostor_Baum1.png
Normal file
|
After Width: | Height: | Size: 124 KiB |
@@ -71,6 +71,11 @@ public class EditorApp extends Application {
|
||||
private PalmOptions palmOptions = new PalmOptions();
|
||||
private final TextField palmNameField = new TextField("Palme1");
|
||||
|
||||
// Aktives Tool + Tastenkürzel-Callbacks
|
||||
private String currentTool = "world";
|
||||
private Runnable onF5 = () -> {}; // Vorschau
|
||||
private Runnable onF6 = () -> {}; // Zufälligen Seed wählen
|
||||
|
||||
// Asset-Panel: Pfad-Map + DnD-Zustand
|
||||
private final Map<TreeItem<String>, Path> itemPaths = new HashMap<>();
|
||||
private TreeItem<String> draggedItem = null;
|
||||
@@ -182,24 +187,28 @@ public class EditorApp extends Application {
|
||||
// ── Modus-Wechsel ────────────────────────────────────────────────────────
|
||||
|
||||
private void switchToTreeGenerator() {
|
||||
currentTool = "tree";
|
||||
topBar.getChildren().set(1, buildTreeToolBar());
|
||||
root.setCenter(buildTreePreviewPanel());
|
||||
root.setRight(buildTreeParamsPanel());
|
||||
}
|
||||
|
||||
private void switchToWorldEditor() {
|
||||
currentTool = "world";
|
||||
topBar.getChildren().set(1, worldToolBar);
|
||||
root.setCenter(worldViewport);
|
||||
root.setRight(toolPanel);
|
||||
}
|
||||
|
||||
private void switchToEzTree() {
|
||||
currentTool = "eztree";
|
||||
topBar.getChildren().set(1, buildEzTreeToolBar());
|
||||
root.setCenter(buildTreePreviewPanel()); // teilt Vorschau-Infrastruktur
|
||||
root.setCenter(buildTreePreviewPanel());
|
||||
root.setRight(buildEzTreeParamsPanel());
|
||||
}
|
||||
|
||||
private void switchToPalm() {
|
||||
currentTool = "palm";
|
||||
topBar.getChildren().set(1, buildPalmToolBar());
|
||||
root.setCenter(buildTreePreviewPanel());
|
||||
root.setRight(buildPalmParamsPanel());
|
||||
@@ -329,13 +338,14 @@ public class EditorApp extends Application {
|
||||
nameLabel.setStyle("-fx-font-weight: bold;");
|
||||
treeNameField.setPrefWidth(130);
|
||||
|
||||
Button previewBtn = new Button("▶ Vorschau");
|
||||
previewBtn.setStyle("-fx-font-weight: bold;");
|
||||
previewBtn.setOnAction(e -> {
|
||||
onF5 = () -> {
|
||||
input.treeGenQueue.offer(
|
||||
new SharedInput.TreeGenRequest(treeParams.copy(), false, treeNameField.getText().trim()));
|
||||
setStatus("Generiere Vorschau…");
|
||||
});
|
||||
};
|
||||
Button previewBtn = new Button("▶ Vorschau [F5]");
|
||||
previewBtn.setStyle("-fx-font-weight: bold;");
|
||||
previewBtn.setOnAction(e -> onF5.run());
|
||||
|
||||
Button exportBtn = new Button("💾 Exportieren als .j3o");
|
||||
exportBtn.setOnAction(e -> {
|
||||
@@ -414,7 +424,17 @@ public class EditorApp extends Application {
|
||||
seedSpinner.setEditable(true);
|
||||
seedSpinner.setMaxWidth(Double.MAX_VALUE);
|
||||
seedSpinner.valueProperty().addListener((o, a, b) -> treeParams.seed = b);
|
||||
inner.getChildren().add(seedSpinner);
|
||||
Button treeRndSeed = new Button("🎲");
|
||||
onF6 = () -> {
|
||||
int s = new java.util.Random().nextInt(100000);
|
||||
treeParams.seed = s;
|
||||
seedSpinner.getValueFactory().setValue(s);
|
||||
onF5.run();
|
||||
};
|
||||
treeRndSeed.setOnAction(e -> onF6.run());
|
||||
HBox treeSeedRow = new HBox(4, seedSpinner, treeRndSeed);
|
||||
HBox.setHgrow(seedSpinner, Priority.ALWAYS);
|
||||
inner.getChildren().add(treeSeedRow);
|
||||
|
||||
// Ast-Tiefe
|
||||
inner.getChildren().add(bold("Ast-Tiefe:"));
|
||||
@@ -525,14 +545,15 @@ public class EditorApp extends Application {
|
||||
nameLabel.setStyle("-fx-font-weight: bold;");
|
||||
ezTreeNameField.setPrefWidth(130);
|
||||
|
||||
Button previewBtn = new Button("▶ Vorschau");
|
||||
previewBtn.setStyle("-fx-font-weight: bold;");
|
||||
previewBtn.setOnAction(e -> {
|
||||
onF5 = () -> {
|
||||
input.ezTreeGenQueue.offer(
|
||||
new SharedInput.EzTreeGenRequest(
|
||||
ezTreeOptions.copy(), false, ezTreeNameField.getText().trim()));
|
||||
setStatus("EZ-Tree: generiere Vorschau…");
|
||||
});
|
||||
};
|
||||
Button previewBtn = new Button("▶ Vorschau [F5]");
|
||||
previewBtn.setStyle("-fx-font-weight: bold;");
|
||||
previewBtn.setOnAction(e -> onF5.run());
|
||||
|
||||
Button exportBtn = new Button("💾 Export .j3o");
|
||||
exportBtn.setOnAction(e -> {
|
||||
@@ -564,7 +585,17 @@ public class EditorApp extends Application {
|
||||
inner.getChildren().add(bold("Zufallssamen:"));
|
||||
Spinner<Integer> seedSp = intSpinner(0, 999999, ezTreeOptions.seed);
|
||||
seedSp.valueProperty().addListener((o, a, b) -> ezTreeOptions.seed = b);
|
||||
inner.getChildren().add(seedSp);
|
||||
Button ezRndSeed = new Button("🎲");
|
||||
onF6 = () -> {
|
||||
int s = new java.util.Random().nextInt(1000000);
|
||||
ezTreeOptions.seed = s;
|
||||
seedSp.getValueFactory().setValue(s);
|
||||
onF5.run();
|
||||
};
|
||||
ezRndSeed.setOnAction(e -> onF6.run());
|
||||
HBox ezSeedRow = new HBox(4, seedSp, ezRndSeed);
|
||||
HBox.setHgrow(seedSp, Priority.ALWAYS);
|
||||
inner.getChildren().add(ezSeedRow);
|
||||
|
||||
inner.getChildren().add(bold("Typ:"));
|
||||
ChoiceBox<String> typeCB = new ChoiceBox<>();
|
||||
@@ -728,14 +759,15 @@ public class EditorApp extends Application {
|
||||
nameLabel.setStyle("-fx-font-weight: bold;");
|
||||
palmNameField.setPrefWidth(130);
|
||||
|
||||
Button previewBtn = new Button("▶ Vorschau");
|
||||
previewBtn.setStyle("-fx-font-weight: bold;");
|
||||
previewBtn.setOnAction(e -> {
|
||||
onF5 = () -> {
|
||||
input.palmGenQueue.offer(
|
||||
new SharedInput.PalmGenRequest(
|
||||
palmOptions.copy(), false, palmNameField.getText().trim()));
|
||||
setStatus("Palme: generiere Vorschau…");
|
||||
});
|
||||
};
|
||||
Button previewBtn = new Button("▶ Vorschau [F5]");
|
||||
previewBtn.setStyle("-fx-font-weight: bold;");
|
||||
previewBtn.setOnAction(e -> onF5.run());
|
||||
|
||||
Button exportBtn = new Button("💾 Export .j3o");
|
||||
exportBtn.setOnAction(e -> {
|
||||
@@ -763,37 +795,93 @@ public class EditorApp extends Application {
|
||||
inner.getChildren().add(bold("Zufallssamen:"));
|
||||
Spinner<Integer> seedSp = intSpinner(0, 999999, palmOptions.seed);
|
||||
seedSp.valueProperty().addListener((o, a, b) -> palmOptions.seed = b);
|
||||
inner.getChildren().add(seedSp);
|
||||
Button palmRndSeed = new Button("🎲");
|
||||
onF6 = () -> {
|
||||
int s = new java.util.Random().nextInt(1000000);
|
||||
palmOptions.seed = s;
|
||||
seedSp.getValueFactory().setValue(s);
|
||||
onF5.run();
|
||||
};
|
||||
palmRndSeed.setOnAction(e -> onF6.run());
|
||||
HBox palmSeedRow = new HBox(4, seedSp, palmRndSeed);
|
||||
HBox.setHgrow(seedSp, Priority.ALWAYS);
|
||||
inner.getChildren().add(palmSeedRow);
|
||||
|
||||
inner.getChildren().addAll(sectionTitle("Stamm"), new Separator());
|
||||
inner.getChildren().add(bold("Anzahl Stämme:"));
|
||||
Spinner<Integer> palmCountSp = intSpinner(1, 3, palmOptions.palmCount);
|
||||
palmCountSp.valueProperty().addListener((o, a, b) -> palmOptions.palmCount = b);
|
||||
inner.getChildren().add(palmCountSp);
|
||||
inner.getChildren().add(ezFloat("Höhe:", 5, 25, palmOptions.trunkHeight,
|
||||
v -> palmOptions.trunkHeight = v));
|
||||
inner.getChildren().add(ezFloat("Radius unten:", 0.1, 1.0, palmOptions.trunkRadiusBottom,
|
||||
v -> palmOptions.trunkRadiusBottom = v));
|
||||
inner.getChildren().add(ezFloat("Radius oben:", 0.05, 0.9, palmOptions.trunkRadiusTop,
|
||||
v -> palmOptions.trunkRadiusTop = v));
|
||||
inner.getChildren().add(ezFloat("Neigung (°):", 0.0, 50.0, palmOptions.lean,
|
||||
v -> palmOptions.lean = v));
|
||||
|
||||
inner.getChildren().addAll(sectionTitle("Kronenschaft"), new Separator());
|
||||
inner.getChildren().add(ezFloat("Höhe:", 0.2, 4.0, palmOptions.crownHeight,
|
||||
v -> palmOptions.crownHeight = v));
|
||||
inner.getChildren().add(ezFloat("Aufweitung:", 1.0, 3.0, palmOptions.crownFlare,
|
||||
v -> palmOptions.crownFlare = v));
|
||||
inner.getChildren().add(ezFloat("Farbe R:", 0, 1, palmOptions.crownR, v -> palmOptions.crownR = v));
|
||||
inner.getChildren().add(ezFloat("Farbe G:", 0, 1, palmOptions.crownG, v -> palmOptions.crownG = v));
|
||||
inner.getChildren().add(ezFloat("Farbe B:", 0, 1, palmOptions.crownB, v -> palmOptions.crownB = v));
|
||||
|
||||
inner.getChildren().addAll(sectionTitle("Wedel"), new Separator());
|
||||
inner.getChildren().add(bold("Anzahl:"));
|
||||
Spinner<Integer> frondCountSp = intSpinner(3, 20, palmOptions.frondCount);
|
||||
Spinner<Integer> frondCountSp = intSpinner(12, 120, palmOptions.frondCount);
|
||||
frondCountSp.valueProperty().addListener((o, a, b) -> palmOptions.frondCount = b);
|
||||
inner.getChildren().add(frondCountSp);
|
||||
inner.getChildren().add(ezFloat("Winkel min (° von oben):", 60, 95, palmOptions.frondAngleMin,
|
||||
inner.getChildren().add(ezFloat("Winkel min (° von oben):", 10, 90, palmOptions.frondAngleMin,
|
||||
v -> palmOptions.frondAngleMin = v));
|
||||
inner.getChildren().add(ezFloat("Winkel max (° von oben):", 85, 130, palmOptions.frondAngleMax,
|
||||
inner.getChildren().add(ezFloat("Winkel max (° von oben):", 90, 170, palmOptions.frondAngleMax,
|
||||
v -> palmOptions.frondAngleMax = v));
|
||||
inner.getChildren().add(ezFloat("Länge:", 2, 12, palmOptions.frondLength,
|
||||
v -> palmOptions.frondLength = v));
|
||||
|
||||
inner.getChildren().addAll(sectionTitle("Blätter (Fiederblättchen)"), new Separator());
|
||||
inner.getChildren().add(bold("Paare pro Wedel:"));
|
||||
Spinner<Integer> pairsSp = intSpinner(3, 16, palmOptions.frondLeafletPairs);
|
||||
pairsSp.valueProperty().addListener((o, a, b) -> palmOptions.frondLeafletPairs = b);
|
||||
inner.getChildren().add(pairsSp);
|
||||
inner.getChildren().add(ezFloat("Wedel-Breite:", 0.2, 4.0, palmOptions.frondWidth,
|
||||
v -> palmOptions.frondWidth = v));
|
||||
inner.getChildren().addAll(sectionTitle("Blätter"), new Separator());
|
||||
inner.getChildren().add(ezFloat("Schwerkraft:", 0.0, 2.0, palmOptions.gravity,
|
||||
v -> palmOptions.gravity = v));
|
||||
inner.getChildren().add(ezFloat("Größe oben (relativ):", 0.0, 1.0, palmOptions.frondMinSizeRatio,
|
||||
v -> palmOptions.frondMinSizeRatio = v));
|
||||
|
||||
inner.getChildren().addAll(sectionTitle("Farben"), new Separator());
|
||||
// Blatt-Textur-Auswahl
|
||||
Label leafTexLabel = new Label("Blatt-Textur:");
|
||||
leafTexLabel.setStyle("-fx-font-weight: bold; -fx-text-fill: #111111;");
|
||||
ComboBox<String> leafTexBox = new ComboBox<>();
|
||||
leafTexBox.getItems().addAll("palm.png", "palm2.png");
|
||||
String currentLeaf = palmOptions.leafTexture != null
|
||||
&& palmOptions.leafTexture.contains("palm2") ? "palm2.png" : "palm.png";
|
||||
leafTexBox.setValue(currentLeaf);
|
||||
leafTexBox.setMaxWidth(Double.MAX_VALUE);
|
||||
leafTexBox.setOnAction(e -> palmOptions.leafTexture = "Textures/leaves/" + leafTexBox.getValue());
|
||||
inner.getChildren().add(new VBox(2, leafTexLabel, leafTexBox));
|
||||
|
||||
// Rinden-Textur-Auswahl (alle Texturen aus dem bark-Ordner)
|
||||
Label barkTexLabel = new Label("Rinden-Textur:");
|
||||
barkTexLabel.setStyle("-fx-font-weight: bold; -fx-text-fill: #111111;");
|
||||
ComboBox<String> barkTexBox = new ComboBox<>();
|
||||
barkTexBox.getItems().addAll(
|
||||
"Bark_Palm.png", "Bark_Palm2.png",
|
||||
"Bark001_Color.jpg", "Bark002_Color.jpg",
|
||||
"Bark003_Color.jpg", "Bark008_Color.jpg"
|
||||
);
|
||||
String currentBark = palmOptions.barkTexture != null
|
||||
? palmOptions.barkTexture.substring(palmOptions.barkTexture.lastIndexOf('/') + 1)
|
||||
: "Bark_Palm.png";
|
||||
barkTexBox.setValue(barkTexBox.getItems().contains(currentBark)
|
||||
? currentBark : barkTexBox.getItems().get(0));
|
||||
barkTexBox.setMaxWidth(Double.MAX_VALUE);
|
||||
barkTexBox.setOnAction(e -> {
|
||||
if (barkTexBox.getValue() != null)
|
||||
palmOptions.barkTexture = "Textures/bark/" + barkTexBox.getValue();
|
||||
});
|
||||
inner.getChildren().add(new VBox(2, barkTexLabel, barkTexBox));
|
||||
|
||||
inner.getChildren().add(ezFloat("Stamm R:", 0, 1, palmOptions.barkR, v -> palmOptions.barkR = v));
|
||||
inner.getChildren().add(ezFloat("Stamm G:", 0, 1, palmOptions.barkG, v -> palmOptions.barkG = v));
|
||||
inner.getChildren().add(ezFloat("Stamm B:", 0, 1, palmOptions.barkB, v -> palmOptions.barkB = v));
|
||||
@@ -1754,6 +1842,8 @@ public class EditorApp extends Application {
|
||||
case D -> input.right = pressed;
|
||||
case Q -> input.up = pressed;
|
||||
case E -> input.down = pressed;
|
||||
case F5 -> { if (pressed) onF5.run(); }
|
||||
case F6 -> { if (pressed) onF6.run(); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import com.jme3.scene.Node;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.texture.FrameBuffer;
|
||||
import com.jme3.texture.Image;
|
||||
import com.jme3.texture.Texture;
|
||||
import com.jme3.texture.Texture2D;
|
||||
import com.jme3.util.BufferUtils;
|
||||
import de.blight.editor.SharedInput;
|
||||
@@ -195,7 +196,9 @@ public class EzTreeState extends BaseAppState {
|
||||
mat.setFloat("WindSpeed", 0.5f);
|
||||
if (opts.bark.textureFile != null) {
|
||||
try {
|
||||
mat.setTexture("BarkMap", assets.loadTexture(opts.bark.textureFile));
|
||||
Texture barkTex = assets.loadTexture(opts.bark.textureFile);
|
||||
barkTex.setWrap(Texture.WrapMode.Repeat);
|
||||
mat.setTexture("BarkMap", barkTex);
|
||||
mat.setBoolean("HasBarkMap", true);
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
@@ -249,8 +252,8 @@ public class EzTreeState extends BaseAppState {
|
||||
|
||||
Node scene = new Node("ezCapScene");
|
||||
scene.addLight(new DirectionalLight(
|
||||
new Vector3f(-0.4f, -1f, -0.5f).normalizeLocal(), ColorRGBA.White));
|
||||
scene.addLight(new AmbientLight(new ColorRGBA(0.35f, 0.35f, 0.35f, 1f)));
|
||||
new Vector3f(-0.4f, -1f, -0.5f).normalizeLocal(), new ColorRGBA(2.0f, 1.85f, 1.5f, 1f)));
|
||||
scene.addLight(new AmbientLight(new ColorRGBA(0.60f, 0.60f, 0.60f, 1f)));
|
||||
scene.attachChild(cloneForCapture(src));
|
||||
vp.attachScene(scene);
|
||||
scene.updateGeometricState();
|
||||
|
||||
@@ -8,6 +8,7 @@ import com.jme3.bounding.BoundingBox;
|
||||
import com.jme3.export.binary.BinaryExporter;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.material.RenderState;
|
||||
import com.jme3.texture.Texture;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.renderer.queue.RenderQueue;
|
||||
@@ -55,8 +56,19 @@ public class PalmGeneratorState extends BaseAppState {
|
||||
SharedInput.PalmGenRequest req = input.palmGenQueue.poll();
|
||||
if (req == null) return;
|
||||
|
||||
Node palm = PalmMeshBuilder.build(req.options());
|
||||
applyMaterials(palm, req.options());
|
||||
PalmOptions opts = req.options();
|
||||
if (opts.leafTexture != null) {
|
||||
try {
|
||||
Texture t = assets.loadTexture(opts.leafTexture);
|
||||
int w = t.getImage().getWidth();
|
||||
int h = t.getImage().getHeight();
|
||||
boolean stemLeft = opts.leafTexture.contains("palm2");
|
||||
opts.leafTextureAspect = stemLeft ? (float) h / w : (float) w / h;
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
|
||||
Node palm = PalmMeshBuilder.build(opts);
|
||||
applyMaterials(palm, opts);
|
||||
palm.updateGeometricState();
|
||||
|
||||
BoundingBox bb = palm.getWorldBound() instanceof BoundingBox b ? b : null;
|
||||
@@ -92,6 +104,10 @@ public class PalmGeneratorState extends BaseAppState {
|
||||
g.setMaterial(buildBarkMat(opts));
|
||||
g.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
|
||||
}
|
||||
case "crown" -> {
|
||||
g.setMaterial(buildCrownMat(opts));
|
||||
g.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
|
||||
}
|
||||
case "leaves" -> {
|
||||
g.setMaterial(buildLeafMat(opts));
|
||||
g.setQueueBucket(RenderQueue.Bucket.Transparent);
|
||||
@@ -109,7 +125,9 @@ public class PalmGeneratorState extends BaseAppState {
|
||||
mat.setFloat("WindSpeed", 0.4f);
|
||||
if (opts.barkTexture != null) {
|
||||
try {
|
||||
mat.setTexture("BarkMap", assets.loadTexture(opts.barkTexture));
|
||||
Texture barkTex = assets.loadTexture(opts.barkTexture);
|
||||
barkTex.setWrap(Texture.WrapMode.Repeat);
|
||||
mat.setTexture("BarkMap", barkTex);
|
||||
mat.setBoolean("HasBarkMap", true);
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
@@ -143,6 +161,29 @@ public class PalmGeneratorState extends BaseAppState {
|
||||
}
|
||||
}
|
||||
|
||||
private Material buildCrownMat(PalmOptions opts) {
|
||||
ColorRGBA color = new ColorRGBA(opts.crownR, opts.crownG, opts.crownB, 1f);
|
||||
try {
|
||||
Material mat = new Material(assets, "MatDefs/TreeLeaf.j3md");
|
||||
mat.setColor("Diffuse", color);
|
||||
mat.setFloat("WindStrength", 0.04f);
|
||||
mat.setFloat("WindSpeed", 0.3f);
|
||||
if (opts.crownTexture != null) {
|
||||
try {
|
||||
Texture tex = assets.loadTexture(opts.crownTexture);
|
||||
tex.setWrap(Texture.WrapMode.Repeat);
|
||||
mat.setTexture("LeafMap", tex);
|
||||
mat.setBoolean("HasLeafMap", true);
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
return mat;
|
||||
} catch (Exception e) {
|
||||
Material mat = new Material(assets, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
mat.setColor("Color", color);
|
||||
return mat;
|
||||
}
|
||||
}
|
||||
|
||||
private void exportPalm(Node palmNode, String name) {
|
||||
try {
|
||||
Path modelDir = ASSET_ROOT.resolve("models");
|
||||
|
||||
@@ -100,7 +100,6 @@ public class TreeGeneratorState extends BaseAppState {
|
||||
|
||||
// ── Lifecycle ─────────────────────────────────────────────────────────────
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
protected void initialize(Application app) {
|
||||
this.app = (SimpleApplication) app;
|
||||
@@ -124,12 +123,12 @@ public class TreeGeneratorState extends BaseAppState {
|
||||
previewScene = new Node("previewScene");
|
||||
previewSunLight = new DirectionalLight(
|
||||
new Vector3f(-0.45f, -1.0f, -0.3f).normalizeLocal(),
|
||||
new ColorRGBA(1.2f, 1.1f, 0.9f, 1f));
|
||||
new ColorRGBA(2.0f, 1.85f, 1.5f, 1f));
|
||||
previewScene.addLight(previewSunLight);
|
||||
previewScene.addLight(new DirectionalLight(
|
||||
new Vector3f(0.4f, -0.6f, -0.8f).normalizeLocal(),
|
||||
new ColorRGBA(0.35f, 0.40f, 0.55f, 1f)));
|
||||
previewScene.addLight(new AmbientLight(new ColorRGBA(0.25f, 0.25f, 0.30f, 1f)));
|
||||
new ColorRGBA(0.70f, 0.75f, 1.0f, 1f)));
|
||||
previewScene.addLight(new AmbientLight(new ColorRGBA(0.55f, 0.55f, 0.60f, 1f)));
|
||||
previewTreeHolder = new Node("treeHolder");
|
||||
previewScene.attachChild(previewTreeHolder);
|
||||
previewScene.attachChild(buildPreviewGround());
|
||||
@@ -140,7 +139,7 @@ public class TreeGeneratorState extends BaseAppState {
|
||||
new DirectionalLightShadowRenderer(assets, 2048, 1);
|
||||
shadowRenderer.setLight(previewSunLight);
|
||||
shadowRenderer.setEdgeFilteringMode(EdgeFilteringMode.PCF4);
|
||||
shadowRenderer.setShadowIntensity(0.55f);
|
||||
shadowRenderer.setShadowIntensity(0.25f);
|
||||
shadowRenderer.setShadowZExtend(80f);
|
||||
previewVP.addProcessor(shadowRenderer);
|
||||
previewTransfer = new FrameTransfer(input.treePreviewImage);
|
||||
@@ -386,7 +385,9 @@ public class TreeGeneratorState extends BaseAppState {
|
||||
mat.setFloat("WindSpeed", 0.5f);
|
||||
if (p.barkTexture != null) {
|
||||
try {
|
||||
mat.setTexture("BarkMap", assets.loadTexture(p.barkTexture));
|
||||
Texture barkTex = assets.loadTexture(p.barkTexture);
|
||||
barkTex.setWrap(Texture.WrapMode.Repeat);
|
||||
mat.setTexture("BarkMap", barkTex);
|
||||
mat.setBoolean("HasBarkMap", true);
|
||||
} catch (Exception tex) {
|
||||
System.err.println("[TreeGenerator] Bark-Textur nicht gefunden: " + p.barkTexture);
|
||||
@@ -467,8 +468,8 @@ public class TreeGeneratorState extends BaseAppState {
|
||||
// Capture-Szene: Kopien der Geometrien + Beleuchtung
|
||||
Node scene = new Node("captureScene");
|
||||
scene.addLight(new DirectionalLight(
|
||||
new Vector3f(-0.4f, -1f, -0.5f).normalizeLocal(), ColorRGBA.White));
|
||||
scene.addLight(new AmbientLight(new ColorRGBA(0.35f, 0.35f, 0.35f, 1f)));
|
||||
new Vector3f(-0.4f, -1f, -0.5f).normalizeLocal(), new ColorRGBA(2.0f, 1.85f, 1.5f, 1f)));
|
||||
scene.addLight(new AmbientLight(new ColorRGBA(0.60f, 0.60f, 0.60f, 1f)));
|
||||
|
||||
Node capTree = cloneForCapture(treeNode);
|
||||
scene.attachChild(capTree);
|
||||
|
||||
@@ -23,26 +23,99 @@ import java.util.Random;
|
||||
public class PalmMeshBuilder {
|
||||
|
||||
public static Node build(PalmOptions opts) {
|
||||
Random rng = new Random(opts.seed);
|
||||
float[] azimuths = computeAzimuths(opts, rng);
|
||||
float[] elevations = computeElevations(opts, rng);
|
||||
int count = Math.max(1, Math.min(3, opts.palmCount));
|
||||
float maxH = FastMath.tan(opts.lean * FastMath.DEG_TO_RAD);
|
||||
float minH = count > 1 ? Math.max(maxH * 0.45f, FastMath.tan(10f * FastMath.DEG_TO_RAD)) : 0f;
|
||||
|
||||
float segSize = 0.5f;
|
||||
int M = Math.max(4, Math.round(opts.trunkHeight / segSize));
|
||||
float stepH = opts.trunkHeight / M;
|
||||
|
||||
// Eigener RNG pro Stamm
|
||||
Random[] rngs = new Random[count];
|
||||
for (int p = 0; p < count; p++)
|
||||
rngs[p] = new Random(opts.seed + (long) p * 73856093L);
|
||||
|
||||
// Azimut-Sektoren + Startrichtungen
|
||||
float[] sectorAz = new float[count];
|
||||
float[] dX = new float[count];
|
||||
float[] dZ = new float[count];
|
||||
for (int p = 0; p < count; p++) {
|
||||
sectorAz[p] = FastMath.TWO_PI * p / count
|
||||
+ (rngs[p].nextFloat() - 0.5f) * (FastMath.TWO_PI / count) * 0.15f;
|
||||
float initH = minH + rngs[p].nextFloat() * Math.max(0f, maxH - minH);
|
||||
dX[p] = FastMath.cos(sectorAz[p]) * initH;
|
||||
dZ[p] = FastMath.sin(sectorAz[p]) * initH;
|
||||
}
|
||||
|
||||
// Alle Stämme gleichzeitig simulieren → ermöglicht Abstoßung
|
||||
float[][] cx = new float[count][M + 1];
|
||||
float[][] cz = new float[count][M + 1];
|
||||
|
||||
// Schwächerer Aufwärtsdrang bei Mehrstamm (höhere Dämpfung = mehr Trägheit der Richtung)
|
||||
float damping = count > 1 ? 0.88f : 0.72f;
|
||||
// Abstoßungskraft skaliert mit Neigungsstärke und Segmentgröße
|
||||
float repStrength = count > 1 ? maxH * stepH * 0.40f : 0f;
|
||||
|
||||
for (int i = 1; i <= M; i++) {
|
||||
for (int p = 0; p < count; p++) {
|
||||
// Zufallswanderung mit reduzierterer Dämpfung
|
||||
dX[p] = dX[p] * damping + (rngs[p].nextFloat() - 0.5f) * maxH * 0.9f;
|
||||
dZ[p] = dZ[p] * damping + (rngs[p].nextFloat() - 0.5f) * maxH * 0.9f;
|
||||
|
||||
// Sektor-Bias (leichter als zuvor, Abstoßung übernimmt die Spreizung)
|
||||
if (count > 1) {
|
||||
dX[p] += FastMath.cos(sectorAz[p]) * minH * 0.10f;
|
||||
dZ[p] += FastMath.sin(sectorAz[p]) * minH * 0.10f;
|
||||
}
|
||||
|
||||
// Abstoßung von anderen Stämmen (Positionen des vorherigen Schritts)
|
||||
for (int q = 0; q < count; q++) {
|
||||
if (q == p) continue;
|
||||
float rx = cx[p][i - 1] - cx[q][i - 1];
|
||||
float rz = cz[p][i - 1] - cz[q][i - 1];
|
||||
float d2 = Math.max(0.09f, rx * rx + rz * rz); // min. 0.3m Abstand
|
||||
float d = FastMath.sqrt(d2);
|
||||
float f = repStrength / d2;
|
||||
dX[p] += (rx / d) * f;
|
||||
dZ[p] += (rz / d) * f;
|
||||
}
|
||||
|
||||
// Geschwindigkeit begrenzen (mehr Spielraum bei Mehrstamm)
|
||||
float velMax = count > 1 ? maxH * 2.0f : maxH;
|
||||
float hLen = FastMath.sqrt(dX[p] * dX[p] + dZ[p] * dZ[p]);
|
||||
if (hLen > velMax) { dX[p] *= velMax / hLen; dZ[p] *= velMax / hLen; }
|
||||
|
||||
cx[p][i] = cx[p][i - 1] + dX[p] * stepH;
|
||||
cz[p][i] = cz[p][i - 1] + dZ[p] * stepH;
|
||||
}
|
||||
}
|
||||
|
||||
Accum barkAcc = new Accum();
|
||||
Accum crownAcc = new Accum();
|
||||
Accum leafAcc = new Accum();
|
||||
|
||||
for (int p = 0; p < count; p++) {
|
||||
float topX = cx[p][M], topZ = cz[p][M];
|
||||
float[] azimuths = computeAzimuths(opts, rngs[p]);
|
||||
float[] elevations = computeElevations(opts, rngs[p]);
|
||||
float[] yOffsets = computeYOffsets(opts, elevations, rngs[p]);
|
||||
addTrunk(barkAcc, opts, cx[p], cz[p], M, stepH);
|
||||
addCrown(crownAcc, opts, topX, topZ);
|
||||
addLeaves(leafAcc, opts, azimuths, elevations, yOffsets, topX, topZ);
|
||||
}
|
||||
|
||||
Node palm = new Node("palm");
|
||||
palm.attachChild(new Geometry("bark", buildBarkMesh(opts)));
|
||||
palm.attachChild(new Geometry("leaves", buildLeafMesh(opts, azimuths, elevations)));
|
||||
palm.attachChild(new Geometry("bark", barkAcc.toMesh()));
|
||||
palm.attachChild(new Geometry("crown", crownAcc.toMesh()));
|
||||
palm.attachChild(new Geometry("leaves", leafAcc.toMesh()));
|
||||
return palm;
|
||||
}
|
||||
|
||||
// ── Bark mesh: trunk only ─────────────────────────────────────────────────
|
||||
|
||||
private static Mesh buildBarkMesh(PalmOptions opts) {
|
||||
Accum acc = new Accum();
|
||||
addTrunk(acc, opts);
|
||||
return acc.toMesh();
|
||||
}
|
||||
|
||||
private static void addTrunk(Accum acc, PalmOptions opts) {
|
||||
int M = opts.trunkSections;
|
||||
private static void addTrunk(Accum acc, PalmOptions opts,
|
||||
float[] cx, float[] cz, int M, float stepH) {
|
||||
int N = opts.trunkSegments;
|
||||
float H = opts.trunkHeight;
|
||||
float r0 = opts.trunkRadiusBottom;
|
||||
@@ -53,14 +126,14 @@ public class PalmMeshBuilder {
|
||||
for (int i = 0; i <= M; i++) {
|
||||
float t = (float) i / M;
|
||||
float r = r0 + (r1 - r0) * t;
|
||||
float y = H * t;
|
||||
float wind = t * 0.4f; // trunk barely sways at root, moderate at crown
|
||||
float y = stepH * i;
|
||||
float wind = t * 0.4f;
|
||||
|
||||
for (int j = 0; j <= N; j++) {
|
||||
float angle = FastMath.TWO_PI * j / N;
|
||||
float nx = FastMath.cos(angle);
|
||||
float nz = FastMath.sin(angle);
|
||||
acc.add(nx * r, y, nz * r, nx, 0f, nz, (float) j / N, t, wind);
|
||||
acc.add(cx[i] + nx * r, y, cz[i] + nz * r, nx, 0f, nz, (float) j / N, t, wind);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,89 +147,168 @@ public class PalmMeshBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Leaf mesh: horizontal bilateral leaflets (Wedel) ─────────────────────
|
||||
// ── Kronenschaft: zuerst breiter, dann oben enger (Bauchprofil) ──────────
|
||||
|
||||
private static Mesh buildLeafMesh(PalmOptions opts, float[] azimuths, float[] elevations) {
|
||||
Accum acc = new Accum();
|
||||
for (int f = 0; f < opts.frondCount; f++) {
|
||||
addFrondLeaflets(acc, opts, azimuths[f], elevations[f]);
|
||||
private static void addCrown(Accum acc, PalmOptions opts, float topX, float topZ) {
|
||||
int N = opts.trunkSegments;
|
||||
int M = 8;
|
||||
float yBot = opts.trunkHeight;
|
||||
float yTop = opts.trunkHeight + opts.crownHeight;
|
||||
float rBot = opts.trunkRadiusTop; // Übergang zum Stamm (unten)
|
||||
float rMax = opts.trunkRadiusTop * opts.crownFlare; // Bauch
|
||||
float rTop = opts.trunkRadiusTop * 0.55f; // Spitze oben (deutlich schmaler)
|
||||
int base = acc.vertexCount;
|
||||
|
||||
for (int i = 0; i <= M; i++) {
|
||||
float t = (float) i / M;
|
||||
float y = yBot + (yTop - yBot) * t;
|
||||
float r = crownRadius(t, rBot, rMax, rTop);
|
||||
float wind = t * 0.12f;
|
||||
for (int j = 0; j <= N; j++) {
|
||||
float angle = FastMath.TWO_PI * j / N;
|
||||
float nx = FastMath.cos(angle);
|
||||
float nz = FastMath.sin(angle);
|
||||
acc.add(topX + nx * r, y, topZ + nz * r, nx, 0f, nz, (float) j / N, t, wind);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < M; i++) {
|
||||
for (int j = 0; j < N; j++) {
|
||||
int v00 = base + i * (N + 1) + j;
|
||||
int v10 = base + (i + 1) * (N + 1) + j;
|
||||
int v01 = base + i * (N + 1) + j + 1;
|
||||
int v11 = base + (i + 1) * (N + 1) + j + 1;
|
||||
acc.tri(v00, v10, v11);
|
||||
acc.tri(v00, v11, v01);
|
||||
}
|
||||
}
|
||||
return acc.toMesh();
|
||||
}
|
||||
|
||||
private static void addFrondLeaflets(Accum acc, PalmOptions opts, float azimuth, float elevation) {
|
||||
/** Bauchprofil: rBot unten → rMax bei ~40 % → rTop oben, sine-geglättet. */
|
||||
private static float crownRadius(float t, float rBot, float rMax, float rTop) {
|
||||
float peakT = 0.40f;
|
||||
if (t <= peakT) {
|
||||
return rBot + (rMax - rBot) * FastMath.sin(FastMath.HALF_PI * t / peakT);
|
||||
} else {
|
||||
return rTop + (rMax - rTop) * FastMath.sin(FastMath.HALF_PI * (1f - t) / (1f - peakT));
|
||||
}
|
||||
}
|
||||
|
||||
// ── Leaf mesh: ein Quad pro Wedel (Textur = vollständiger Wedel) ──────────
|
||||
|
||||
private static void addLeaves(Accum acc, PalmOptions opts, float[] azimuths,
|
||||
float[] elevations, float[] yOffsets, float baseX, float baseZ) {
|
||||
for (int f = 0; f < opts.frondCount; f++) {
|
||||
addFrond(acc, opts, azimuths[f], elevations[f], yOffsets[f], baseX, baseZ);
|
||||
}
|
||||
}
|
||||
|
||||
private static final int SEGS = 6; // Längsunterteilungen
|
||||
private static final int COLS = 3; // Spalten: linke Kante, Mitte, rechte Kante
|
||||
|
||||
private static void addFrond(Accum acc, PalmOptions opts, float azimuth, float elevation,
|
||||
float yOffset, float baseX, float baseZ) {
|
||||
float sinE = FastMath.sin(elevation);
|
||||
float cosE = FastMath.cos(elevation);
|
||||
float cosA = FastMath.cos(azimuth);
|
||||
float sinA = FastMath.sin(azimuth);
|
||||
|
||||
float dx = sinE * cosA; // frond direction vector
|
||||
float dx = sinE * cosA;
|
||||
float dy = cosE;
|
||||
float dz = sinE * sinA;
|
||||
|
||||
// Horizontal projection for leaflet orientation (keeps leaflets truly flat)
|
||||
float hLen = FastMath.sqrt(dx * dx + dz * dz);
|
||||
if (hLen < 1e-5f) hLen = 1e-5f;
|
||||
float hx = dx / hLen;
|
||||
float hz = dz / hLen;
|
||||
float sx = -dz / hLen;
|
||||
float sz = dx / hLen;
|
||||
|
||||
// Side direction perpendicular to frond in horizontal plane:
|
||||
// (hx,0,hz) × (0,1,0) = (-hz, 0, hx)
|
||||
float sx = -hz;
|
||||
float sz = hx;
|
||||
// Wedel starten an der Kronenspitze (XZ = Stammspitze + Lean-Offset)
|
||||
float baseY = opts.trunkHeight + opts.crownHeight + yOffset;
|
||||
|
||||
int K = opts.frondLeafletPairs;
|
||||
float step = opts.frondLength / K;
|
||||
float halfW = step * 0.30f; // leaflet thickness along frond axis
|
||||
float baseY = opts.trunkHeight;
|
||||
// Größe abhängig vom Elevationswinkel: aufwärts = kleiner, hängend = volle Größe
|
||||
float minRad = opts.frondAngleMin * FastMath.DEG_TO_RAD;
|
||||
float maxRad = opts.frondAngleMax * FastMath.DEG_TO_RAD;
|
||||
float droopT = (maxRad - minRad) > 1e-5f
|
||||
? FastMath.clamp((elevation - minRad) / (maxRad - minRad), 0f, 1f)
|
||||
: 1f;
|
||||
float sizeScale = opts.frondMinSizeRatio + (1f - opts.frondMinSizeRatio) * droopT;
|
||||
|
||||
// Tip width is a fixed fraction of base width — gives natural taper
|
||||
float sizeBase = opts.frondWidth;
|
||||
float sizeTip = opts.frondWidth * 0.18f;
|
||||
|
||||
for (int k = 0; k < K; k++) {
|
||||
float tPos = (float) k / Math.max(1, K - 1); // 0→1 along frond, starts at trunk
|
||||
float leafSize = sizeBase + (sizeTip - sizeBase) * tPos;
|
||||
|
||||
float ax = dx * opts.frondLength * tPos;
|
||||
float ay = baseY + dy * opts.frondLength * tPos;
|
||||
float az = dz * opts.frondLength * tPos;
|
||||
|
||||
// Wind: 0 near trunk tip, more at frond tip; extra delta for outer leaflet edge
|
||||
float windBase = tPos * 0.65f; // inner edge of leaflet
|
||||
float windTip = tPos * 0.65f + 0.35f; // outer edge of leaflet (leaf tip)
|
||||
|
||||
addLeafletQuad(acc, ax, ay, az, hx, hz, sx, sz, leafSize, halfW, +1f, windBase, windTip);
|
||||
addLeafletQuad(acc, ax, ay, az, hx, hz, sx, sz, leafSize, halfW, -1f, windBase, windTip);
|
||||
}
|
||||
}
|
||||
|
||||
private static void addLeafletQuad(Accum acc,
|
||||
float ax, float ay, float az,
|
||||
float hx, float hz,
|
||||
float sx, float sz,
|
||||
float leafSize, float halfW, float side,
|
||||
float windInner, float windOuter) {
|
||||
// All 4 vertices at Y = ay (truly horizontal, normal = +Y)
|
||||
float p0x = ax - hx * halfW, p0z = az - hz * halfW;
|
||||
float p1x = ax + hx * halfW, p1z = az + hz * halfW;
|
||||
float p2x = p1x + sx * side * leafSize, p2z = p1z + sz * side * leafSize;
|
||||
float p3x = p0x + sx * side * leafSize, p3z = p0z + sz * side * leafSize;
|
||||
float scaledLength = opts.frondLength * sizeScale;
|
||||
float halfW = opts.leafTextureAspect > 0f
|
||||
? scaledLength * opts.leafTextureAspect * 0.5f
|
||||
: opts.frondWidth * sizeScale * 0.5f;
|
||||
boolean stemLeft = opts.leafTexture != null && opts.leafTexture.contains("palm2");
|
||||
float g = opts.gravity;
|
||||
|
||||
int base = acc.vertexCount;
|
||||
|
||||
// p0, p1 = inner edge (attached to frond), p2, p3 = outer tip
|
||||
acc.add(p0x, ay, p0z, 0f, 1f, 0f, 0f, 0f, windInner);
|
||||
acc.add(p1x, ay, p1z, 0f, 1f, 0f, 1f, 0f, windInner);
|
||||
acc.add(p2x, ay, p2z, 0f, 1f, 0f, 1f, 1f, windOuter);
|
||||
acc.add(p3x, ay, p3z, 0f, 1f, 0f, 0f, 1f, windOuter);
|
||||
// 7 Reihen × 3 Spalten
|
||||
for (int row = 0; row <= SEGS; row++) {
|
||||
float t = (float) row / SEGS; // 0 = Stamm, 1 = Spitze
|
||||
|
||||
acc.tri(base, base + 1, base + 2);
|
||||
acc.tri(base, base + 2, base + 3);
|
||||
// Position entlang Frond-Achse (Startpunkt = Lean-Offset der Stammspitze)
|
||||
float fx = baseX + dx * scaledLength * t;
|
||||
float fy = baseY + dy * scaledLength * t;
|
||||
float fz = baseZ + dz * scaledLength * t;
|
||||
|
||||
// Längsdurchhang: quadratisch, wächst zur Spitze
|
||||
float droopLen = g * t * t * scaledLength;
|
||||
|
||||
for (int col = 0; col < COLS; col++) {
|
||||
float s = (float) col / (COLS - 1); // 0 = links, 1 = rechts
|
||||
float side = s * 2f - 1f; // -1, 0, +1
|
||||
|
||||
// Seitenkanten hängen zusätzlich durch (nimmt zur Spitze zu)
|
||||
float droopSide = g * FastMath.abs(side) * halfW * t;
|
||||
|
||||
// Breite konvergiert zur Stammspitze: bei t=0 alle Vertices im Zentrum
|
||||
float x = fx + sx * halfW * side * t;
|
||||
float y = fy - droopLen - droopSide;
|
||||
float z = fz + sz * halfW * side * t;
|
||||
|
||||
float u = stemLeft ? t : s;
|
||||
float v = stemLeft ? s : t;
|
||||
|
||||
acc.add(x, y, z, 0f, 1f, 0f, u, v, t * 0.8f);
|
||||
}
|
||||
}
|
||||
|
||||
// Dreiecke für 6×2 Zellen
|
||||
for (int row = 0; row < SEGS; row++) {
|
||||
for (int col = 0; col < COLS - 1; col++) {
|
||||
int v00 = base + row * COLS + col;
|
||||
int v10 = base + (row + 1) * COLS + col;
|
||||
int v01 = base + row * COLS + col + 1;
|
||||
int v11 = base + (row + 1) * COLS + col + 1;
|
||||
acc.tri(v00, v10, v11);
|
||||
acc.tri(v00, v11, v01);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Random frond placement ────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Vertikaler Versatz: Minimum (steil aufwärts) = Stammspitze (Offset 0),
|
||||
* höhere Werte (stärker hängend) = unterhalb der Stammspitze (negativer Offset).
|
||||
*/
|
||||
private static float[] computeYOffsets(PalmOptions opts, float[] elevations, Random rng) {
|
||||
float minRad = opts.frondAngleMin * FastMath.DEG_TO_RAD;
|
||||
float maxRad = opts.frondAngleMax * FastMath.DEG_TO_RAD;
|
||||
float maxDrop = opts.crownHeight * 0.6f; // Wedel bleiben innerhalb des Kronenschafts
|
||||
float[] result = new float[opts.frondCount];
|
||||
for (int f = 0; f < opts.frondCount; f++) {
|
||||
float elevNorm = (maxRad - minRad) > 1e-5f
|
||||
? FastMath.clamp((elevations[f] - minRad) / (maxRad - minRad), 0f, 1f)
|
||||
: 0.5f;
|
||||
// elevNorm 0 = aufwärts → Offset 0 (Stammspitze)
|
||||
// elevNorm 1 = hängend → Offset -maxDrop (unterhalb der Stammspitze)
|
||||
float base = -elevNorm * maxDrop;
|
||||
float noise = (rng.nextFloat() - 0.5f) * opts.trunkRadiusTop * 0.5f;
|
||||
result[f] = Math.min(0f, base + noise); // nie über Stammspitze hinaus
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static float[] computeAzimuths(PalmOptions opts, Random rng) {
|
||||
float[] a = new float[opts.frondCount];
|
||||
for (int f = 0; f < opts.frondCount; f++) {
|
||||
|
||||
@@ -7,25 +7,36 @@ public class PalmOptions {
|
||||
// Trunk
|
||||
public float trunkHeight = 12f;
|
||||
public float trunkRadiusBottom = 0.35f;
|
||||
public float trunkRadiusTop = 0.30f; // tapers but stays thick
|
||||
public float trunkRadiusTop = 0.175f; // default = trunkRadiusBottom / 2
|
||||
public int trunkSections = 10;
|
||||
public int trunkSegments = 8;
|
||||
public float lean = 15f; // max. Neigungswinkel in Grad (0 = gerade, 50 = stark)
|
||||
public int palmCount = 1; // 1–3 Stämme vom gleichen Ursprung
|
||||
|
||||
// Fronds
|
||||
public int frondCount = 10;
|
||||
public float frondAngleMin = 70f; // degrees from vertical (Y-up)
|
||||
public float frondAngleMax = 110f;
|
||||
public float frondLength = 6.5f;
|
||||
public int frondLeafletPairs = 8;
|
||||
public float frondWidth = 1.4f; // max leaflet width at frond base
|
||||
public int frondCount = 80;
|
||||
public float frondAngleMin = 45f; // degrees from vertical (Y-up)
|
||||
public float frondAngleMax = 105f;
|
||||
public float frondLength = 6.5f;
|
||||
public float frondWidth = 1.4f;
|
||||
public float frondMinSizeRatio = 0.4f; // Größe der aufwärtszeigenden Wedel relativ zu den hängenden (0–1)
|
||||
|
||||
// Colors
|
||||
public float barkR = 0.68f, barkG = 0.54f, barkB = 0.34f;
|
||||
public float leafR = 0.22f, leafG = 0.65f, leafB = 0.14f;
|
||||
|
||||
// Textures
|
||||
public String barkTexture = "Textures/bark/Bark008_Color.jpg";
|
||||
public String leafTexture = "Textures/leaves/palm.png";
|
||||
public String barkTexture = "Textures/bark/Bark_Palm.png";
|
||||
public String leafTexture = "Textures/leaves/palm.png";
|
||||
public float leafTextureAspect = 0f; // width/length-Verhältnis der Textur, 0 = frondWidth nutzen
|
||||
public float gravity = 0.3f; // Durchhang: 0 = gerade, höher = stärker hängend
|
||||
|
||||
// Kronenschaft
|
||||
public float crownHeight = 1.2f;
|
||||
public float crownFlare = 1.6f; // max. Aufweitung = trunkRadiusTop × crownFlare
|
||||
public float crownR = 0.22f, crownG = 0.58f, crownB = 0.14f;
|
||||
public String crownTexture = "Textures/leaves/palmcrown.png";
|
||||
|
||||
|
||||
public PalmOptions copy() {
|
||||
PalmOptions c = new PalmOptions();
|
||||
@@ -35,16 +46,24 @@ public class PalmOptions {
|
||||
c.trunkRadiusTop = trunkRadiusTop;
|
||||
c.trunkSections = trunkSections;
|
||||
c.trunkSegments = trunkSegments;
|
||||
c.lean = lean;
|
||||
c.palmCount = palmCount;
|
||||
c.frondCount = frondCount;
|
||||
c.frondAngleMin = frondAngleMin;
|
||||
c.frondAngleMax = frondAngleMax;
|
||||
c.frondLength = frondLength;
|
||||
c.frondLeafletPairs = frondLeafletPairs;
|
||||
c.frondWidth = frondWidth;
|
||||
c.frondMinSizeRatio = frondMinSizeRatio;
|
||||
c.barkR = barkR; c.barkG = barkG; c.barkB = barkB;
|
||||
c.leafR = leafR; c.leafG = leafG; c.leafB = leafB;
|
||||
c.barkTexture = barkTexture;
|
||||
c.leafTexture = leafTexture;
|
||||
c.barkTexture = barkTexture;
|
||||
c.leafTexture = leafTexture;
|
||||
c.leafTextureAspect = leafTextureAspect;
|
||||
c.gravity = gravity;
|
||||
c.crownHeight = crownHeight;
|
||||
c.crownFlare = crownFlare;
|
||||
c.crownR = crownR; c.crownG = crownG; c.crownB = crownB;
|
||||
c.crownTexture = crownTexture;
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ application {
|
||||
}
|
||||
|
||||
ext {
|
||||
jmeVersion = '3.7.0-stable'
|
||||
jmeVersion = '3.9.0-stable'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
@@ -9,8 +9,8 @@ subprojects {
|
||||
apply plugin: 'java'
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_21
|
||||
targetCompatibility = JavaVersion.VERSION_21
|
||||
sourceCompatibility = JavaVersion.VERSION_26
|
||||
targetCompatibility = JavaVersion.VERSION_26
|
||||
}
|
||||
|
||||
compileJava.options.encoding = 'UTF-8'
|
||||
|
||||