Compare commits

...

3 Commits

Author SHA1 Message Date
dd32ec65ca Commit 2026-08-17 19:41:07 +02:00
c09502ad3a Menü Buttons angepasst, Screenshot Funktion optimiert 2026-08-17 12:41:21 +02:00
ee8c235655 Fehler beim ERstellen von Voxel auf Meereshöhe behoben 2026-08-17 11:26:19 +02:00
20 changed files with 1414 additions and 30 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 156 B

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 333 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 333 B

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 333 B

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

@@ -1477,6 +1477,7 @@ public class EditorApp extends Application {
saveItem.setAccelerator(javafx.scene.input.KeyCombination.keyCombination("Ctrl+S")); saveItem.setAccelerator(javafx.scene.input.KeyCombination.keyCombination("Ctrl+S"));
saveItem.setOnAction(e -> { input.saveRequested = true; setStatus("Speichern…"); }); saveItem.setOnAction(e -> { input.saveRequested = true; setStatus("Speichern…"); });
MenuItem screenshotItem = new MenuItem("Screenshot"); MenuItem screenshotItem = new MenuItem("Screenshot");
screenshotItem.setAccelerator(javafx.scene.input.KeyCombination.keyCombination("Ctrl+F12"));
screenshotItem.setOnAction(e -> takeEditorScreenshot()); screenshotItem.setOnAction(e -> takeEditorScreenshot());
MenuItem settingsItem = new MenuItem("Einstellungen…"); MenuItem settingsItem = new MenuItem("Einstellungen…");
settingsItem.setOnAction(e -> openSettings()); settingsItem.setOnAction(e -> openSettings());
@@ -6442,6 +6443,7 @@ public class EditorApp extends Application {
} }
private void takeEditorScreenshot() { private void takeEditorScreenshot() {
playScreenshotClick();
WritableImage image = primaryStage.getScene().snapshot(null); WritableImage image = primaryStage.getScene().snapshot(null);
String timestamp = java.time.LocalDateTime.now() String timestamp = java.time.LocalDateTime.now()
.format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss")); .format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss"));
@@ -6456,6 +6458,34 @@ public class EditorApp extends Application {
} }
} }
private void playScreenshotClick() {
Thread t = new Thread(() -> {
try {
int sr = 44100;
int frames = (int) (sr * 0.08);
byte[] buf = new byte[frames * 2];
java.util.Random rng = new java.util.Random();
for (int i = 0; i < frames; i++) {
double env = Math.exp(-i / (sr * 0.012));
double s = (rng.nextGaussian() * 0.55 + Math.sin(2 * Math.PI * 3200.0 * i / sr) * 0.45) * env * 0.85;
short val = (short) Math.max(-32767, Math.min(32767, s * 32767));
buf[i * 2] = (byte) (val & 0xff);
buf[i * 2 + 1] = (byte) ((val >> 8) & 0xff);
}
javax.sound.sampled.AudioFormat fmt = new javax.sound.sampled.AudioFormat(sr, 16, 1, true, false);
javax.sound.sampled.DataLine.Info info = new javax.sound.sampled.DataLine.Info(javax.sound.sampled.SourceDataLine.class, fmt);
javax.sound.sampled.SourceDataLine line = (javax.sound.sampled.SourceDataLine) javax.sound.sampled.AudioSystem.getLine(info);
line.open(fmt);
line.start();
line.write(buf, 0, buf.length);
line.drain();
line.close();
} catch (Exception ignored) {}
}, "screenshot-click");
t.setDaemon(true);
t.start();
}
private void handleTextureImport(javafx.stage.Window owner) { private void handleTextureImport(javafx.stage.Window owner) {
FileChooser fc = new FileChooser(); FileChooser fc = new FileChooser();
fc.setTitle("Texturen importieren (nicht-PNG wird automatisch konvertiert)"); fc.setTitle("Texturen importieren (nicht-PNG wird automatisch konvertiert)");

View File

@@ -994,9 +994,27 @@ public class VoxelEditorState extends BaseAppState {
if (cy == aCy) { if (cy == aCy) {
currentTop = Math.max(0, Math.min(VoxelChunk.SIZE - 1, currentTop = Math.max(0, Math.min(VoxelChunk.SIZE - 1,
(int)(anchor - cy * (float) VoxelChunk.CELLS))); (int)(anchor - cy * (float) VoxelChunk.CELLS)));
} else { } else if (cy < aCy) {
// Chunks außerhalb des Anker-Chunks ohne bestehende Voxel: überspringen. // Chunk unterhalb der Terrain-Oberfläche: sofort komplett auffüllen.
// Verhindert flache Schnitt-Kante bei Y=0 / Wasserrand.
float chunkBaseY = cy * (float) VoxelChunk.CELLS;
if (chunkBaseY + VoxelChunk.SIZE >= -10f) {
int yFloor = Math.max(0, (int) Math.ceil(-10f - chunkBaseY));
for (int lly = yFloor; lly < VoxelChunk.SIZE; lly++) {
chunk.setDensity(lx, lly, lz, (byte) 127);
}
}
continue; continue;
} else {
// Chunk oberhalb des Ankers: nur weiter bauen wenn der Chunk darunter
// an seiner Oberkante (ly=SIZE-1) bereits solid ist.
// Das erlaubt Voxeln aus dem Unterwasserbereich (aCy=-1) in cy=0
// hineinzuwachsen, sobald cy=-1 bis world Y=0 aufgefüllt ist.
VoxelChunk below = chunks.get(chunkKey(cx, cy - 1, cz));
if (below == null || below.getDensity(lx, VoxelChunk.SIZE - 1, lz) <= 0) {
continue;
}
currentTop = 0;
} }
} }
int newTop = Math.min(VoxelChunk.SIZE - 1, currentTop + colStep); int newTop = Math.min(VoxelChunk.SIZE - 1, currentTop + colStep);
@@ -1010,8 +1028,10 @@ public class VoxelEditorState extends BaseAppState {
if (chunk.getDensity(lx, ly, lz) > 0) { currentTop = ly; break; } if (chunk.getDensity(lx, ly, lz) > 0) { currentTop = ly; break; }
} }
if (currentTop < 0) continue; // Nichts zu entfernen if (currentTop < 0) continue; // Nichts zu entfernen
int newTop = Math.max(0, currentTop - colStep); // newTop darf negativ werden: dann werden ALLE Voxel inkl. ly=0 entfernt.
for (int ly = newTop + 1; ly <= currentTop; ly++) { // Math.max(0,...) würde den untersten Voxel bei Y=0 permanent einfrieren.
int newTop = currentTop - colStep;
for (int ly = Math.max(0, newTop + 1); ly <= currentTop; ly++) {
chunk.setDensity(lx, ly, lz, Byte.MIN_VALUE); chunk.setDensity(lx, ly, lz, Byte.MIN_VALUE);
} }
} }
@@ -1474,14 +1494,29 @@ public class VoxelEditorState extends BaseAppState {
if (currentTopLY >= 0) { if (currentTopLY >= 0) {
startLY = currentTopLY; startLY = currentTopLY;
} else { } else {
// Terrain-Oberfläche als Ankerpunkt
int tCy = VoxelChunk.worldYToCy(th); int tCy = VoxelChunk.worldYToCy(th);
if (cy == tCy) { if (cy == tCy) {
// Terrain-Chunk: schrittweise von der Oberfläche nach oben wachsen lassen.
startLY = Math.max(0, Math.min(VoxelChunk.SIZE - 1, startLY = Math.max(0, Math.min(VoxelChunk.SIZE - 1,
(int)(th - cy * (float) VoxelChunk.CELLS))); (int)(th - cy * (float) VoxelChunk.CELLS)));
} else { } else if (cy < tCy) {
// Chunk ohne bestehende Voxel und nicht der Terrain-Chunk: überspringen. // Chunk unterhalb der Terrain-Oberfläche: sofort komplett auffüllen
// (verhindert flache Schnitt-Kante bei Y=0 / Wasserrand).
float chunkBaseY = cy * (float) VoxelChunk.CELLS;
if (chunkBaseY + VoxelChunk.SIZE < -10f) continue; // unter Fundament-Limit
int yFloor = Math.max(0, (int) Math.ceil(-10f - chunkBaseY));
for (int ly = yFloor; ly < VoxelChunk.SIZE; ly++) {
chunk.setDensity(lx, ly, lz, (byte) 127);
}
continue; continue;
} else {
// Chunk über der Terrain-Oberfläche (cy > tCy): weiter bauen
// wenn der Chunk darunter an seiner Oberkante solid ist.
VoxelChunk below = chunks.get(chunkKey(cx, cy - 1, cz));
if (below == null || below.getDensity(lx, VoxelChunk.SIZE - 1, lz) <= 0) {
continue;
}
startLY = 0;
} }
} }
int newTop = Math.min(startLY + step, targetLY); // nie über Ziel-Zelle int newTop = Math.min(startLY + step, targetLY); // nie über Ziel-Zelle
@@ -1549,10 +1584,21 @@ public class VoxelEditorState extends BaseAppState {
float th = terrainH(wx, wz); float th = terrainH(wx, wz);
if (!Float.isFinite(th)) continue; if (!Float.isFinite(th)) continue;
int thCy = VoxelChunk.worldYToCy(th); int thCy = VoxelChunk.worldYToCy(th);
if (cy != thCy) continue; // Terrain-Oberfläche in anderem Chunk if (cy == thCy) {
currentTopWY = th; currentTopWY = th;
startLY = Math.max(0, Math.min(VoxelChunk.SIZE - 1, startLY = Math.max(0, Math.min(VoxelChunk.SIZE - 1,
(int)(th - cy * (float) VoxelChunk.CELLS))); (int)(th - cy * (float) VoxelChunk.CELLS)));
} else if (cy > thCy) {
// Chunk über dem Terrain: weiter bauen wenn Chunk darunter an Oberkante solid.
VoxelChunk below = chunks.get(chunkKey(cx, cy - 1, cz));
if (below == null || below.getDensity(lx, VoxelChunk.SIZE - 1, lz) <= 0) continue;
// ly=0 immer solid setzen step könnte 0 sein und die Lücke bei Y=0 offen lassen.
chunk.setDensity(lx, 0, lz, (byte) 127);
startLY = 0;
currentTopWY = VoxelChunk.toWorldY(cy, 0);
} else {
continue; // cy < thCy: underground, kein Bedarf für Smooth
}
} }
int step = (int) Math.round((avgH - currentTopWY) * blend); int step = (int) Math.round((avgH - currentTopWY) * blend);
@@ -1662,10 +1708,21 @@ public class VoxelEditorState extends BaseAppState {
float th = terrainH(wx, wz); float th = terrainH(wx, wz);
if (!Float.isFinite(th)) continue; if (!Float.isFinite(th)) continue;
int thCy = VoxelChunk.worldYToCy(th); int thCy = VoxelChunk.worldYToCy(th);
if (cy != thCy) continue; if (cy == thCy) {
currentTopWY = th; currentTopWY = th;
startLY = Math.max(0, Math.min(VoxelChunk.SIZE - 1, startLY = Math.max(0, Math.min(VoxelChunk.SIZE - 1,
(int)(th - cy * (float) VoxelChunk.CELLS))); (int)(th - cy * (float) VoxelChunk.CELLS)));
} else if (cy > thCy) {
// Chunk über dem Terrain: weiter bauen wenn Chunk darunter an Oberkante solid.
VoxelChunk below = chunks.get(chunkKey(cx, cy - 1, cz));
if (below == null || below.getDensity(lx, VoxelChunk.SIZE - 1, lz) <= 0) continue;
// ly=0 immer solid setzen step könnte 0 sein und die Lücke bei Y=0 offen lassen.
chunk.setDensity(lx, 0, lz, (byte) 127);
startLY = 0;
currentTopWY = VoxelChunk.toWorldY(cy, 0);
} else {
continue; // cy < thCy: underground, kein Bedarf für Slope
}
} }
int step = (int) Math.round((targetH - currentTopWY) * blend); int step = (int) Math.round((targetH - currentTopWY) * blend);

