Compare commits
3 Commits
c9ab15bda5
...
dd32ec65ca
| Author | SHA1 | Date | |
|---|---|---|---|
| dd32ec65ca | |||
| c09502ad3a | |||
| ee8c235655 |
|
Before Width: | Height: | Size: 156 B After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 1.8 MiB |
|
Before Width: | Height: | Size: 2.0 MiB |
BIN
blight-assets/src/main/resources/Textures/menu/backup/button.png
Normal file
|
After Width: | Height: | Size: 333 B |
|
Before Width: | Height: | Size: 333 B After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 333 B After Width: | Height: | Size: 6.8 KiB |
@@ -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)");
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
46
blight-fbx-test/build.gradle
Normal 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
|
||||||
|
}
|
||||||
1152
blight-fbx-test/src/main/java/de/blight/fbxtest/FbxTestApp.java
Normal file
12
blight-fbx-test/src/main/resources/logback.xml
Normal 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>
|
||||||
@@ -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");
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
BIN
blight-map/src/main/map/chunks/voxel_17_0_08.blvc
Normal file
BIN
blight-map/src/main/map/chunks/voxel_17_0_09.blvc
Normal file
BIN
blight-map/src/main/map/chunks/voxel_17_m1_09.blvc
Normal 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'
|
||||||
|
|||||||