From e06078c54506145f4ab9d9c29b5f26b11d4972ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Sun, 5 Mar 2023 14:21:01 +0100 Subject: [PATCH 1/4] intercept --- src/voronoi.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/voronoi.js b/src/voronoi.js index 5764eae..aaa2c3b 100644 --- a/src/voronoi.js +++ b/src/voronoi.js @@ -294,6 +294,11 @@ export default class Voronoi { } return j; } + // Returns the intercept of a line passing through (x0, y0) and (x1, y1) + // with the horizontal line y = ym. + _intercept(x0, y0, x1, y1, ym) { + return (x0 * (y1 - ym) + x1 * (ym - y0)) / (y1 - y0); + } _project(x0, y0, vx, vy) { let t = Infinity, c, x, y; if (vy < 0) { // top From b2466b13ae6db9376afec4673bd0fe29623bbff1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Sun, 5 Mar 2023 14:21:18 +0100 Subject: [PATCH 2/4] robust intercept --- src/voronoi.js | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/voronoi.js b/src/voronoi.js index aaa2c3b..f2f90e1 100644 --- a/src/voronoi.js +++ b/src/voronoi.js @@ -152,17 +152,17 @@ export default class Voronoi { return this.delaunay._step(i, x, y) === i; } *neighbors(i) { - const epsilon = 1e-12; const ci = this._clip(i); if (ci) for (const j of this.delaunay.neighbors(i)) { const cj = this._clip(j); // find the common edge if (cj) loop: for (let ai = 0, li = ci.length; ai < li; ai += 2) { for (let aj = 0, lj = cj.length; aj < lj; aj += 2) { - if (Math.abs(ci[ai] - cj[aj]) < epsilon - && Math.abs(ci[ai + 1] - cj[aj + 1]) < epsilon - && Math.abs(ci[(ai + 2) % li] - cj[(aj + lj - 2) % lj]) < epsilon - && Math.abs(ci[(ai + 3) % li] - cj[(aj + lj - 1) % lj]) < epsilon) { + if (ci[ai] == cj[aj] + && ci[ai + 1] == cj[aj + 1] + && ci[(ai + 2) % li] == cj[(aj + lj - 2) % lj] + && ci[(ai + 3) % li] == cj[(aj + lj - 1) % lj] + ) { yield j; break loop; } @@ -239,14 +239,17 @@ export default class Voronoi { return P; } _clipSegment(x0, y0, x1, y1, c0, c1) { + // for more robustness, always consider the segment in the same order + const flip = y0 < y1 || (y0 === y1 && x0 < x1); + if (flip) [x0, y0, x1, y1, c0, c1] = [x1, y1, x0, y0, c1, c0]; while (true) { - if (c0 === 0 && c1 === 0) return [x0, y0, x1, y1]; + if (c0 === 0 && c1 === 0) return flip ? [x1, y1, x0, y0] : [x0, y0, x1, y1]; if (c0 & c1) return null; let x, y, c = c0 || c1; - if (c & 0b1000) x = x0 + (x1 - x0) * (this.ymax - y0) / (y1 - y0), y = this.ymax; - else if (c & 0b0100) x = x0 + (x1 - x0) * (this.ymin - y0) / (y1 - y0), y = this.ymin; - else if (c & 0b0010) y = y0 + (y1 - y0) * (this.xmax - x0) / (x1 - x0), x = this.xmax; - else y = y0 + (y1 - y0) * (this.xmin - x0) / (x1 - x0), x = this.xmin; + if (c & 0b1000) x = this._intercept(x0, y0, x1, y1, y = this.ymax); + else if (c & 0b0100) x = this._intercept(x0, y0, x1, y1, y = this.ymin); + else if (c & 0b0010) y = this._intercept(y0, x0, y1, x1, x = this.xmax); + else y = this._intercept(y0, x0, y1, x1, x = this.xmin); if (c0) x0 = x, y0 = y, c0 = this._regioncode(x0, y0); else x1 = x, y1 = y, c1 = this._regioncode(x1, y1); } From fa1b634431c62d4686b947055e389ba4cba1004a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Tue, 7 Mar 2023 06:30:34 +0100 Subject: [PATCH 3/4] _intercept is not needed; we can use c0 and c1 to order; more tests --- src/voronoi.js | 15 +++++---------- test/voronoi-test.js | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/voronoi.js b/src/voronoi.js index f2f90e1..80c21df 100644 --- a/src/voronoi.js +++ b/src/voronoi.js @@ -240,16 +240,16 @@ export default class Voronoi { } _clipSegment(x0, y0, x1, y1, c0, c1) { // for more robustness, always consider the segment in the same order - const flip = y0 < y1 || (y0 === y1 && x0 < x1); + const flip = c0 < c1; if (flip) [x0, y0, x1, y1, c0, c1] = [x1, y1, x0, y0, c1, c0]; while (true) { if (c0 === 0 && c1 === 0) return flip ? [x1, y1, x0, y0] : [x0, y0, x1, y1]; if (c0 & c1) return null; let x, y, c = c0 || c1; - if (c & 0b1000) x = this._intercept(x0, y0, x1, y1, y = this.ymax); - else if (c & 0b0100) x = this._intercept(x0, y0, x1, y1, y = this.ymin); - else if (c & 0b0010) y = this._intercept(y0, x0, y1, x1, x = this.xmax); - else y = this._intercept(y0, x0, y1, x1, x = this.xmin); + if (c & 0b1000) x = x0 + (x1 - x0) * (this.ymax - y0) / (y1 - y0), y = this.ymax; + else if (c & 0b0100) x = x0 + (x1 - x0) * (this.ymin - y0) / (y1 - y0), y = this.ymin; + else if (c & 0b0010) y = y0 + (y1 - y0) * (this.xmax - x0) / (x1 - x0), x = this.xmax; + else y = y0 + (y1 - y0) * (this.xmin - x0) / (x1 - x0), x = this.xmin; if (c0) x0 = x, y0 = y, c0 = this._regioncode(x0, y0); else x1 = x, y1 = y, c1 = this._regioncode(x1, y1); } @@ -297,11 +297,6 @@ export default class Voronoi { } return j; } - // Returns the intercept of a line passing through (x0, y0) and (x1, y1) - // with the horizontal line y = ym. - _intercept(x0, y0, x1, y1, ym) { - return (x0 * (y1 - ym) + x1 * (ym - y0)) / (y1 - y0); - } _project(x0, y0, vx, vy) { let t = Infinity, c, x, y; if (vy < 0) { // top diff --git a/test/voronoi-test.js b/test/voronoi-test.js index 9cc1cb1..c543a98 100644 --- a/test/voronoi-test.js +++ b/test/voronoi-test.js @@ -88,3 +88,21 @@ it("voronoi.neighbors returns the correct neighbors", () => { const voronoi = Delaunay.from(points).voronoi([0, 0, 100, 90]); assert.deepStrictEqual([0, 1, 2, 3].map(i => [...voronoi.neighbors(i)].sort()), [[1], [0, 2, 3], [1, 3], [1, 2]]); }); + +it("voronoi.neighbors returns the correct neighbors, flipped vertically", () => { + const points = [[10, -10], [36, -27], [90, -19], [50, -75]]; + const voronoi = Delaunay.from(points).voronoi([0, -90, 100, 0]); + assert.deepStrictEqual([0, 1, 2, 3].map(i => [...voronoi.neighbors(i)].sort()), [[1], [0, 2, 3], [1, 3], [1, 2]]); +}); + +it("voronoi.neighbors returns the correct neighbors, flipped horizontally", () => { + const points = [[-10, 10], [-36, 27], [-90, 19], [-50, 75]]; + const voronoi = Delaunay.from(points).voronoi([-100, 0, 0, 90]); + assert.deepStrictEqual([0, 1, 2, 3].map(i => [...voronoi.neighbors(i)].sort()), [[1], [0, 2, 3], [1, 3], [1, 2]]); +}); + +it("voronoi.neighbors returns the correct neighbors, rotated", () => { + const points = [[-10, -10], [-36, -27], [-90, -19], [-50, -75]]; + const voronoi = Delaunay.from(points).voronoi([-100, -90, 0, 0]); + assert.deepStrictEqual([0, 1, 2, 3].map(i => [...voronoi.neighbors(i)].sort()), [[1], [0, 2, 3], [1, 3], [1, 2]]); +}); From 87f41ad63ca081b2981f25116da74bea7aa38e36 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Tue, 7 Mar 2023 10:00:33 -0500 Subject: [PATCH 4/4] strict equality, prettier --- src/voronoi.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/voronoi.js b/src/voronoi.js index 80c21df..200394d 100644 --- a/src/voronoi.js +++ b/src/voronoi.js @@ -158,11 +158,10 @@ export default class Voronoi { // find the common edge if (cj) loop: for (let ai = 0, li = ci.length; ai < li; ai += 2) { for (let aj = 0, lj = cj.length; aj < lj; aj += 2) { - if (ci[ai] == cj[aj] - && ci[ai + 1] == cj[aj + 1] - && ci[(ai + 2) % li] == cj[(aj + lj - 2) % lj] - && ci[(ai + 3) % li] == cj[(aj + lj - 1) % lj] - ) { + if (ci[ai] === cj[aj] + && ci[ai + 1] === cj[aj + 1] + && ci[(ai + 2) % li] === cj[(aj + lj - 2) % lj] + && ci[(ai + 3) % li] === cj[(aj + lj - 1) % lj]) { yield j; break loop; }