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

Figure out sign conventions #4

Closed
raphlinus opened this issue Jan 2, 2019 · 15 comments · Fixed by #47
Closed

Figure out sign conventions #4

raphlinus opened this issue Jan 2, 2019 · 15 comments · Fixed by #47

Comments

@raphlinus
Copy link
Contributor

One of the current points of confusion is whether the coordinate system is y-up or y-down. The former is more conventional in math, the latter completely standard in graphics these days (though this was less true, PostScript was y-up, as are fonts to this day).

Related issues are conventions around whether a positive area shape is clockwise or withershins, and whether a clockwise circle has positive or negative signed curvature. The winding number should match area - it should be positive if the point is inside a positive area shape. Either convention will do, but it should be consistent.

Another thing that falls in the same category is the convention of Vec2::cross. The right hand rule says right ^ up = 1, but is up (0, 1) or (0, -1)? For this, I think we pick one and document it.

I do think kurbo is interesting in both math and graphics contexts. For methods like Vec2::from_angle and Affine::rotate it's hard to just pick a convention, so I'm inclined to split them into _y_up and _y_down variants, so that the user makes an explicit choice. For the signed area stuff, I'm not sure there's a strong convention; e^{iπt} is anticlockwise in a y-up coordinate space as t increases. Do people know of other conventions we can follow? Something along the lines of JuliaLang/julia#8750 which surveys rounding conventions of many languages might be a model.

Tagging @behdad @jrus @Pomax @nical @Connicpu @kvark as they might have horses in this race.

@Pomax
Copy link

Pomax commented Jan 3, 2019

Call me crazy, but if the idea is to, basically, "be the best library" then it feels like there should be a way to bootstrap the coordinate system so that after calling the appropriate function, the library "just works" using whatever convention it should be using, given the user's need (and then I'd suggest the computer graphics convention as default, with (0,0) in the top left).

And then whether that takes the form of a static call, or per-call option, or even literally a duplicated library with the two different y conventions should probably be informed by "what performs best".

@raphlinus
Copy link
Contributor Author

@Pomax I'm open to that, but want to make sure it doesn't come at a significant cost of compile time, usability, doc clarity, etc.

Sketching this out, here are a couple ways this might look:

One is to have a zero-size type for Y direction, then instead of Affine and Vec2 you have Affine<YUp> and Vec2<YDown>. You might have From conversions that flip the sign of y as well, so you can interop. However, I fear that this might be really bad for ergonomics - I think the user would get annoyed frequently having to constantly specify this explicitly.

Another possibility is to do this at the module level: instead of use kurbo::{Affine, Vec2} as now, write use kurbo::y_up::{Affine, Vec2}. Internally that might be implemented as above.

I see the value, it feels more correct, but I'm hesitating. It's really only a small handful of methods (essentially rotations) where you might really want both conventions, and for the other stuff, as long as it's documented, users will be adapt and be happy, I think.

MathWorld defines positive area as counterclockwise, and then of course gives the shoelace formula assuming y-up. Looking at Cairo, which was designed by people I trust, they define a rectangle as clockwise (given width and height of the same sign).

So this is where I'm leaning right now, though quite open to discussion:

The convention of y-up or y-down is not specified. Positive area is widdershins in a y-up space, clockwise in y-down. I'm inclined to define theta the same way (a clockwise rotation in y-down), but I see Cairo's rotate and probably other things define it as anticlockwise, so in that case I'll have separate rotate_y_up and rotate_y_down methods.

Curvature and winding number are consistent with area: the curvature of a smooth convex path of positive area is positive, and the winding number inside a positive area path is positive. The shoelace formula as conventionally written, in xy coordinates, has the correct sign.

@behdad
Copy link

behdad commented Jan 3, 2019

I suggest defining things in terms of X/Y sign and agnostic of Y direction: when traveling in the direction of the positive X axis, then turning towards positive Y axis is a positive angle, positive curvature, results in positive area, and positive winding number.

@raphlinus
Copy link
Contributor Author

@behdad This seems simplest and most intuitive to me. I also think I got Cairo wrong, it's consistent with that, and Web Canvas (which seems to follow Cairo really closely) is the same. I'll go with this unless something radical changes my mind. Now I think the main challenge is documentation and naming; you certainly don't want to call the maximal y coordinate of a bounding box rectangle "bottom," even though that would be most natural in a y-down world.

@behdad
Copy link

behdad commented Jan 3, 2019

Right. The most commonly type is done wrong in a y-up model is to model rectangle's y with it's "top", and positive y height towards y-down... Most other shapes are intuitive.

@nical
Copy link

nical commented Jan 3, 2019

Now I think the main challenge is documentation and naming; you certainly don't want to call the maximal y coordinate of a bounding box rectangle "bottom,"

