roads: enable Rugged+Trail carving + per-tier character (D-022)
All four road tiers now carve into the 3D world. Rugged and Trail were generated, exported and parsed all along, but nothing ever consumed them — which is why county roads visible on the 2D map did not exist in 3D. Tier identity is now carried into the carve via a RoadSegment struct, so each tier gets its own width, shoulder and grade-smoothing from one tunable block in Constants: highways widest and holding a grade, trails narrow and hugging the land. Rugged and Trail surface as dirt rather than asphalt. Because tiers now have different reach, nearest-by-distance was no longer a sound way to pick the governing road — a nearby footpath could shadow a highway whose shoulder still covered the column. The carve now considers only roads whose shoulder actually reaches, and takes the closest of those. The per-chunk cull pad is derived from the widest shoulder plus a margin, so widening a road cannot silently truncate it at chunk edges. Untouched: density field, Marching Cubes interpolation/welding, chunk dims, the .dat contract, the 2D A* generation, and mesher normals. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a827595d61
commit
f9ea2da3f4
7 changed files with 255 additions and 130 deletions
|
|
@ -10,8 +10,8 @@ namespace IslaApocalypse.Core
|
|||
/// <param name="biome">The 2D biome pixel from the MapData.</param>
|
||||
/// <param name="surfaceHeight">The maximum height of the terrain at this X/Z coordinate.</param>
|
||||
/// <param name="currentY">The Y coordinate of the specific voxel we are painting.</param>
|
||||
/// <param name="isRoad">Is this coordinate part of the highway/branch road network?</param>
|
||||
public static byte GetVoxelID(Biome biome, int surfaceHeight, int currentY, bool isRoad)
|
||||
/// <param name="roadMaterial">What this column's road is paved with, or 0 if it is not a road.</param>
|
||||
public static byte GetVoxelID(Biome biome, int surfaceHeight, int currentY, byte roadMaterial)
|
||||
{
|
||||
// 1. Air Check: If we are above the terrain, it's Air.
|
||||
if (currentY > surfaceHeight) return BlockRegistry.AIR;
|
||||
|
|
@ -19,8 +19,9 @@ namespace IslaApocalypse.Core
|
|||
// 2. Bedrock Check: The very bottom of the world is indestructible.
|
||||
if (currentY == 0) return BlockRegistry.BEDROCK;
|
||||
|
||||
// 3. Infrastructure Check: If this is a road, and we are exactly on the surface, paint asphalt.
|
||||
if (isRoad && currentY == surfaceHeight) return BlockRegistry.ASPHALT;
|
||||
// 3. Infrastructure Check: If this is a road, and we are exactly on the surface,
|
||||
// paint whatever that road tier is surfaced with (asphalt, dirt, ...).
|
||||
if (roadMaterial != BlockRegistry.AIR && currentY == surfaceHeight) return roadMaterial;
|
||||
|
||||
int depthBelowSurface = surfaceHeight - currentY;
|
||||
|
||||
|
|
@ -91,13 +92,13 @@ namespace IslaApocalypse.Core
|
|||
/// <param name="idNear">The material on the shallower side of the boundary.</param>
|
||||
/// <param name="idFar">The material on the deeper side.</param>
|
||||
/// <param name="blend">0 = fully idNear, 1 = fully idFar.</param>
|
||||
public static void GetBlendedVoxelIDs(Biome biome, bool isRoad, float depthBelowSurface,
|
||||
public static void GetBlendedVoxelIDs(Biome biome, byte roadMaterial, float depthBelowSurface,
|
||||
float blendBand, out byte idNear, out byte idFar, out float blend)
|
||||
{
|
||||
// Above the surface can happen by a hair on interpolated vertices; treat as surface.
|
||||
float depth = Mathf.Max(depthBelowSurface, 0.0f);
|
||||
|
||||
byte skinID = GetSkinMaterial(biome, isRoad);
|
||||
byte skinID = GetSkinMaterial(biome, roadMaterial);
|
||||
byte shallowID = GetShallowMaterial(biome);
|
||||
|
||||
// Two boundaries exist: skin->shallow and shallow->stone. Work out
|
||||
|
|
@ -130,9 +131,10 @@ namespace IslaApocalypse.Core
|
|||
}
|
||||
|
||||
/// <summary>The topmost material: what you actually walk on.</summary>
|
||||
private static byte GetSkinMaterial(Biome biome, bool isRoad)
|
||||
private static byte GetSkinMaterial(Biome biome, byte roadMaterial)
|
||||
{
|
||||
if (isRoad) return BlockRegistry.ASPHALT;
|
||||
// A road paves over whatever biome it crosses, with its own tier's surface.
|
||||
if (roadMaterial != BlockRegistry.AIR) return roadMaterial;
|
||||
|
||||
switch (biome)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -30,7 +30,12 @@ namespace IslaApocalypse.Core
|
|||
// or storage depends on them.
|
||||
public float[,] SurfaceHeights = new float[Constants.CHUNK_SIZE_X + 1, Constants.CHUNK_SIZE_Z + 1];
|
||||
public Biome[,] ColumnBiomes = new Biome[Constants.CHUNK_SIZE_X + 1, Constants.CHUNK_SIZE_Z + 1];
|
||||
public bool[,] ColumnIsRoad = new bool[Constants.CHUNK_SIZE_X + 1, Constants.CHUNK_SIZE_Z + 1];
|
||||
|
||||
// What this column's road surface is paved with — asphalt for highways,
|
||||
// dirt for trails, and 0 (BlockRegistry.AIR) for "not a road at all".
|
||||
// Carries the tier's material rather than a plain yes/no, so a footpath
|
||||
// doesn't get painted like a motorway.
|
||||
public byte[,] ColumnRoadMaterial = new byte[Constants.CHUNK_SIZE_X + 1, Constants.CHUNK_SIZE_Z + 1];
|
||||
|
||||
// For our future delta-save system
|
||||
public bool IsPlayerProtected = false;
|
||||
|
|
|
|||
|
|
@ -1,45 +1,119 @@
|
|||
using Godot;
|
||||
|
||||
namespace IslaApocalypse.Core
|
||||
{
|
||||
public static class Constants
|
||||
{
|
||||
// Chunk Dimensions
|
||||
public const int CHUNK_SIZE_X = 24;
|
||||
public const int CHUNK_SIZE_Z = 24;
|
||||
public const int CHUNK_HEIGHT = 256; // Our agreed-upon depth!
|
||||
|
||||
// World Generation
|
||||
public const float ISO_LEVEL = 0.0f; // The threshold for Marching Cubes
|
||||
public const float VOXEL_SCALE = 1.0f; // 1 Pixel = 1 Meter
|
||||
public static class Constants
|
||||
{
|
||||
// Chunk Dimensions
|
||||
public const int CHUNK_SIZE_X = 24;
|
||||
public const int CHUNK_SIZE_Z = 24;
|
||||
public const int CHUNK_HEIGHT = 256; // Our agreed-upon depth!
|
||||
|
||||
// World Generation
|
||||
public const float ISO_LEVEL = 0.0f; // The threshold for Marching Cubes
|
||||
public const float VOXEL_SCALE = 1.0f; // 1 Pixel = 1 Meter
|
||||
|
||||
// --- APPEARANCE TUNING ---------------------------------------------
|
||||
// How many metres it takes for one surface material to fade into the
|
||||
// next (grass -> dirt -> stone) on the rendered mesh.
|
||||
//
|
||||
// TUNE THIS BY EYE: bigger = softer, more gradual fade.
|
||||
// 0.0 = hard switch, exactly like the old banded look
|
||||
// 2.0 = default, a gentle two-metre blend
|
||||
// 4.0+ = very soft, materials smear into each other
|
||||
//
|
||||
// Colour only. This does NOT affect terrain shape, collision, or what
|
||||
// block is actually stored in a voxel.
|
||||
public const float BLEND_BAND_METERS = 2.0f;
|
||||
// --- APPEARANCE TUNING ---------------------------------------------
|
||||
// How many metres it takes for one surface material to fade into the
|
||||
// next (grass -> dirt -> stone) on the rendered mesh.
|
||||
//
|
||||
// TUNE THIS BY EYE: bigger = softer, more gradual fade.
|
||||
// 0.0 = hard switch, exactly like the old banded look
|
||||
// 2.0 = default, a gentle two-metre blend
|
||||
// 4.0+ = very soft, materials smear into each other
|
||||
//
|
||||
// Colour only. This does NOT affect terrain shape, collision, or what
|
||||
// block is actually stored in a voxel.
|
||||
public const float BLEND_BAND_METERS = 2.0f;
|
||||
|
||||
// --- ROAD GRADE TUNING ---------------------------------------------
|
||||
// How much a road "holds a grade" instead of following every bump in
|
||||
// the ground underneath it. Roads exist to be DRIVEN on, so this is a
|
||||
// driveability setting, not a cosmetic one.
|
||||
//
|
||||
// 0.0 = hugs the land: the roadbed follows the terrain exactly.
|
||||
// Never steps, but inherits every lump the ground has.
|
||||
// 1.0 = holds a grade (default): each stretch of road is a straight
|
||||
// ramp between its two path points, cutting through small
|
||||
// bumps and filling small dips. Smoothest to drive.
|
||||
//
|
||||
// Values in between mix the two. Raise toward 1.0 for a more
|
||||
// engineered road, lower toward 0.0 for a more rustic one.
|
||||
//
|
||||
// NOTE: this affects road ELEVATION (terrain shape under the road),
|
||||
// not colour. Tune it properly once vehicles exist to drive on it.
|
||||
public const float ROAD_GRADE_SMOOTHING = 1.0f;
|
||||
}
|
||||
}
|
||||
// --- ROAD CHARACTER BY TIER (D-022) ---------------------------------
|
||||
// A road is carved to match how BUILT it is. One gradient, four steps:
|
||||
// a highway is engineered, a trail is a footpath worn into the ground.
|
||||
//
|
||||
// Three knobs per tier:
|
||||
// RoadRadius - half-width of the flat carriageway, in metres.
|
||||
// ShoulderRadius - how far out the carve eases back to natural
|
||||
// ground. Must always be LARGER than RoadRadius.
|
||||
// GradeSmoothing - 0.0 hugs the land (follows every bump)
|
||||
// 1.0 holds a grade (straight ramp between path
|
||||
// points, cutting bumps and filling dips).
|
||||
// SurfaceMaterial- what the carriageway is paved with.
|
||||
//
|
||||
// TUNE THESE BY EYE. They are starting values chosen to make the
|
||||
// hierarchy visible, not final ones. A trail being bumpy is CORRECT —
|
||||
// nobody drives a truck down a footpath.
|
||||
//
|
||||
// NOTE: these affect road ELEVATION (terrain shape) and paint, not the
|
||||
// terrain noise. Tune the highway properly once vehicles can drive it.
|
||||
|
||||
public const float HIGHWAY_ROAD_RADIUS = 4.0f; // 8 m carriageway
|
||||
public const float HIGHWAY_SHOULDER_RADIUS = 12.0f;
|
||||
public const float HIGHWAY_GRADE_SMOOTHING = 1.0f; // fully engineered
|
||||
|
||||
public const float BRANCH_ROAD_RADIUS = 3.0f;
|
||||
public const float BRANCH_SHOULDER_RADIUS = 9.0f;
|
||||
public const float BRANCH_GRADE_SMOOTHING = 0.7f;
|
||||
|
||||
public const float RUGGED_ROAD_RADIUS = 2.0f;
|
||||
public const float RUGGED_SHOULDER_RADIUS = 6.0f;
|
||||
public const float RUGGED_GRADE_SMOOTHING = 0.4f;
|
||||
|
||||
public const float TRAIL_ROAD_RADIUS = 1.5f;
|
||||
public const float TRAIL_SHOULDER_RADIUS = 4.0f;
|
||||
public const float TRAIL_GRADE_SMOOTHING = 0.1f; // barely built at all
|
||||
|
||||
/// <summary>
|
||||
/// The widest reach any road has. The per-chunk road cull pads by this
|
||||
/// (plus a margin) so a road just outside a chunk still carves into it.
|
||||
/// Computed from the values above, so raising a shoulder can never
|
||||
/// silently outgrow the cull and truncate roads at chunk edges.
|
||||
/// </summary>
|
||||
public static readonly float MAX_SHOULDER_RADIUS = Mathf.Max(
|
||||
Mathf.Max(HIGHWAY_SHOULDER_RADIUS, BRANCH_SHOULDER_RADIUS),
|
||||
Mathf.Max(RUGGED_SHOULDER_RADIUS, TRAIL_SHOULDER_RADIUS));
|
||||
|
||||
/// <summary>Safety margin added to the road cull, in metres.</summary>
|
||||
public const float ROAD_CULL_MARGIN = 3.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Looks up the carve settings for a road tier. One place to tune.
|
||||
/// </summary>
|
||||
public static RoadProfile GetRoadProfile(RoadTier tier)
|
||||
{
|
||||
switch (tier)
|
||||
{
|
||||
case RoadTier.Highway:
|
||||
return new RoadProfile(HIGHWAY_ROAD_RADIUS, HIGHWAY_SHOULDER_RADIUS,
|
||||
HIGHWAY_GRADE_SMOOTHING, BlockRegistry.ASPHALT);
|
||||
case RoadTier.Branch:
|
||||
return new RoadProfile(BRANCH_ROAD_RADIUS, BRANCH_SHOULDER_RADIUS,
|
||||
BRANCH_GRADE_SMOOTHING, BlockRegistry.ASPHALT);
|
||||
case RoadTier.Rugged:
|
||||
return new RoadProfile(RUGGED_ROAD_RADIUS, RUGGED_SHOULDER_RADIUS,
|
||||
RUGGED_GRADE_SMOOTHING, BlockRegistry.DIRT);
|
||||
default: // Trail
|
||||
return new RoadProfile(TRAIL_ROAD_RADIUS, TRAIL_SHOULDER_RADIUS,
|
||||
TRAIL_GRADE_SMOOTHING, BlockRegistry.DIRT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// How one tier of road is carved. Values come from Constants above.
|
||||
/// </summary>
|
||||
public struct RoadProfile
|
||||
{
|
||||
public float RoadRadius;
|
||||
public float ShoulderRadius;
|
||||
public float GradeSmoothing;
|
||||
public byte SurfaceMaterial;
|
||||
|
||||
public RoadProfile(float roadRadius, float shoulderRadius, float gradeSmoothing, byte surfaceMaterial)
|
||||
{
|
||||
RoadRadius = roadRadius;
|
||||
ShoulderRadius = shoulderRadius;
|
||||
GradeSmoothing = gradeSmoothing;
|
||||
SurfaceMaterial = surfaceMaterial;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,13 @@ namespace IslaApocalypse.Core
|
|||
// The master list of shared identifiers for the game
|
||||
public enum Biome { Ocean, Lake, Beach, Paradise, Tropical, Jungle, Wasteland, Crater, Mountain, Snow }
|
||||
public enum TownTier { Village, Hub, Capitol, Outpost, IslandLoot, MiniPOI }
|
||||
public enum MapHalf { Any, Left, Right }
|
||||
|
||||
public enum MapHalf { Any, Left, Right }
|
||||
|
||||
// How "built" a road is, most engineered first. Drives how wide and how
|
||||
// smooth it gets carved into the 3D world (see D-022).
|
||||
//
|
||||
// NOTE: unlike Biome and TownTier above, this one is NOT written into the
|
||||
// .dat file — the blueprint stores each tier in its own section, so this
|
||||
// enum never becomes a serialized number. It is safe to reorder or extend.
|
||||
public enum RoadTier { Highway, Branch, Rugged, Trail }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -410,9 +410,9 @@ namespace IslaApocalypse.Core
|
|||
/// colour itself now comes from the per-column data below, which is not rounded.</param>
|
||||
/// <param name="surfaceHeights">True (unrounded) surface height per X/Z column.</param>
|
||||
/// <param name="columnBiomes">Biome per X/Z column.</param>
|
||||
/// <param name="columnIsRoad">Whether each X/Z column is roadbed.</param>
|
||||
/// <param name="columnRoadMaterial">Road surface material per X/Z column, or 0 if not a road.</param>
|
||||
public static ArrayMesh GenerateMesh(float[,,] scalarField, byte[,,] blockIDs,
|
||||
float[,] surfaceHeights, Biome[,] columnBiomes, bool[,] columnIsRoad,
|
||||
float[,] surfaceHeights, Biome[,] columnBiomes, byte[,] columnRoadMaterial,
|
||||
float isolevel = 0.0f, float voxelScale = 1.0f)
|
||||
{
|
||||
var st = new SurfaceTool();
|
||||
|
|
@ -509,7 +509,7 @@ namespace IslaApocalypse.Core
|
|||
|
||||
BiomePalette.GetBlendedVoxelIDs(
|
||||
columnBiomes[colX, colZ],
|
||||
columnIsRoad[colX, colZ],
|
||||
columnRoadMaterial[colX, colZ],
|
||||
depthBelowSurface,
|
||||
Constants.BLEND_BAND_METERS,
|
||||
out byte idNear, out byte idFar, out float blendAmount);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue