public class ScatteredObjectParams
{
public string name;
// Prefabs, amounts
public List<GameObject> prefabs;
public bool randomAmount;
public Vector2Int amountRange;
// Secondary objects - the hierarchical aspect
public float secondaryDiskSize;
public List<SecondaryObjectParams> secondaryPrefabs;
// Sample validation constraints
public float poissonRadius;
public BiomeType biome;
public Vector2 altitudeRange;
public Vector2 slopeRange;
public float noiseThreshold;
public float noiseFrequency;
// Gameobject placement constraints
public float altitudeOffset;
public Vector2 rotationYRange;
public Vector2 scaleRange;
}
// Could be extended if we want more complex positioning rules
public class SecondaryObjectParams
{
public string name;
public GameObject prefab;
public Vector2Int amountRange;
}
public static class Scattering
{
public static List<GameObject> ScatterObject(ScatteredObjectParams objParams, ...)
{
// 1. Generate a Poisson disk distribution for primary samples
List<Vector2> primarySamples = PoissonSampling.Generate(objParams.poissonRadius);
// 2. Validate each sample
List<Vector2> validSamples = new List<Vector2>();
foreach (var sample in primarySamples)
{
if (IsValid(sample, objParams))
validSamples.Add(sample);
}
// 3. Instantiate objects
foreach (var sample in validSamples)
{
GameObject obj = Instantiate(Utils.RandomElement(objParams.prefabs));
obj.transform.position = sample;
obj.transform.rotation = Utils.RandomRotationY(objParams.rotationYRange);
obj.transform.localScale = Utils.RandomScale(objParams.scaleRange);
ret.Add(obj);
// 4. Scatter secondary objects around sample position
if (objParams.secondaryPrefabs.Count > 0)
{
List<GameObject> secondaryObjects = ScatterSecondaryObjects(
objParams.secondaryPrefabs,
sample,
objParams.secondaryDiskSize
);
ret.AddRange(secondaryObjects);
}
}
return ret;
}
public static bool IsValid(Vector2 sample, ScatteredObjectParams objParams, ...)
{
// 1. Check biome
if (objParams.biome != terrain.GetBiome(sample.x, sample.y))
return false;
// 2. Check altitude
float altitude = terrain.GetHeight(sample.x, sample.y);
if (altitude < objParams.altitudeRange.x || altitude > objParams.altitudeRange.y)
return false;
// 3. Check slope
float slope = terrain.GetSlope(sample.x, sample.y);
if (slope < objParams.slopeRange.x || slope > objParams.slopeRange.y)
return false;
// 4. Check noise threshold
float noise = Noise.PerlinNoise(sample.x, sample.y, objParams.noiseFrequency);
if (noise < objParams.noiseThreshold)
return false;
return true;
}
public static List<GameObject> ScatterSecondaryObjects(
List<SecondaryObjectParams> secondaryParams,
ScatteredObjectParams objParams,
Vector2 sample,
float diskSize,
...)
{
List<GameObject> ret = new List<GameObject>();
foreach (var param in secondaryParams)
{
int amount = Utils.RandomInt(param.amountRange.x, param.amountRange.y);
for (int i = 0; i < amount; i++)
{
Vector2 secondarySample = sample + Utils.RandomVector2(diskSize);
if (IsValid(secondarySample, objParams))
{
GameObject obj = Instantiate(param.prefab);
obj.transform.position = secondarySample;
obj.transform.rotation = Utils.RandomRotationY(param.rotationYRange);
obj.transform.localScale = Utils.RandomScale(param.scaleRange);
ret.Add(obj);
}
}
}
return ret;
}
}