From e9f12bc9d530fcf55ac278c07e3aeb7ae46e25f9 Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Wed, 22 May 2019 17:09:09 +0300 Subject: [PATCH 1/3] a much more accurate algorithm for approximating round line joins --- src/data/bucket/line_bucket.js | 41 +++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/src/data/bucket/line_bucket.js b/src/data/bucket/line_bucket.js index 412a56e57d4..399c33991f3 100644 --- a/src/data/bucket/line_bucket.js +++ b/src/data/bucket/line_bucket.js @@ -52,6 +52,9 @@ const EXTRUDE_SCALE = 63; const COS_HALF_SHARP_CORNER = Math.cos(75 / 2 * (Math.PI / 180)); const SHARP_CORNER_OFFSET = 15; +// Angle per triangle for approximating round line joins. +const DEG_PER_TRIANGLE = 30; + // The number of bits that is used to store the line distance in the buffer. const LINE_DISTANCE_BUFFER_BITS = 15; @@ -331,12 +334,17 @@ class LineBucket implements Bucket { * */ - // Calculate the length of the miter (the ratio of the miter to the width). - // Find the cosine of the angle between the next and join normals - // using dot product. The inverse of that is the miter length. + // calculate cosines of the angle (and its half) using dot product + const cosAngle = prevNormal.x * nextNormal.x + prevNormal.y * nextNormal.y; const cosHalfAngle = joinNormal.x * nextNormal.x + joinNormal.y * nextNormal.y; + + // Calculate the length of the miter (the ratio of the miter to the width) + // as the inverse of cosine of the angle between next and join normals const miterLength = cosHalfAngle !== 0 ? 1 / cosHalfAngle : Infinity; + // approximate angle from cosine + const approxAngle = 2 * Math.sqrt(2 - 2 * cosHalfAngle); + const isSharpCorner = cosHalfAngle < COS_HALF_SHARP_CORNER && prevVertex && nextVertex; if (isSharpCorner && i > first) { @@ -420,21 +428,18 @@ class LineBucket implements Bucket { // Create a round join by adding multiple pie slices. The join isn't actually round, but // it looks like it is at the sizes we render lines at. - // Add more triangles for sharper angles. - // This math is just a good enough approximation. It isn't "correct". - const n = Math.floor((0.5 - (cosHalfAngle - 0.5)) * 8); - let approxFractionalJoinNormal; - - for (let m = 0; m < n; m++) { - approxFractionalJoinNormal = nextNormal.mult((m + 1) / (n + 1))._add(prevNormal)._unit(); - this.addPieSliceVertex(currentVertex, this.distance, approxFractionalJoinNormal, lineTurnsLeft, segment, lineDistances); - } - - this.addPieSliceVertex(currentVertex, this.distance, joinNormal, lineTurnsLeft, segment, lineDistances); - - for (let k = n - 1; k >= 0; k--) { - approxFractionalJoinNormal = prevNormal.mult((k + 1) / (n + 1))._add(nextNormal)._unit(); - this.addPieSliceVertex(currentVertex, this.distance, approxFractionalJoinNormal, lineTurnsLeft, segment, lineDistances); + // pick the number of triangles for approximating round join by based on the angle between normals + const n = Math.round((approxAngle * 180 / Math.PI) / DEG_PER_TRIANGLE); + + for (let m = 1; m < n; m++) { + const t = m / n; + // approximate spherical interpolation https://observablehq.com/@mourner/approximating-geometric-slerp + const t2 = t - 0.5; + const A = 1.0904 + cosAngle * (-3.2452 + cosAngle * (3.55645 - cosAngle * 1.43519)); + const B = 0.848013 + cosAngle * (-1.06021 + cosAngle * 0.215638); + const ot = t + t * t2 * (t - 1) * (A * t2 * t2 + B); + const approxFractionalNormal = prevNormal.mult(1 - ot)._add(nextNormal.mult(ot))._unit(); + this.addPieSliceVertex(currentVertex, this.distance, approxFractionalNormal, lineTurnsLeft, segment, lineDistances); } } From 858e45a79b521b13f5d6b79359aacaeab0dad943 Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Wed, 22 May 2019 18:11:29 +0300 Subject: [PATCH 2/3] fast path for even-triangle round join approximation --- src/data/bucket/line_bucket.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/data/bucket/line_bucket.js b/src/data/bucket/line_bucket.js index 399c33991f3..507c300ef21 100644 --- a/src/data/bucket/line_bucket.js +++ b/src/data/bucket/line_bucket.js @@ -432,13 +432,15 @@ class LineBucket implements Bucket { const n = Math.round((approxAngle * 180 / Math.PI) / DEG_PER_TRIANGLE); for (let m = 1; m < n; m++) { - const t = m / n; - // approximate spherical interpolation https://observablehq.com/@mourner/approximating-geometric-slerp - const t2 = t - 0.5; - const A = 1.0904 + cosAngle * (-3.2452 + cosAngle * (3.55645 - cosAngle * 1.43519)); - const B = 0.848013 + cosAngle * (-1.06021 + cosAngle * 0.215638); - const ot = t + t * t2 * (t - 1) * (A * t2 * t2 + B); - const approxFractionalNormal = prevNormal.mult(1 - ot)._add(nextNormal.mult(ot))._unit(); + let t = m / n; + if (t !== 0.5) { + // approximate spherical interpolation https://observablehq.com/@mourner/approximating-geometric-slerp + const t2 = t - 0.5; + const A = 1.0904 + cosAngle * (-3.2452 + cosAngle * (3.55645 - cosAngle * 1.43519)); + const B = 0.848013 + cosAngle * (-1.06021 + cosAngle * 0.215638); + t = t + t * t2 * (t - 1) * (A * t2 * t2 + B); + } + const approxFractionalNormal = prevNormal.mult(1 - t)._add(nextNormal.mult(t))._unit(); this.addPieSliceVertex(currentVertex, this.distance, approxFractionalNormal, lineTurnsLeft, segment, lineDistances); } } From 655559f387191044f1027339b78c55c25e587d41 Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Tue, 30 Jul 2019 20:00:58 +0300 Subject: [PATCH 3/3] increase round join approximation precision --- src/data/bucket/line_bucket.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/bucket/line_bucket.js b/src/data/bucket/line_bucket.js index 507c300ef21..15d02877dc7 100644 --- a/src/data/bucket/line_bucket.js +++ b/src/data/bucket/line_bucket.js @@ -53,7 +53,7 @@ const COS_HALF_SHARP_CORNER = Math.cos(75 / 2 * (Math.PI / 180)); const SHARP_CORNER_OFFSET = 15; // Angle per triangle for approximating round line joins. -const DEG_PER_TRIANGLE = 30; +const DEG_PER_TRIANGLE = 20; // The number of bits that is used to store the line distance in the buffer. const LINE_DISTANCE_BUFFER_BITS = 15;