View File

@@ -0,0 +1,46 @@
plugins {
id 'java'
}
ext {
jmeVersion = '3.9.0-stable'
mainClassName = 'de.blight.fbxtest.FbxTestApp'
}
dependencies {
implementation "org.jmonkeyengine:jme3-core:${jmeVersion}"
implementation "org.jmonkeyengine:jme3-desktop:${jmeVersion}"
implementation "org.jmonkeyengine:jme3-lwjgl3:${jmeVersion}"
implementation "org.jmonkeyengine:jme3-plugins:${jmeVersion}"
implementation 'com.github.stephengold:MonkeyWrench:1.0.0'
runtimeOnly 'ch.qos.logback:logback-classic:1.5.18'
implementation 'org.slf4j:slf4j-api:2.0.17'
}
tasks.register('extractNatives', Copy) {
def nativeConf = configurations.runtimeClasspath.resolvedConfiguration
.resolvedArtifacts
.findAll { it.name.contains('natives') }
.collect { zipTree(it.file) }
from nativeConf
into "${buildDir}/natives"
duplicatesStrategy = DuplicatesStrategy.INCLUDE
}
tasks.register('run', JavaExec) {
group = 'application'
description = 'Startet den FBX-Tester'
mainClass = mainClassName
classpath = sourceSets.main.runtimeClasspath
javaLauncher = javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(26)
}
jvmArgs = [
'--add-opens', 'java.base/java.lang=ALL-UNNAMED',
'--add-opens', 'java.desktop/sun.awt=ALL-UNNAMED',
'--enable-native-access=ALL-UNNAMED',
"-Djava.library.path=${buildDir}/natives",
]
dependsOn extractNatives
workingDir = rootDir
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,12 @@
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss} %-5level [%logger{20}] %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
</root>
<logger name="com.jme3" level="WARN"/>
<logger name="com.github.stephengold" level="INFO"/>
</configuration>

