forked from mirrors/0ad
Implement a ConvexPolygonPlacer that returns all points in the convex hull of some given points.
Implements the Gift-Wrapping algorithm to compute the convex hull.
Replace the latter half of the pathplacer with this new placer, refs
#892.
Improves the performance a bit, refs #5011.
Use the new placer on Kerala and Hyrcanian Shores to replace the
workaround from 903e094f62, refs #4855
(the area is not supposed to be parallel to the meandering river).
This was SVN commit r21082.
This commit is contained in:
@@ -107,11 +107,6 @@ paintRiver({
|
||||
"heightLand": heightLand,
|
||||
"meanderShort": 20,
|
||||
"meanderLong": 0,
|
||||
"landFunc": (position, shoreDist1, shoreDist2) => {
|
||||
|
||||
if (waterPosition + shoreDist1 > highlandsPosition)
|
||||
clHighlands.add(position);
|
||||
},
|
||||
"waterFunc": (position, height, riverFraction) => {
|
||||
|
||||
if (height < heightShore2)
|
||||
@@ -122,6 +117,18 @@ paintRiver({
|
||||
});
|
||||
Engine.SetProgress(20);
|
||||
|
||||
g_Map.log("Marking highlands area");
|
||||
createArea(
|
||||
new ConvexPolygonPlacer(
|
||||
[
|
||||
new Vector2D(mapBounds.left, mapBounds.top - highlandsPosition),
|
||||
new Vector2D(mapBounds.right, mapBounds.top - highlandsPosition),
|
||||
new Vector2D(mapBounds.left, mapBounds.bottom),
|
||||
new Vector2D(mapBounds.right, mapBounds.bottom)
|
||||
].map(pos => pos.rotateAround(startAngle, mapCenter)),
|
||||
Infinity),
|
||||
new TileClassPainter(clHighlands));
|
||||
|
||||
g_Map.log("Creating fish");
|
||||
for (let i = 0; i < scaleByMapSize(10, 20); ++i)
|
||||
createObjectGroupsDeprecated(
|
||||
|
||||
@@ -104,15 +104,23 @@ paintRiver({
|
||||
"heightLand": heightLand,
|
||||
"meanderShort": 20,
|
||||
"meanderLong": 0,
|
||||
"landFunc": (position, shoreDist1, shoreDist2) => {
|
||||
if (waterPosition - shoreDist2 > mountainPosition)
|
||||
clMountains.add(position);
|
||||
},
|
||||
"waterFunc": (position, height, riverFraction) => {
|
||||
clWater.add(position);
|
||||
}
|
||||
});
|
||||
|
||||
g_Map.log("Marking mountain area");
|
||||
createArea(
|
||||
new ConvexPolygonPlacer(
|
||||
[
|
||||
new Vector2D(mountainPosition, mapBounds.top),
|
||||
new Vector2D(mountainPosition, mapBounds.bottom),
|
||||
new Vector2D(mapBounds.right, mapBounds.top),
|
||||
new Vector2D(mapBounds.right, mapBounds.bottom)
|
||||
].map(pos => pos.rotateAround(startAngle - Math.PI / 2, mapCenter)),
|
||||
Infinity),
|
||||
new TileClassPainter(clMountains));
|
||||
|
||||
g_Map.log("Creating shores");
|
||||
for (let i = 0; i < scaleByMapSize(20, 120); ++i)
|
||||
{
|
||||
|
||||
@@ -94,14 +94,8 @@ function PathPlacer(start, end, width, waviness, smoothness, offset, tapering, f
|
||||
|
||||
PathPlacer.prototype.place = function(constraint)
|
||||
{
|
||||
var failed = 0;
|
||||
|
||||
let pathLength = this.start.distanceTo(this.end);
|
||||
|
||||
var size = g_Map.getSize();
|
||||
var gotRet = [];
|
||||
for (var i = 0; i < size; ++i)
|
||||
gotRet[i] = new Uint8Array(size); // bool / uint8
|
||||
let numStepsWaviness = 1 + Math.floor(pathLength / 4 * this.waviness);
|
||||
let numStepsLength = 1 + Math.floor(pathLength / 4 * this.smoothness);
|
||||
let offset = 1 + Math.floor(pathLength / 4 * this.offset);
|
||||
@@ -144,87 +138,34 @@ PathPlacer.prototype.place = function(constraint)
|
||||
|
||||
let taperedWidth = (1 - step1 * this.tapering) * this.width / 2;
|
||||
|
||||
let point1 = Vector2D.sub(noiseStart, Vector2D.mult(noisePerpendicular, taperedWidth)).round();
|
||||
let point2 = Vector2D.add(noiseEnd, Vector2D.mult(noisePerpendicular, taperedWidth)).round();
|
||||
|
||||
segments1.push({ "x": point1.x, "z": point1.y });
|
||||
segments2.push({ "x": point2.x, "z": point2.y });
|
||||
segments1.push(Vector2D.sub(noiseStart, Vector2D.mult(noisePerpendicular, taperedWidth)).round());
|
||||
segments2.push(Vector2D.add(noiseEnd, Vector2D.mult(noisePerpendicular, taperedWidth)).round());
|
||||
}
|
||||
|
||||
var retVec = [];
|
||||
// Draw path segments
|
||||
var num = segments1.length - 1;
|
||||
for (var j = 0; j < num; ++j)
|
||||
let size = g_Map.getSize();
|
||||
let gotRet = new Array(size).fill(0).map(i => new Uint8Array(size));
|
||||
let retVec = [];
|
||||
let failed = 0;
|
||||
|
||||
for (let j = 0; j < segments1.length - 1; ++j)
|
||||
{
|
||||
// Fill quad formed by these 4 points
|
||||
// Note the code assumes these points have been rounded to integer values
|
||||
var pt11 = segments1[j];
|
||||
var pt12 = segments1[j+1];
|
||||
var pt21 = segments2[j];
|
||||
var pt22 = segments2[j+1];
|
||||
let points = new ConvexPolygonPlacer([segments1[j], segments1[j + 1], segments2[j], segments2[j + 1]], Infinity).place(new NullConstraint());
|
||||
if (!points)
|
||||
continue;
|
||||
|
||||
var tris = [[pt12, pt11, pt21], [pt12, pt21, pt22]];
|
||||
|
||||
for (var t = 0; t < 2; ++t)
|
||||
for (let point of points)
|
||||
{
|
||||
// Sort vertices by min z
|
||||
var tri = tris[t].sort(
|
||||
function(a, b)
|
||||
{
|
||||
return a.z - b.z;
|
||||
}
|
||||
);
|
||||
|
||||
// Fills in a line from (z, x1) to (z,x2)
|
||||
var fillLine = function(z, x1, x2)
|
||||
if (!constraint.allows(point))
|
||||
{
|
||||
var left = Math.round(Math.min(x1, x2));
|
||||
var right = Math.round(Math.max(x1, x2));
|
||||
for (var x = left; x <= right; x++)
|
||||
{
|
||||
let position = new Vector2D(x, z);
|
||||
if (constraint.allows(position))
|
||||
{
|
||||
if (g_Map.inMapBounds(position) && !gotRet[x][z])
|
||||
{
|
||||
retVec.push(position);
|
||||
gotRet[x][z] = 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
failed++;
|
||||
}
|
||||
};
|
||||
|
||||
var A = tri[0];
|
||||
var B = tri[1];
|
||||
var C = tri[2];
|
||||
|
||||
var dx1 = (B.z != A.z) ? ((B.x - A.x) / (B.z - A.z)) : 0;
|
||||
var dx2 = (C.z != A.z) ? ((C.x - A.x) / (C.z - A.z)) : 0;
|
||||
var dx3 = (C.z != B.z) ? ((C.x - B.x) / (C.z - B.z)) : 0;
|
||||
|
||||
if (A.z == B.z)
|
||||
{
|
||||
fillLine(A.z, A.x, B.x);
|
||||
++failed;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
|
||||
if (g_Map.inMapBounds(point) && !gotRet[point.x][point.y])
|
||||
{
|
||||
for (var z = A.z; z <= B.z; z++)
|
||||
{
|
||||
fillLine(z, A.x + dx1*(z - A.z), A.x + dx2*(z - A.z));
|
||||
}
|
||||
}
|
||||
if (B.z == C.z)
|
||||
{
|
||||
fillLine(B.z, B.x, C.x);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var z = B.z + 1; z < C.z; z++)
|
||||
{
|
||||
fillLine(z, B.x + dx3*(z - B.z), A.x + dx2*(z - A.z));
|
||||
}
|
||||
retVec.push(point);
|
||||
gotRet[point.x][point.y] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -269,3 +210,74 @@ RandomPathPlacer.prototype.place = function(constraint)
|
||||
|
||||
return points;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns all points on the tilegrid within the convex hull of the given positions.
|
||||
*/
|
||||
function ConvexPolygonPlacer(points, failFraction = 0)
|
||||
{
|
||||
this.polygonVertices = this.getConvexHull(points.map(point => point.clone().round()));
|
||||
this.failFraction = failFraction;
|
||||
};
|
||||
|
||||
ConvexPolygonPlacer.prototype.place = function(constraint)
|
||||
{
|
||||
let points = [];
|
||||
let count = 0;
|
||||
let failed = 0;
|
||||
|
||||
for (let point of getPointsInBoundingBox(getBoundingBox(this.polygonVertices)))
|
||||
{
|
||||
if (this.polygonVertices.some((vertex, i) =>
|
||||
distanceOfPointFromLine(this.polygonVertices[i], this.polygonVertices[(i + 1) % this.polygonVertices.length], point) > 0))
|
||||
continue;
|
||||
|
||||
++count;
|
||||
|
||||
if (g_Map.inMapBounds(point) && constraint.allows(point))
|
||||
points.push(point);
|
||||
else
|
||||
++failed;
|
||||
}
|
||||
|
||||
return failed <= this.failFraction * count ? points : undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Applies the gift-wrapping algorithm.
|
||||
* Returns a sorted subset of the given points that are the vertices of the convex polygon containing all given points.
|
||||
*/
|
||||
ConvexPolygonPlacer.prototype.getConvexHull = function(points)
|
||||
{
|
||||
let uniquePoints = [];
|
||||
for (let point of points)
|
||||
if (uniquePoints.every(p => p.x != point.x || p.y != point.y))
|
||||
uniquePoints.push(point);
|
||||
|
||||
// Start with the leftmost point
|
||||
let result = [uniquePoints.reduce((leftMost, point) => point.x < leftMost.x ? point : leftMost, uniquePoints[0])];
|
||||
|
||||
// Add the vector most left of the most recently added point until a cycle is reached
|
||||
while (result.length < uniquePoints.length)
|
||||
{
|
||||
let nextLeftmostPoint;
|
||||
|
||||
// Of all points, find the one that is leftmost
|
||||
for (let point of uniquePoints)
|
||||
{
|
||||
if (point == result[result.length - 1])
|
||||
continue;
|
||||
|
||||
if (!nextLeftmostPoint || distanceOfPointFromLine(nextLeftmostPoint, result[result.length - 1], point) <= 0)
|
||||
nextLeftmostPoint = point;
|
||||
}
|
||||
|
||||
// If it was a known one, then the remaining points are inside this hull
|
||||
if (result.indexOf(nextLeftmostPoint) != -1)
|
||||
break;
|
||||
|
||||
result.push(nextLeftmostPoint);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user