penrose
penrose copied to clipboard
Generic transformations
As mentioned in https://github.com/penrose/penrose/issues/478#issuecomment-952312338 by @joshsunshine:
Penrose should allow generic transformations, including transformations of any shape. These transformations should be consistent and expressive.
Related:
- #709
- #712
- #724
Here we should lean heavily on the way SVG performs transformations (especially since this is our main output format): https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform
In past discussions, I don't think we've done a great job of separating out two questions:
- Internal representation of transformations. How should the layout and rendering engine represent transformations? Here the right choice in most graphics systems is to have a uniform encoding of all transformations, via transformation matrices. This way we don't need heterogeneous/redundant logic on how to rotate a rectangle vs. stretch an ellipse vs. ... Everything just boils down to application and differentiation of matrices.
- External interface to transformations. This has been the biggest debate in the past: how should Style programmers specify transformations? Should we give them natural parameters like
angle:, or should we ask them to define transformation matrices, or should we do something in between? I think the answer here is "all of the above"—but: all transformations should ultimately get transformed into matrices.
A uniform interface in style might look like
Shape {
transform: rotate(30.) then translate(10.,20.) then mat3( 1., 0., 4; 0., 1., 5.; 0., 0., 1. )
}
This line composes three transformations: a rotation by 30 degrees, a translation by 10 in the x-direction and 20 in the y-direction, and then another transformation given by a 3x3 matrix in homogeneous coordinates (here representing a translation by (4,5)). Internally each of these gets turned into a transformation matrix, and these matrices are multiplied. (Note by the way the use of the then keyword, which is critical for disambiguating the composition order. Many graphics APIs assume left-to-right or right-to-left without making this composition order clear from the syntax.)
There are some semantics to work out. For instance, I don't think we should ditch parameters like center and radius for a circle, even though these parameters could in principle be emulated by applying a translation and scale (respectively) to a canonical circle, like the unit circle centered at the origin. Instead, I would adopt this mental model:
- The shape parameters (like
centerandradius) define the reference shape. - All subsequent transformations get applied to this reference shape.
Especially important is the meaning of center. For instance, should transformations get applied relative to the shape's center? Seems like that would be the best way to satisfy the "principle of least surprise." Though we have to keep in mind that a rotation matrix by default represents a rotation around the origin (0,0). So, consider a shape like this one:
Rectangle {
center: (10.,20.)
w: 15.
h: 5.
transform: rotate(30.)
}
The transformation we actually need to apply here is:
- first translate by
-center - then apply a rotation by 30 degrees
- then translate by
+centerThis is not hard to implement internally, and the programmer never has to think about it. They simply need to know that "all transformations are expressed relative to the given center."
Another related issue is that we at some point need to support groups, a la the SVG group tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g
Here we will need to think carefully about how transformations get applied to groups, and to elements within those groups. Basically this is the idea of a scene graph: https://en.wikipedia.org/wiki/Scene_graph.
Closing in favor of #1329.