Vectors and Matrices
Style supports dense n-dimensional vector and matrix types, and standard operations on these types. These types behave largely like small, dense matrix types found in other languages (such as GLSL), with some specific differences noted below. Note that these types are meant largely for manipulating small 2D, 3D, and 4D vectors/matrices in the context of standard graphics operations (transformations, perspective projection, etc.), and may not perform well for larger matrix manipulations. Like all other objects in Style, the value of any vector or matrix entry can be declared as unknown (?
) and determined automatically via optimization by the layout engine.
Additional vector and matrix functions may be available—refer to the function library for additional information.
Vector and matrix types
Style is designed to support n-dimensional dense vectors of type vecN
, and square n-dimensional matrices of type matNxN
, where in both cases N
is an integer greater than or equal to 2. Some types commonly used for diagramming are vec2
(representing points in the plane) and mat3x3
(representing linear transformations of three-dimensional space).
Note that some library functions are meaningful only for vectors or matrices of a specific size. For instance, the function cross(u,v)
computes a cross product, which is well-defined only in three dimensions, and hence assumes that both u
and v
have type vec3
.
Initializing vectors and matrices
A vector is constructed by specifying its components. For instance,
vec2 u = (1.23, 4.56)
constructs a 2-dimensional vector with x
-component 1.23
and y
-component 4.56
. As noted above, unknown values can be used as components, e.g.,
vec2 p = (?, 0.0)
specifies a point p
that sits on the x
-axis with an unknown x
-coordinate which is determined by the optimizer, according to any constraints and objectives involving p
. More advanced initializers (e.g., initializing a 3-vector from a 2-vector and a scalar) are currently not supported, but are planned for future language versions. In most cases, the same functionality can currently be emulated by directly referencing components of a vector, e.g.,
vec3 a = ( b[0], b[1], 1.0 )
A matrix is constructed by specifying a list of vectors. Each vector corresponds to a row (not a column) of the matrix. For instance,
mat2x2 A = ((1,2),(3,4))
initializes a 2x2 matrix where the top row has entries 1, 2 and the bottom row has entries 3, 4. Rows can also reference existing vectors, e.g.,
vec2 a1 = (1, 2)
vec2 a2 = (3, 4)
mat2x2 A = (a1, a2)
builds the same matrix as above. As with vectors, matrix entries can be unknown. E.g.,
scalar d = ?
mat3x3 D = ((d, 0, 0), (0, d, 0), (0, 0, d))
describes a 3x3 diagonal matrix, where all three diagonal entries take the same, undetermined value d
.
Vector and matrix element access
Individual elements of a vecN
can be accessed using square brackets, and an index i
between 0
and N
-1 (inclusive). For instance,
vec3 u = (1, 2, 3)
scalar y = u[1]
will extract the y
-coordinate of u
(i.e. y=2
). Matrix entries are similarly accessed:
mat2x2 M = ((?, ?), (?, ?))
scalar trM = M[0][0] + M[1][1]
constructs an expression for the trace of M
. In this case, since the elements of M
are declared as unknown scalars, the value of the trace will depend on the entry values of the optimized matrix.
WARNING
At present, a single index cannot be used to extract a vector from a matrix or list. For instance, the following usage is not valid:
mat3x3 M = ( (1,2,3), (4,5,6), (7,8,9) )
vec3 row1 = M[0] -- attempt to extract row 1 of M
Instead, the individual components must be enumerated:
mat3x3 M = ( (1,2,3), (4,5,6), (7,8,9) )
vec3 row1 = ( M[0][0], M[0][1], M[0][2] ) -- extract row 1 of M
More concise indexing is planned for future language versions; see issue #1509.
Vector and matrix operations
Like scalar
variables, vector and matrix types support a variety of standard arithmetic operations, listed below. Here we assume that c
and d
have type scalar
, u
and v
have type vecN
, and A
and B
have type matNxN
(all for the same size N
).
Note that an elementwise operation is one that gets applied independently to each entry of a vector or matrix. For instance, if u = (6, 8, 9)
and v = (3, 2, 3)
, then the elementwise division operation u ./ v
yields the vector (2, 4, 3)
(i.e. six divided by three, eight divided by two, and nine divided by three).
The then
keyword. Style provides a special keyword then
that can be useful for describing sequences of spatial transformations. In English, one might naturally say something like, "flip it over, then put it in the oven" to mean that a dish should first be flipped over, and only then be put in the oven. (In fact, reversing this order could be quite dangerous!) Likewise, the then
keyword in Style means that a sequence of transformations should be applied from left-to-right, in the same order as in natural language. For example,
mat4x4 transform = translate(x,y) then rotate(theta) then scale(a,b)
indicates that translation happens first, rotation happens second, and scaling happens third. The resulting transformation is equivalent to writing matrix multiplication in the usual right-to-left order, i.e.,
mat4x4 transform = rotate(theta) * scale(a,b) * translate(x,y)
In general, A1 then A2 then ... then An = An * ... * A2 * A1
. The use of the then
keyword simply makes it easier in some cases to understand the correct meaning of a sequence of operations, by just reading code in the usual left-to-right order.
Scalar-Vector
c * v
— product ofv
andc
(from the left)v * c
— product ofv
andc
(from the right)v / c
— quotient ofv
byc
Scalar-Matrix
c * A
— product ofA
andc
(from the left)A * c
— product ofA
andc
(from the right)A / c
— quotient ofA
byc
Vector-Vector
u + v
— sum ofu
andv
u - v
— difference ofu
andv
u .* v
— elementwise product ofu
andv
u ./ v
— elementwise quotient ofu
andv
Vector-Matrix
A*u
— matrix-vector productAu
u*A
— matrix vector productuᵀA
Matrix-Matrix
A * B
— matrix-matrix productAB
A + B
— sum ofA
andB
A - B
— difference ofA
andB
A .* B
— elementwise product ofA
andB
A ./ B
— elementwise quotient ofA
andB
A'
— matrix transposeAᵀ
A then B
— matrix-matrix productBA
Vector and matrix functions
A variety of methods are available for constructing standard matrices (click through for more detailed description):
2D and 3D transformation matrices
Penrose provides matrices which express basic 2D and 3D spatial transformations (rotation, translation, etc.). The definitions of these functions are designed to closely follow the standard definitions found in SVG and CSS (2D) and OpenGL (3D), so that code from these languages can be easily ported to Style. Since 2D is the most common case, 2D transformations are referred to by the simple names rotate
, translate
, scale
, skew
, and shear
. Other functions are referred to by dimension, e.g., rotate3d()
; a suffix of h
indicates that a transformation is expressed in homogeneous coordinates (e.g., rotate3dh()
). A full list is given below (click through for more detailed descriptions).
Composition of transformations
As noted above, transformations can be combined using the then
keyword. For instance,
mat4x4 transform = translate(x,y) then rotate(theta) then scale(a,b),
which performs the given transformations in left-to-right order. (If preferred, one can also compose transformations in right-to-left order using ordinary matrix multiplication.)
Linear vs. affine transformations, homogeneous coordinates
An important concept when working with spatial transformations is the distinction between linear and affine transformations. A linear transformation of a point in -dimensional space is one that can be expressed via matrix-vector multiplication for some matrix . Whereas some basic transformations, like rotation, can be expressed this way, other standard transformations cannot. For instance, there is no way to encode translation by a vector as a linear transformation, i.e., there is no matrix such that . This transformation is an example of an affine transformation. Any affine transformation can be encoded as a linear transformation "one dimension up," by writing our points in homogeneous coordinates. In particular, if we work with the coordinates (i.e., we append 1 to the end of the original coordinate vector), then we can find a matrix such that describes the result of translation, also in homogeneous coordinates. To recover the translated vector in ordinary Cartesian coordinates, we then divide the first components of by its final, "extra" coordinate. For further information, see this discussion.
Transformations in Style follow a standard naming convention:
- Transformations with no suffix refer to 2D transformations expressed in homogeneous coordinates (
rotate()
,translate()
,scale()
, etc.). These functions can all be composed with each-other as one would naturally expect. - Transformations with a suffix
2D
refer to linear 2D transformations, expressed in ordinary Cartesian coordinates (rotate2D()
,scale2D()
, etc.). Note that there is notranslate2D()
, because this transformation is affine; likewise, linear 2D transformations cannot be directly composed with affine 2D transformations. - Transformations with a suffix
3D
refer to linear 3D transformations, expressed in ordinary Cartesian coordinates (rotate3D()
,scale3D()
, etc.). - Transformations with a suffix
3dh
refer to affine 3D transformations, expressed in homogeneous coordinates (rotate3Dh()
,translate3Dh
, etc.).
Style also provides helper functions for converting to/from homogeneous coordinates:
Example usage:
vec3 p = (1,2,3) -- express a 3D point in ordinary Cartesian coordinates
-- express a 3D transformation in 3+1 homogeneous coordinates
mat4x4 A = translate3dh(x,y,z) then rotate3dh(theta,u) then scale3dh(a,b,c)
-- transform the point by converting to/from homogeneous coordinates
vec3 q = fromHomogeneous( A * toHomogeneous(p) )
2D transformations (affine)
2D transformations (linear)
3D transformations (linear)
3D transformations (affine)
Camera matrices
Penrose also provides camera projection matrices, which are helpful for making 3D diagrams (see especially the dinoshade
example). These functions closely match the standard definitions in OpenGL:
Finally, the utility function matrixMultiplyList
is useful for applying the same transformation to many points (e.g., points in a 3D mesh):