math#

This module provides mathematical utilities, including fixed-point numbers, sin, cos & div lookup tables, 2D vectors and rectangles.

Fixed-Point Numbers#

Fixed-point arithmetic allows you to treat some integer as if it were fractional, which is much faster than using floats on the GBA.

The Fixed type makes working with ‘24.8’ fixed-point values most convenient. These can be created using the fp template. For example:

let a = fp(1.25)
let b = fp(8.5)
assert a + b == fp(9.75)

Working at other precisions can be done with the FixedN type, and using a different underlying integer type is supported with FixedT.

These are created with the toFixed template:

let k = (1.5).toFixed(int8, 4)  # an 8-bit value with 4 bits of precision

static:
  echo typeof(k)  # prints "FixedT[int8, 4]"

Types#

type FixedT[T: SomeInteger, N: static int] = distinct T#

A fixed-point number based on type T, with N bits of precision.

type FixedN[N: static int] = FixedT[int, N]#

A signed 32-bit fixed-point number with N bits of precision.

type Fixed = FixedN[8]#

A signed 32-bit fixed-point number with 8 bits of precision.

Conversion Templates#

template fp(n: SomeNumber|FixedT): Fixed#

Convert a value to fixed-point with 8 bits of precision.

template toFixed(n: SomeNumber, F: typedesc[FixedT]): untyped#

Convert a number to the fixed-point type F.

template toFixed[T, N](n: FixedT, F: typedesc[FixedT[T,N]]): untyped#

Convert from one fixed-point format to another.

template toFixed(n: SomeNumber|FixedT, T: typedesc[SomeInteger], N: static int): untyped#

Convert a value to fixed-point, with a base type of T using N bits of precision.

template toInt(n: FixedT): int#

Convert a fixed-point value to an integer.

template toFloat32(n: FixedT): float32#

Convert a fixed-point value to floating point.

template raw[F: FixedT](a: F): untyped#

Arithmetic Operators#

The basic operators available to fixed-point numbers are given below. These are implemented as templates to encourage the Nim compiler to evaluate them at compile-time.

Note

There are operators defined between fixed-point and integer types, so fp(0.5) * 2 is allowed, and will give a fixed-point result.

However, there are no implicit converters defined, and you can’t mix fixed-point types of differing size and precision.

To add two fixed-point numbers of different precision together, one must be converted to the other:

let a = fp(0.5)               # 8 bits precision
let b = (0.5).toFixed(10)     # 10 bits precision

# let sum1 = a + b            # Not allowed!

let sum2 = a + fp(b)          # Ok, both 8 fractional bits
let sum3 = a.toFixed(10) + b  # Ok, both 10 fractional bits
template `+`[F: FixedT](a, b: F): F#
template `-`[F: FixedT](a, b: F): F#
template `*`[F: FixedT](a, b: F): F#
template `/`[F: FixedT](a, b: F): F#
template `==`[F: FixedT](a, b: F): bool#
template `<`[F: FixedT](a, b: F): bool#
template `<=`[F: FixedT](a, b: F): bool#
template `+`[F: FixedT](a: F): F#
template `-`[F: FixedT](a: F): F#
template `+=`[F: FixedT](a: var F, b: F)#
template `-=`[F: FixedT](a: var F, b: F)#
template `*=`[F: FixedT](a: var F, b: F)#
template `/=`[F: FixedT](a: var F, b: F)#
template `+`[F: FixedT, I: SomeInteger](a: F, b: I): F#
template `-`[F: FixedT, I: SomeInteger](a: F, b: I): F#
template `*`[F: FixedT, I: SomeInteger](a: F, b: I): F#
template `/`[F: FixedT, I: SomeInteger](a: F, b: I): F#
template `+`[F: FixedT, I: SomeInteger](a: I, b: F): F#
template `-`[F: FixedT, I: SomeInteger](a: I, b: F): F#
template `*`[F: FixedT, I: SomeInteger](a: I, b: F): F#
template `==`[F: FixedT, I: SomeInteger](a: F, b: I): bool#
template `<`[F: FixedT, I: SomeInteger](a: F, b: I): bool#
template `<=`[F: FixedT, I: SomeInteger](a: F, b: I): bool#
template `==`[F: FixedT, I: SomeInteger](a: I, b: F): bool#
template `<`[F: FixedT, I: SomeInteger](a: I, b: F): bool#
template `<=`[F: FixedT, I: SomeInteger](a: I, b: F): bool#
template `+=`[F: FixedT, I: SomeInteger](a: var F, b: I)#
template `-=`[F: FixedT, I: SomeInteger](a: var F, b: I)#
template `*=`[F: FixedT, I: SomeInteger](a: var F, b: I)#
template `/=`[F: FixedT, I: SomeInteger](a: var F, b: I)#
template `shr`[F: FixedT, I: SomeInteger](a: F, b: I): F#
template `shl`[F: FixedT, I: SomeInteger](a: F, b: I): F#
template mul64[F: FixedT](a, b: F): F#

