Gras System so angepasst, dass es auf dem gebackenen Voxel kram funktioniert
This commit is contained in:
@@ -218,6 +218,8 @@ public class GrassVertexState extends BaseAppState {
|
||||
float jmeY = (float) (edit.screenY() * input.viewportScaleY);
|
||||
Vector3f hit = raycastSurface(jmeX, jmeY, getApplication().getCamera());
|
||||
if (hit == null) continue;
|
||||
VoxelEditorState ves = getStateManager().getState(VoxelEditorState.class);
|
||||
if (ves != null && ves.terrainTypeAt(hit.x, hit.z) == VoxelEditorState.TerrainType.VOXEL_UNBAKED) continue;
|
||||
if (edit.action() > 0) addBlades(hit);
|
||||
else removeBlades(hit);
|
||||
}
|
||||
@@ -294,14 +296,22 @@ public class GrassVertexState extends BaseAppState {
|
||||
if (changed) dirtyChunks[ci] = true;
|
||||
}
|
||||
|
||||
VoxelEditorState ves = getStateManager().getState(VoxelEditorState.class);
|
||||
|
||||
// Neue Halme mit Falloff und Gleichmäßigkeits-Variation setzen
|
||||
for (int i = 0; i < density; i++) {
|
||||
float angle = rng.nextFloat() * (float) (Math.PI * 2);
|
||||
float r = rng.nextFloat() * radius;
|
||||
float bx = center.x + (float) Math.cos(angle) * r;
|
||||
float bz = center.z + (float) Math.sin(angle) * r;
|
||||
float by = terrain.getHeight(new Vector2f(bx, bz));
|
||||
if (Float.isNaN(by)) continue;
|
||||
float by;
|
||||
if (ves != null) {
|
||||
by = ves.heightAt(bx, bz);
|
||||
if (ves.isTooSteep(bx, bz)) continue;
|
||||
} else {
|
||||
by = terrain.getHeight(new Vector2f(bx, bz));
|
||||
if (Float.isNaN(by)) continue;
|
||||
}
|
||||
float distRatio = r / radius;
|
||||
float h = height * brushFalloff(distRatio)
|
||||
* (1f + variation * (rng.nextFloat() * 2f - 1f));
|
||||
|
||||
@@ -255,6 +255,8 @@ public class PlacedObjectState extends BaseAppState {
|
||||
Ray ray = new Ray(near, far.subtract(near).normalizeLocal());
|
||||
Vector3f contact = raycastSurface(ray);
|
||||
if (contact == null) continue;
|
||||
VoxelEditorState ves = getStateManager().getState(VoxelEditorState.class);
|
||||
if (ves != null && ves.terrainTypeAt(contact.x, contact.z) == VoxelEditorState.TerrainType.VOXEL_UNBAKED) continue;
|
||||
float radius = (float) input.grassTool.brushRadius.getValue();
|
||||
if (edit.action() > 0) paintGrass(contact.x, contact.z, radius);
|
||||
else eraseGrass(contact.x, contact.z, radius);
|
||||
@@ -292,6 +294,7 @@ public class PlacedObjectState extends BaseAppState {
|
||||
float baseH = (float) input.grassTool.grassHeight.getValue();
|
||||
int slot = input.grassActiveSlot;
|
||||
Random rng = new Random();
|
||||
VoxelEditorState ves = getStateManager().getState(VoxelEditorState.class);
|
||||
for (int i = 0; i < n; i++) {
|
||||
float angle = rng.nextFloat() * FastMath.TWO_PI;
|
||||
float dist = FastMath.sqrt(rng.nextFloat()) * radius;
|
||||
@@ -299,6 +302,7 @@ public class PlacedObjectState extends BaseAppState {
|
||||
float bz = cz + dist * FastMath.sin(angle);
|
||||
if (bx < -TERRAIN_HALF || bx > TERRAIN_HALF
|
||||
|| bz < -TERRAIN_HALF || bz > TERRAIN_HALF) continue;
|
||||
if (ves != null && ves.isTooSteep(bx, bz)) continue;
|
||||
float h = baseH * (0.7f + rng.nextFloat() * 0.6f);
|
||||
int ci = chunkIndex(bx, bz);
|
||||
if (ci >= 0) {
|
||||
@@ -356,6 +360,8 @@ public class PlacedObjectState extends BaseAppState {
|
||||
}
|
||||
if (chunkTufts[idx].isEmpty()) return;
|
||||
|
||||
VoxelEditorState ves = getStateManager().getState(VoxelEditorState.class);
|
||||
|
||||
Map<Integer, List<float[]>> bySlot = new LinkedHashMap<>();
|
||||
for (GrassTuft t : chunkTufts[idx]) {
|
||||
long seed = (long) Float.floatToRawIntBits(t.x()) * 0x9E3779B9L
|
||||
@@ -367,8 +373,13 @@ public class PlacedObjectState extends BaseAppState {
|
||||
float oz = (rng.nextFloat() - 0.5f) * TUFT_SPREAD * 2f;
|
||||
float bx = t.x() + ox;
|
||||
float bz = t.z() + oz;
|
||||
float th = terrain.getHeight(new Vector2f(bx, bz));
|
||||
if (Float.isNaN(th)) continue;
|
||||
float th;
|
||||
if (ves != null) {
|
||||
th = ves.heightAt(bx, bz);
|
||||
} else {
|
||||
th = terrain.getHeight(new Vector2f(bx, bz));
|
||||
if (Float.isNaN(th)) continue;
|
||||
}
|
||||
float bh = t.height() * (0.7f + rng.nextFloat() * 0.6f);
|
||||
blades.add(new float[]{bx, th, bz, bh});
|
||||
}
|
||||
|
||||
@@ -1392,11 +1392,19 @@ public class TerrainEditorState extends BaseAppState {
|
||||
contactPoint = raycastSurface(ray);
|
||||
if (contactPoint != null) {
|
||||
brushRadius = (float) input.grassTool.brushRadius.getValue();
|
||||
VoxelEditorState ves = getStateManager().getState(VoxelEditorState.class);
|
||||
if (ves != null && ves.terrainTypeAt(contactPoint.x, contactPoint.z) == VoxelEditorState.TerrainType.VOXEL_UNBAKED) {
|
||||
contactPoint = null;
|
||||
}
|
||||
}
|
||||
} else if (layer == SharedInput.LAYER_GRASS_VERTEX) {
|
||||
contactPoint = raycastSurface(ray);
|
||||
if (contactPoint != null) {
|
||||
brushRadius = (float) input.grassVertexTool.brushRadius.getValue();
|
||||
VoxelEditorState ves = getStateManager().getState(VoxelEditorState.class);
|
||||
if (ves != null && ves.terrainTypeAt(contactPoint.x, contactPoint.z) == VoxelEditorState.TerrainType.VOXEL_UNBAKED) {
|
||||
contactPoint = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1154,6 +1154,87 @@ public class VoxelEditorState extends BaseAppState {
|
||||
return 0f;
|
||||
}
|
||||
|
||||
// ── Zentrale Terrain-Abfragen ─────────────────────────────────────────────
|
||||
|
||||
/** Terrain-Typ an einer Weltposition. */
|
||||
public enum TerrainType { BASE, VOXEL_BAKED, VOXEL_UNBAKED }
|
||||
|
||||
/**
|
||||
* Liefert den Terrain-Typ an einer Weltposition.
|
||||
* BASE = nur Basis-Terrain; VOXEL_BAKED = mind. ein gebackener Chunk in der Spalte;
|
||||
* VOXEL_UNBAKED = Voxel-Daten vorhanden, aber kein Bake-Ergebnis auf der Festplatte.
|
||||
*/
|
||||
public TerrainType terrainTypeAt(float worldX, float worldZ) {
|
||||
int cx = VoxelChunk.worldXToCx(worldX);
|
||||
int cz = VoxelChunk.worldZToCz(worldZ);
|
||||
boolean hasVoxel = false;
|
||||
boolean hasBaked = false;
|
||||
for (int cy = -2; cy <= 10; cy++) {
|
||||
VoxelChunk chunk = chunks.get(chunkKey(cx, cy, cz));
|
||||
if (chunk != null && !chunk.isEmpty()) {
|
||||
hasVoxel = true;
|
||||
if (VoxelChunkIO.bakedExists(cx, cy, cz)) {
|
||||
hasBaked = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!hasVoxel) return TerrainType.BASE;
|
||||
return hasBaked ? TerrainType.VOXEL_BAKED : TerrainType.VOXEL_UNBAKED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Nächster Oberflächentreffer für den gegebenen Ray (Terrain + Voxel-Geo + Baked Sculpt).
|
||||
* Zentrale Alternative zu den lokalen raycastSurface()-Methoden in den einzelnen States.
|
||||
*/
|
||||
public Vector3f clickAt(Ray ray) {
|
||||
Vector3f best = null;
|
||||
float bestDistSq = Float.MAX_VALUE;
|
||||
|
||||
if (terrainNode != null) {
|
||||
CollisionResults hits = new CollisionResults();
|
||||
terrainNode.collideWith(ray, hits);
|
||||
if (hits.size() > 0) {
|
||||
best = hits.getClosestCollision().getContactPoint();
|
||||
bestDistSq = ray.getOrigin().distanceSquared(best);
|
||||
}
|
||||
}
|
||||
|
||||
Vector3f vp = raycastVoxelGeometry(ray);
|
||||
if (vp != null) {
|
||||
float d = ray.getOrigin().distanceSquared(vp);
|
||||
if (d < bestDistSq) { best = vp; bestDistSq = d; }
|
||||
}
|
||||
|
||||
SculptedMeshEditorState smes = getStateManager().getState(SculptedMeshEditorState.class);
|
||||
if (smes != null) {
|
||||
Vector3f sp = smes.raycastGeometry(ray);
|
||||
if (sp != null) {
|
||||
float d = ray.getOrigin().distanceSquared(sp);
|
||||
if (d < bestDistSq) { best = sp; }
|
||||
}
|
||||
}
|
||||
|
||||
return best;
|
||||
}
|
||||
|
||||
/** true wenn das Gefälle an (worldX, worldZ) 45° überschreitet. */
|
||||
public boolean isTooSteep(float worldX, float worldZ) {
|
||||
// Gebackenes Mesh: Flächennormale direkt aus dem Mesh — genauer als Höhen-Sampling.
|
||||
SculptedMeshEditorState smes = getStateManager().getState(SculptedMeshEditorState.class);
|
||||
if (smes != null) {
|
||||
Ray r = new Ray(new Vector3f(worldX, 500f, worldZ), new Vector3f(0f, -1f, 0f));
|
||||
Vector3f[] hit = smes.raycastGeometryWithNormal(r);
|
||||
if (hit != null) {
|
||||
return hit[1].y < 0.7071f; // cos(45°)
|
||||
}
|
||||
}
|
||||
// Fallback für Basis-Terrain und rohes Voxel: Höhen-Sampling mit 1 WE Abstand.
|
||||
float h0 = heightAt(worldX, worldZ);
|
||||
float dhx = heightAt(worldX + 1f, worldZ) - h0;
|
||||
float dhz = heightAt(worldX, worldZ + 1f) - h0;
|
||||
return dhx * dhx + dhz * dhz > 1.0f; // tan²(45°) = 1
|
||||
}
|
||||
|
||||
/** Höchste bekannte Oberfläche an (worldX, worldZ): max(Terrain, Voxel, Baked). */
|
||||
public float heightAt(float worldX, float worldZ) {
|
||||
float h = terrainH(worldX, worldZ);
|
||||
|
||||
Reference in New Issue
Block a user