Weiter daran gearbeitet, die welt aus dem editor ins game zu bringen

This commit is contained in:
2026-05-23 09:07:44 +02:00
parent f9a77cc321
commit 728f506e97
36 changed files with 3728 additions and 77 deletions

View File

@@ -6,19 +6,24 @@ import com.jme3.app.state.BaseAppState;
import com.jme3.asset.AssetManager;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;
import com.jme3.bullet.collision.shapes.HeightfieldCollisionShape;
import com.jme3.bullet.control.CharacterControl;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.bullet.util.CollisionShapeFactory;
import com.jme3.light.*;
import com.jme3.material.Material;
import com.jme3.material.RenderState;
import com.jme3.math.*;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.*;
import com.jme3.scene.VertexBuffer;
import com.jme3.scene.shape.*;
import com.jme3.shadow.*;
import com.jme3.terrain.geomipmap.*;
import com.jme3.texture.*;
import com.jme3.util.BufferUtils;
import com.jme3.util.SkyFactory;
import java.nio.ByteBuffer;
import de.blight.common.MapData;
import de.blight.common.MapIO;
import de.blight.game.config.KeyBindings;
@@ -27,6 +32,8 @@ import de.blight.game.control.ThirdPersonCamera;
import de.blight.game.state.GrassState;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class WorldScene extends BaseAppState {
@@ -73,9 +80,9 @@ public class WorldScene extends BaseAppState {
protected void onEnable() {
buildLighting();
TerrainQuad terrain = buildTerrain();
buildDecorations(terrain);
if (loadedMapData != null) {
rootNode.attachChild(buildGebirge(loadedMapData));
app.getStateManager().attach(new GrassState(loadedMapData, terrain));
}
@@ -181,23 +188,31 @@ public class WorldScene extends BaseAppState {
}
}
// Höhe in der Weltmitte als Spawn-Grundlage
float centerHeight = heights[(GAME_VERTS / 2) * GAME_VERTS + (GAME_VERTS / 2)];
spawnY = centerHeight + 3f;
// Spawn über dem höchsten Punkt Basis-Terrain UND Gebirge-Oberkante
float minH = Float.MAX_VALUE, maxH = -Float.MAX_VALUE;
for (float h : heights) { if (h < minH) minH = h; if (h > maxH) maxH = h; }
float midH = (minH + maxH) * 0.5f;
float maxUpperTop = maxH;
for (float h : map.upperTop) { if (h > maxUpperTop) maxUpperTop = h; }
spawnY = maxUpperTop + 20f;
TerrainQuad terrain = new TerrainQuad("terrain", 65, GAME_VERTS, heights);
terrain.setLocalScale(8f, 1f, 8f); // 512 Zellen * 8 WE = 4096 WE pro Achse
terrain.setLocalScale(8f, 1f, 8f);
terrain.setShadowMode(RenderQueue.ShadowMode.Receive);
applyTerrainMaterial(terrain, map);
rootNode.attachChild(terrain);
applyTerrainMaterial(terrain, 32f);
RigidBodyControl terrainPhysics = new RigidBodyControl(
CollisionShapeFactory.createMeshShape(terrain), 0f);
// jBullet subtrahiert midH intern in getVertex() → Physics-Body bei midH
// damit Kollisionsfläche und sichtbares Terrain übereinstimmen.
HeightfieldCollisionShape hcs = new HeightfieldCollisionShape(
heights, terrain.getLocalScale());
RigidBodyControl terrainPhysics = new RigidBodyControl(hcs, 0f);
terrain.addControl(terrainPhysics);
bulletAppState.getPhysicsSpace().add(terrainPhysics);
terrainPhysics.setPhysicsLocation(new Vector3f(0f, midH, 0f));
rootNode.attachChild(terrain);
System.out.println("[WorldScene] Karte geladen, Spawn Y=" + spawnY);
System.out.println("[WorldScene] Karte geladen, Spawn Y=" + spawnY
+ " maxGebirgeH=" + maxUpperTop);
return terrain;
}
@@ -223,32 +238,234 @@ public class WorldScene extends BaseAppState {
terrain.setLocalScale(0.5f, 0.5f, 0.5f);
terrain.setShadowMode(RenderQueue.ShadowMode.Receive);
applyTerrainMaterial(terrain, 64f);
applyTerrainMaterial(terrain, null);
rootNode.attachChild(terrain);
RigidBodyControl terrainPhysics = new RigidBodyControl(
CollisionShapeFactory.createMeshShape(terrain), 0f);
terrain.addControl(terrainPhysics);
bulletAppState.getPhysicsSpace().add(terrainPhysics);
rootNode.attachChild(terrain);
return terrain;
}
private void applyTerrainMaterial(TerrainQuad terrain, float texScale) {
Material mat = new Material(assetManager, "Common/MatDefs/Terrain/Terrain.j3md");
// -----------------------------------------------------------------------
// Gebirge (obere Gesteinsschicht)
// -----------------------------------------------------------------------
private Node buildGebirge(MapData map) {
final int VERTS = 513;
final int CELLS = 512;
final float CELL = 8f;
final float ORIGIN = -2048f;
final int CHUNK = 16;
final int NCHUNK = CELLS / CHUNK;
Node node = new Node("gebirge");
Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
try {
Texture rock = assetManager.loadTexture("Textures/Terrain/Rock2/rock.jpg");
rock.setWrap(Texture.WrapMode.Repeat);
mat.setTexture("DiffuseMap", rock);
mat.setColor("Diffuse", new ColorRGBA(0.45f, 0.32f, 0.25f, 1f));
} catch (Exception e) {
mat.setBoolean("UseMaterialColors", true);
mat.setColor("Diffuse", new ColorRGBA(0.18f, 0.12f, 0.08f, 1f));
}
mat.setColor("Ambient", new ColorRGBA(0.10f, 0.07f, 0.05f, 1f));
mat.setColor("Specular", new ColorRGBA(0.06f, 0.05f, 0.04f, 1f));
mat.setFloat("Shininess", 6f);
mat.getAdditionalRenderState().setFaceCullMode(RenderState.FaceCullMode.Off);
mat.getAdditionalRenderState().setPolyOffset(2f, 2f);
for (int chunkZ = 0; chunkZ < NCHUNK; chunkZ++) {
for (int chunkX = 0; chunkX < NCHUNK; chunkX++) {
List<Float> pos = new ArrayList<>();
List<Float> norm = new ArrayList<>();
List<Float> uv = new ArrayList<>();
List<Integer> idx = new ArrayList<>();
int vtx = 0;
for (int lcz = 0; lcz < CHUNK; lcz++) {
int cz = chunkZ * CHUNK + lcz;
for (int lcx = 0; lcx < CHUNK; lcx++) {
int cx = chunkX * CHUNK + lcx;
if (map.upperHole[cz * CELLS + cx] != 0) continue;
float wx0 = ORIGIN + cx * CELL, wx1 = wx0 + CELL;
float wz0 = ORIGIN + cz * CELL, wz1 = wz0 + CELL;
float t00 = map.upperTop[cz*VERTS+cx], t10 = map.upperTop[cz*VERTS+cx+1];
float t01 = map.upperTop[(cz+1)*VERTS+cx], t11 = map.upperTop[(cz+1)*VERTS+cx+1];
float b00 = map.upperBottom[cz*VERTS+cx], b10 = map.upperBottom[cz*VERTS+cx+1];
float b01 = map.upperBottom[(cz+1)*VERTS+cx], b11 = map.upperBottom[(cz+1)*VERTS+cx+1];
// UV: eine Textur-Kachel pro Zelle (8 WE)
float u0 = cx, u1 = cx+1f, v0 = cz, v1 = cz+1f;
// Oben (XZ-Ebene)
vtx = quad(pos,norm,uv,idx,vtx,
wx0,t00,wz0, wx1,t10,wz0, wx1,t11,wz1, wx0,t01,wz1, 0,1,0,
u0,v0, u1,v0, u1,v1, u0,v1);
// Unten
vtx = quad(pos,norm,uv,idx,vtx,
wx0,b00,wz0, wx0,b01,wz1, wx1,b11,wz1, wx1,b10,wz0, 0,-1,0,
u0,v0, u0,v1, u1,v1, u1,v0);
// Seiten UV: horizontal = Zellposition, vertikal = Höhe normiert
if (gebirgeHole(map,cx,cz-1,CELLS)) vtx = quad(pos,norm,uv,idx,vtx,
wx1,t10,wz0, wx0,t00,wz0, wx0,b00,wz0, wx1,b10,wz0, 0,0,-1,
u1,t10, u0,t00, u0,b00, u1,b10);
if (gebirgeHole(map,cx,cz+1,CELLS)) vtx = quad(pos,norm,uv,idx,vtx,
wx0,t01,wz1, wx1,t11,wz1, wx1,b11,wz1, wx0,b01,wz1, 0,0,1,
u0,t01, u1,t11, u1,b11, u0,b01);
if (gebirgeHole(map,cx-1,cz,CELLS)) vtx = quad(pos,norm,uv,idx,vtx,
wx0,t00,wz0, wx0,t01,wz1, wx0,b01,wz1, wx0,b00,wz0, -1,0,0,
v0,t00, v1,t01, v1,b01, v0,b00);
if (gebirgeHole(map,cx+1,cz,CELLS)) vtx = quad(pos,norm,uv,idx,vtx,
wx1,t11,wz1, wx1,t10,wz0, wx1,b10,wz0, wx1,b11,wz1, 1,0,0,
v1,t11, v0,t10, v0,b10, v1,b11);
}
}
if (idx.isEmpty()) continue;
Mesh mesh = new Mesh();
mesh.setBuffer(VertexBuffer.Type.Position, 3, BufferUtils.createFloatBuffer(toFA(pos)));
mesh.setBuffer(VertexBuffer.Type.Normal, 3, BufferUtils.createFloatBuffer(toFA(norm)));
mesh.setBuffer(VertexBuffer.Type.TexCoord, 2, BufferUtils.createFloatBuffer(toFA(uv)));
mesh.setBuffer(VertexBuffer.Type.Index, 3, BufferUtils.createIntBuffer(toIA(idx)));
mesh.updateBound();
mesh.updateCounts();
Geometry geom = new Geometry("gebirge_" + chunkX + "_" + chunkZ, mesh);
geom.setMaterial(mat);
geom.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
RigidBodyControl rbc = new RigidBodyControl(
CollisionShapeFactory.createMeshShape(geom), 0f);
geom.addControl(rbc);
bulletAppState.getPhysicsSpace().add(rbc);
node.attachChild(geom);
}
}
return node;
}
private boolean gebirgeHole(MapData map, int cx, int cz, int CELLS) {
if (cx < 0 || cx >= CELLS || cz < 0 || cz >= CELLS) return true;
return map.upperHole[cz * CELLS + cx] != 0;
}
/** Fügt ein Quad (2 Dreiecke) mit Position, Normal, UV und Index hinzu. */
private int quad(List<Float> pos, List<Float> norm, List<Float> uv, List<Integer> idx, int v,
float x0, float y0, float z0, float x1, float y1, float z1,
float x2, float y2, float z2, float x3, float y3, float z3,
float nx, float ny, float nz,
float u0, float v0, float u1, float v1,
float u2, float v2, float u3, float v3) {
pos.add(x0); pos.add(y0); pos.add(z0);
pos.add(x1); pos.add(y1); pos.add(z1);
pos.add(x2); pos.add(y2); pos.add(z2);
pos.add(x3); pos.add(y3); pos.add(z3);
for (int i = 0; i < 4; i++) { norm.add(nx); norm.add(ny); norm.add(nz); }
uv.add(u0); uv.add(v0);
uv.add(u1); uv.add(v1);
uv.add(u2); uv.add(v2);
uv.add(u3); uv.add(v3);
idx.add(v); idx.add(v+1); idx.add(v+2);
idx.add(v); idx.add(v+2); idx.add(v+3);
return v + 4;
}
private float[] toFA(List<Float> l) {
float[] a = new float[l.size()];
for (int i = 0; i < l.size(); i++) a[i] = l.get(i);
return a;
}
private int[] toIA(List<Integer> l) {
int[] a = new int[l.size()];
for (int i = 0; i < l.size(); i++) a[i] = l.get(i);
return a;
}
private void applyTerrainMaterial(TerrainQuad terrain, MapData map) {
if (map != null) {
try {
Material mat = new Material(assetManager, "Common/MatDefs/Terrain/Terrain.j3md");
Texture tex1 = loadTexOrFallback("Textures/Terrain/splat/grass.jpg",
new ColorRGBA(0.28f, 0.58f, 0.18f, 1f));
Texture tex2 = loadTexOrFallback("Textures/Terrain/splat/road.jpg",
new ColorRGBA(0.55f, 0.50f, 0.40f, 1f));
Texture tex3 = loadTexOrFallback("Textures/Terrain/splat/Gravel.jpg",
new ColorRGBA(0.45f, 0.35f, 0.25f, 1f));
tex1.setWrap(Texture.WrapMode.Repeat);
tex2.setWrap(Texture.WrapMode.Repeat);
tex3.setWrap(Texture.WrapMode.Repeat);
mat.setTexture("Tex1", tex1); mat.setFloat("Tex1Scale", 512f);
mat.setTexture("Tex2", tex2); mat.setFloat("Tex2Scale", 512f);
mat.setTexture("Tex3", tex3); mat.setFloat("Tex3Scale", 512f);
// Ältere Maps haben splatR=0 → Gras (Tex1) wäre unsichtbar; auf 255 setzen.
byte[] splatR = map.splatR;
boolean rAllZero = true;
for (byte b : splatR) { if (b != 0) { rAllZero = false; break; } }
if (rAllZero) {
splatR = new byte[splatR.length];
java.util.Arrays.fill(splatR, (byte) 255);
}
int sz = MapData.SPLAT_SIZE;
ByteBuffer splatBuf = BufferUtils.createByteBuffer(sz * sz * 4);
for (int i = 0; i < sz * sz; i++) {
splatBuf.put(splatR[i]);
splatBuf.put(map.splatG[i]);
splatBuf.put(map.splatB[i]);
splatBuf.put((byte) 0);
}
splatBuf.flip();
Texture2D splatTex = new Texture2D(new Image(Image.Format.RGBA8, sz, sz, splatBuf));
splatTex.setWrap(Texture.WrapMode.EdgeClamp);
splatTex.setMinFilter(Texture.MinFilter.BilinearNoMipMaps);
splatTex.setMagFilter(Texture.MagFilter.Bilinear);
mat.setTexture("Alpha", splatTex);
terrain.setMaterial(mat);
return;
} catch (Exception e) {
System.err.println("[WorldScene] Splat-Material fehlgeschlagen: " + e.getMessage());
}
}
// Fallback: einfaches Gras-Material
try {
Material mat = new Material(assetManager, "Common/MatDefs/Terrain/TerrainLighting.j3md");
Texture grass = assetManager.loadTexture("Textures/gras.png");
grass.setWrap(Texture.WrapMode.Repeat);
mat.setTexture("Tex1", grass);
mat.setFloat("Tex1Scale", texScale);
mat.setTexture("DiffuseMap", grass);
mat.setFloat("DiffuseMap_0_scale", 32f);
mat.setBoolean("useTriPlanarMapping", false);
terrain.setMaterial(mat);
} catch (Exception e) {
// Fallback: einfarbiges Material
mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
mat.setBoolean("UseMaterialColors", true);
mat.setColor("Diffuse", new ColorRGBA(0.28f, 0.58f, 0.18f, 1f));
mat.setColor("Ambient", new ColorRGBA(0.15f, 0.30f, 0.09f, 1f));
terrain.setMaterial(mat);
}
}
private Texture loadTexOrFallback(String path, ColorRGBA color) {
try {
return assetManager.loadTexture(path);
} catch (Exception e) {
ByteBuffer buf = BufferUtils.createByteBuffer(4);
buf.put((byte)(color.r * 255)).put((byte)(color.g * 255))
.put((byte)(color.b * 255)).put((byte)(color.a * 255));
buf.flip();
return new Texture2D(new Image(Image.Format.RGBA8, 1, 1, buf));
}
terrain.setMaterial(mat);
}
// -----------------------------------------------------------------------