Multiply two fixed-point values using 64-bit math (to help avoid overflows)

template div64[F: FixedT](a, b: F): F#

Divide two fixed-point values using 64-bit math (to help avoid overflows)

template abs[F: FixedT](a: F): F#

General Math Functions#

func flr(n: FixedT): int#

Convert a fixed-point number to an integer, always rounding down.

func ceil(n: FixedT): int#

Convert a fixed-point number to an integer, always rounding up.

proc sgn[T: SomeNumber](x: T): int#

Get the sign of a number.

Returns -1 when x is negative, 1 when x is positive, or 0 when x is 0.

(This procedure comes from the Nim standard library.)

func sgn(x: FixedT): int#

Get the sign of a fixed-point number.

Returns -1 when x is negative, 1 when x is positive, or 0 when x is 0.

func sgn2(x: SomeNumber|FixedT): int#

Returns 1 or -1 depending on the sign of x.

Note: This never returns 0. Use sgn if you want something that does.

func approach[T: SomeNumber|FixedT](x: var T, target, step: T)#

Move x towards target by step without exceeding target.

step should be a positive number.

func lerp[A: SomeNumber|FixedT, F: FixedT](a, b: A, t: F): A#

Linear interpolation between a and b using the weight given by t.

t should be a fixed point value in the range of 0.0 .. 1.0.


Lookup Tables#

type Angle = uint32#

An angle value, where 0x10000 is equivalent to 2π.

func luSin(theta: Angle): FixedN[12]#

Look-up a sine value.

Theta:

An unsigned integer angle, where 0x10000 is a full turn.

Returns a 20.12 fixed-point number between -1.0 and 1.0.

func luCos(theta: Angle): FixedN[12]#

Look-up a cosine value.

Theta:

An unsigned integer angle, where 0x10000 is a full turn.

Returns a 20.12 fixed-point number between -1.0 and 1.0.

func luDiv(x: range[0..256]): FixedN[16]#

Look-up a division value between 0 and 256.

Returns 1/x, represented as a 16.16 fixed point number.

func luLerp[A: SomeInteger|FixedT, F: FixedT](lut: openArray[A], x: F): A#

Linear interpolator for LUTs.

An LUT (lookup table) is essentially the discrete form of a function, f(x). You can get values for non-integer x via (linear) interpolation between f(x) and f(x+1).

Lut:

The LUT to interpolate from.

X:

Fixed-point number to interpolate at.

Example:

let myLut* {.importc.}: array[100, int16]  # some array of data.

let n: int16 = luLerp(myLut, fp(10.75))    # get a value ¾ between the 10th and 11th entry of `myLut`.

2D Vectors#

type Vec2i = object#

Integer 2D vector/point type

type Vec2f = object#

Fixed-point 24:8 2D vector/point type

func vec2i(x, y: int): Vec2i#

Initialise an integer vector

func vec2i(): Vec2i#

Initialise an integer vector to 0,0

func vec2i(v: Vec2f): Vec2i#

Convert an integer vector to a fixed-point vector

func vec2f(x: SomeNumber|FixedT, y: SomeNumber|FixedT): Vec2f#

Initialise a fixed-point vector

func vec2f(): Vec2f#

Initialise a fixed-point vector to 0,0

func vec2f(v: Vec2i): Vec2f#

Convert a fixed-point vector to an integer vector

func `+`(a, b: Vec2i): Vec2i#

Add two vectors

func `-`(a, b: Vec2i): Vec2i#

Subtract two vectors

func `*`(a, b: Vec2i): Vec2i#

Component-wise multiplication of two vectors

func `*`(a: Vec2i, n: int): Vec2i#

Scale vector by n

func `*`(n: int, a: Vec2i): Vec2i#

Scale vector by n

func `/`(a, b: Vec2i): Vec2i#

Component-wise division of two vectors

