Skip to content

Comments

3D circle center through 3 points#1029

Open
SimonAndreys wants to merge 3 commits intocetz-package:masterfrom
SimonAndreys:3D_circle_center
Open

3D circle center through 3 points#1029
SimonAndreys wants to merge 3 commits intocetz-package:masterfrom
SimonAndreys:3D_circle_center

Conversation

@SimonAndreys
Copy link

Re-wrote the function calculate-circle-center-3pt in src/utils.typ
Previously, this function took three 3D vectors as input but was essentially 2D, taking the z-coordinate from the first vector. Now the output is the center of the circle passing through the 3 point even if they have not the same z-coordinates.
Note : if two of the points are equal, there is no uniqueness of the circle center. Originally it returned the center passing through the 2 different points with x-coordinate 0, which seems arbitrary. Now it returns the midpoint between the two different points.
The function fails if the 3 points are aligned and different.
This is a first step towards making arc-through and circle-through work with 3D points with different z-coordinates.
All test passed.

@johannes-wolf johannes-wolf self-requested a review January 31, 2026 12:52
@johannes-wolf johannes-wolf added this to the 0.5.0 milestone Jan 31, 2026
Copy link
Member

@johannes-wolf johannes-wolf left a comment

Choose a reason for hiding this comment

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

Thank you! Some small things to fix. I have not yet tested it.

src/util.typ Outdated
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 ligne-pt(a, c, 0.5) } else if a == c { return line-pt(a, b, 0.5) }
Copy link
Member

Choose a reason for hiding this comment

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

Typo: ligne-ptline-pt – maybe we should change that to vector.lerp.

src/util.typ Outdated
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
assert(
Copy link
Member

Choose a reason for hiding this comment

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

Since Typst evaluates all arguments of assert, this should be a panic inside an if.

+ str(c.at(2))
+ ") ",
)
let lambda = w2 * (v2 - vw) / denom
Copy link
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
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.

src/util.typ Outdated
// If two points are the same we take the midpoint with the two other.
if a == b or b == c { return ligne-pt(a, c, 0.5) } else if a == c { return line-pt(a, b, 0.5) }
// we compute the vectors b-a and c-a and the norm of their cross product
let (vx, vy, vz) = (0, 1, 2).map(i => b.at(i) - a.at(i)) // v=b-a
Copy link
Member

Choose a reason for hiding this comment

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

You can use range(0, 2) instead of (0, 1, 2) – same for line 114 and 145.

@johannes-wolf johannes-wolf self-requested a review February 2, 2026 10:01
@johannes-wolf
Copy link
Member

johannes-wolf commented Feb 2, 2026

Ok, I've tested the changes. There is a problem with circle-through: We have to transform the circle. This is tricky because of the anchors; I've played around with it and the problem is, if the normal of the plane the circle is on points backwards (negative z), the anchor rotation flips – which is expected but a breaking change.

The current implementation does not take the point order into account, but if we want to support 3D circles, we cannot have a „default“ behavior that always works.

One option would be switching to the current behavior if the normal is $\lambda \cdot e_3, \lambda \in\mathbb{R}$.

@SimonAndreys
Copy link
Author

I understand, for any circle there are two choices of "normal direction" (defining "up" and so defining what is a counterclockwise rotation). When you flip the circle you end up with angles with opposite order (anti-trigonometric), and maybe 0deg exchanged with 180deg.
I feel like having a switch in behavior for horizontal circles would be confusing. I think the switch in behavior should be controlled by the user.
Here is how I see it : we have some "oriented 3D circle" object, which consists in a circle, a choice of a normal direction, and a "0deg" reference point on the circle, thus allowing well defined anchors. Then circle-through(a, b, c) can have two behaviors :

  1. by default, it returns the oriented circle where we chose the normal direction to be the one with positive z-coordinate and the reference point to be the one with highest x-coordinate (and some convention for when the circle is in a "vertical" plane). Some discontinuity happens when the circle gets vertical but at least we have the usual behavior for horizontal circles.
  2. The "advanced" behavior which depends on the order of the points : it would return an oriented circle with normal vector (b-a)cross(c-a) and with reference point a. This way we do not have discontinuity, but we don't have the default behavior even when the circle is horizontal.

@johannes-wolf
Copy link
Member

Sounds good!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants