Compare commits

...

3 Commits

15 changed files with 446 additions and 82 deletions

View File

@@ -80,6 +80,22 @@ public final class MapData {
/** Splatmap Alpha-Kanal Dritte Gruppe [SPLAT_SIZE²]. */
public final byte[] thirdSplatA;
/** Voxel-Splatmap Kanal 1-4 (AlphaMap für Sculpted-Meshes, unabhängig vom Basis-Terrain) [SPLAT_SIZE²]. */
public final byte[] voxelSplatR;
public final byte[] voxelSplatG;
public final byte[] voxelSplatB;
public final byte[] voxelSplatA;
/** Voxel-Splatmap Kanal 5-8 (AlphaMap_1) [SPLAT_SIZE²]. */
public final byte[] voxelUpperSplatR;
public final byte[] voxelUpperSplatG;
public final byte[] voxelUpperSplatB;
public final byte[] voxelUpperSplatA;
/** Voxel-Splatmap Kanal 9-12 (AlphaMap_2) [SPLAT_SIZE²]. */
public final byte[] voxelThirdSplatR;
public final byte[] voxelThirdSplatG;
public final byte[] voxelThirdSplatB;
public final byte[] voxelThirdSplatA;
/** Texturpfade für dritte Gruppe (4 Slots, "" = Standard-Textur). */
public final String[] thirdTextures = new String[]{"", "", "", ""};
/** Normal-Map-Pfade für dritte Gruppe (4 Slots, "" = keine Normal-Map). */
@@ -155,6 +171,18 @@ public final class MapData {
thirdSplatG = new byte [SPLAT_SIZE * SPLAT_SIZE];
thirdSplatB = new byte [SPLAT_SIZE * SPLAT_SIZE];
thirdSplatA = new byte [SPLAT_SIZE * SPLAT_SIZE];
voxelSplatR = new byte [SPLAT_SIZE * SPLAT_SIZE];
voxelSplatG = new byte [SPLAT_SIZE * SPLAT_SIZE];
voxelSplatB = new byte [SPLAT_SIZE * SPLAT_SIZE];
voxelSplatA = new byte [SPLAT_SIZE * SPLAT_SIZE];
voxelUpperSplatR = new byte [SPLAT_SIZE * SPLAT_SIZE];
voxelUpperSplatG = new byte [SPLAT_SIZE * SPLAT_SIZE];
voxelUpperSplatB = new byte [SPLAT_SIZE * SPLAT_SIZE];
voxelUpperSplatA = new byte [SPLAT_SIZE * SPLAT_SIZE];
voxelThirdSplatR = new byte [SPLAT_SIZE * SPLAT_SIZE];
voxelThirdSplatG = new byte [SPLAT_SIZE * SPLAT_SIZE];
voxelThirdSplatB = new byte [SPLAT_SIZE * SPLAT_SIZE];
voxelThirdSplatA = new byte [SPLAT_SIZE * SPLAT_SIZE];
grassDensity = new byte [SPLAT_SIZE * SPLAT_SIZE];
grassHeightMap = new byte [SPLAT_SIZE * SPLAT_SIZE];
grassTextureMap = new byte [SPLAT_SIZE * SPLAT_SIZE];

View File

@@ -29,6 +29,7 @@ import java.util.zip.*;
* Altes Format (v≤9) wird beim Laden bilinear hochskaliert.
* 16 Kartengröße halbiert: TERRAIN_VERTS=8193, SPLAT_SIZE=1025, UPPER_CELLS=256.
* Format v10-v15 wird beim Laden bilinear auf neue Größe herunterskaliert.
* 17 Voxel-Splatmaps (12 Kanäle RGBA×3) getrennt von Basis-Terrain-Splatmaps.
*/
public final class MapIO {
@@ -65,7 +66,7 @@ public final class MapIO {
}
private static final int MAGIC = 0x424C4947; // "BLIG"
private static final int VERSION = 16;
private static final int VERSION = 17;
// Größen alter Saves für Migration
private static final int V10_TERRAIN_VERTS = 16385; // v10-v15
@@ -172,6 +173,13 @@ public final class MapIO {
writeStrings(out, data.upperDisplacementMaps);
writeStrings(out, data.thirdDisplacementMaps);
writeFloats(out, data.diffuseScales);
// v17: voxel-splatmaps (unabhängig vom Basis-Terrain)
out.write(data.voxelSplatR); out.write(data.voxelSplatG);
out.write(data.voxelSplatB); out.write(data.voxelSplatA);
out.write(data.voxelUpperSplatR); out.write(data.voxelUpperSplatG);
out.write(data.voxelUpperSplatB); out.write(data.voxelUpperSplatA);
out.write(data.voxelThirdSplatR); out.write(data.voxelThirdSplatG);
out.write(data.voxelThirdSplatB); out.write(data.voxelThirdSplatA);
}
// Atomares Umbenennen: erst wenn die Datei vollständig ist ersetzen wir die alte.
// ATOMIC_MOVE schlägt auf manchen Systemen cross-device fehl → Fallback auf REPLACE_EXISTING.
@@ -377,6 +385,14 @@ public final class MapIO {
readStrings(in, data.thirdDisplacementMaps);
readFloats(in, data.diffuseScales);
}
if (version >= 17) {
in.readFully(data.voxelSplatR); in.readFully(data.voxelSplatG);
in.readFully(data.voxelSplatB); in.readFully(data.voxelSplatA);
in.readFully(data.voxelUpperSplatR); in.readFully(data.voxelUpperSplatG);
in.readFully(data.voxelUpperSplatB); in.readFully(data.voxelUpperSplatA);
in.readFully(data.voxelThirdSplatR); in.readFully(data.voxelThirdSplatG);
in.readFully(data.voxelThirdSplatB); in.readFully(data.voxelThirdSplatA);
}
}
return data;
}

View File

@@ -1713,6 +1713,7 @@ public class EditorApp extends Application {
input.activeLayer = SharedInput.LAYER_PLAY_TOOL;
input.playToolMode = SharedInput.PlayToolMode.EDIT;
root.setRight(buildPlayToolPanel());
Platform.runLater(() -> { if (gamePlayBtn != null) gamePlayBtn.requestFocus(); });
});
worldHintLabel = new Label(camHintText());

View File

