/**
* Generates a more random clump of points. It randomly creates circles around the edges of the current clump.s
*
* @param {number} minRadius - minimum radius of the circles.
* @param {number} maxRadius - maximum radius of the circles.
* @param {number} numCircles - number of circles.
* @param {number} [failFraction] - Percentage of place attempts allowed to fail.
* @param {Vector2D} [centerPosition]
* @param {number} [maxDistance] - Farthest distance from the center.
* @param {number[]} [queue] - When given, uses these radiuses for the first circles.
*/
function ChainPlacer(minRadius, maxRadius, numCircles, failFraction = 0, centerPosition = undefined, maxDistance = 0, queue = [])
{
this.minRadius = minRadius;
this.maxRadius = maxRadius;
this.numCircles = numCircles;
this.failFraction = failFraction;
this.maxDistance = maxDistance;
this.queue = queue.map(radius => Math.floor(radius));
this.centerPosition = undefined;
if (centerPosition)
this.setCenterPosition(centerPosition);
}
ChainPlacer.prototype.setCenterPosition = function(position)
{
this.centerPosition = deepfreeze(position.clone().round());
};
ChainPlacer.prototype.place = function(constraint)
{
// Preliminary bounds check
if (!g_Map.inMapBounds(this.centerPosition) || !constraint.allows(this.centerPosition))
return undefined;
let points = [];
let size = g_Map.getSize();
let failed = 0;
let count = 0;
let gotRet = new Array(size).fill(0).map(p => new Array(size).fill(-1));
--size;
this.minRadius = Math.min(this.maxRadius, Math.max(this.minRadius, 1));
let edges = [this.centerPosition];
for (let i = 0; i < this.numCircles; ++i)
{
let chainPos = pickRandom(edges);
let radius = this.queue.length ? this.queue.pop() : randIntInclusive(this.minRadius, this.maxRadius);
let radius2 = Math.square(radius);
let bbox = getPointsInBoundingBox(getBoundingBox([
new Vector2D(Math.max(0, chainPos.x - radius), Math.max(0, chainPos.y - radius)),
new Vector2D(Math.min(chainPos.x + radius, size), Math.min(chainPos.y + radius, size))
]));
for (let position of bbox)
{
if (position.distanceToSquared(chainPos) >= radius2)
continue;
++count;
if (!g_Map.inMapBounds(position) || !constraint.allows(position))
{
++failed;
continue;
}
let state = gotRet[position.x][position.y];
if (state == -1)
{
points.push(position);
gotRet[position.x][position.y] = -2;
}
else if (state >= 0)
{
edges.splice(state, 1);
gotRet[position.x][position.y] = -2;
for (let k = state; k < edges.length; ++k)
--gotRet[edges[k].x][edges[k].y];
}
}
for (let pos of bbox)
{
if (this.maxDistance &&
(Math.abs(this.centerPosition.x - pos.x) > this.maxDistance ||
Math.abs(this.centerPosition.y - pos.y) > this.maxDistance))
continue;
if (gotRet[pos.x][pos.y] != -2)
continue;
if (pos.x > 0 && gotRet[pos.x - 1][pos.y] == -1 ||
pos.y > 0 && gotRet[pos.x][pos.y - 1] == -1 ||
pos.x < size && gotRet[pos.x + 1][pos.y] == -1 ||
pos.y < size && gotRet[pos.x][pos.y + 1] == -1)
{
edges.push(pos);
gotRet[pos.x][pos.y] = edges.length - 1;
}
}
}
return failed > count * this.failFraction ? undefined : points;
};