Compare commits

...

2 Commits

Author SHA1 Message Date
528b2ea576 Probleme mit der Camera und Voxel Terrain behoben 2026-08-21 15:56:41 +02:00
08e1df06d4 Gras (Textur) Pinsel Bugs behoben 2026-08-21 15:47:31 +02:00
5 changed files with 96 additions and 11 deletions

View File

@@ -147,6 +147,33 @@ public class PlacedObjectState extends BaseAppState {
grassNode = new Node("grassNode");
((SimpleApplication) app).getRootNode().attachChild(grassNode);
applyAllSlotMaterials();
initAspectRatios();
}
private void initAspectRatios() {
String[] allPaths = new String[8];
allPaths[0] = input.grassTexturePath != null ? input.grassTexturePath : "";
String[] slots = input.grassTextureSlots;
for (int i = 0; i < 7; i++) {
allPaths[i + 1] = (i < slots.length && slots[i] != null) ? slots[i] : "";
}
float[] ars = input.grassAspectRatios.clone();
for (int s = 0; s < 8; s++) {
String p = allPaths[s];
if (p == null || p.isEmpty()) continue;
try {
com.jme3.texture.Texture tex = assetManager.loadTexture(
new com.jme3.asset.TextureKey(p, false));
if (tex != null && tex.getImage() != null) {
int w = tex.getImage().getWidth();
int h = tex.getImage().getHeight();
if (w > 0 && h > 0) ars[s] = (float) w / h;
}
} catch (Exception e) {
log.debug("[PlacedObjectState] Aspect-Ratio nicht lesbar für Slot {}: {}", s, e.getMessage());
}
}
input.grassAspectRatios = ars;
}
@Override

View File

