Skip to content

Moving, scaling and rotating with Matrix

Michal Štrba edited this page Apr 2, 2017 · 23 revisions

In this part, we'll learn how to move, rotate and scale using Matrix and how to manipulate geometry primitives.

Geometry

First, we'll learn how to manipulate geometry primitives as it's very important for pretty much everything. If you're not familiar with the mathematical notion of a vector, I strongly suggest you study that first.

Every solid game library comes with a set of types that represent geometry primitives, such as vectors, rectangles, and so on. Pixel is no exception. Pixel comes with 3 major geometry primitives:

  • Vector pixel.Vec. Positions, movements (translations), velocities, accelerations, and so on.
  • Rectangle pixel.Rect. Mainly picture frames (portions for sprites) and bounds.
  • Matrix pixel.Matrix. All kinds of linear transformations: movements, rotations, scaling.

Each one of these primitives is implemented to be very flexible as well as easy to use. Let's take a look at each one of them!

Vector

Pixel being a 2D game library, vector in Pixel is a 2D vector with two coordinates: X and Y. If you take a look at the documentation of pixel.Vec, you'll notice that it's implemented quite unusually as a complex number (a number with real and imaginary components).

type Vec complex128

If you don't know anything about complex numbers, don't worry. This implementation comes with major benefits. Since complex numbers are first-class citizens in Go with support of addition and subtraction using standard operators, the same is now possible with vectors in Pixel. To create a vector, use pixel.V constructor.

u := pixel.V(2.7, 5)
v := pixel.V(10, 3.14)
w := u + v
fmt.Println(w.X()) // 12.7

First two lines construct two 2D vectors with X and Y coordinates (2.7, 5) and (10, 3.14) respectively. The third line is interesting. Go does not support operator overloading, but since pixel.Vec is implemented as complex128, addition can be done naturally through the + operator. That's neat. We can subtract the vectors too.

fmt.Println(w - v) // prints Vec(2.7, 5)

Multiplying the vectors is trickier though. Doing u * v produces a complex number product of the two vectors, which is almost never what we want. Multiplying by a float64 like u * c, where c is a float64 variable is also not possible, because Go is a strictly typed language. For these tasks, Pixel provides two methods: Vec.Scaled and Vec.ScaledXY.

u := pixel.V(2, 1)
v := u.Scaled(10)  // (20, 10)
w := u.ScaledXY(v) // (40, 10)

Scaled multiplies by a float64 scalar, ScaledXY multiplies by another vector, component-wise (X with X, Y with Y).

Constructing a zero vector (0, 0) is surprisingly easy due to Go's constant semantics. It can be expressed as a simple 0.

u := pixel.V(0, 0)
u = 0 // u is unchanged, 0 is equivalent to pixel.V(0, 0)

Pixel also provides several methods for dealing with the individual X and Y components of a vector. Check out the documentation, here are some of them.

u := pixel.V(0, 0)
u += pixel.X(10)
u -= pixel.Y(10)
fmt.Println(u) // prints Vec(10, -10)
u = u.WithX(5)
fmt.Println(u) // prints Vec(5, -10)

Rotating, uniting, doting, crossing and so on is available too.