Voronoi图。如何获取/绘制《财富》扫描线算法的基线?

时间:2019-02-24 20:26:25

标签: javascript algorithm voronoi

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:我知道在算法的某些部分中可以使用树搜索来存储数据,但是由于这是更高级的方法,因此我想将其简单地存储在数组中,那么我将自己学习如何进行“升级”。

0 个答案:

没有答案