GRAPHMAT(3)

πŸ“¦ version 0.11, in ubuntu noble

πŸ¦‹ GRAPHMAT(3) πŸ¦‹

Library Functions Manual ✨

okay so bestie, buckle UP πŸ’– because we're about to meet the entire graphmat squad β€” every single one of them, no cap. this is the 3d-graphics-and-matrices-and-vectors friend group and they are all iconic. part one of two, so don't you dare wander off. 🫢


🌸 NAME 🌸

The whole crew, in the order the original rolls them out:

m_alloc2, m_free2, v_alloc2, v_free2, m_alloc3, m_free3, v_alloc3,
v_free3, m_cpy2, m_unity2, v_cpy2, v_fill2, v_unity2, v_zero2, m_cpy3,
m_unity3, v_cpy3, v_fill3, v_unity3, v_zero3, m_det2, v_len2, vtmv_mul2,
vv_inprod2, m_inv2, m_tra2, mm_add2, mm_mul2, mm_sub2, mtmm_mul2,
sm_mul2, mv_mul2, sv_mul2, v_homo2, v_norm2, vv_add2, vv_sub2, vvt_mul2,
m_det3, v_len3, vtmv_mul3, vv_inprod3, m_inv3, m_tra3, mm_add3, mm_mul3,
mm_sub3, mtmm_mul3, sm_mul3, mv_mul3, sv_mul3, v_homo3, v_norm3,
vv_add3, vv_cross3, vv_sub3, vvt_mul3, miraxis2, mirorig2, mirplane2,
rot2, scaorig2, scaplane2, scaxis2, transl2, miraxis3, mirorig3,
mirplane3, prjorthaxis, prjpersaxis, rot3, scaorig3, scaplane3, scaxis3,
transl3 - 3d graphics and associated matrix and vector routines

so yeah. 3d graphics and associated matrix and vector routines. that's the whole vibe of the page. πŸ’…


✨ SYNOPSIS ✨

first things first, you gotta let it in:

#include <graphmat.h>

πŸŽ€ Data initialisation

/* Data initialisation */

hmat2_t   *m_alloc2(m_result)
hmat2_t   *m_result;

void      m_free2(matrix)
hmat2_t   *matrix;

hvec2_t   *v_alloc2(v_result)
hvec2_t   *v_result;

void      v_free2(vector)
hmat2_t   *vector;

hmat3_t   *m_alloc3(m_result)
hmat3_t   *m_result;

void      m_free3(matrix)
hmat3_t   *matrix;

hvec3_t   *v_alloc3(v_result)
hvec3_t   *v_result;

void      v_free3(vector)
hmat3_t   *vector;

hmat2_t   *m_cpy2(m_source, m_result)
hmat2_t   *m_source, *m_result;

hmat2_t   *m_unity2( m_result)
hmat2_t   *m_result;

hvec2_t   *v_cpy2(v_source, v_result)
hvec2_t   *v_source, *v_result;

hvec2_t   *v_fill2(x, y, w, v_result)
double    x, y, w;
hvec2_t   *v_result;

hvec2_t   *v_unity2(axis, v_result)
b_axis    axis;
hvec2_t   *v_result;

hvec2_t   *v_zero2(v_result)
hvec2_t   *v_result;

hmat3_t   *m_cpy3(m_source, m_result)
hmat3_t   *m_source, *m_result;

hmat3_t   *m_unity3(m_result)
hmat3_t   *m_result;

hvec3_t   *v_cpy3(v_source, v_result)
hvec3_t   *v_source, *v_result;

hvec3_t   *v_fill3(x, y, z, w, v_result)
double    x, y, z, w;
hvec3_t   *v_result;

hvec3_t   *v_unity3(axis, v_result)
b_axis    axis;
hvec3_t   *v_result;

hvec3_t   *v_zero3(vector)
hvec3_t   *vector;

πŸ¦‹ Basic Linear Algebra

/* Basic Linear Algebra */

double    m_det2(matrix)
hmat2_t   *matrix;

double    v_len2(vector)
hvec2_t   *vector;

double    vtmv_mul2(vector, matrix)
hvec2_t   *vector;
hmat2_t   *matrix;

double    vv_inprod2(vectorA, vectorB)
hvec2_t   *vectorA, *vectorB;

hmat2_t   *m_inv2(matrix, m_result)
hmat2_t   *matrix, *m_result;

hmat2_t   *m_tra2(matrix, m_result)
hmat2_t   *matrix, *m_result;

