Weltkarte: Vektor-Konturen für Wald/Klippen, 8m Textur-Filter, Render-Reihenfolge
- Cliff- und Wald-Randlinien via Marching Squares + Chaikin als Canvas-Vektoren statt Pixel-Kanten im Raster → immer scharf wie Küstenlinie und Areas - COARSE von 512px (4m/px) auf 256px (8m/px) erhöht + Bilinear-Interpolation der Flachfarben → dünne Texturen (<8m) verschwinden, keine Block-Artefakte bei Hires-Zoom - Render-Reihenfolge: Klippen → Wasser → Wald (laut User-Anforderung) - buildCliffOverlay: nur noch weiche Grau-Füllung, keine pixel-basierten Kanten - computeForestPixels: nur noch Füllung, Randlinie kommt als Vektor-Overlay Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1080,24 +1080,15 @@ public final class WorldMapRenderer {
|
||||
float[] voxelSurface, List<PlacedWater> waters,
|
||||
List<PlacedWaterfall> waterfalls) {
|
||||
final int SIZE = COLOR_MAP_SIZE;
|
||||
final int COARSE = 512;
|
||||
final int SS = MapData.SPLAT_SIZE;
|
||||
|
||||
float[] heights = buildTerrainSamples(m, SIZE, voxelSurface);
|
||||
float[] waterH = buildWaterHeightMap(waters, SIZE);
|
||||
float cellWC = WORLD_SIZE / (COARSE - 1);
|
||||
|
||||
// Coarse-Typ-Map bei 512×512 (4m/px) – filtert dünne Texturen heraus
|
||||
byte[] coarseTypes = new byte[COARSE * COARSE];
|
||||
for (int mz = 0; mz < COARSE; mz++) {
|
||||
float wz = -WORLD_HALF + mz * cellWC;
|
||||
for (int mx = 0; mx < COARSE; mx++) {
|
||||
float wx = -WORLD_HALF + mx * cellWC;
|
||||
coarseTypes[mz * COARSE + mx] = (byte) getTerrainTile(m, SS, wx, wz, slotColors);
|
||||
}
|
||||
}
|
||||
// Coarse-Typ-Map bei 256×256 (8m/px) – filtert Texturen <8m heraus
|
||||
int[] coarseColors = buildCoarseColorMap(m, SS, slotColors, 256);
|
||||
|
||||
// Hochskalieren (NN) auf SIZE×SIZE und Flachfarbe zuweisen
|
||||
// Bilinear-Interpolation der Flachfarben auf SIZE×SIZE → keine Block-Artefakte
|
||||
int[] result = new int[SIZE * SIZE];
|
||||
for (int py = 0; py < SIZE; py++) {
|
||||
for (int px = 0; px < SIZE; px++) {
|
||||
@@ -1107,15 +1098,44 @@ public final class WorldMapRenderer {
|
||||
int[] wc = TILE_FALLBACK_RGB[TILE_WATER];
|
||||
result[idx] = (wc[0] << 16) | (wc[1] << 8) | wc[2];
|
||||
} else {
|
||||
int cx = Math.min(COARSE - 1, (int)((float) px / (SIZE - 1) * (COARSE - 1)));
|
||||
int cz = Math.min(COARSE - 1, (int)((float) py / (SIZE - 1) * (COARSE - 1)));
|
||||
result[idx] = schematicColor(coarseTypes[cz * COARSE + cx] & 0xFF);
|
||||
result[idx] = sampleCoarseBilinear(coarseColors, 256, px, py, SIZE);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Baut eine Coarse-Farbkarte bei gegebener Auflösung: 1 Pixel = WORLD_SIZE/coarseSize m. */
|
||||
private static int[] buildCoarseColorMap(MapData m, int SS, int[] slotColors, int coarseSize) {
|
||||
float cellW = WORLD_SIZE / (coarseSize - 1);
|
||||
int[] colors = new int[coarseSize * coarseSize];
|
||||
for (int mz = 0; mz < coarseSize; mz++) {
|
||||
float wz = -WORLD_HALF + mz * cellW;
|
||||
for (int mx = 0; mx < coarseSize; mx++) {
|
||||
float wx = -WORLD_HALF + mx * cellW;
|
||||
colors[mz * coarseSize + mx] = schematicColor(getTerrainTile(m, SS, wx, wz, slotColors));
|
||||
}
|
||||
}
|
||||
return colors;
|
||||
}
|
||||
|
||||
/** Bilinear-Sample einer Coarse-Farbkarte (packed RGB) auf einen Ziel-Pixel. */
|
||||
private static int sampleCoarseBilinear(int[] coarseColors, int coarseSize, int px, int py, int targetSize) {
|
||||
float fx = (float) px / (targetSize - 1) * (coarseSize - 1);
|
||||
float fz = (float) py / (targetSize - 1) * (coarseSize - 1);
|
||||
int cx0 = (int) fx, cx1 = Math.min(cx0 + 1, coarseSize - 1);
|
||||
int cz0 = (int) fz, cz1 = Math.min(cz0 + 1, coarseSize - 1);
|
||||
float tx = fx - cx0, tz = fz - cz0;
|
||||
int c00 = coarseColors[cz0 * coarseSize + cx0];
|
||||
int c10 = coarseColors[cz0 * coarseSize + cx1];
|
||||
int c01 = coarseColors[cz1 * coarseSize + cx0];
|
||||
int c11 = coarseColors[cz1 * coarseSize + cx1];
|
||||
int r = clamp((int)(((c00>>16)&0xFF)*(1-tx)*(1-tz)+((c10>>16)&0xFF)*tx*(1-tz)+((c01>>16)&0xFF)*(1-tx)*tz+((c11>>16)&0xFF)*tx*tz));
|
||||
int g = clamp((int)(((c00>>8)&0xFF)*(1-tx)*(1-tz)+((c10>>8)&0xFF)*tx*(1-tz)+((c01>>8)&0xFF)*(1-tx)*tz+((c11>>8)&0xFF)*tx*tz));
|
||||
int b = clamp((int)((c00&0xFF)*(1-tx)*(1-tz)+(c10&0xFF)*tx*(1-tz)+(c01&0xFF)*(1-tx)*tz+(c11&0xFF)*tx*tz));
|
||||
return (r << 16) | (g << 8) | b;
|
||||
}
|
||||
|
||||
/**
|
||||
* Berechnet das schematische Cliff-Overlay (ARGB, COLOR_MAP_SIZE×COLOR_MAP_SIZE):
|
||||
* graue Füllung wo Steigung > 30°, dunklere Linie an der oberen Kante (Kamm),
|
||||
@@ -1127,9 +1147,7 @@ public final class WorldMapRenderer {
|
||||
float cellW = WORLD_SIZE / (SIZE - 1);
|
||||
int dPx = Math.max(1, (int)(1.5f / cellW));
|
||||
|
||||
boolean[] cliffMask = new boolean[SIZE * SIZE];
|
||||
float[] gradX = new float[SIZE * SIZE];
|
||||
float[] gradZ = new float[SIZE * SIZE];
|
||||
float[] floatMask = new float[SIZE * SIZE];
|
||||
for (int py = 0; py < SIZE; py++) {
|
||||
for (int px = 0; px < SIZE; px++) {
|
||||
int idx = py * SIZE + px;
|
||||
@@ -1140,49 +1158,19 @@ public final class WorldMapRenderer {
|
||||
float dist = dPx * cellW;
|
||||
double slope = Math.toDegrees(Math.atan(Math.sqrt(
|
||||
Math.pow((hR - hL) / (2 * dist), 2) + Math.pow((hD - hU) / (2 * dist), 2))));
|
||||
cliffMask[idx] = slope > 30.0;
|
||||
gradX[idx] = (hR - hL) / (2 * dist);
|
||||
gradZ[idx] = (hD - hU) / (2 * dist);
|
||||
floatMask[idx] = slope > 30.0 ? 1f : 0f;
|
||||
}
|
||||
}
|
||||
|
||||
float[] floatMask = new float[SIZE * SIZE];
|
||||
for (int i = 0; i < SIZE * SIZE; i++) { floatMask[i] = cliffMask[i] ? 1f : 0f; }
|
||||
float[] softMask = boxBlurFloat(floatMask, SIZE, 3);
|
||||
|
||||
int[] result = new int[SIZE * SIZE];
|
||||
// Füllung: weiches Grau, max 33% alpha
|
||||
for (int i = 0; i < SIZE * SIZE; i++) {
|
||||
if (softMask[i] > 0.05f) {
|
||||
int alpha = clamp((int)(softMask[i] * 85));
|
||||
result[i] = (alpha << 24) | 0x999090;
|
||||
}
|
||||
}
|
||||
// Kantenerkennung: obere Kante (Kamm) dunkler, untere Kante (Fuß) heller
|
||||
for (int py = 1; py < SIZE - 1; py++) {
|
||||
for (int px = 1; px < SIZE - 1; px++) {
|
||||
int idx = py * SIZE + px;
|
||||
if (!cliffMask[idx]) { continue; }
|
||||
float gx = gradX[idx], gz = gradZ[idx];
|
||||
float glen = (float) Math.sqrt(gx * gx + gz * gz);
|
||||
if (glen < 1e-6f) { continue; }
|
||||
float upX = -gx / glen, upZ = -gz / glen; // bergauf
|
||||
float dnX = gx / glen, dnZ = gz / glen; // bergab
|
||||
// Obere Kante: bergauf-Nachbar ist kein Cliff
|
||||
int uhx = px + Math.round(upX * 2), uhz = py + Math.round(upZ * 2);
|
||||
if (uhx >= 0 && uhx < SIZE && uhz >= 0 && uhz < SIZE
|
||||
&& !cliffMask[uhz * SIZE + uhx]) {
|
||||
result[idx] = (0xCC << 24) | 0x504848;
|
||||
continue;
|
||||
}
|
||||
// Untere Kante: bergab-Nachbar ist kein Cliff
|
||||
int dhx = px + Math.round(dnX), dhz = py + Math.round(dnZ);
|
||||
if (dhx >= 0 && dhx < SIZE && dhz >= 0 && dhz < SIZE
|
||||
&& !cliffMask[dhz * SIZE + dhx]) {
|
||||
result[idx] = (0x88 << 24) | 0x686060;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1219,17 +1207,8 @@ public final class WorldMapRenderer {
|
||||
float[] waterH = buildWaterHeightMap(waters, SIZE);
|
||||
int written = 0;
|
||||
|
||||
// Coarse-Typ-Map (4m/px) – identische Filterung wie buildTerrainColorMap
|
||||
final int COARSE = 512;
|
||||
float cellWC = WORLD_SIZE / (COARSE - 1);
|
||||
byte[] coarseTypes = new byte[COARSE * COARSE];
|
||||
for (int mz = 0; mz < COARSE; mz++) {
|
||||
float wz = -WORLD_HALF + mz * cellWC;
|
||||
for (int mx = 0; mx < COARSE; mx++) {
|
||||
float wx = -WORLD_HALF + mx * cellWC;
|
||||
coarseTypes[mz * COARSE + mx] = (byte) getTerrainTile(m, SS, wx, wz, slotColors);
|
||||
}
|
||||
}
|
||||
// Coarse-Farbkarte bei 256×256 (8m/px) – identische Filterung wie buildTerrainColorMap
|
||||
int[] coarseColors = buildCoarseColorMap(m, SS, slotColors, 256);
|
||||
|
||||
for (int cy = 0; cy < COLS; cy++) {
|
||||
for (int cx = 0; cx < COLS; cx++) {
|
||||
@@ -1249,9 +1228,7 @@ public final class WorldMapRenderer {
|
||||
tg = (WATER_COLOR >> 8) & 0xFF;
|
||||
tb = WATER_COLOR & 0xFF;
|
||||
} else {
|
||||
int ctX = Math.min(COARSE - 1, (int)((float) gx / (SIZE - 1) * (COARSE - 1)));
|
||||
int ctZ = Math.min(COARSE - 1, (int)((float) gy / (SIZE - 1) * (COARSE - 1)));
|
||||
int col = schematicColor(coarseTypes[ctZ * COARSE + ctX] & 0xFF);
|
||||
int col = sampleCoarseBilinear(coarseColors, 256, gx, gy, SIZE);
|
||||
tr = (col >> 16) & 0xFF;
|
||||
tg = (col >> 8) & 0xFF;
|
||||
tb = col & 0xFF;
|
||||
|
||||
Reference in New Issue
Block a user