在this guide之后,我试图实现一种Fortune算法,以生成Voronoi图。这个想法既可以在画布上表示输出,也可以将输出作为存储的数据获取,但是我在遵循指南时遇到了问题。
这是我当前的代码。按run
查看视觉输出。
function Metric(x, y, mt) {
if (mt==1) { // Euclidean
return Math.sqrt(x*x + y*y)
}
if (mt==2) { // Manhattan
return Math.abs(x) + Math.abs(y)
}
if (mt==3) { // Minkovski
return(Math.pow(Math.pow(Math.abs(x),3) + Math.pow(Math.abs(y),3),0.33333))
}
}
function Distance(p1, p2, mt) {
return Metric(p1[0], p1[1], mt) - Metric(p2[0], p2[1], mt);
}
function Voronoi(width, height, points) {
this.canvas = document.getElementById('canvas');
this.context = this.canvas.getContext('2d');
this.canvas.width = this.width = width;
this.canvas.height = this.height = height;
this.points = points;
this.sweepline = 0;
this.sitesInsideSweepline = [];
}
Voronoi.prototype.GenerateRandomPoints = function() {
let points = [];
for (let i=0; i < this.points; i++) {
points.push([Math.round(Math.random() * this.width), Math.round(Math.random() * this.height)]);
}
return points;
}
Voronoi.prototype.GenerateRandomSortedPoints = function() {
this.generatedSites = this.GenerateRandomPoints().sort(function(a, b) {
let keyA = a[1];
let keyB = b[1];
if (keyA < keyB) return -1;
if (keyA > keyB) return 1;
return 0;
});
}
Voronoi.prototype.ParabolaY = function(p1, x) {
return (1/(2 * (p1[1] - this.sweepline))) * Math.pow(x - p1[0], 2) + (p1[1] + this.sweepline)/2;
}
Voronoi.prototype.DrawPoint = function(p, r, g, b) {
this.context.fillStyle = "rgb("+r+","+g+","+b+")";
this.context.strokeStyle = "rgb("+r+","+g+","+b+")";
this.context.fillRect(p[0], p[1], 5, 5);
}
Voronoi.prototype.DrawSites = function() {
for(let site of this.generatedSites) {
this.DrawPoint(site, 255, 0, 0);
}
}
Voronoi.prototype.DrawLine = function(p1, p2, r, g, b) {
this.context.strokeStyle = "rgb("+r+","+g+","+b+")";
this.context.beginPath();
this.context.moveTo(p1[0], p1[1]);
this.context.lineTo(p2[0], p2[1]);
this.context.stroke();
}
Voronoi.prototype.DrawParabola = function(p1) {
this.context.strokeStyle = "rgb(0, 0, 255)";
this.context.beginPath();
this.context.moveTo(0, 0);
for (let x=0; x < this.width; x++) {
this.context.lineTo(x, this.ParabolaY(p1, x));
}
this.context.stroke();
}
Voronoi.prototype.DrawIntersection = function(p1, p2) {
let [c, i] = p1,
[k, j] = p2,
l = this.sweepline;
let px = (-Math.sqrt(Math.pow((2*k)/(j-l) - (2*c)/(i-l),2) - 4*(1/(i-l) - 1/(j-l))*((c*c)/(i-l) + i - (k*k)/(j-l)- j)) + (2*c)/(i-l) - (2*k)/(j-l))/(2*(1/(i-l) - 1/(j-l)));
let qx = (Math.sqrt(Math.pow((2*k)/(j-l) - (2*c)/(i-l),2) - 4*(1/(i-l) - 1/(j-l))*((c*c)/(i-l) + i - (k*k)/(j-l)- j)) + (2*c)/(i-l) - (2*k)/(j-l))/(2*(1/(i-l) - 1/(j-l)));
let py = this.ParabolaY(p1, px);
let qy = this.ParabolaY(p1, qx);
this.DrawPoint([px, py], 0, 255, 0);
this.DrawPoint([qx, qy], 0, 255, 0);
this.context.strokeStyle = "rgb(255, 0, 0)";
this.context.beginPath();
this.context.moveTo(px, py);
for (let x=px; x<qx; x++) {
this.context.lineTo(x, this.ParabolaY(p1, x));
}
this.context.stroke();
return [[px, py], [qx, qy], p1, p2];
}
Voronoi.prototype.DrawFrame = function(deltaTime) {
// Clean the canvas
this.context.clearRect(0, 0, this.width, this.height);
// Draw dots
this.DrawSites();
// Move and draw sweepline (that green line)
this.sweepline += deltaTime;
this.DrawLine([0, this.sweepline], [this.width, this.sweepline], 0, 255, 0);
// Check which sites are above or below the sweepline, remove them and store them
if (this.sites.length > 0) {
if (this.sites[0][1] < this.sweepline) {
this.sitesInsideSweepline.push(this.sites.shift());
}
}
// Check and draw sites parabolas and intersections (points where two parabolas touch)
let intersections = [];
for (let site of this.sitesInsideSweepline) {
this.DrawParabola(site);
for (let site2 of this.sitesInsideSweepline) {
if (site != site2) {
intersections.push(this.DrawIntersection(site, site2));
}
}
}
// ATTEMPT TO MAKE BEACHLINE -START-
let intersectionPlain = [];
for (let intersection of intersections) {
intersectionPlain.push([intersection[0], intersection[2], intersection[3]]);
intersectionPlain.push([intersection[1], intersection[2], intersection[3]]);
}
// Try to check which intersections are the "frontline" or beach of the graph
let beachline = [];
for (let intersection of intersectionPlain) {
let best = intersection;
for (let intersection2 of intersectionPlain) {
if (best != intersection2) {
if (this.ParabolaY(intersection2[0], best[0][0]) > best[0][1]) {
best = intersection2;
}
}
}
if (!beachline.includes(best)) {
beachline.push(best);
}
}
beachline = beachline.sort(function(a, b) {
let keyA = a[0][0];
let keyB = b[0][0];
if (keyA < keyB) return -1;
if (keyA > keyB) return 1;
return 0;
});
// Try to draw them
for (let i=0; i <= (beachline.length-2); i++) {
let p1 = beachline[i];
let p2 = beachline[i+1];
// Beach points
this.DrawPoint(p1[0], 0, 0, 255);
this.DrawPoint(p2[0], 0, 0, 255);
// Beach arcs
this.context.strokeStyle = "rgb(0, 255, 0)";
this.context.beginPath();
this.context.moveTo(p1[0][0], p1[0][1]);
for (let x=p1[0][0]; x<p2[0][0]; x++) {
this.context.lineTo(x, this.ParabolaY(p1[1], x));
}
this.context.stroke();
}
// ATTEMPT TO MAKE BEACHLINE -END-
/*for (let p of beachline) {
for (let q of beachline) {
console.log(q, p);
let DisQP = Distance(q, p, 1);
let DisQS = q[1] - this.sweepline;
if (DisQP < DisQS) {
console.log(q, "above", p);
} else if (DisQP == DisQS) {
console.log(q, "on", p);
} else if (DisQP > DisQS) {
console.log(q, "below", p);
}
}
}*/
if (this.sweepline >= this.height) {
this.sweepline = 0;
this.sitesInsideSweepline = [];
this.sites = Array.from(this.generatedSites);
}
}
Voronoi.prototype.Run = function() {
this.GenerateRandomSortedPoints();
this.sites = Array.from(this.generatedSites);
let t = this;
setInterval(function(deltaTime){
t.DrawFrame(deltaTime);
}, 20, 2);
}
document.addEventListener("DOMContentLoaded", function(event) {
let voronoi = new Voronoi(500, 500, 5);
voronoi.Run();
});
<html>
<body>
<canvas id="canvas"></canvas>
</body>
</html>
(然后,该代码的起始函数位于document.addEventListener("DOMContentLoaded", function(event) {...})
的结尾。
如您所见,我能够生成抛物线并找到每个抛物线与另一个抛物线的截距。而且,我能够用不同的颜色绘制这些线条。现在,下一步是找到海滩线,但我不知道如何实现。
我认为我必须遍历每个拦截,并且以某种方式无法弄清哪些对(及其弧线)是否在海滩线上,也许是从拦截中遍历Y轴的每个点或再次迭代并检查值,但我不知道该怎么做。我试图这样做,但是效果不佳。
与链接指南中的当前指南相比,我需要更好的指南来获得优势。我没有数学背景,所以不知道如何计算。我使用WolframAlpha方程求解器(其中i=y1; j=y2; c=x1; k=x2; x=sweepline.y
)发现了截取两个抛物线的公式。
PD:我知道在算法的某些部分中可以使用树搜索来存储数据,但是由于这是更高级的方法,因此我想将其简单地存储在数组中,那么我将自己学习如何进行“升级”。