hmat2_t   *mm_add2(matrixA, matrixB, m_result)
hmat2_t   *matrixA, *matrixB, *m_result;

hmat2_t   *mm_mul2(matrixA, matrixB, m_result)
hmat2_t   *matrixA, *matrixB, *m_result;

hmat2_t   *mm_sub2(matrixA, matrixB, m_result)
hmat2_t   *matrixA, *matrixB, *m_result;

hmat2_t   *mtmm_mul2(matrixA, matrixB, m_result)
hmat2_t   *matrixA, *matrixB, *m_result;

hmat2_t   *sm_mul2(scalar, matrix, m_result)
double    scalar;
hmat2_t   *matrix, *m_result;

hmat2_t   *vvt_mul2(vectorA, vectorB, m_result)
hvec2_t   *vectorA, *vectorB;
hmat2_t   *m_result;

hvec2_t   *mv_mul2(matrix, vector, v_result)
hmat2     *matrix;
hvec2_t   *vector, *v_result;

hvec2_t   *sv_mul2(scalar, vector, v_result)
double    scalar;
hvec2_t   *vector, *v_result;

hvec2_t   *v_homo2(vector, v_result)
hvec2_t   *vector, *v_result;

hvec2_t   *v_norm2(vector, v_result)
hvec2_t   *vector, *v_result;

hvec2_t   *vv_add2(vectorA, vectorB, v_result)
hvec2_t   *vectorA, *vectorB, *v_result;

hvec2_t   *vv_sub2(vectorA, vectorB, v_result)
hvec2_t   *vectorA, *vectorB, *v_result;

double    m_det3(matrix)
hmat3_t   *matrix;

double    v_len3(vector)
hvec3_t   *vector;

double    vtmv_mul3(vector, matrix)
hvec3_t   *vector;
hmat3_t   *matrix;

double    vv_inprod3(vectorA, vectorB)
hvec3_t   *vectorA, *vectorB;

hmat3_t   *m_inv3(matrix, m_result)
hmat3_t   *matrix, *m_result;

hmat3_t   *m_tra3(matrix, m_result)
hmat3_t   *matrix, *m_result;

hmat3_t   *mm_add3(matrixA, matrixB, m_result)
hmat3_t   *matrixA, *matrixB, *m_result;

hmat3_t   *mm_mul3(matrixA, matrixB, m_result)
hmat3_t   *matrixA, *matrixB, *m_result;

hmat3_t   *mm_sub3(matrixA, matrixB, m_result)
hmat3_t   *matrixA, *matrixB, *m_result;

hmat3_t   *mtmm_mul3(matrixA, matrixB, m_result)
hmat3_t   *matrixA, *matrixB, *m_result;

hmat3_t   *sm_mul3(scalar, matrix, m_result)
double    scalar;
hmat3_t   *matrix, *m_result;

hmat3_t   *vvt_mul3(vectorA, vectorB, m_result)
hvec3_t   *vectorA, *vectorB;
hmat3_t   *m_result;

hvec3_t   *mv_mul3(matrix, vector, v_result)
hmat3_t   *matrix;
*hvec3_t  *vector, *v_result;

hvec3_t   *sv_mul3(scalar, vec, v_result)
double    scalar;
hvec3_t   *vector, *v_result;

hvec3_t   *v_homo3(vector, v_result)
hvec3_t   *vector, *v_result;

hvec3_t   *v_norm3(vector, v_result)
hvec3_t   *vector, *v_result;

hvec3_t   *vv_add3(vectorA, vectorB, v_result)
hvec3_t   *vectorA, *vectorB, *v_result;

hvec3_t   *vv_cross3(vectorA, vectorB, v_result)
hvec3_t   *vectorA, *vectorB, *v_result;

hvec3_t   *vv_sub3(vectorA, vectorB, v_result)
hvec3_t   *vectorA, *vectorB, *v_result;

πŸ’… Elementary transformations

/* Elementary transformations */

hmat2_t   *miraxis2(axis, m_result)
b_axis    axis;
hmat2_t   *m_result;

hmat2_t   *mirorig2(m_result)
hmat2_t   *m_result;

hmat2_t   *rot2( rotation, m_result)
double    rotation;
hmat2_t   *m_result;

hmat2_t   *scaorig2(scale, m_result)
double    scale;
hmat2_t   *m_result;

hmat2_t   *scaxis2(scale, axis, m_result)
double    scale;
b_axis    axis;
hmat2_t   *m_result;

hmat2_t   *transl2(translation, m_result)
hvec2_t   *translation;
hmat2_t   *m_result;

hmat3_t   *miraxis3(axis, m_result)
b_axis    axis;
hmat3_t   *m_result;

hmat3_t   *mirorig3(m_result)
hmat3_t   *m_result;

hmat3_t   *mirplane3(plane, m_result)
b_axis    plane;
hmat3_t   *m_result;

hmat3_t   *prjorthaxis(axis, m_result)
b_axis    axis;
hmat3_t   *m_result;

hmat3_t   *prjpersaxis(axis, m_result)
b_axis    axis;
hmat3_t   *m_result;

hmat3_t   *rot3( rotation, axis, m_result)
double    rotation;
b_axis    axis;
hmat3_t   *m_result;

hmat3_t   *scaorig3(scale, m_result)
double    scale;
hmat3_t   *m_result;

hmat3_t   *scaplane(scale, plane, m_result)
double    scale;
b_axis    plane;
hmat3_t   *m_result;

hmat3_t   *scaxis3(scale, axis, m_result)
double    scale;
b_axis    axis;
hmat3_t   *m_result;

hmat3_t   *transl3(translation, m_result)
hvec3_t   *translation;
hmat3_t   *m_result;

πŸ’– DESCRIPTION πŸ’–

okay so the tea β˜•: these are matrix and vector routines for 3d graphics in homogeneous coordinates β€” basic linear algebra plus the elementary transformations. that's it, that's the whole job. πŸ’…

and the way it's built? multi-level, bestie. four floors, one very cute building 🏒✨:

  1. Level1 : the data level.
  2. Level 2: the data initialisation level.
  3. Level 3: basic linear algebra level.
  4. Level 4: elementary transformation level.

🧚 Level 1, the data structures

and here's how Level 1, the data structures, is realised, fr fr β€” look at these little unions and structs, they're giving main character energy:

typedef union
{
double a[3];
struct
{
          double x, y, w;
} s;
} hvec2_t;

typedef union
{
double a[4];
struct
{
          double x, y, z, w;
} s;
} hvec3_t;

typedef struct
{
double m[3][3];
} hmat2_t;

typedef struct
{
double m[4][4];
} hmat3_t;

so hvec2_t and hvec3_t are unions (array face ✨ struct face ✨), and hmat2_t / hmat3_t are plain structs holding the grid. iconic.


πŸŽ€ Accessing the data

you don't gotta poke at the guts by hand, bestie β€” the data elements of a vector or a matrix can be accessed with these macros πŸ’–:

#define   v_x( vec )((vec).s.x)
#define   v_y( vec )((vec).s.y)
#define   v_z( vec )((vec).s.z)
#define   v_w( vec )((vec).s.w)
#define   v_elem( vec, i )((vec).a[(i)])
#define   m_elem( mat, i, j )((mat).m[(i)][(j)])

and the axis enum, which shows up everywhere in this library, is literally just:

typedef enum
{
          X_AXIS, Y_AXIS, Z_AXIS
} b_axis;

three axes, zero drama. 🌈


🫢 How the functions are sorted

listen, the ordering is very deliberate, okay? the functions are sorted:

  1. first on the level in which they belong,
  2. then on their return value,
  3. and then on their name.

so it's not random chaos, it's curated. πŸŽ€


πŸ‘‘ NAMES πŸ‘‘

okay this part is lowkey the most important thing to internalise, so read it twice bestie. πŸ’€

the function names begin with an abbreviation of the type of operand, and in which order the operations will be carried out on that operand. then comes the order of and which operation will be carried out, followed by the type of coordinates.

and the example they give, which is chef's kiss:

vtmv_mul3(vector, matrix) : first take the transpose of vector, multiply the transpose with matrix, this result is multiplied by the incoming vector, all coordinates are homogeneous 3d coordinates.

see?? the name literally is the recipe. 🧚 no cap, once you clock that pattern you can read the whole library like a group chat. πŸ”₯


and that's the whole setup, bestie β€” the types, the macros, the axes, the naming rules… all locked in. the actual functions get their own moment coming right up, so stay tuned. πŸ’…

🫢 USAGE 🫢

okay so here's the tea, bestie: all the "functions" may have actually been implemented as macro's, which means you cannot take the address of a function. πŸ’€ That's just how it is, no cap.

But here's the good news that's guaranteed: the arguments of every function/macro get evaluated only once β€” except for the result argument, which can get evaluated multiple times. πŸ™ˆ So don't put anything cursed in there, iykyk.

Also: all operations can be used in place ✨ but if your data is overlapping you get unspecified results. It's giving "we warned you." πŸŽ€


Now, about memory, because this is the part where you gotta pay attention 🧚:

If the parameter v_result or m_result of a function, or the parameter of an initialisation function, equals NULL, then space for that parameter gets dynamically allocated using malloc(). Otherwise, the parameter is assumed to hold a pointer to a memory area that can be used. Either way, a pointer to the used area (which may have been newly allocated) is always returned. 🫢

And if an error happens β€” like memory couldn't be allocated, or someone tried to divide by zero 😭, or someone tried to invert a singular matrix πŸ’€ β€” then a general error-routine gets called. That routine takes two parameters: gm_errno and gm_func.

  • gm_errno β€” the error type, which is one of these constants: DIV0, NOMEM or MATSING.
  • gm_func β€” a pointer to a string holding the name of the function where the error went down.

A pointer to the error routine is defined like this, verbatim bestie:

void (* gm_error)(gm_errno, gm_func);
gm_error_t gm_errno;
char *gm_func;

And gm_error_t is defined as:

typedef enum
{
DIV0, NOMEM, MATSING
} gm_error_t;

The default error handler will abort after printing a diagnostic. You can redirect gm_error to your own error handler if you want to be fancy ✨ β€” but it is not advisable to return from the error handler, because error recovery is not expected to take place. Like, don't. πŸ™…β€β™€οΈ


A few lil structural facts you need in your brain 🧠:

  • Matrices are of type hmat3_t or hmat2_t for 2d or 3d coordinates, respectively.
  • Vectors are of type hvec3_t or hvec2_t.
  • The elements of a vector can be accessed in two manners: the first is by the name of an element of a structure, the second is like an array. Slay, options. πŸ’…
  • A plane is described by the normal to that plane, with the assumption made that the origin is an element of the plane.
  • rotation is assumed to be a radial. ⭐
  • If a function is deallocating memory, it will check if the incoming pointer is a NULL pointer. Safety first, periodt. 🫢

✨ /* Level2 : Data initialisation */ ✨

  • m_alloc2(), v_alloc2(), m_alloc3(), v_alloc3() β€” allocate memory for a data item of type hmat2_t, hvec2_t, hmat3_t and hvec3_t respectively. πŸŽ€
  • m_free2(), v_free2(), m_free3(), v_free3() β€” reclaim the storage allocated previously. bye babe πŸ‘‹
  • m_cpy2(), m_cpy3() β€” copies m_matrix into m_result.
  • m_unity2(), m_unity3() β€” returns the unity matrix. (2d respectively 3d homogeneous coordinates)
  • v_cpy2(), v_cpy3() β€” copies v_source into v_result. (2d respectively 3d homogeneous coordinates)
  • v_fill2(), v_fill3() β€” fills v_result according the given values.
  • v_unity2(), v_unity3() β€” returns the unity vector with w = 1.0, the incoming basic axis axis = 1.0, and the other element(s) are 0.0; (2d respectively 3d homogeneous coordinates)
  • v_zero2(), v_zero3() β€” return a vector with w = 1.0 and the other elements 0.0;
  • m_cpy2(), m_cpy3() β€” copies m_source into m_result. (2d respectively 3d homogeneous coordinates)
ngl bestie, m_cpy2()/m_cpy3() really said "I contain multitudes" and showed up twice in this list. Both versions are here, exactly as the original page gives them. 🫢

