/*
* Approximate SDF of a heightfield.
* Positive outside the surface, negative inside.
*/
float SDFHeightfield(float3 p) {
return sampleHeight(p.xz) - p.y;
}
public class CoastalErosionSettings {
// Minimum slope for the seed to be accepted
public float seedMinSlope;
// Acceptable altitude range for seeds
public Vector2 seedElevationRange;
// A random probability of acceptance for more variety
public float seedAcceptanceProbability;
// Sphere radii will be randomly computed from this min, max range
public Vector2 radiusRange;
// Number of carving step per seed, randomly within this range
public Vector2Int growthStepRange;
// Number of sphere carved at each growth step
public Vector2Int growthSphereRange;
}
// Characterization of a seed point.
public class SeedPoint {
public Vector3 point;
public Vector3 normal;
}
/*
* Compute the set of seeds for coastal erosion from the base heightfield.
* Scan the heightfield and look for the steep parts around sea elevation.
*/
public List<Vector3> GetCoastalErosionSeeds(CoastalErosionSettings settings, ...) {
List<Vector3> ret = new List<Vector3>();
for (int x = 0; x < terrain.Width(); x++) {
for (int y = 0; y < terrain.Height(); y++) {
Vector3 point = terrain.Point(x, y);
// Altitude
if (!Utils.WithinRange(point.y, settings.seedElevationRange))
continue;
// Slope
float slope = terrain.GetSlope(x, y);
if (slope < settings.seedMinSlope)
continue;
// Probability
float p = Random.value;
if (p > settings.seedAcceptanceProbability)
continue;
ret.Add(new SeedPoint(point, terrain.Normal(x, y)));
}
}
return ret;
}
/*
* Returns a set of spheres more or less randomly positioned along a direction.
*/
public List<Sphere> GrowFromSeed(Vector3 seed, Vector3 direction, CoastalErosionSettings settings, ...) {
List<Sphere> ret = new List<Sphere>();
ret.Add(new Sphere(seed, Utils.RandomRange(settings.radiusRange)));
// Growth dir slightly downward, and globally horizontal
Vector3 growthDir = new Vector3(direction.x, -0.1f, direction.z);
// Sphere positioning loop
Vector3 p = seed;
int nbStep = Utils.RandomRange(settings.growthStepRange);
for (int i = 0; i < nbStep; i++) {
// Random amount of sphere at each step around p, each with random radii
int nbSpheres = Utils.RandomRange(settings.growthSphereRange);
var newSpheres = Utils.RandomSpheresAroundPoint(p, nbSpheres, settings.radiusRange);
ret.AddRange(newSpheres);
// Move along direction
p = p + direction;
}
}
/*
* Perform the coastal erosion algorithm. Returns a set of spheres that should be carved out of the
* base heightfield in implicit form.
*/
public List<Sphere> CoastalErosion(CoastalErosionSettings settings, ...) {
// Get seeds
var seeds = GetCoastalErosionSeeds(settings, ...);
// Carving with spheres, in the normal direction
List<Sphere> ret = new List<Sphere>();
foreach (var seed in seeds) {
var newSpheres = GrowFromSeed(seed.point, -seed.normal, settings, ...);
ret.AddRange(newSpheres);
}
return ret;
}
float3 Warp(float3 p) {
return p + float3(1, 0, 0); // constant offset
}
float SDF(float3 p) {
float3 q = Warp(p):
... // we continue with the rest of the implicit function
}
// Your typical perlin or value noise
float3 Noise(float3 p) {
...
}
float3 Warp(float3 p) {
// Warp amplitude from 1D noise
float w = Noise(float3(0.0f, p.y, 0.0f) * frequency) * amplitude;
// Warp direction, from the base heightfield
float3 d = HeightfieldGradient(p);
d = float3(d.x, 0.0f, d.z);
// Final warp
return p + d * w;
}
float SDF(float3 p) {
float3 q = Warp(p):
...
}