Right. We have a couple of these naming issues in euclid, which doesn't matter for servo/gecko work but other users tend to be opinionated about these things and there's been complaints.

@jrus
Copy link

jrus commented Jan 5, 2019

A quarter-turn rotation I is a unit bivector. The most straight-forward convention when dealing with a designated orthonormal basis {ex, ey} is for positive to mean the bivector I = exey, irrespective of which way on screen ey is pointing.

(Which is to say, behdad has it right here.)

An angle measure or a winding number is inherently a bivector-valued quantity, θI, oriented like the plane of rotation. We exponentiate it to get a complex-valued rotation exp(θI) = cosh(θI) + sinh(θI) = cos(θ) + Isin(θ). This generalizes to quaternions, etc. (just replace I with an arbitrary bivector). It is only for historical reasons that we describe rotations and angles using a scalar angle measure, with the planar orientation stripped away.

Vec2::cross is the same story. It really should be Vec2::wedge, as in the bivector-valued quanitity v1v2 = B = bI some bivector B which is a scalar multiple of a natural unit bivector I = exey for a designated orthonormal basis {ex, ey}. The “cross product” in general is an abomination; pretending that a bivector is really just a kind of 1-vector perpendicular to a plane instead of having the orientation of the plane itself has confused generations of undergraduate STEM students and doesn’t generalize at all to dimensions other than 3.


In e.g. SVG it is very easy to apply a global flip to the y axis by attaching a transformation to a top-level group, whereupon everything should keep working as expected for mathy folks.

When writing technical explanations of vector graphics concepts it is often a good idea IMO to work with a y-up coordinate system so that people familiar with math conventions won’t be confused, even though to achieve the same result a reader might need to apply a top-level transformation to their drawings.

raphlinus added a commit that referenced this issue Jan 9, 2019
Add `as_coeffs` method to Affine so we can get the coefficients out
(necessary for conversion to Direct2D types, etc).

Also tweak documentation to explain the convention for rotation
(partially addressing #4) and the augmented matrix representation of
Affine, particularly that it's backwards from PostScript.

This doesn't address all of #4, it doesn't change any behavior.
@raphlinus
Copy link
Contributor Author

I do want to get this right, and think the above PR is a step in the right direction.

Consensus on renaming cross to wedge?

@behdad
Copy link

behdad commented Jan 9, 2019

Consensus on renaming cross to wedge?

This is the first time I'm seeing that terminology. Which is to say, it's probably not a good idea to use it.

@behdad
Copy link

behdad commented Jan 9, 2019

Reading more.. if you are returning a vector, I believe the cross name is correct. wedge is only accurate if you truly have a bivector type.

@kvark
Copy link

kvark commented Jan 9, 2019

@behdad agreed, also relevant - kvark/mint#32

@raphlinus
Copy link
Contributor Author

MathWorld has this as cross product with the × notation. The sense I get is that the current naming is appropriate, but I could add proper bivector operations at some point.

@behdad
Copy link

behdad commented Jan 9, 2019

MathWorld has this as cross product with the × notation. The sense I get is that the current naming is appropriate, but I could add proper bivector operations at some point.

Correct. The two are related but separate concepts.

https://en.wikipedia.org/wiki/Exterior_algebra

@jrus
Copy link

jrus commented Jan 10, 2019

Reading more.. if you are returning a vector, I believe the cross name is correct. wedge is only accurate if you truly have a bivector type.

Not a vector, a scalar https://github.com/linebender/kurbo/blob/master/src/vec2.rs#L29-L35

(You want to think about the sign of the value returned from cross as well. Right now the code is what I expect but the comment suggests the opposite.)

It’s a purely conceptual thing. The scalar should be thought of as representing a plane-oriented magnitude instead of the magnitude of a vector perpendicular to the plane.

In 2D, there’s not really such a thing as a “cross product” per se.


Anyway, I don’t think it matters that particular projects use the concept of a “cross product”. There’s not even a problem with projects calling their 2-D wedge product “cross”, if they want.

I’m just griping more generally because the whole STEM community would benefit from switching away from 3D cross products, which introduce the confusing notion of two “types” of vectors (“axial” and “polar”) which behave subtly differently where the differences must be minded and every vector operation means something different depending on which types of vectors it takes as input, &c. – and which don’t generalize at all to other dimensions. Once it is pointed out axial vectors are really just bivectors in disguise, a whole bunch of previously arbitrary seeming nonsense falls away. But this is all largely off topic in this context of a 2D project/discussion.

@kud1ing
Copy link

kud1ing commented Jun 26, 2019

I've opened a PR to the wiki with a file discussing this/giving an overview: linebender/wiki#2

raphlinus added a commit that referenced this issue Jul 22, 2019
Document sign conventions in Shape. Add tests to confirm this is
indeed the behavior. Fix broken cases. Some doc touchup.

Fixes #4
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 a pull request may close this issue.

7 participants