@@ -26,12 +26,15 @@ public class ThirdPersonCamera {
private static final float MIN_VERTICAL_ANGLE = -0.12f; // Kamera kann bis Kniehöhe sinken
private static final float MAX_VERTICAL_ANGLE = FastMath.HALF_PI - 0.1f;
private static final float TARGET_HEIGHT = 1.0f;
private static final float CAM_CLEARANCE = 0.3f; // Rückzugsabstand bei Terrain-Kollision
private final Camera cam;
private final InputManager inputManager;
private final KeyBindings keyBindings;
private Spatial target;
/** Liefert den Trefferanteil (0..1) eines Strahls von from→to; 1 = kein Treffer. */
private java.util.function.BiFunction<com.jme3.math.Vector3f, com.jme3.math.Vector3f, Float> raycastProvider;
private float yaw = 0f;
private float pitch = 0.4f;
@@ -59,6 +62,11 @@ public class ThirdPersonCamera {
public void setPaused(boolean paused) { this.paused = paused; }
/** Raycast-Anbieter: gibt Trefferanteil (0..1) zurück; 1 = freie Sichtlinie. */
public void setRaycastProvider(java.util.function.BiFunction<com.jme3.math.Vector3f, com.jme3.math.Vector3f, Float> provider) {
this.raycastProvider = provider;
}
// -----------------------------------------------------------------------
private void registerMappings() {
@@ -111,6 +119,18 @@ public class ThirdPersonCamera {
float z = distance * FastMath.cos(pitch) * FastMath.cos(yaw);
Vector3f camPos = pivot.add(x, y, z);
if (raycastProvider != null) {
float fraction = raycastProvider.apply(pivot, camPos);
if (fraction < 1f) {
Vector3f dir = camPos.subtract(pivot);
float rayLen = dir.length();
dir.normalizeLocal();
float safeLen = Math.max(MIN_DISTANCE * 0.5f, fraction * rayLen - CAM_CLEARANCE);
camPos = pivot.add(dir.mult(safeLen));
}
}
cam.setLocation(camPos);
cam.lookAt(pivot, Vector3f.UNIT_Y);
}

View File

@@ -318,6 +318,19 @@ public class WorldScene extends BaseAppState {
thirdPersonCam = new ThirdPersonCamera(app.getCamera(), app.getInputManager(), keyBindings);
thirdPersonCam.setTarget(character);
thirdPersonCam.setRaycastProvider((from, to) -> {
com.jme3.bullet.PhysicsSpace ps =
bulletAppState != null ? bulletAppState.getPhysicsSpace() : null;
if (ps == null) return 1f;
java.util.List<com.jme3.bullet.collision.PhysicsRayTestResult> hits = ps.rayTest(from, to);
float best = 1f;
for (com.jme3.bullet.collision.PhysicsRayTestResult hit : hits) {
Object uo = hit.getCollisionObject().getUserObject();
if (uo == physicsChar || uo == character) continue;
if (hit.getHitFraction() < best) best = hit.getHitFraction();
}
return best;
});
MainCharacter mc = findMainCharacter();
if (mc != null) {

View File

@@ -36,18 +36,18 @@ public class GrassState extends BaseAppState
private static final Logger log = LoggerFactory.getLogger(GrassState.class);
private static final int TERRAIN_HALF = 2048;
private static final int CHUNK_SIZE = 128;
private static final int CHUNKS_PER_AXIS = (TERRAIN_HALF * 2) / CHUNK_SIZE; // 32
private static final int CHUNK_COUNT = CHUNKS_PER_AXIS * CHUNKS_PER_AXIS; // 1024
private static final int CHUNK_SIZE = de.blight.common.ChunkTerrainIO.CHUNK_SIZE; // 128
private static final int TERRAIN_HALF = de.blight.common.ChunkTerrainIO.WORLD_SIZE / 2; // 1024
private static final int CHUNKS_PER_AXIS = de.blight.common.ChunkTerrainIO.CHUNKS_PER_AXIS; // 16
private static final int CHUNK_COUNT = de.blight.common.ChunkTerrainIO.CHUNK_COUNT; // 256
private static final int BLADES_PER_TUFT = 4;
private static final float TUFT_SPREAD = 0.5f;
private static final float BLADE_WIDTH = 0.18f;
private static final int INIT_PER_FRAME = 4;
private final TerrainChunkState terrainChunkState;
private Node grassNode;
private float[] slotAspectRatios = new float[8];
@SuppressWarnings({"unchecked", "rawtypes"})
private final List<GrassTuft>[] chunkTufts = new List[CHUNK_COUNT];
@@ -69,8 +69,9 @@ public class GrassState extends BaseAppState
for (int i = 0; i < CHUNK_COUNT; i++) chunkTufts[i] = new ArrayList<>();
GrassTuftIO.GrassData data = null;
try {
GrassTuftIO.GrassData data = GrassTuftIO.load();
data = GrassTuftIO.load();
if (data != null) {
initSlotMaterials(app.getAssetManager(), data.slotPaths());
for (GrassTuft t : data.tufts()) {
@@ -85,6 +86,28 @@ public class GrassState extends BaseAppState
if (slotMaterials.isEmpty()) {
slotMaterials.put(0, buildGrassMat(app.getAssetManager(), ""));
}
initAspectRatios(app.getAssetManager(),
data != null ? data.slotPaths() : null);
}
private void initAspectRatios(AssetManager assets, String[] slotPaths) {
java.util.Arrays.fill(slotAspectRatios, 1f);
if (slotPaths == null) return;
for (int s = 0; s < Math.min(slotAspectRatios.length, slotPaths.length); s++) {
String p = slotPaths[s];
if (p == null || p.isEmpty()) continue;
try {
com.jme3.texture.Texture tex = assets.loadTexture(
new com.jme3.asset.TextureKey(p, false));
if (tex != null && tex.getImage() != null) {
int w = tex.getImage().getWidth();
int h = tex.getImage().getHeight();
if (w > 0 && h > 0) slotAspectRatios[s] = (float) w / h;
}
} catch (Exception e) {
log.debug("[GrassState] Aspect-Ratio nicht lesbar Slot {}: {}", s, e.getMessage());
}
}
}
@Override
@@ -228,8 +251,10 @@ public class GrassState extends BaseAppState
if (entry.getValue().isEmpty()) continue;
Material mat = getSlotMaterial(entry.getKey());
if (mat == null) continue;
Geometry geo = new Geometry("grass_" + idx + "_s" + entry.getKey(),
buildGrassMesh(entry.getValue()));
int slot = entry.getKey();
float ar = (slot < slotAspectRatios.length) ? slotAspectRatios[slot] : 1f;
Geometry geo = new Geometry("grass_" + idx + "_s" + slot,
buildGrassMesh(entry.getValue(), ar));
geo.setMaterial(mat);
node.attachChild(geo);
}
@@ -288,7 +313,7 @@ public class GrassState extends BaseAppState
// ── Mesh: Kreuz-Quad mit UV ───────────────────────────────────────────────
private static Mesh buildGrassMesh(List<float[]> blades) {
private static Mesh buildGrassMesh(List<float[]> blades, float ar) {
int n = blades.size();
FloatBuffer pos = BufferUtils.createFloatBuffer(n * 8 * 3);
FloatBuffer uv = BufferUtils.createFloatBuffer(n * 8 * 2);
@@ -298,7 +323,7 @@ public class GrassState extends BaseAppState
int vi = 0;
for (float[] blade : blades) {
float x = blade[0], y = blade[1], z = blade[2], h = blade[3];
float w = Math.max(0.05f, h * BLADE_WIDTH);
float w = Math.max(0.05f, h * ar * 0.5f);
pos.put(x-w).put(y ).put(z); uv.put(0).put(0); nrm.put(0).put(1).put(0);
pos.put(x+w).put(y ).put(z); uv.put(1).put(0); nrm.put(0).put(1).put(0);