Using radius perturbance (as per the question)
This is a matter of combining different frequencies of noise. Let's ignore fine perturbations for now and look at the basis first, which as you state, needs to match around t == 0.0
, and let's consider the problem by "unrolling" the circumference of your circle into a straight line.
A simple way to solve this is using a sine or cosine wave, which repeat endlessly and seamlessly, having always the same value at 0
and 2*pi
radians. You can thus create a (co)sine wave with a large amplitude, say 10.0, and then randomly perturb each point by a much smaller magnitude, say 2.0. You can combine just 2 waveforms, or many waveforms of differing frequencies and amplitudes (like Perlin Noise).
const amplitudeLarge = 10.0;
const amplitudeSmall = 2.0;
float calcPerturbedRadius(theta, amplitudeLarge, amplitudeSmall)
{
return amplitudeLarge * sin(theta)
+ amplitudeSmall * sin(theta);
}
for (let i = 0; i < radiusPointsCount; i++)
{
let frac = i / radiusPointsCount;
let theta = frac * (pi * 2);
pointsRadii[i] = calcPerturbedRadius(theta, amplitudeLarge, amplitudeSmall);
//render etc.
}
By combining multiple large waveforms (say 2 that are both using amplitudeLarge
) you can create a more interesting "coarse" basis upon which you will apply your "fine" (per point) adjustments. A cursory web search for "combining sine waves" will give you more insight into this.
Alternative 1: Diffusion clouds
For context, the use case is the creation of objects in an Asteroids
(the arcade game) sort of style. Those asteroids simply used a
uniformly distributed random radius for each point in the polygon, but
that produces objects more jaggedy than what I would like, and lead to
more visually spherical objects as you add more points. But my
polygons still have few enough points where a full application of
Perlin noise or some other fractal method would be overkill I would
think. Any ideas would be appreciated, thanks.
There's another way to achieve smooth, organic outlines, using image processing instead of radius perturbation: Using a small image / grid of NxN, where N is your maximum height / width of your asteroid form, plot a few points at random between 0..N-1
in each axis, setting these to some maximum value (255, 1024, or what have you). Then run multiple diffusion passes over this grayscale "image" (2D array), passing dc = 0.2
initially:
function diffuse(mapOld, mapNew, width, dc)
{
mapNew.set(mapOld);
let px = 0, py = 0, nx = 0, ny = 0; //integers
for (let y = 0; y < width; y++)
{
py = y - 1; if (py < 0) py = py + width;
ny = y + 1; if (ny >= width) ny = ny - width;
for (let x = 0; x < width; x++)
{
px = x - 1; if (px < 0) px = px + width;
nx = x + 1; if (nx >= width) nx = nx - width;
mapNew[y * width + x] = dc *
(
mapOld[ y * width + x] + //centre
mapNew[py * width + x] + //east
mapNew[ny * width + x] + //west
mapNew[ y * width + px] + //north (or south)
mapNew[ y * width + nx] //south (or north)
);
}
}
mapOld.set(mapNew);
}
const numPasses = 10;
const dc = 0.2;
for (let p = 0; p < numPasses; p++)
{
diffuse(mapOld, mapNew, N, dc)
}
On every step, the randomly-plotted cells diffuse into neighbouring cells. This has the effect of softening outlines, like a cloud. Points plotted near each other will gradually merge together in a cloud-like form.
Finally, any cells with values greater than 0.0 are clamped to 1.0, anything else is left as zero. This should give you fairly rounded outlines.
Play with dc
and numPasses
to get the effect you need. You may also need to create a large grid and only randomly plot nearer to the centre. You may also need to play with the resolution to get the smoothness you want for your asteroids.
Alternative 2: Metaballs
Another alternative for organic outlines is 2D metaballs. There are tons of sources and implementations across the web, so I will not go into detail here.