Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A more accurate algorithm for approximating round line joins #8275

Merged
merged 3 commits into from
Jul 30, 2019
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 25 additions & 18 deletions src/data/bucket/line_bucket.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = 20;

// The number of bits that is used to store the line distance in the buffer.
const LINE_DISTANCE_BUFFER_BITS = 15;

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -420,21 +428,20 @@ 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++) {
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);
}
}

Expand Down