View File

@@ -50,6 +50,7 @@ public class BlightGame extends SimpleApplication {
private AudioSettings audioSettings; private AudioSettings audioSettings;
private ScreenshotAppState screenshotState; private ScreenshotAppState screenshotState;
private Path screenshotDir; private Path screenshotDir;
private com.jme3.audio.AudioNode screenshotClickSound;
private WorldScene worldScene; private WorldScene worldScene;
private ConfigScreen configScreen; private ConfigScreen configScreen;
private GraphicsScreen graphicsScreen; private GraphicsScreen graphicsScreen;
@@ -380,8 +381,12 @@ public class BlightGame extends SimpleApplication {
try { try {
screenshotDir = BlightHome.resolve("screenshots"); screenshotDir = BlightHome.resolve("screenshots");
Files.createDirectories(screenshotDir); Files.createDirectories(screenshotDir);
screenshotState = new ScreenshotAppState(screenshotDir + File.separator, "screenshot"); screenshotState = new ScreenshotAppState("", "screenshot");
stateManager.attach(screenshotState); stateManager.attach(screenshotState);
screenshotClickSound = new com.jme3.audio.AudioNode(assetManager, "audio/static/screenshot_click.ogg", com.jme3.audio.AudioData.DataType.Buffer);
screenshotClickSound.setPositional(false);
screenshotClickSound.setLooping(false);
screenshotClickSound.setVolume(1f);
log.info("Screenshots werden gespeichert in: {}", screenshotDir.toAbsolutePath()); log.info("Screenshots werden gespeichert in: {}", screenshotDir.toAbsolutePath());
} catch (IOException e) { } catch (IOException e) {
log.warn("Screenshot-Verzeichnis konnte nicht angelegt werden", e); log.warn("Screenshot-Verzeichnis konnte nicht angelegt werden", e);
@@ -389,8 +394,12 @@ public class BlightGame extends SimpleApplication {
inputManager.addMapping("Screenshot", new KeyTrigger(KeyInput.KEY_F12)); inputManager.addMapping("Screenshot", new KeyTrigger(KeyInput.KEY_F12));
inputManager.addListener((ActionListener) (name, isPressed, tpf) -> { inputManager.addListener((ActionListener) (name, isPressed, tpf) -> {
if (isPressed && screenshotState != null) { if (isPressed && screenshotState != null) {
log.info("[Screenshot] Speichere in: {}", screenshotDir.toAbsolutePath()); String ts = java.time.LocalDateTime.now()
.format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss"));
screenshotState.setFileName(screenshotDir + java.io.File.separator + "blight_" + ts + "_");
log.info("[Screenshot] Speichere: blight_{}.png", ts);
screenshotState.takeScreenshot(); screenshotState.takeScreenshot();
if (screenshotClickSound != null) screenshotClickSound.playInstance();
} }
}, "Screenshot"); }, "Screenshot");

View File

@@ -16,6 +16,8 @@ import com.jme3.texture.image.ColorSpace;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.awt.image.ConvolveOp;
import java.awt.image.Kernel;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
@@ -127,6 +129,8 @@ public class NinePatch {
// Guide-Pixel durch Rahmen-Nachbarfarbe ersetzen // Guide-Pixel durch Rahmen-Nachbarfarbe ersetzen
stripGuides(img, w, h, l - 1, w - r, t - 1, h - b); stripGuides(img, w, h, l - 1, w - r, t - 1, h - b);
img = gaussianBlur(img, 0.0f);
Texture2D tex = toJmeTexture(img, w, h); Texture2D tex = toJmeTexture(img, w, h);
return new NinePatch(assets, tex, w, h, l, r, t, b); return new NinePatch(assets, tex, w, h, l, r, t, b);
} }
@@ -219,6 +223,26 @@ public class NinePatch {
return 0; // Fallback: transparent return 0; // Fallback: transparent
} }
/**
* Weicher Gauss-Blur (3×3-Kernel) auf das Bild glättet Übergänge an Patch-Grenzen.
* sigma=0 deaktiviert den Blur.
*/
private static BufferedImage gaussianBlur(BufferedImage img, float sigma) {
if (sigma <= 0f) { return img; }
float s2 = 2f * sigma * sigma;
float[] data = {
(float) Math.exp(-2 / s2), (float) Math.exp(-1 / s2), (float) Math.exp(-2 / s2),
(float) Math.exp(-1 / s2), 1f, (float) Math.exp(-1 / s2),
(float) Math.exp(-2 / s2), (float) Math.exp(-1 / s2), (float) Math.exp(-2 / s2)
};
float sum = 0; for (float v : data) sum += v;
for (int i = 0; i < data.length; i++) data[i] /= sum;
ConvolveOp op = new ConvolveOp(new Kernel(3, 3, data), ConvolveOp.EDGE_NO_OP, null);
BufferedImage out = new BufferedImage(img.getWidth(), img.getHeight(), img.getType());
op.filter(img, out);
return out;
}
/** /**
* Konvertiert ein BufferedImage in eine JME3-Textur. * Konvertiert ein BufferedImage in eine JME3-Textur.
* Zeilen werden von unten nach oben gelesen (entspricht JME3-Y-Flip beim PNG-Laden). * Zeilen werden von unten nach oben gelesen (entspricht JME3-Y-Flip beim PNG-Laden).
@@ -266,7 +290,7 @@ public class NinePatch {
*/ */
public Node build(float x, float y, float w, float h, float z) { public Node build(float x, float y, float w, float h, float z) {
tex.setWrap(Texture.WrapMode.EdgeClamp); tex.setWrap(Texture.WrapMode.EdgeClamp);
tex.setMagFilter(Texture.MagFilter.Nearest); tex.setMagFilter(Texture.MagFilter.Bilinear);
Material mat = new Material(assets, "Common/MatDefs/Misc/Unshaded.j3md"); Material mat = new Material(assets, "Common/MatDefs/Misc/Unshaded.j3md");
mat.setTexture("ColorMap", tex); mat.setTexture("ColorMap", tex);
@@ -282,22 +306,75 @@ public class NinePatch {
float sx1 = x + l, sx2 = x + w - r; float sx1 = x + l, sx2 = x + w - r;
float sy1 = y + b, sy2 = y + h - t; float sy1 = y + b, sy2 = y + h - t;
// Kachelgröße des Mittelbereichs in Bildschirmpixeln
float cw = (u2 - u1) * texW;
float ch = (v2 - v1) * texH;
Node n = new Node("ninepatch"); Node n = new Node("ninepatch");
// Reihe unten // Reihe unten Ecken fix, untere Kante Mirror-X
n.attachChild(q(mat, x, y, l, b, 0, 0, u1, v1, z)); n.attachChild(q(mat, x, y, l, b, 0, 0, u1, v1, z));
n.attachChild(q(mat, sx1, y, sx2-sx1, b, u1, 0, u2, v1, z)); tileHEdge(n, mat, sx1, y, sx2, b, u1, u2, 0, v1, cw, texW, z);
n.attachChild(q(mat, sx2, y, r, b, u2, 0, 1, v1, z)); n.attachChild(q(mat, sx2, y, r, b, u2, 0, 1, v1, z));
// Reihe mitte // Reihe mitte Seiten Mirror-Y, Mitte Mirror-X+Y
n.attachChild(q(mat, x, sy1, l, sy2-sy1, 0, v1, u1, v2, z)); tileVEdge(n, mat, x, sy1, l, sy2, 0, u1, v1, v2, ch, texH, z);
n.attachChild(q(mat, sx1, sy1, sx2-sx1, sy2-sy1, u1, v1, u2, v2, z)); tileCenterQuads(n, mat, sx1, sy1, sx2, sy2, u1, v1, u2, v2, cw, ch, texW, texH, z);
n.attachChild(q(mat, sx2, sy1, r, sy2-sy1, u2, v1, 1, v2, z)); tileVEdge(n, mat, sx2, sy1, r, sy2, u2, 1f, v1, v2, ch, texH, z);
// Reihe oben // Reihe oben Ecken fix, obere Kante Mirror-X
n.attachChild(q(mat, x, sy2, l, t, 0, v2, u1, 1, z)); n.attachChild(q(mat, x, sy2, l, t, 0, v2, u1, 1f, z));
n.attachChild(q(mat, sx1, sy2, sx2-sx1, t, u1, v2, u2, 1, z)); tileHEdge(n, mat, sx1, sy2, sx2, t, u1, u2, v2, 1f, cw, texW, z);
n.attachChild(q(mat, sx2, sy2, r, t, u2, v2, 1, 1, z)); n.attachChild(q(mat, sx2, sy2, r, t, u2, v2, 1f, 1f, z));
return n; return n;
} }
/** Mitte: Mirror-Repeat in X und Y. */
private static void tileCenterQuads(Node n, Material mat,
float sx1, float sy1, float sx2, float sy2,
float u1, float v1, float u2, float v2,
float cw, float ch,
float texW, float texH, float z) {
int iz = 0;
for (float py = sy1; py < sy2; py += ch, iz++) {
float th = Math.min(ch, sy2 - py);
float vA = (iz & 1) == 0 ? v1 : v2;
float vB = (iz & 1) == 0 ? v1+th/texH : v2-th/texH;
int ix = 0;
for (float px = sx1; px < sx2; px += cw, ix++) {
float tw = Math.min(cw, sx2 - px);
float uA = (ix & 1) == 0 ? u1 : u2;
float uB = (ix & 1) == 0 ? u1+tw/texW : u2-tw/texW;
n.attachChild(q(mat, px, py, tw, th, uA, vA, uB, vB, z));
}
}
}
/** Horizontaler Randstreifen (oben/unten): Mirror-Repeat nur in X. */
private static void tileHEdge(Node n, Material mat,
float sx1, float py, float sx2, float ph,
float u1, float u2, float vMin, float vMax,
float cw, float texW, float z) {
int ix = 0;
for (float px = sx1; px < sx2; px += cw, ix++) {
float tw = Math.min(cw, sx2 - px);
float uA = (ix & 1) == 0 ? u1 : u2;
float uB = (ix & 1) == 0 ? u1+tw/texW : u2-tw/texW;
n.attachChild(q(mat, px, py, tw, ph, uA, vMin, uB, vMax, z));
}
}
/** Vertikaler Randstreifen (links/rechts): Mirror-Repeat nur in Y. */
private static void tileVEdge(Node n, Material mat,
float px, float sy1, float pw, float sy2,
float uMin, float uMax, float v1, float v2,
float ch, float texH, float z) {
int iz = 0;
for (float py = sy1; py < sy2; py += ch, iz++) {
float th = Math.min(ch, sy2 - py);
float vA = (iz & 1) == 0 ? v1 : v2;
float vB = (iz & 1) == 0 ? v1+th/texH : v2-th/texH;
n.attachChild(q(mat, px, py, pw, th, uMin, vA, uMax, vB, z));
}
}
private static Geometry q(Material mat, private static Geometry q(Material mat,
float x, float y, float w, float h, float x, float y, float w, float h,
float uMin, float vMin, float uMax, float vMax, float uMin, float vMin, float uMax, float vMax,

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -7,3 +7,4 @@ include 'blight-lang'
include 'blight-editor' include 'blight-editor'
include 'blight-game' include 'blight-game'
include 'blight-vegetation-generator' include 'blight-vegetation-generator'
include 'blight-fbx-test'