πŸ”₯ /* level3 : Basic Linear Algebra */ πŸ”₯

  • m_det2(), m_det3() β€” calculates the determinant of the incoming matrix. The determinant is calculated in cartesian rather than homogeneous coordinates.
  • v_len2(), v_len3() β€” calculates the length of the cathesian part of the homogeneous vector.
  • vtmv_mul2(), vtmv_mul3() β€” calculate the result of the transpose of the incoming vector multiplied by the incoming matrix multiplied by the incoming vector. (2d respectively 3d homogeneous coordinates)
  • vv_inprod2(), vv_inprod3() β€” calculates the geometrical innerproduct (vector . vector) of vectorA and vectorB.
  • m_inv2(), m_inv3() β€” calculates the inverse of matrix. It is an error if the matrix in singular. πŸ’€
  • m_tra2(), m_tra3() β€” calculates the transpose matrix. (2d respectively 3d homogeneous coordinates)
  • mm_add2(), mm_sub2(), mm_add3(), mm_sub3() β€” calculates the result of matrixA + respectively - matrixB. This operation is unspecified in the sense of homogeneous coordinates; the matrices are taken in their normal, mathematial sense. πŸ™ˆ
  • mm_mul2(), mm_mul3() β€” calculates the result of matrixA*matrixB. (2d respectively 3d homogeneous coordinates)
  • mtmm_mul2(), mtmm_mul3() β€” calculates the result of the transpose of the incoming matrixA multiplied by matrixB multiplied by matrixA. (2d respectively 3d homogeneous coordinates)
  • sm_mul2(), sm_mul3() β€” calculates the result of scalar*matrix. (2d respectively 3d homogeneous coordinates)
  • mv_mul2(), mv_mul3() β€” calculates the result of matrix*vector. (2d respectively 3d homogeneous coordinates)
  • sv_mul2(), sv_mul3() β€” calculates the result of scalar*vector. (2d respectively 3d homogeneous coordinates)
  • v_homo2(), v_homo3() β€” homogenize vector so that the w component becomes 1.0 but the length of the vector in homogeneous coordinates stays the same. (2d respectively 3d homogeneous coordinates)
  • v_norm2(), v_norm3() β€” normalises the incoming vector so the length of the cartesian vector becomes 1.0. The homogeneous length stays the same. (2d respectively 3d homogeneous coordinates)
  • vv_add2(), vv_sub2(), vv_add3(), vv_sub3() β€” calculates the result of vectorA + respectively - vectorB. These operations are done in the mathematical sense. Be careful with homogeneous coordinates, as not every possible input makes sense.
  • vvt_mul2(), vvt_mul3() β€” calculates the result of vectorA multiplied by the transpose of vectorB. (2d respectively 3d homogeneous coordinates)
  • vv_cross3() β€” calculates the geometrical crossproduct (vectorA x vectorB) of two vectors. (3d homogeneous coordinates)

πŸ’… /* level4 : Elementary transformations */ πŸ’…

  • miraxis2(), miraxis3() β€” calculates the mirror matrix with respect to axis. (2d respectively 3d homogeneous coordinates)
  • mirorg2(), mirorg3() β€” calculates the mirror matrix relative to the origin. (2d respectively 3d homogeneous coordinates)
  • mirplane3() β€” calculates the mirror matrix relative to a plane. (3d homogeneous coordinates)
  • rot2() β€” calculates the rotation matrix over rotation relative to the origin. (2d homogeneous coordinates)
  • rot3() β€” calculates the rotation matrix over rotation along axis. (3d homogeneous coordinates)
  • scaorg2(), scaorg3() β€” calculates the matrix of scaling with scale relative to the origin. (2d respectively 3d homogeneous coordinates)
  • scaplane3() β€” calculates the matrix of scaling with scale relative to a plane of which plane is the normal. (3d homogeneous coordinates)
  • scaxis2(), scaxis3() β€” calculates the matrix of scaling with scale relative to the line given by axis. (2d respectively 3d homogeneous coordinates)
  • transl2(), transl3() β€” calculates the translation matrix over translation. (2d respectively 3d homogeneous coordinates)
  • prjorthaxis() β€” calculates the orthographic projection matrix along axis. (3d homogeneous coordinates)
  • prjpersaxis() β€” calculates the perspective projection with along axis. The focus is in the origin. The projection plane is on distance 1.0 before the camera. (3d homogeneous coordinates)

😭 CAVEATS 😭

bestie, listen, this is the part where things get spicy:

Vector addition and subtraction and matrix addition and subtraction are not defined for homogeneous coordinates. One can add and subtract a point vector and a free vector, but you have to normalise the point vector first. And the result of the subtraction of two point vectors is a free vector. πŸ¦‹

Also, calculating the determinant of a matrix and the length of a vector is unspecified in the sense of homogeneous coordinates. πŸ™ˆ

ngl these are the exact kinds of things that will silently ruin your whole render and you'll be staring at the screen like "why is it weird" β€” so like, don't do it. πŸ’€

🫢 RETURN VALUES 🫢

There are six types of return values, and here they are, all of them, no skipping:

  • void
  • double
  • *hvec3_t
  • *hvec2_t
  • *hmat3_t
  • *hmat2_t

πŸ‘‘ SEE ALSO πŸ‘‘


πŸŽ€ NOTE πŸŽ€

Library file is /usr/local/lib/libgraphmat.a πŸ’–


✨ AUTHOR ✨

Hans Gringhuis. Klamer Schutte


and that's the whole vibe check, bestie β€” this edition of GRAPHMAT(3) is from 15 September 1992, and you've officially read the whole thing. iconic behavior, honestly. go forth and multiply your matrices responsibly πŸ¦‹πŸ’…βœ¨

πŸ“¦ where this page comes from