
A collection of real-time procedural visual algorithms implemented in UE5 C++ using Instanced Static Meshes. Circle Packing uses exponential size bias for natural distributions; Ulam Spirals plot primes on a 2D grid; Poisson Disc Sampling generates blue-noise scatter; Diffusion-Limited Aggregation grows fractal trees. All rendered as ISM instances — thousands of elements at 60 FPS with zero per-element actor cost.
-log(1-α) / ExponentBias) produces small-circle-dominated distributions; overlap check uses distance² vs (r1+r2)² — 0 sqrt calls.FInterpTo call per tick, creating an organic "inflate" fill effect.
-log(1 - FRand()) / ExponentBias biases the radius distribution toward small circles, matching the organic look of real circle-packing art. Overlap is tested as a squared-distance comparison — no sqrt.
bool ACirclePackingManager::TrySpawnNewCircle()
{
// Exponential bias: favours small radii — matches organic packing art
const float Alpha = FMath::FRand();
const float Radius = -FMath::Loge(1.f - Alpha) / ExponentBias;
if (Radius < MinRadius || Radius > MaxRadius) return false;
// Random position within spawn bounds
FVector2D Pos(FMath::FRandRange(-SpawnHalfExtent, SpawnHalfExtent),
FMath::FRandRange(-SpawnHalfExtent, SpawnHalfExtent));
if (IsOverlapping(Pos, Radius)) return false;
FCircleData& C = Circles.AddDefaulted_GetRef();
C.Position = Pos;
C.TargetRadius = Radius;
C.CurrentRadius = 0.f; // starts at 0, grows to target
C.Color = FLinearColor::MakeRandomColor();
C.EmissiveScale = FMath::FRandRange(EmissiveMin, EmissiveMax);
return true;
}
bool ACirclePackingManager::IsOverlapping(const FVector2D& Pos, float R) const
{
for (const FCircleData& C : Circles)
{
const float DistSq = FVector2D::DistSquared(Pos, C.Position);
const float MinSep = (R + C.TargetRadius) * (R + C.TargetRadius);
if (DistSq < MinSep) return true; // no sqrt needed
}
return false;
}
// Per-tick: grow radius and update ISM custom float data
void ACirclePackingManager::Tick(float DT)
{
Super::Tick(DT);
for (int32 i = 0; i < Circles.Num(); ++i)
{
FCircleData& C = Circles[i];
C.CurrentRadius = FMath::FInterpTo(C.CurrentRadius, C.TargetRadius, DT, GrowSpeed);
// Pack RGBA + emissive into ISM per-instance custom data
CircleISM->SetCustomDataValue(i, 0, C.Color.R);
CircleISM->SetCustomDataValue(i, 1, C.Color.G);
CircleISM->SetCustomDataValue(i, 2, C.Color.B);
CircleISM->SetCustomDataValue(i, 3, C.EmissiveScale);
}
}