func `/`(a: Vec2i, n: int): Vec2i#

Scale vector by 1/n

func `/`(n: int, a: Vec2i): Vec2i#

Scale vector by 1/n

func dot(a, b: Vec2i): int#

Dot product of two vectors

func `-`(a: Vec2i): Vec2i#

Equivalent to a * -1

func `+=`(a: var Vec2i, b: Vec2i)#

Vector compound addition

func `-=`(a: var Vec2i, b: Vec2i)#

Vector compound subtraction

func `*=`(a: var Vec2i, b: Vec2i)#

Vector component-wise compound multiplicatoin

func `*=`(a: var Vec2i, n: int)#

Compound scale a vector by n

func `/=`(a: var Vec2i, b: Vec2i)#

Vector component-wise compound division

func `/=`(a: var Vec2i, n: int)#

Compound scale a vector by 1/n

func `+`(a, b: Vec2i): Vec2i#

Add two vectors

func `-`(a, b: Vec2i|Vec2f): Vec2f#

Subtract two fixed-point vectors

func `*`(a: Vec2f, b: Vec2i|Vec2f): Vec2f#

Component-wise multiplication of two vectors

func `*`(a: Vec2f, n: Fixed|int): Vec2f#

Scale a fixed-point vector by n

func `*`(n: Fixed|int, a: Vec2f): Vec2f#

Scale a fixed-point vector by n

func `/`(a: Vec2f, b: Vec2i|Vec2f): Vec2f#

Component-wise division of two vectors

func `/`(a: Vec2f, n: Fixed|int): Vec2f#

Scale a fixed-point vector by 1/n

func `/`(n: Fixed|int, a: Vec2f): Vec2f#

Scale a fixed-point vector by 1/n

func dot(a, b: Vec2i): int#

Dot product of two vectors

func `-`(a: Vec2f): Vec2f#

Equivalent to a * -1

func `+=`(a: var Vec2i, b: Vec2i)#

Vector compound addition

func `-=`(a: var Vec2i, b: Vec2i)#

Vector compound subtraction

func `*=`(a: var Vec2f, b: Vec2i|Vec2f)#

Vector component-wise compound multiplicatoin

func `/=`(a: var Vec2f, b: Vec2i|Vec2f)#

Vector component-wise compound division

func `*=`(a: var Vec2f, n: Fixed|int)#

Compound scale a vector by n

func `/=`(a: var Vec2f, n: Fixed|int)#

Compound scale a vector by 1/n

Vector Conversion#

func initBgPoint(x = 0'i16, y = 0'i16): BgPoint#

Create a new pair of values used by the BG scroll registers, e.g.

bgofs[0] = initBgPoint(10, 20)
func toBgPoint(a: Vec2i): BgPoint#

Convert a vector to a pair of values used by the BG scroll registers, e.g.

bgofs[0] = pos.toBgPoint()
func toBgPoint(a: Vec2f): BgPoint#

Convert a fixed-point vector to a pair of values used by the BG scroll registers, e.g.

bgofs[0] = pos.toBgPoint()

Rectangles#

type Rect = object#

Rectangle type. Ranges from left..right-1, top..bottom-1

func rectBounds(left, top, right, bottom: int): Rect#
func rectAt(x, y, width, height: int): Rect#
func x(r: Rect): int#
func y(r: Rect): int#
func w(r: Rect): int#
func h(r: Rect): int#
func width(r: Rect): int#
func height(r: Rect): int#
func `x=`(r: var Rect, x: int)#
func `y=`(r: var Rect, y: int)#
func `w=`(r: var Rect, w: int)#
func `h=`(r: var Rect, h: int)#
func `width=`(r: var Rect, w: int)#
func `height=`(r: var Rect, h: int)#
func move(r: var Rect, dx, dy: int)#

Move rectangle by (dx, dy)

func move(r: var Rect, vec: Vec2i)#

Move rectangle by vec

func inflate(r: var Rect, n: int)#

Increase size of rectangle by n on all sides

func inflate(r: var Rect, dw, dh: int)#

Increase size of rectangle by dw horizontally, dh vertically

func center(r: Rect): Vec2i#

Get the center point of a rectangle

func `center=`(r: var Rect, p: Vec2i)#

Set the center point of a rectangle

func topLeft(r: Rect): Vec2i#
func topRight(r: Rect): Vec2i#
func bottomLeft(r: Rect): Vec2i#
func bottomRight(r: Rect): Vec2i#