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[cint, 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): cint

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

func ceil(n: FixedT): cint

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): cint

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): cint

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

    Field    

    Type    

x cint
y cint
type Vec2f = object

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

    Field    

    Type    

x Fixed
y Fixed
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

    Field    

    Type    

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