@@ -451,8 +451,14 @@ public class SceneObjectState extends BaseAppState {
// Property-Änderungen (Position, Rotation, Textur, Solid)
SharedInput.ObjectPropertyChange prop;
boolean propChanged = false;
while ((prop = input.objectPropertyQueue.poll()) != null) {
handlePropertyChange(prop);
propChanged = true;
}
if (propChanged) {
refreshBedArrow();
refreshBenchArrow();
}
// Löschen
@@ -830,10 +836,10 @@ public class SceneObjectState extends BaseAppState {
float ox = meta.interactableOffsetX();
float oy = meta.interactableOffsetY();
float oz = meta.interactableOffsetZ();
float wx2 = wx + ox * cos - oz * sin;
float wx2 = wx + ox * cos + oz * sin;
float wy2 = wy + placementOffY + oy;
float wz2 = wz + ox * sin + oz * cos;
float roty2 = rotY + meta.interactableRotY();
float wz2 = wz - ox * sin + oz * cos;
float roty2 = meta.interactableRotY() - rotY;
if (meta.interactableType() == de.blight.common.model.InteractableType.BED) {
Bed bed = new Bed();
bed.setLiegeX(wx2); bed.setLiegeY(wy2); bed.setLiegeZ(wz2);
@@ -1204,7 +1210,8 @@ public class SceneObjectState extends BaseAppState {
private void updateTerrainLevelIndicator() {
if (terrainRingNode == null) return;
if (selectedIndices.isEmpty() || input.activeLayer != SharedInput.LAYER_OBJECTS_EDIT) {
if (selectedIndices.isEmpty() || input.activeLayer != SharedInput.LAYER_OBJECTS_EDIT
|| input.objectEditTool == SharedInput.EDIT_TOOL_ROTATE) {
terrainRingNode.setCullHint(Spatial.CullHint.Always);
terrainStickNode.setCullHint(Spatial.CullHint.Always);
return;
@@ -1898,6 +1905,8 @@ public class SceneObjectState extends BaseAppState {
clearSubSelection();
activeGizmo = -1;
updateGizmoVisibility();
hideBedArrow();
hideBenchArrow();
input.selectedObjectInfo = null;
input.objectSelectionChanged = true;
@@ -1940,10 +1949,10 @@ public class SceneObjectState extends BaseAppState {
float oy = meta.interactableOffsetY();
float oz = meta.interactableOffsetZ();
float wx = so.getWorldX() + ox * cos - oz * sin;
float wx = so.getWorldX() + ox * cos + oz * sin;
float wy = so.getGroundY() + oy;
float wz = so.getWorldZ() + ox * sin + oz * cos;
float iRotY = rotY + meta.interactableRotY();
float wz = so.getWorldZ() - ox * sin + oz * cos;
float iRotY = meta.interactableRotY() - rotY;
try {
if (isBed) {
@@ -2526,10 +2535,10 @@ public class SceneObjectState extends BaseAppState {
float ox = meta.interactableOffsetX();
float oy = meta.interactableOffsetY();
float oz = meta.interactableOffsetZ();
float wx = so.getWorldX() + ox * cos - oz * sin;
float wx = so.getWorldX() + ox * cos + oz * sin;
float wy = so.getGroundY() + oy;
float wz = so.getWorldZ() + ox * sin + oz * cos;
return new float[]{wx, wy, wz, rotY + meta.interactableRotY()};
float wz = so.getWorldZ() - ox * sin + oz * cos;
return new float[]{wx, wy, wz, meta.interactableRotY() - rotY};
}
/**

View File

@@ -530,9 +530,9 @@ public class SculptedMeshEditorState extends BaseAppState {
}
}
com.jme3.texture.Texture2D a0 = tes.getSplatTex();
com.jme3.texture.Texture2D a1 = tes.getUpperSplatTex();
com.jme3.texture.Texture2D a2 = tes.getThirdSplatTex();
com.jme3.texture.Texture2D a0 = tes.getVoxelSplatTex();
com.jme3.texture.Texture2D a1 = tes.getVoxelUpperSplatTex();
com.jme3.texture.Texture2D a2 = tes.getVoxelThirdSplatTex();
if (a0 != null) { mat.setTexture("AlphaMap", a0); }
if (a1 != null) { mat.setTexture("AlphaMap_1", a1); }
if (a2 != null) { mat.setTexture("AlphaMap_2", a2); }

View File

@@ -142,6 +142,22 @@ public class TerrainEditorState extends BaseAppState {
private Image thirdSplatImage;
private Texture2D thirdSplatTex;
// ── Voxel-Splatmaps (Sculpted-Mesh, unabhängig vom Basis-Terrain) ─────────
private byte[] voxelSplatR, voxelSplatG, voxelSplatB, voxelSplatA;
private ByteBuffer voxelSplatBuf;
private Image voxelSplatImage;
private Texture2D voxelSplatTex;
private byte[] voxelUpperSplatR, voxelUpperSplatG, voxelUpperSplatB, voxelUpperSplatA;
private ByteBuffer voxelUpperSplatBuf;
private Image voxelUpperSplatImage;
private Texture2D voxelUpperSplatTex;
private byte[] voxelThirdSplatR, voxelThirdSplatG, voxelThirdSplatB, voxelThirdSplatA;
private ByteBuffer voxelThirdSplatBuf;
private Image voxelThirdSplatImage;
private Texture2D voxelThirdSplatTex;
// ── Kameraposition ────────────────────────────────────────────────────────
private static final float DEFAULT_CAM_Y = 50f;
private static final float DEFAULT_PITCH = (float) (Math.PI / 4); // intern +45° → Kamera schaut 45° nach unten (JME 3.9: dir.y = -sin(pitch))
@@ -476,6 +492,19 @@ public class TerrainEditorState extends BaseAppState {
input.thirdTexturePaths, 0, MapData.TEXTURE_SLOTS);
System.arraycopy(loadedMapData.thirdNormalMaps, 0,
input.thirdNormalMapPaths, 0, MapData.TEXTURE_SLOTS);
// Voxel-Splatmaps (Sculpted-Meshes, seit Version 17)
voxelSplatR = loadedMapData.voxelSplatR.clone();
voxelSplatG = loadedMapData.voxelSplatG.clone();
voxelSplatB = loadedMapData.voxelSplatB.clone();
voxelSplatA = loadedMapData.voxelSplatA.clone();
voxelUpperSplatR = loadedMapData.voxelUpperSplatR.clone();
voxelUpperSplatG = loadedMapData.voxelUpperSplatG.clone();
voxelUpperSplatB = loadedMapData.voxelUpperSplatB.clone();
voxelUpperSplatA = loadedMapData.voxelUpperSplatA.clone();
voxelThirdSplatR = loadedMapData.voxelThirdSplatR.clone();
voxelThirdSplatG = loadedMapData.voxelThirdSplatG.clone();
voxelThirdSplatB = loadedMapData.voxelThirdSplatB.clone();
voxelThirdSplatA = loadedMapData.voxelThirdSplatA.clone();
if (loadedMapData.normalMapOrder != null && loadedMapData.normalMapOrder.length == 12)
input.normalMapOrder = loadedMapData.normalMapOrder.clone();
if (loadedMapData.diffuseScales != null && loadedMapData.diffuseScales.length == 12)
@@ -507,6 +536,18 @@ public class TerrainEditorState extends BaseAppState {
thirdSplatG = new byte[SPLAT_SIZE * SPLAT_SIZE];
thirdSplatB = new byte[SPLAT_SIZE * SPLAT_SIZE];
thirdSplatA = new byte[SPLAT_SIZE * SPLAT_SIZE];
voxelSplatR = new byte[SPLAT_SIZE * SPLAT_SIZE];
voxelSplatG = new byte[SPLAT_SIZE * SPLAT_SIZE];
voxelSplatB = new byte[SPLAT_SIZE * SPLAT_SIZE];
voxelSplatA = new byte[SPLAT_SIZE * SPLAT_SIZE];
voxelUpperSplatR = new byte[SPLAT_SIZE * SPLAT_SIZE];
voxelUpperSplatG = new byte[SPLAT_SIZE * SPLAT_SIZE];
voxelUpperSplatB = new byte[SPLAT_SIZE * SPLAT_SIZE];
voxelUpperSplatA = new byte[SPLAT_SIZE * SPLAT_SIZE];
voxelThirdSplatR = new byte[SPLAT_SIZE * SPLAT_SIZE];
voxelThirdSplatG = new byte[SPLAT_SIZE * SPLAT_SIZE];
voxelThirdSplatB = new byte[SPLAT_SIZE * SPLAT_SIZE];
voxelThirdSplatA = new byte[SPLAT_SIZE * SPLAT_SIZE];
}
// A-Kanal aus alten Saves bereinigen (war für Fluss-Textur; nicht mehr verwendet)
@@ -545,6 +586,39 @@ public class TerrainEditorState extends BaseAppState {
thirdSplatTex.setMinFilter(Texture.MinFilter.BilinearNoMipMaps);
thirdSplatTex.setMagFilter(Texture.MagFilter.Bilinear);
voxelSplatBuf = BufferUtils.createByteBuffer(SPLAT_SIZE * SPLAT_SIZE * 4);
for (int i = 0; i < SPLAT_SIZE * SPLAT_SIZE; i++) {
voxelSplatBuf.put(voxelSplatR[i]).put(voxelSplatG[i]).put(voxelSplatB[i]).put(voxelSplatA[i]);
}
voxelSplatBuf.flip();
voxelSplatImage = new Image(Image.Format.RGBA8, SPLAT_SIZE, SPLAT_SIZE, voxelSplatBuf, ColorSpace.Linear);
voxelSplatTex = new Texture2D(voxelSplatImage);
voxelSplatTex.setWrap(Texture.WrapMode.EdgeClamp);
voxelSplatTex.setMinFilter(Texture.MinFilter.BilinearNoMipMaps);
voxelSplatTex.setMagFilter(Texture.MagFilter.Bilinear);
voxelUpperSplatBuf = BufferUtils.createByteBuffer(SPLAT_SIZE * SPLAT_SIZE * 4);
for (int i = 0; i < SPLAT_SIZE * SPLAT_SIZE; i++) {
voxelUpperSplatBuf.put(voxelUpperSplatR[i]).put(voxelUpperSplatG[i]).put(voxelUpperSplatB[i]).put(voxelUpperSplatA[i]);
}
voxelUpperSplatBuf.flip();
voxelUpperSplatImage = new Image(Image.Format.RGBA8, SPLAT_SIZE, SPLAT_SIZE, voxelUpperSplatBuf, ColorSpace.Linear);
voxelUpperSplatTex = new Texture2D(voxelUpperSplatImage);
voxelUpperSplatTex.setWrap(Texture.WrapMode.EdgeClamp);
voxelUpperSplatTex.setMinFilter(Texture.MinFilter.BilinearNoMipMaps);
voxelUpperSplatTex.setMagFilter(Texture.MagFilter.Bilinear);
voxelThirdSplatBuf = BufferUtils.createByteBuffer(SPLAT_SIZE * SPLAT_SIZE * 4);
for (int i = 0; i < SPLAT_SIZE * SPLAT_SIZE; i++) {
voxelThirdSplatBuf.put(voxelThirdSplatR[i]).put(voxelThirdSplatG[i]).put(voxelThirdSplatB[i]).put(voxelThirdSplatA[i]);
}
voxelThirdSplatBuf.flip();
voxelThirdSplatImage = new Image(Image.Format.RGBA8, SPLAT_SIZE, SPLAT_SIZE, voxelThirdSplatBuf, ColorSpace.Linear);
voxelThirdSplatTex = new Texture2D(voxelThirdSplatImage);
voxelThirdSplatTex.setWrap(Texture.WrapMode.EdgeClamp);
voxelThirdSplatTex.setMinFilter(Texture.MinFilter.BilinearNoMipMaps);
voxelThirdSplatTex.setMagFilter(Texture.MagFilter.Bilinear);
// loadedMapData wird ab hier nicht mehr gebraucht (letzter Verbraucher war
// dieser Merge). Freigeben, weil terrainHeight allein ~1 GB (16385² floats)
// belegt und sonst die ganze Session über unnötig im Heap hängt.
@@ -954,6 +1028,139 @@ public class TerrainEditorState extends BaseAppState {
}
}
/** Malt Voxel-Basis-Texturen (Slots 1-4) auf die Voxel-Splatmap. */
private void applyVoxelTexturePaint(Vector3f contact, float strength, int textureIndex) {
float radius = (float) input.textureTool.brushRadius.getValue();
float softEdge = radius + Math.min(radius * 0.5f, 2f * SPLAT_WE_PER_PX);
int centerPX = Math.round((contact.x + WORLD_HALF) / SPLAT_WE_PER_PX);
int centerPZ = (SPLAT_SIZE - 1) - Math.round((contact.z + WORLD_HALF) / SPLAT_WE_PER_PX);
int pixR = (int) Math.ceil(softEdge / SPLAT_WE_PER_PX);
boolean changed = false;
for (int dz = -pixR; dz <= pixR; dz++) {
int pz = centerPZ + dz;
if (pz < 0 || pz >= SPLAT_SIZE) continue;
for (int dx = -pixR; dx <= pixR; dx++) {
int px = centerPX + dx;
if (px < 0 || px >= SPLAT_SIZE) continue;
float distWE = FastMath.sqrt(dx * dx + dz * dz) * SPLAT_WE_PER_PX;
if (distWE >= softEdge) continue;
float t = distWE / softEdge;
float falloff = (1f + FastMath.cos(FastMath.PI * t)) * 0.5f;
float blend = (textureIndex == 0) ? falloff : strength * falloff;
int idx = pz * SPLAT_SIZE + px;
float curG = (voxelSplatG[idx] & 0xFF) / 255f;
float curB = (voxelSplatB[idx] & 0xFF) / 255f;
float curA = (voxelSplatA[idx] & 0xFF) / 255f;
float tgG = (textureIndex == 1) ? 1f : 0f;
float tgB = (textureIndex == 2) ? 1f : 0f;
float tgA = (textureIndex == 3) ? 1f : 0f;
voxelSplatR[idx] = (byte) 255;
voxelSplatG[idx] = (byte) Math.round((curG + (tgG - curG) * blend) * 255f);
voxelSplatB[idx] = (byte) Math.round((curB + (tgB - curB) * blend) * 255f);
voxelSplatA[idx] = (byte) Math.round((curA + (tgA - curA) * blend) * 255f);
int bi = idx * 4;
voxelSplatBuf.put(bi, voxelSplatR[idx]);
voxelSplatBuf.put(bi + 1, voxelSplatG[idx]);
voxelSplatBuf.put(bi + 2, voxelSplatB[idx]);
voxelSplatBuf.put(bi + 3, voxelSplatA[idx]);
changed = true;
}
}
if (changed) { voxelSplatBuf.rewind(); voxelSplatImage.setUpdateNeeded(); }
}
/** Malt Voxel-Slots 5-8 auf die Voxel-Upper-Splatmap. */
private void applyVoxelUpperTexturePaint(Vector3f contact, float strength, int textureIndex) {
float radius = (float) input.textureTool.brushRadius.getValue();
float softEdge = radius + Math.min(radius * 0.5f, 2f * SPLAT_WE_PER_PX);
int centerPX = Math.round((contact.x + WORLD_HALF) / SPLAT_WE_PER_PX);
int centerPZ = (SPLAT_SIZE - 1) - Math.round((contact.z + WORLD_HALF) / SPLAT_WE_PER_PX);
int pixR = (int) Math.ceil(softEdge / SPLAT_WE_PER_PX);
boolean changed = false;
for (int dz = -pixR; dz <= pixR; dz++) {
int pz = centerPZ + dz;
if (pz < 0 || pz >= SPLAT_SIZE) continue;
for (int dx = -pixR; dx <= pixR; dx++) {
int px = centerPX + dx;
if (px < 0 || px >= SPLAT_SIZE) continue;
float distWE = FastMath.sqrt(dx * dx + dz * dz) * SPLAT_WE_PER_PX;
if (distWE >= softEdge) continue;
float t = distWE / softEdge;
float falloff = (1f + FastMath.cos(FastMath.PI * t)) * 0.5f;
int idx = pz * SPLAT_SIZE + px;
if (textureIndex < 0) {
voxelUpperSplatR[idx] = (byte) Math.round((voxelUpperSplatR[idx] & 0xFF) / 255f * (1f - falloff) * 255f);
voxelUpperSplatG[idx] = (byte) Math.round((voxelUpperSplatG[idx] & 0xFF) / 255f * (1f - falloff) * 255f);
voxelUpperSplatB[idx] = (byte) Math.round((voxelUpperSplatB[idx] & 0xFF) / 255f * (1f - falloff) * 255f);
voxelUpperSplatA[idx] = (byte) Math.round((voxelUpperSplatA[idx] & 0xFF) / 255f * (1f - falloff) * 255f);
} else {
float blend = strength * falloff;
float curR = (voxelUpperSplatR[idx] & 0xFF) / 255f;
float curG = (voxelUpperSplatG[idx] & 0xFF) / 255f;
float curB = (voxelUpperSplatB[idx] & 0xFF) / 255f;
float curA = (voxelUpperSplatA[idx] & 0xFF) / 255f;
voxelUpperSplatR[idx] = (byte) Math.round((curR + ((textureIndex == 0 ? 1f : 0f) - curR) * blend) * 255f);
voxelUpperSplatG[idx] = (byte) Math.round((curG + ((textureIndex == 1 ? 1f : 0f) - curG) * blend) * 255f);
voxelUpperSplatB[idx] = (byte) Math.round((curB + ((textureIndex == 2 ? 1f : 0f) - curB) * blend) * 255f);
voxelUpperSplatA[idx] = (byte) Math.round((curA + ((textureIndex == 3 ? 1f : 0f) - curA) * blend) * 255f);
}
int bi = idx * 4;
voxelUpperSplatBuf.put(bi, voxelUpperSplatR[idx]);
voxelUpperSplatBuf.put(bi + 1, voxelUpperSplatG[idx]);
voxelUpperSplatBuf.put(bi + 2, voxelUpperSplatB[idx]);
voxelUpperSplatBuf.put(bi + 3, voxelUpperSplatA[idx]);
changed = true;
}
}
if (changed) { voxelUpperSplatBuf.rewind(); voxelUpperSplatImage.setUpdateNeeded(); }
}
/** Malt Voxel-Slots 9-12 auf die Voxel-Third-Splatmap. */
private void applyVoxelThirdTexturePaint(Vector3f contact, float strength, int textureIndex) {
float radius = (float) input.textureTool.brushRadius.getValue();
float softEdge = radius + Math.min(radius * 0.5f, 2f * SPLAT_WE_PER_PX);
int centerPX = Math.round((contact.x + WORLD_HALF) / SPLAT_WE_PER_PX);
int centerPZ = (SPLAT_SIZE - 1) - Math.round((contact.z + WORLD_HALF) / SPLAT_WE_PER_PX);
int pixR = (int) Math.ceil(softEdge / SPLAT_WE_PER_PX);
boolean changed = false;
for (int dz = -pixR; dz <= pixR; dz++) {
int pz = centerPZ + dz;
if (pz < 0 || pz >= SPLAT_SIZE) continue;
for (int dx = -pixR; dx <= pixR; dx++) {
int px = centerPX + dx;
if (px < 0 || px >= SPLAT_SIZE) continue;
float distWE = FastMath.sqrt(dx * dx + dz * dz) * SPLAT_WE_PER_PX;
if (distWE >= softEdge) continue;
float t = distWE / softEdge;
float falloff = (1f + FastMath.cos(FastMath.PI * t)) * 0.5f;
int idx = pz * SPLAT_SIZE + px;
if (textureIndex < 0) {
voxelThirdSplatR[idx] = (byte) Math.round((voxelThirdSplatR[idx] & 0xFF) / 255f * (1f - falloff) * 255f);
voxelThirdSplatG[idx] = (byte) Math.round((voxelThirdSplatG[idx] & 0xFF) / 255f * (1f - falloff) * 255f);
voxelThirdSplatB[idx] = (byte) Math.round((voxelThirdSplatB[idx] & 0xFF) / 255f * (1f - falloff) * 255f);
voxelThirdSplatA[idx] = (byte) Math.round((voxelThirdSplatA[idx] & 0xFF) / 255f * (1f - falloff) * 255f);
} else {
float blend = strength * falloff;
float curR = (voxelThirdSplatR[idx] & 0xFF) / 255f;
float curG = (voxelThirdSplatG[idx] & 0xFF) / 255f;
float curB = (voxelThirdSplatB[idx] & 0xFF) / 255f;
float curA = (voxelThirdSplatA[idx] & 0xFF) / 255f;
voxelThirdSplatR[idx] = (byte) Math.round((curR + ((textureIndex == 0 ? 1f : 0f) - curR) * blend) * 255f);
voxelThirdSplatG[idx] = (byte) Math.round((curG + ((textureIndex == 1 ? 1f : 0f) - curG) * blend) * 255f);
voxelThirdSplatB[idx] = (byte) Math.round((curB + ((textureIndex == 2 ? 1f : 0f) - curB) * blend) * 255f);
voxelThirdSplatA[idx] = (byte) Math.round((curA + ((textureIndex == 3 ? 1f : 0f) - curA) * blend) * 255f);
}
int bi = idx * 4;
voxelThirdSplatBuf.put(bi, voxelThirdSplatR[idx]);
voxelThirdSplatBuf.put(bi + 1, voxelThirdSplatG[idx]);
voxelThirdSplatBuf.put(bi + 2, voxelThirdSplatB[idx]);
voxelThirdSplatBuf.put(bi + 3, voxelThirdSplatA[idx]);
changed = true;
}
}
if (changed) { voxelThirdSplatBuf.rewind(); voxelThirdSplatImage.setUpdateNeeded(); }
}
// ── Update-Schleife ───────────────────────────────────────────────────────
@Override
@@ -1159,27 +1366,44 @@ public class TerrainEditorState extends BaseAppState {
Vector3f far = cam.getWorldCoordinates(new Vector2f(jmeX, jmeY), 1f);
com.jme3.math.Ray ray = new com.jme3.math.Ray(near, far.subtract(near).normalizeLocal());
Vector3f contact = raycastSurface(ray);
if (contact == null) continue;
if (ves != null && ves.terrainTypeAt(contact.x, contact.z) == VoxelEditorState.TerrainType.VOXEL_UNBAKED) continue;
SurfaceHit hit = raycastSurfaceWithType(ray);
if (hit == null) continue;
Vector3f contact = hit.point;
if (!hit.voxelMesh && ves != null && ves.terrainTypeAt(contact.x, contact.z) == VoxelEditorState.TerrainType.VOXEL_UNBAKED) continue;
int selIdx = input.textureTool.textureIndex.getSelectedIndex();
float str = (float) input.textureTool.brushStrength.getValue();
if (edit.action() > 0) {
// Linksklick: Slot malen
if (selIdx >= 8) {
applyThirdTexturePaint(contact, str, selIdx - 8); // 0=R(Slot9),1=G(Slot10),...
} else if (selIdx >= 4) {
applyUpperTexturePaint(contact, str, selIdx - 4); // 0=R(Slot5),1=G(Slot6),...
if (hit.voxelMesh) {
// Sculpted-Mesh-Treffer → Voxel-Splatmaps
if (edit.action() > 0) {
if (selIdx >= 8) {
applyVoxelThirdTexturePaint(contact, str, selIdx - 8);
} else if (selIdx >= 4) {
applyVoxelUpperTexturePaint(contact, str, selIdx - 4);
} else {
applyVoxelTexturePaint(contact, str, selIdx);
}
} else {
applyTexturePaint(contact, str, selIdx);
applyVoxelTexturePaint(contact, str, 0);
applyVoxelUpperTexturePaint(contact, str, -1);
applyVoxelThirdTexturePaint(contact, str, -1);
}
} else {
// Rechtsklick: immer auf Basis (Slot 1) zurücksetzen
applyTexturePaint(contact, str, 0); // Base → Slot 1 (Gras)
applyUpperTexturePaint(contact, str, -1); // Upper-Kanäle → 0
applyThirdTexturePaint(contact, str, -1); // Third-Kanäle → 0
// Basis-Terrain-Treffer → normale Splatmaps
if (edit.action() > 0) {
if (selIdx >= 8) {
applyThirdTexturePaint(contact, str, selIdx - 8);
} else if (selIdx >= 4) {
applyUpperTexturePaint(contact, str, selIdx - 4);
} else {
applyTexturePaint(contact, str, selIdx);
}
} else {
applyTexturePaint(contact, str, 0);
applyUpperTexturePaint(contact, str, -1);
applyThirdTexturePaint(contact, str, -1);
}
}
}
}
@@ -1194,22 +1418,38 @@ public class TerrainEditorState extends BaseAppState {
public com.jme3.texture.Texture2D getUpperSplatTex() { return upperSplatTex; }
public com.jme3.texture.Texture2D getThirdSplatTex() { return thirdSplatTex; }
public com.jme3.texture.Texture2D getVoxelSplatTex() { return voxelSplatTex; }
public com.jme3.texture.Texture2D getVoxelUpperSplatTex() { return voxelUpperSplatTex; }
public com.jme3.texture.Texture2D getVoxelThirdSplatTex() { return voxelThirdSplatTex; }
private static final class SurfaceHit {
final Vector3f point;
final boolean voxelMesh;
SurfaceHit(Vector3f p, boolean v) { point = p; voxelMesh = v; }
}
/**
* Raycast gegen Basis-Terrain, ungebackenes Voxel-Terrain und gebackene Sculpted-Meshes.
* Gibt den zur Kamera nächstgelegenen Treffer zurück, oder null wenn keines getroffen wurde.
*/
private Vector3f raycastSurface(com.jme3.math.Ray ray) {
SurfaceHit h = raycastSurfaceWithType(ray);
return h != null ? h.point : null;
}
private SurfaceHit raycastSurfaceWithType(com.jme3.math.Ray ray) {
CollisionResults hits = new CollisionResults();
terrain.collideWith(ray, hits);
Vector3f best = hits.size() > 0 ? hits.getClosestCollision().getContactPoint() : null;
float bestDistSq = best != null ? ray.getOrigin().distanceSquared(best) : Float.MAX_VALUE;
boolean bestIsVoxelMesh = false;
VoxelEditorState ves = getStateManager().getState(VoxelEditorState.class);
if (ves != null) {
Vector3f vp = ves.raycastVoxelGeometry(ray);
if (vp != null) {
float d = ray.getOrigin().distanceSquared(vp);
if (d < bestDistSq) { best = vp; bestDistSq = d; }
if (d < bestDistSq) { best = vp; bestDistSq = d; bestIsVoxelMesh = false; }
}
}
@@ -1218,10 +1458,10 @@ public class TerrainEditorState extends BaseAppState {
Vector3f sp = smes.raycastGeometry(ray);
if (sp != null) {
float d = ray.getOrigin().distanceSquared(sp);
if (d < bestDistSq) { best = sp; }
if (d < bestDistSq) { best = sp; bestIsVoxelMesh = true; }
}
}
return best;
return best != null ? new SurfaceHit(best, bestIsVoxelMesh) : null;
}
/** Gibt die Terrain-Höhe (Welt-Y) an der angegebenen Welt-XZ-Position zurück. */
@@ -1268,6 +1508,18 @@ public class TerrainEditorState extends BaseAppState {
final byte[] thG = thirdSplatG != null ? thirdSplatG.clone() : null;
final byte[] thB = thirdSplatB != null ? thirdSplatB.clone() : null;
final byte[] thA = thirdSplatA != null ? thirdSplatA.clone() : null;
final byte[] vxR = voxelSplatR != null ? voxelSplatR.clone() : null;
final byte[] vxG = voxelSplatG != null ? voxelSplatG.clone() : null;
final byte[] vxB = voxelSplatB != null ? voxelSplatB.clone() : null;
final byte[] vxA = voxelSplatA != null ? voxelSplatA.clone() : null;
final byte[] vxUR = voxelUpperSplatR != null ? voxelUpperSplatR.clone() : null;
final byte[] vxUG = voxelUpperSplatG != null ? voxelUpperSplatG.clone() : null;
final byte[] vxUB = voxelUpperSplatB != null ? voxelUpperSplatB.clone() : null;
final byte[] vxUA = voxelUpperSplatA != null ? voxelUpperSplatA.clone() : null;
final byte[] vxTR = voxelThirdSplatR != null ? voxelThirdSplatR.clone() : null;
final byte[] vxTG = voxelThirdSplatG != null ? voxelThirdSplatG.clone() : null;
final byte[] vxTB = voxelThirdSplatB != null ? voxelThirdSplatB.clone() : null;
final byte[] vxTA = voxelThirdSplatA != null ? voxelThirdSplatA.clone() : null;
final String[] texPaths = input.terrainTexturePaths.clone();
final String[] normalPaths = input.terrainNormalMapPaths.clone();
final String[] upperPaths = input.upperTexturePaths.clone();
@@ -1345,6 +1597,20 @@ public class TerrainEditorState extends BaseAppState {
System.arraycopy(thirdPaths, 0, data.thirdTextures, 0, MapData.TEXTURE_SLOTS);
System.arraycopy(thirdNormPaths, 0, data.thirdNormalMaps, 0, MapData.TEXTURE_SLOTS);
}
if (vxR != null) {
System.arraycopy(vxR, 0, data.voxelSplatR, 0, data.voxelSplatR.length);
System.arraycopy(vxG, 0, data.voxelSplatG, 0, data.voxelSplatG.length);
System.arraycopy(vxB, 0, data.voxelSplatB, 0, data.voxelSplatB.length);
System.arraycopy(vxA, 0, data.voxelSplatA, 0, data.voxelSplatA.length);
System.arraycopy(vxUR, 0, data.voxelUpperSplatR, 0, data.voxelUpperSplatR.length);
System.arraycopy(vxUG, 0, data.voxelUpperSplatG, 0, data.voxelUpperSplatG.length);
System.arraycopy(vxUB, 0, data.voxelUpperSplatB, 0, data.voxelUpperSplatB.length);
System.arraycopy(vxUA, 0, data.voxelUpperSplatA, 0, data.voxelUpperSplatA.length);
System.arraycopy(vxTR, 0, data.voxelThirdSplatR, 0, data.voxelThirdSplatR.length);
System.arraycopy(vxTG, 0, data.voxelThirdSplatG, 0, data.voxelThirdSplatG.length);
System.arraycopy(vxTB, 0, data.voxelThirdSplatB, 0, data.voxelThirdSplatB.length);
System.arraycopy(vxTA, 0, data.voxelThirdSplatA, 0, data.voxelThirdSplatA.length);
}
data.normalMapOrder = input.normalMapOrder.clone();
data.diffuseScales = diffScaleSnap.clone();
data.voxelFlatSlot = voxelFlatSlot;

View File

@@ -12,7 +12,7 @@ public class KeyBindings {
public int jump = KeyInput.KEY_SPACE;
public int sprint = KeyInput.KEY_LSHIFT;
public int walk = KeyInput.KEY_LMENU;
public int interact = KeyInput.KEY_E;
public int interact = KeyInput.KEY_F;
public int turnLeft = KeyInput.KEY_Q;
public int turnRight = KeyInput.KEY_E;
public int inventory = KeyInput.KEY_I;

View File

@@ -64,8 +64,6 @@ public class PlayerInputControl {
// Bone-Namen nach Phase-0-Bone-Log eintragen:
private static final String BONE_LEFT_FOOT = "mixamorig:LeftFoot";
private static final String BONE_RIGHT_FOOT = "mixamorig:RightFoot";
// Y-Schwelle im Model-Space: Fuß gilt als am Boden wenn Y < FOOT_GROUND_Y
private static final float FOOT_GROUND_Y = 0.05f;
/** Wassertiefe (m) ab der Laufen/Sprinten gesperrt ist und Watgeräusch spielt. */
public static final float WATER_WADING_DEPTH = 0.5f;
@@ -75,16 +73,23 @@ public class PlayerInputControl {
private com.jme3.audio.AudioNode wadingNode;
/** Aktuell berechnete Wassertiefe einmal pro Update() gesetzt, in pollFootsteps() gelesen. */
private float currentWaterDepth = 0f;
private float lastLeftFootY = Float.MAX_VALUE;
private float lastRightFootY = Float.MAX_VALUE;
// Velocity-based footstep detection: fires at the local Y-minimum of each foot (no fixed threshold).
private float lastLeftFootY = Float.MAX_VALUE;
private float lastRightFootY = Float.MAX_VALUE;
private boolean leftFootDescending = false;
private boolean rightFootDescending = false;
private float stepCooldown = 0f;
private static final float MIN_STEP_INTERVAL = 0.15f;
// ── Anim-Offsets ─────────────────────────────────────────────────────────
private Map<String, AnimOffset> animOffsets = new LinkedHashMap<>();
/** Basis-Local-Translation des Visual-Nodes; wird beim Laden des AnimSet einmalig gespeichert. */
private Vector3f visualBaseTranslation = new Vector3f();
private final Vector3f animOffsetCurrent = new Vector3f();
private final Vector3f animOffsetFrom = new Vector3f();
private final Vector3f animOffsetTarget = new Vector3f();
private float animOffsetSpeed = 0f;
// Zeit-basierter Lerp über AnimPlayer.BLEND_DURATION exakt wie im AnimPreviewState (Editor).
private float animOffsetElapsed = Float.MAX_VALUE; // MAX_VALUE = stabil / kein Lerp aktiv
// ── Navigation ────────────────────────────────────────────────────────────
private CharacterNavigator navigator = null;
@@ -422,6 +427,7 @@ public class PlayerInputControl {
if (physicsChar != null) physicsChar.setWalkDirection(Vector3f.ZERO);
autopilotDir = null;
clearAnimOffset();
if (animPlayer != null) animPlayer.stop();
if (arriveRadius > 0f) {
navigator.navigateTo(target, speed, arriveRadius, onArrival, onFailed);
} else {
@@ -466,16 +472,11 @@ public class PlayerInputControl {
runningClip = animPlayer.getCurrentClip();
}
if (visual != null && animOffsetSpeed > 0f) {
Vector3f delta = animOffsetTarget.subtract(animOffsetCurrent);
float dist = delta.length();
float step = animOffsetSpeed * tpf;
if (dist <= step) {
animOffsetCurrent.set(animOffsetTarget);
animOffsetSpeed = 0f;
} else {
animOffsetCurrent.addLocal(delta.normalizeLocal().multLocal(step));
}
if (visual != null && animOffsetElapsed < AnimPlayer.BLEND_DURATION) {
animOffsetElapsed += tpf;
float t = Math.min(1f, animOffsetElapsed / AnimPlayer.BLEND_DURATION);
animOffsetCurrent.set(animOffsetFrom);
animOffsetCurrent.interpolateLocal(animOffsetTarget, t);
visual.setLocalTranslation(visualBaseTranslation.clone().addLocal(animOffsetCurrent));
}
@@ -626,7 +627,7 @@ public class PlayerInputControl {
currentAnim = target;
}
pollFootsteps();
pollFootsteps(tpf);
}
private void playAction(AnimationAction action) {
@@ -670,26 +671,31 @@ public class PlayerInputControl {
log.info("{}", sb);
}
private void pollFootsteps() {
private void resetFootstepState() {
lastLeftFootY = Float.MAX_VALUE;
lastRightFootY = Float.MAX_VALUE;
leftFootDescending = false;
rightFootDescending = false;
stepCooldown = 0f;
}
private void pollFootsteps(float tpf) {
if (!physicsChar.onGround() || blockingAnimActive || lockedInPlace) {
stopWading();
lastLeftFootY = Float.MAX_VALUE;
lastRightFootY = Float.MAX_VALUE;
resetFootstepState();
return;
}
float speed = physicsChar.getWalkDirection().length();
if (speed < 0.001f) {
stopWading();
lastLeftFootY = Float.MAX_VALUE;
lastRightFootY = Float.MAX_VALUE;
resetFootstepState();
return;
}
if (currentWaterDepth > WATER_WADING_DEPTH) {
// Im Wasser: Watgeräusch abspielen, Fußgeräusche unterdrücken
startWading();
lastLeftFootY = Float.MAX_VALUE;
lastRightFootY = Float.MAX_VALUE;
resetFootstepState();
return;
}
@@ -699,20 +705,29 @@ public class PlayerInputControl {
String gait = currentAnimToGait(currentAnim);
if (gait == null) return;
stepCooldown = Math.max(0f, stepCooldown - tpf);
com.jme3.anim.Joint lf = armature.getJoint(BONE_LEFT_FOOT);
com.jme3.anim.Joint rf = armature.getJoint(BONE_RIGHT_FOOT);
if (lf != null) {
float y = lf.getModelTransform().getTranslation().y;
if (y < FOOT_GROUND_Y && lastLeftFootY >= FOOT_GROUND_Y) {
// Descend→ascend transition = local Y-minimum = foot touches ground
boolean desc = (lastLeftFootY - y) > 0.001f;
if (!desc && leftFootDescending && stepCooldown <= 0f) {
triggerStep(gait);
stepCooldown = MIN_STEP_INTERVAL;
}
leftFootDescending = desc;
lastLeftFootY = y;
}
if (rf != null) {
float y = rf.getModelTransform().getTranslation().y;
if (y < FOOT_GROUND_Y && lastRightFootY >= FOOT_GROUND_Y) {
boolean desc = (lastRightFootY - y) > 0.001f;
if (!desc && rightFootDescending && stepCooldown <= 0f) {
triggerStep(gait);
stepCooldown = MIN_STEP_INTERVAL;
}
rightFootDescending = desc;
lastRightFootY = y;
}
}
@@ -770,9 +785,10 @@ public class PlayerInputControl {
if (off == null) { clearAnimOffset(); return; }
Quaternion facing = visual.getLocalRotation().clone();
Vector3f worldOffset = facing.mult(new Vector3f(off.tx, off.ty, off.tz));
animOffsetTarget.set(worldOffset);
if (animOffsetCurrent.distanceSquared(worldOffset) > 1e-4f) {
animOffsetSpeed = 5.0f;
animOffsetFrom.set(animOffsetCurrent);
animOffsetTarget.set(worldOffset);
animOffsetElapsed = 0f;
}
log.debug("[AnimOffset] Clip '{}' → Ziel ({},{},{})", clip, worldOffset.x, worldOffset.y, worldOffset.z);
}
@@ -790,9 +806,10 @@ public class PlayerInputControl {
if (off == null) { clearAnimOffsetInstant(); return; }
Quaternion facing = visual.getLocalRotation().clone();
Vector3f worldOffset = facing.mult(new Vector3f(off.tx, off.ty, off.tz));
animOffsetFrom.set(worldOffset);
animOffsetTarget.set(worldOffset);
animOffsetCurrent.set(worldOffset);
animOffsetSpeed = 0f;
animOffsetElapsed = Float.MAX_VALUE;
visual.setLocalTranslation(visualBaseTranslation.clone().addLocal(animOffsetCurrent));
log.info("[AnimOffset] Sofort: action={} clip='{}' → ({},{},{})", action, clip,
worldOffset.x, worldOffset.y, worldOffset.z);
@@ -800,8 +817,11 @@ public class PlayerInputControl {
public void clearAnimOffset() {
log.info("[AnimOffset] clearAnimOffset() → Lerp zu 0, current=({},{},{})", animOffsetCurrent.x, animOffsetCurrent.y, animOffsetCurrent.z);
animOffsetTarget.set(0, 0, 0);
animOffsetSpeed = 5.0f;
if (animOffsetCurrent.lengthSquared() > 1e-4f) {
animOffsetFrom.set(animOffsetCurrent);
animOffsetTarget.set(0, 0, 0);
animOffsetElapsed = 0f;
}
}
/**
@@ -812,18 +832,20 @@ public class PlayerInputControl {
if (visual == null) return;
Quaternion facing = visual.getLocalRotation().clone();
Vector3f worldOffset = facing.mult(new Vector3f(tx, ty, tz));
animOffsetFrom.set(worldOffset);
animOffsetTarget.set(worldOffset);
animOffsetCurrent.set(worldOffset);
animOffsetSpeed = 0f;
animOffsetElapsed = Float.MAX_VALUE;
visual.setLocalTranslation(visualBaseTranslation.clone().addLocal(animOffsetCurrent));
log.info("[AnimOffset] Instant: ({},{},{}) → world ({},{},{})", tx, ty, tz, worldOffset.x, worldOffset.y, worldOffset.z);
}
/** Setzt den Visual-Versatz sofort auf 0 zurück (kein Lerp). */
public void clearAnimOffsetInstant() {
animOffsetFrom.set(0, 0, 0);
animOffsetTarget.set(0, 0, 0);
animOffsetCurrent.set(0, 0, 0);
animOffsetSpeed = 0f;
animOffsetElapsed = Float.MAX_VALUE;
if (visual != null) {
visual.setLocalTranslation(visualBaseTranslation.clone());
}

View File

@@ -238,19 +238,22 @@ public class SculptedMeshState extends BaseAppState {
private Material buildMaterial() {
Material mat = new Material(app.getAssetManager(), "MatDefs/BakedTerrain.j3md");
// DiffuseArray + DiffuseScales + AlphaMaps vom Terrain-Material übernehmen,
// damit die gemalten Splatmap-Texturen auch ingame sichtbar sind.
// DiffuseArray + DiffuseScales vom Terrain-Material übernehmen (selbe Textur-Slots).
// AlphaMaps kommen aus den Voxel-spezifischen Splatmap-Arrays in MapData.
if (terrainMaterial != null) {
com.jme3.material.MatParam diffArr = terrainMaterial.getParam("DiffuseArray");
if (diffArr != null) mat.setParam("DiffuseArray", diffArr.getVarType(), diffArr.getValue());
com.jme3.material.MatParam scales = terrainMaterial.getParam("DiffuseScales");
if (scales != null) mat.setParam("DiffuseScales", scales.getVarType(), scales.getValue());
for (String key : new String[]{"AlphaMap", "AlphaMap_1", "AlphaMap_2"}) {
com.jme3.material.MatParam p = terrainMaterial.getParam(key);
if (p != null) mat.setTexture(key, (Texture) p.getValue());
}
}
if (mapData != null) {
com.jme3.texture.Texture2D a0 = buildVoxelAlphaMap(mapData.voxelSplatR, mapData.voxelSplatG, mapData.voxelSplatB, mapData.voxelSplatA);
com.jme3.texture.Texture2D a1 = buildVoxelAlphaMap(mapData.voxelUpperSplatR, mapData.voxelUpperSplatG, mapData.voxelUpperSplatB, mapData.voxelUpperSplatA);
com.jme3.texture.Texture2D a2 = buildVoxelAlphaMap(mapData.voxelThirdSplatR, mapData.voxelThirdSplatG, mapData.voxelThirdSplatB, mapData.voxelThirdSplatA);
mat.setTexture("AlphaMap", a0);
mat.setTexture("AlphaMap_1", a1);
mat.setTexture("AlphaMap_2", a2);
}
// Klippen-Textur: steiler Slot aus MapData für Flat/Steep-Blending
@@ -261,4 +264,21 @@ public class SculptedMeshState extends BaseAppState {
return mat;
}
private com.jme3.texture.Texture2D buildVoxelAlphaMap(byte[] r, byte[] g, byte[] b, byte[] a) {
int size = MapData.SPLAT_SIZE;
java.nio.ByteBuffer buf = com.jme3.util.BufferUtils.createByteBuffer(size * size * 4);
for (int i = 0; i < size * size; i++) {
buf.put(r[i]).put(g[i]).put(b[i]).put(a[i]);
}
buf.flip();
com.jme3.texture.Image img = new com.jme3.texture.Image(
com.jme3.texture.Image.Format.RGBA8, size, size, buf,
com.jme3.texture.image.ColorSpace.Linear);
com.jme3.texture.Texture2D tex = new com.jme3.texture.Texture2D(img);
tex.setWrap(Texture.WrapMode.EdgeClamp);
tex.setMinFilter(Texture.MinFilter.BilinearNoMipMaps);
tex.setMagFilter(Texture.MagFilter.Bilinear);
return tex;
}
}

View File

@@ -148,7 +148,9 @@ public class WorldInteractableState extends BaseAppState {
@Override
protected void onEnable() {
inputManager.addMapping(INTERACT_ACTION, new KeyTrigger(keyBindings.interact));
inputManager.addMapping(INTERACT_ACTION,
new MouseButtonTrigger(MouseInput.BUTTON_LEFT),
new KeyTrigger(keyBindings.interact));
inputManager.addMapping(GET_UP_ACTION, new MouseButtonTrigger(MouseInput.BUTTON_RIGHT));
inputManager.addListener(interactListener, INTERACT_ACTION);
inputManager.addListener(getUpListener, GET_UP_ACTION);

View File

@@ -1,9 +0,0 @@
{
"id": "19f0ce17-838d-4938-8739-fc17de491657",
"benchType": "Simple",
"sitzX": 237.17992,
"sitzY": 6.99606,
"sitzZ": -891.853,
"sitzRotY": -0.8307737,
"sitzSet": true
}

View File

@@ -0,0 +1,9 @@
{
"id": "9ac9943d-0e12-4d5c-8323-0e2b92eebdec",
"benchType": "Simple",
"sitzX": 236.63928,
"sitzY": 6.9953,
"sitzZ": -888.1745,
"sitzRotY": 4.7967663,
"sitzSet": true
}

View File

@@ -2,10 +2,10 @@
Models/northcoast/wrack1.j3o 286.06750 -3.50554 -947.28595 -1.76657 1.00000 -0.00000 -0.50184 false true true 30.00000 80.00000 120.00000
Models/trees/palm/palm_20260816_213338.j3o 277.36694 2.74803 -956.39905 0.43292 1.00000 0.00000 0.00000 false true true 30.00000 80.00000 120.00000
Models/trees/palm/palm_20260816_213338.j3o 280.27863 2.66144 -928.60248 -1.51992 1.00000 -0.00000 0.00000 false true true 30.00000 80.00000 120.00000
Models/imported/bank1.j3o 237.17992 6.49606 -891.85303 -2.40157 1.00000 -0.00000 0.00000 true true true 30.00000 80.00000 120.00000 BENCH 19f0ce17-838d-4938-8739-fc17de491657
Models/trees/palm/palm_20260816_213341.j3o 249.12691 6.49606 -895.80035 -0.96942 1.00000 -0.00000 0.00000 false true true 30.00000 80.00000 120.00000
Models/trees/palm/palm_20260816_213341.j3o 243.51527 6.49606 -894.84039 -2.48591 1.00000 -0.00000 0.00000 false true true 30.00000 80.00000 120.00000
Models/trees/palm/palm_20260816_213338.j3o 233.49467 4.51015 -892.32526 2.66339 1.00000 0.00000 0.00000 false true true 30.00000 80.00000 120.00000
Models/trees/palm/palm_20260816_213341.j3o 247.05431 6.44288 -888.47949 1.23606 1.00000 0.00000 0.00000 false true true 30.00000 80.00000 120.00000
Models/trees/palm/palm_20260816_213341.j3o 239.43253 6.49157 -886.23218 -2.49448 1.00000 -0.00000 0.00000 false true true 30.00000 80.00000 120.00000
Models/trees/palm/palm_20260816_213341.j3o 270.73062 3.06689 -913.67090 -1.45828 1.00000 -0.00000 0.00000 false true true 30.00000 80.00000 120.00000
Models/imported/bank1.j3o 236.63928 6.49530 -888.17450 -3.22597 1.00000 0.00000 0.00000 true true true 30.00000 80.00000 120.00000 BENCH 9ac9943d-0e12-4d5c-8323-0e2b92eebdec