Skip to content
Open
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
57 changes: 18 additions & 39 deletions src/util.typ
Original file line number Diff line number Diff line change
Expand Up @@ -99,53 +99,32 @@
return (calc.cos(angle) * rx + x, calc.sin(angle) * ry + y, z)
}

/// Calculates the center of a circle from 3 points. The z coordinate is taken from point a.
/// Calculates the center of a circle from 3 points. The points are 3D, the center is in the plane containing the 3 points. It fails if the points are aligned and distinct. If two of the points are the same, the circle center is not unique; we take the midpoint.
/// The center p is computed by solving for scalars lambda, mu such that p=a+lambda(b-a)+mu(c-a). Using the scalar product we obtain that lambda=||w||^2(||v||^2-scal(v,w))/denom where v=b-a, w=c-a and denom=2(||v||^2||w||^2-scal(v,w)^2), and mu has the reverse formula. denom is zero iff a, b, c are aligned.
///
/// - a (vector): Point 1
/// - b (vector): Point 2
/// - c (vector): Point 3
/// -> vector
#let calculate-circle-center-3pt(a, b, c) = {
let m-ab = line-pt(a, b, .5)
let m-bc = line-pt(b, c, .5)
let m-cd = line-pt(c, a, .5)

let args = () // a, c, b, d
for i in range(0, 3) {
let (p1, p2) = ((a,b,c).at(calc.rem(i,3)),
(b,c,a).at(calc.rem(i,3)))
let m = line-pt(p1, p2, .5)
let n = line-normal(p1, p2)

// Find a line with a non upwards normal
if n.at(0) == 0 { continue }

let la = n.at(1) / n.at(0)
args.push(la)
args.push(m.at(1) - la * m.at(0))

// We need only 2 lines
if args.len() == 4 { break }
}
// If two points are the same we take the midpoint with the two other.
if a == b or b == c { return vector.lerp(a, c, 0.5) } else if a == c { return vector.lerp(a, b, 0.5) }
// we compute the vectors b-a and c-a and the norm of their cross product
let (vx, vy, vz) = range(3).map(i => b.at(i) - a.at(i)) // v=b-a
let (wx, wy, wz) = range(3).map(i => c.at(i) - a.at(i)) // w=c-a
let v2 = (vx * vx + vy * vy + vz * vz) // ||v||^2
let w2 = (wx * wx + wy * wy + wz * wz) // ||w||^2
let vw = vx * wx + vy * wy + vz * wz // <v, w>
let denom = 2 * (v2 * w2 - calc.pow(vw, 2)) // 2*norm of "v cross w"
// if the points are aligned, we fail with error message returning the coordinates of the points
if denom==0 {panic( "The points are aligned, and no two points are equal, so the circle center is at infinity. Coordinates: a=(" + str(a.at(0)) + ", " + str(a.at(1)) + ", " + str(a.at(2)) + ") and b =(" + str(b.at(0)) + ", " + str(b.at(1)) + ", " + str(b.at(2)) + "), and c = ( " + str(c.at(0)) + ", " + str(c.at(1)) + ", " + str(c.at(2)) + ") ",)}
let lambda = w2 * (v2 - vw) / denom
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indentation is off.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I use typstyle to format the code, linebreaks and indentation are automatic... I put this string back to one line.

let mu = v2 * (w2 - vw) / denom
let p = range(3).map(i => lambda * (vx, vy, vz).at(i) + mu * (wx, wy, wz).at(i) + a.at(i)) // p=lambda v+ mu w+a
return p
}

// Find intersection point of two 2d lines
// L1: a*x + c
// L2: b*x + d
let line-intersection-2d(a, c, b, d) = {
if a - b == 0 {
if c == d {
return (0, c, 0)
}
return none
}
let x = (d - c)/(a - b)
let y = a * x + c
return (x, y)
}

assert(args.len() == 4, message: "Could not find circle center")
return vector.as-vec(line-intersection-2d(..args), init: (0, 0, a.at(2)))
}

/// Converts a {{number}} to "canvas units"
/// - ctx (context): The current context object.
Expand Down
Loading