Repository navigation
Home
- to return to the top of the docs, press the
Homekey on your keyboard - ignore anything prefixed with an underscore (i.e., call
soup_draw(...), not_soup_draw(...)) - doing things marked with this emoji β can be a bit dangerous if you don't understand exactly what you're doing
- doing things marked with this emoji π‘ can be a bit safer :)
- for help while cow is running, especially if you forget the hotkeys, just press
?on the keyboard (don't forget to hold shift) -
realis a typedef fordouble -
vecXusually means "this can be either avec2or avec3" -
void foo(real x, real y, real z = 0.0);means "you can call this function without specifyingzand the default argument (in this case0.0) will be filled in for you"; i.e.,foo(2.0, 1.0)is equivalent tofoo(2.0, 1.0, 0.0); - unless otherwise specified, angles are in radians
- coordinate systems are always right-handed
- the world y-axis points up
- good luck; have fun; make moovies -jim
here is an empty cow.cpp app
void app() {
// ... (setup)
while (cow_begin_frame()) {
// ... (loop)
}
}and here is the same app with some comments
void app() {
// hi!--i'm an "app"
// code *here* (before the loop) runs only once; good place to...
// - set up a camera
// - allocate memory
// - precompute anything that won't change during the app
while (cow_begin_frame()) {
// hello!--this is the app's "loop", which runs over and over until the user quits
// code *here* (inside the loop) is run once per frame; good place to...
// - update whatever stuff you stored in memory before
// - draw stuff!
// - interact with stuff!
// - immediate mode graphical user interface!
}
}a small app might look something like this
- don't panic, experiment!
void app() {
Camera2D camera = { 5.0 };
vec2 foo[] = { { -1.0, -1.0 }, { -1.0, 1.0 }, { 1.0, 1.0 }, { 1.0, -1.0 } };
bool bar = false;
while (cow_begin_frame()) {
camera_move(&camera);
mat4 PV = camera_get_PV(&camera);
gui_checkbox("bar", &bar);
widget_drag(PV, 4, foo);
soup_draw(PV, SOUP_QUADS, 4, foo, NULL, (!bar) ? monokai.red : monokai.blue);
}
}to run a couple apps sequentially, we can do this...
// void app_A() {
// while (cow_begin_frame()) {
// printf("A\n");
// }
// }
//
// void app_B() {
// while (cow_begin_frame()) {
// printf("B\n");
// }
// }
//
// int main() {
// _cow_init();
// _cow_reset();
// app_A();
// _cow_reset();
// app_B();
// }...but you will probably prefer the β¨APP apiβ¨, which uses macro magic to give us menu controls (press ? while running to explore)
-
APPS { ... }is a main menu block -
APP(name);is an app, which should be a functionvoid name() { ... }with awhile (cow_begin_frame()) { ... }loop inside it
void app_A() {
while (cow_begin_frame()) {
printf("A\n");
}
}
void app_B() {
while (cow_begin_frame()) {
printf("B\n");
}
}
int main() {
APPS {
APP(app_A);
APP(app_B);
}
}note: the syntax must be followed carefully; don't mix and match with _cow_init();, _cow_reset();; don't write app_A(); instead of APP(app_A);
some useful macros (written here as if they were functions)
void ASSERT(bool condition);-
ASSERT(condition);crashes the program isconditionis false
PI
TAU
HUGE_VAL
TINY_VAL-
PIis$\pi$ -
TAUis$2\pi$ -
HUGE_VALis big real number -
TINY_VALis a tiny real number
real RAD(real degrees);
real DEG(real radians);-
RAD(theta)converts from degrees -> radians; returns$\frac{\pi}{180.0}*\theta$ -
DEG(theta)converts from radians -> degrees; returns$\frac{180.0}{\pi}*\theta$
real ABS(real a);
real SGN(real a);
real MIN(real a, real b);
real MAX(real a, real b);
real AVG(real a, real b);-
ABS(a)return$|a|$ -
SGN(a)returns$\text{sgn}(a)$ -
MIN(a, b)returns$\min(a,b)$ -
MAX(a, b)returns$\max(a,b)$ -
AVG(a, b)return$(a + b)/2$
bool IS_ZERO(real a);
bool ARE_EQUAL(real a, real b);
bool IS_BETWEEN(real p, real a, real b);-
IS_ZERO(a)returns whetherais approximately zero -
ARE_EQUAL(a, b)returns whetheraandbare approximately equal -
IS_BETWEEN(p, a, b)returns whetherpis approximately in the interval$[a, b]$
real LERP(real t, real a, real b);
vecX LERP(real t, vecX a, vecX b);
real INVERSE_LERP(real p, real a, real b);
real LINEAR_REMAP(real p, real a, real b, real c, real d);
real CLAMP(real p, real a, real b);-
LERP(t, a, b)returns$\mathbf{p} = (1-t)*\mathbf{a} + t*\mathbf{b}$ i.e. "linearly interpolates" aka "lerps" betweenaandb -
INVERSE_LERP(p, a, b)returns$t = \frac{p - a}{b - a}$ -
LINEAR_REMAP(p, a, b, c, d)returnsLERP(INVERSE_LERP(p, a, b), c, d) -
CLAMP(p, a, b)returns$\min\left(\max(p,a),b\right),$ i.e. "clamps"$p$ to the range$[a, b]$
// convert from Fahrenheit -> Celsius
real celsius = LINEAR_REMAP(fahrenheit, 32.0, 212.0, 0.0, 100.0);// enforce "theta must be between -90 deg and +90 deg"
theta = CLAMP(theta, -RAD(90), RAD(90));β int _COUNT_OF(T fixed_size_array[]);
-
_COUNT_OF(foo)returns the number of elements in fixed size arrayfoo; use at your own risk
simple std::vector alternative
template <typename T> struct StretchyBuffer {
int length;
int _capacity;
T *data;
T &operator [](int index) { return data[index]; }
};
template <typename T> void sbuff_push_back(StretchyBuffer<T> *buffer, T element);
template <typename T> void sbuff_free(StretchyBuffer<T> *buffer);
template <typename T> void sbuff_insert(StretchyBuffer<T> *buffer, int i, T element);
template <typename T> void sbuff_delete(StretchyBuffer<T> *buffer, int i);-
int lengthis the number of elements currently stored in the stretchy buffer -
sbuff_push_back(&buffer, element);appends (a copy of)elementtobuffer's internaldataarray -
sbuff_free(&buffer);freesbuffer's internaldataarray and zeroes out the struct -
sbuff_insert(&buffer, i, element);insertselementat indexiand moves the rest of the elements over -
sbuff_delete(&buffer, i);deletes theelementat indexiand moves the rest of the elements over π‘ don't call this method while iterating over a StretchyBuffer!
// print "42" in an incredibly roundabout fashion
StretchyBuffer<int> numbers = {};
sbuff_push_back(&numbers, 4);
sbuff_push_back(&numbers, 2);
ASSERT(numbers.length == 2);
printf("%d%d\n", numbers[0], numbers[1]);
sbuff_free(&numbers);real random_real(real lower_bound, real upper_bound);
real random_sign();-
random_real(lower_bound, upper_bound)returns a random real in thelower_boundtoupper_boundrange -
random_sign()returns either1.0or-1.0with equal probability
all of cow.cpp's "state" that you might need to access directly is bundled up in this single struct
note: this data is understood to be read-only
struct Globals {
mat4 NDC_from_Screen;
mat4 Identity; // NDC_from_NDC-
globals.NDC_from_Screenis the transform that maps from screen coordinates (i.e. pixel coordinates; good for writing GUI) to normalized device coordinates aka NDC (what OpenGL needs to draw something) -
globals.Identityis the 4x4 identity matrix π€·ββοΈ; it is also the matrix that takes us from NDC to NDC
bool key_pressed[];
bool key_held[];
bool key_released[];
bool key_shift_held;-
globals.key_pressed[key]is whether that key was just pressed; key can be'a','b', ...,'0','1', ...,COW_KEY_SPACE,COW_KEY_TAB,COW_KEY_ARROW_LEFT,COW_KEY_ARROW_RIGHT,COW_KEY_ESCAPE,COW_KEY_BACKSPACE,COW_KEY_ENTER -
globals.key_held[key]is whether that key is currently being held -
globals.key_released[key]is whether that key was just released -
globals.key_shift_heldis whether shift is currently held
bool mouse_left_pressed;
bool mouse_left_held;
bool mouse_left_released;
bool mouse_right_pressed;
bool mouse_right_held;
bool mouse_right_released;
real mouse_wheel_offset;
vec2 mouse_position_Screen;
vec2 mouse_position_NDC;
vec2 mouse_change_in_position_Screen;
vec2 mouse_change_in_position_NDC;
int _mouse_owner;
};
Globals globals;-
globals.mouse_left_pressedis whether the left mouse button was just pressed -
globals.mouse_wheel_offsetis how far (i believe in "clicks") the mouse wheel was scrolled since last frame; this is a signed quantity -
globals.mouse_position_Screenis the position of the mouse in screen coordinates -
globals.mouse_position_NDCis the position of the mouse in normalized device coordinates -
globals.mouse_change_in_position_Screenis the change in the mouse's position since last frame in screen coordinates -
globals.mouse_change_in_position_NDCis the change in the mouse's position since last frame in normalized device coordinates -
globals._mouse_owneris used internally, and says which part of cow.cpp currently "owns" the mouse input; it can beCOW_MOUSE_OWNER_NONE,COW_MOUSE_OWNER_GUI,COW_MOUSE_OWNER_WIDGET
get the mouse position (and change in position) in world coordinates
vec2 mouse_get_position(mat4 PV);
vec2 mouse_get_change_in_position(mat4 PV);-
mouse_get_position(PV)returns the mouse's position in world coordinates, given matrix$\mathbf{PV}$ (typically from a Camera2D) -
mouse_get_change_in_position(PV)returns the mouse's change in position since the last frame in world coordinates, given matrix$\mathbf{PV}$
vec2 s_mouse = mouse_get_position(PV);
soup_draw(PV, SOUP_POINTS, 1, &s_mouse, NULL, monokai.red);a simple 2D camera and 3D perspective/orthographic orbit camera
struct Camera2D {
real screen_height_World;
real o_x;
real o_y;
};-
real screen_height_Worldis the height of the screen in world coordinates -
real o_xis the x-coordinate of the camera's position -
real o_yis the y-coordinate of the camera's position
struct Camera3D {
union {
real persp_distance_to_origin;
real ortho_screen_height_World;
};
real angle_of_view;
real theta;
real phi;
real o_x;
real o_y;note: to use an ortho camera, set angle_of_view to 0
-
real persp_distance_to_originis the distance from a perspective camera's (world) position to the world origin -
real ortho_screen_height_Worldis the height of the screen in world coordinates for an ortho camera -
real angle_of_viewis the angle of view for a perspective camera; set to 0 to use an ortho camera -
real thetais the camera's yaw -
real phiis the camera's pitch -
real o_xis analogous to the same field in aCamera2D(though hard to summarize in words) -
real o_yis analogous to the same field in aCamera2D(though hard to summarize in words)
void camera_move(CameraXD *camera);
void camera_attach_to_gui(CameraXD *camera);
mat4 camera_get_C(CameraXD *camera);
mat4 camera_get_V(CameraXD *camera);
mat4 camera_get_P(CameraXD *camera);
mat4 camera_get_PV(CameraXD *camera);note: CameraXD means the function can be called on a Camera2D or a Camera3D
-
camera_move(camera);lets you move the camera using mouse controls; rotate by holding the left mouse button, pan by holding right mouse button, zoom with the mouse wheel -
camera_attach_to_gui(camera);reads out the camera to the gui with sliders -
camera_get_C(camera)returns the camera matrix
-
camera_get_V(camera)returns the view matrix$\mathbf{V}=\mathbf{C}^{-1}$ -
camera_get_P(camera)returns the projection matrix$\mathbf{P}$ -
camera_get_PV(camera)returns$\mathbf{P}\mathbf{V}$
Camera2D camera = { 10.0 };
while (cow_begin_frame()) {
camera_move(&camera);
mat4 PV = camera_get_PV(&camera);
// ...
}Camera3D camera = { 10.0, RAD(45) };
while (cow_begin_frame()) {
camera_move(&camera);
mat4 PV = camera_get_PV(&camera);
// ...
}immediate mode gui; thanks Casey Muratori!
void gui_printf(const char *format, ...);
void gui_readout(char *name, int *variable);
void gui_readout(char *name, real *variable);
bool gui_button(char *name, int hotkey = '\0');
void gui_checkbox(char *name, bool *variable, int hotkey = '\0');
void gui_slider(
char *name,
int *variable,
int lower_bound,
int upper_bound,
int decrement_hotkey = '\0',
int increment_hotkey = '\0',
bool loop = false);
void gui_slider(
char *name,
real *variable,
real lower_bound,
real upper_bound,
bool readout_in_degrees = false);-
gui_printf(...);follows the syntax ofprintf(...)but prints a line to the gui instead of to the terminal -
gui_readout(name, variable);prints a variable to the gui so you can inspect it -
gui_button(name, hotkey)returns true iff you just pressed the corresponding button or hotkey -
gui_checkbox(name, variable, hotkey);lets you toggle the booleanvariabletrue / false -
gui_slider(name, variable, lower_bound, upper_bound, decrement_hotkey, increment_hotkey, loop);lets you scrub the integervariablein the inclusivelower_boundtoupper_boundrange either with the mouse or usingdecrement_hotkeyandincrement_hotkey; ifloopis true, then `variable will wrap around (instead of being clamped) when hotkey'd outside of the range -
gui_slider(name, variable, lower_bound, upper_bound, readout_in_degrees);lets you scrub the realvariablein thelower_boundtoupper_boundrange using the mouse; ifvariableis an angle in radians, you can pass true forreadout_in_degreesin order to read it out in degrees
bool paused = false;
real time = 0.0;
while (cow_begin_frame()) {
gui_checkbox("paused", &paused, 'p');
gui_readout("time", &time);
if (gui_button("reset")) {
time = 0.0;
}
if (!paused) {
time += .0167;
}
}real t = 0.0;
int map = 0;
while (cow_begin_frame()) {
gui_slider("t", &t, 0.0, 1.0);
gui_slider("map", &map, 0, 1, 'j', 'k', true);
vec3 color = (map == 0) ? color_plasma(t) : color_rainbow_swirl(t);
eso_begin(globals.Identity, SOUP_QUADS);
eso_color(color);
eso_vertex(-1, -1);
eso_vertex(-1, 1);
eso_vertex( 1, 1);
eso_vertex( 1, -1);
eso_end();
}monokai is the codebase's unofficial color palette; here are the colors as vec3's
monokai.red
monokai.orange
monokai.yellow
monokai.green
monokai.blue
monokai.purple
monokai.brown
monokai.white
monokai.gray
monokai.blackwe also have a few useful functions that return colors
vec3 color_kelly(int i);
vec3 color_plasma(real t);
vec3 color_rainbow_swirl(real t);-
color_kelly(i)returns thei-th (not counting black or white) kelly color; there are 20 kelly colors: yellow, purple, ...;iis wrapped around if outside the range -
color_plasma(real t)returns a color from the plasma colormap;tis clamped to the range$[0, 1]$ -
color_rainbow_swirl(real t)returns a color from a silly cyclic rainbow colormap
triangle-soup, "point-soup" (point cloud), line...soup? the possibilities are truly endless
void soup_draw(
mat4 PVM,
int primitive,
int num_vertices,
vecX *vertex_positions, // X is 2 (XY) or 3 (XYZ)
vecY *vertex_colors, // Y is 3 (RGB) or 4 (RGBA)
vecY color_if_vertex_colors_is_NULL,
real size_in_pixels = 0,
bool use_world_units_instead_of_pixels = false,
bool force_draw_on_top = false);-
mat4 PVMis the transform matrix -
int primitivecan beSOUP_POINTS,SOUP_LINES,SOUP_LINE_STRIP,SOUP_LINE_LOOP,SOUP_TRIANGLES,SOUP_TRIANGLE_FAN,SOUP_TRIANGLE_STRIP,SOUP_OUTLINED_TRIANGLESSOUP_QUADS,SOUP_OUTLINED_QUADS -
int num_verticesis the number of vertices -
vecX *vertex_positionsare the positions of those vertices -
vecY *vertex_colorsare the colors of those vertices; pass NULL to draw all vertices the same color -
vecY color_if_vertex_colors_is_NULLis the color used for drawing ifvertex_colorsis NULL -
real size_in_pixelsis the size of the primitive in pixels; ignored when drawing triangles and quads; pass 0 to use default size -
bool use_world_units_instead_of_pixelschanges the interpretation ofsize_in_pixelsto be world units -
bool force_draw_on_topis whether you want to force the primitives to be drawn on top of everything else
int num_vertices = 4;
vec2 *foo = (vec2 *) calloc(num_vertices, sizeof(vec2));
int k = 0;
foo[k++] = { -1.0, -1.0 };
foo[k++] = { -1.0, 1.0 };
foo[k++] = { 1.0, 1.0 };
foo[k++] = { 1.0, -1.0 };
soup_draw(PV, SOUP_QUADS, num_vertices, foo, NULL, monokai.red);
free(foo);vec2 foo[] = { { -1.0, -1.0 }, { -1.0, 1.0 }, { 1.0, 1.0 }, { 1.0, -1.0 } };
soup_draw(PV, SOUP_QUADS, _COUNT_OF(foo), foo, NULL, monokai.red);StretchyBuffer<vec2> vertex_positions = {};
sbuff_push_back(&vertex_positions, { -1.0, -1.0 });
sbuff_push_back(&vertex_positions, { -1.0, 1.0 });
sbuff_push_back(&vertex_positions, { 1.0, 1.0 });
sbuff_push_back(&vertex_positions, { 1.0, -1.0 });
soup_draw(PV, SOUP_POINTS, vertex_positions.length, vertex_positions.data, NULL, monokai.red);vec2 s = { 1.0, 1.0 };
soup_draw(PV, SOUP_POINTS, 1, &s, NULL, monokai.red);"easy soup"; it's a wrapper around soup_draw(...) that handles memory for you
- "eso" rhymes with "miso" :)
void eso_begin(mat4 PVM, int primitive, real size_in_pixels = 0, bool use_world_units_instead_of_pixels = false, bool force_draw_on_top = false);
void eso_color(real r, real g, real b, real a = 1.0);
void eso_color(vec3 rgb, real a = 1.0);
void eso_vertex(real x, real y, real z = 0.0);
void eso_vertex(vec2 xy);
void eso_vertex(vec3 xyz);
void eso_end();note: eso_color(...) and eso_vertex(...) have a multiple (equivalent) signatures; call whichever version is most convenient for your data
- call
eso_begin(...);to start a drawing - call
eso_color(...);to set the color used by subsequent calls toeso_vertex(...) - call
eso_vertex(...);to draw a vertex - call
eso_end(...);to finish the drawing
note: to actually draw something, you must call eso_begin(...); first, followed by at least one call to eso_color(...);, followed by at least one call to eso_vertex(...); followed by a single call to eso_end(...); ππ
// draw a box (2D)
eso_begin(PV, SOUP_QUADS);
eso_color(1.0, 0.0, 0.0);
eso_vertex(-1.0, -1.0);
eso_vertex(-1.0, 1.0);
eso_vertex( 1.0, 1.0);
eso_vertex( 1.0, -1.0);
eso_end();// draw some axes :)
vec3 o = { 0.0, 0.0, 0.0 };
vec3 x = { 0.1, 0.0, 0.0 };
vec3 y = { 0.0, 0.1, 0.0 };
vec3 z = { 0.0, 0.0, 0.1 };
eso_begin(PV, SOUP_LINES);
eso_color(monokai.red);
eso_vertex(o);
eso_vertex(o + x);
eso_color(monokai.green);
eso_vertex(o);
eso_vertex(o + y);
eso_color(monokai.blue);
eso_vertex(o);
eso_vertex(o + z);
eso_end();general-purpose function for the very basic drawing of indexed triangle meshes (Phong shading or a single texture; can also do matcap)
void mesh_draw(
mat4 P,
mat4 V,
mat4 M,
int num_triangles,
int3 *triangle_indices,
int num_vertices,
vec3 *vertex_positions,
vec3 *vertex_normals,
vec3 *vertex_colors,
vec3 color_if_vertex_colors_is_NULL = { 1.0, 1.0, 1.0 },
vec2 *vertex_texCoords = NULL,
char *texture_filename = NULL);-
mat4 Pis a projection matrix$\mathbf{P}$ -
mat4 Vis a view matrix$\mathbf{V}$ ; pass$\mathbf{I}$ (i.e.globals.Identity) if doing matcap shading -
mat4 Mis a model matrix$\mathbf{M}$ ; pass$\mathbf{VM}$ if doing matcap shading -
int num_trianglesis the number of triangles in the indexed triangle mesh -
int3 *triangle_indicesare the indices of those triangles -
int num_verticesis the number of vertices -
vec3 *vertex_positionsare the positions of those vertices -
vec3 *vertex_normalsare the normals of those vertices; pass NULL to not use lighting -
vec3 *vertex_colorsare the colors of those vertices; pass NULL to draw all vertices the same color -
vec3 color_if_vertex_colors_is_NULLis the color used for drawing ifvertex_colorsis NULL -
vec3 *vertex_texture_coordinatesare the texture coordinates of those vertices; pass NULL to not use texture -
char *texture_filenameis the filename of the texture used ifvertex_texture_coordinatesis not NULL; if using a texture you created in code, pass the name of that texture
mesh_draw(
P,
V,
M,
num_triangles,
triangle_indices,
num_vertices,
vertex_positions,
vertex_normals,
NULL,
monokai.blue);a couple convenient structs and some builtin meshes (with easy-to-use draw methods)
this is 3D soup
struct Soup3D {
int primitive;
int num_vertices;
vec3 *vertex_positions;
vec3 *vertex_colors;
void draw(...);
};
void Soup3D::draw(
mat4 PVM,
vec3 color_if_vertex_colors_is_NULL = { 1.0, 0.0, 1.0 },
real size_in_pixels = 0,
bool force_draw_on_top = false);this is a 3D indexed triangle mesh
struct IndexedTriangleMesh3D {
int num_vertices;
int num_triangles;
vec3 *vertex_positions;
vec3 *vertex_normals;
vec3 *vertex_colors;
int3 *triangle_indices;
vec2 *vertex_texture_coordinates;
char *texture_filename;
void draw(...);
};
void IndexedTriangleMesh3D::draw(
mat4 P,
mat4 V,
mat4 M,
vec3 color_if_vertex_colors_is_NULL = { 1.0, 0.0, 1.0 },
char *texture_filename_if_texture_filename_is_NULL = NULL);here are the builtin meshes
// Soup3D
library.soups.axes // has vertex colors!--x-axis is red, y-axis is green, z-axis is blue
library.soups.tet
library.soups.box // [-1.0, 1.0]^3
library.soups.bunny
library.soups.teapot
// IndexedTriangleMesh3D
library.meshes.triangle
library.meshes.square // [-1.0, 1.0]^2; has texture coordinates!
library.meshes.box // [-1.0, 1.0]^3;
library.meshes.cone // unit radius and height; base is centered at origin (on xz-plane)
library.meshes.cylinder // unit radius and height; base is centered at origin (on xz-plane)
library.meshes.sphere // unit radius; centered at origin
library.meshes.bunny
library.meshes.teapothere is some simple usage code
library.soups.axes.draw(PV);library.meshes.bunny.draw(P, V, globals.Identity, monokai.blue);library.meshes.bunny.draw(P, globals.Identity, V * M, {}, "codebase/matcap.png");API for creating textures in code (as opposed to loading them from a file) TODO (NEXT VERSION): i, j should be x, y NOT y, x like they are now
struct Texture {
char *name;
int width;
int height;
int number_of_channels;
u8 *data;
};
Texture texture_create(char *texture_name, int width, int height, int number_of_channels);-
texture_create(...)returns a mysteriousTexturestruct you need to hold onto and pass around -
texture_nameis a unique name for your texture; should be a NULL-terminated string -
widthis the width of the texture in pixels -
heightis the height of the texture in pixels -
number_of_channelsis the number of channels per pixel; 4 for RGBA, 3 for RGB, 1 for R (red)
void texture_set_pixel(Texture *texture, int i, int j, real r, real g = 0.0, real b = 0.0, real a = 1.0);
void texture_set_pixel(Texture *texture, int i, int j, vec3 rgb, real a = 1.0);
void texture_get_pixel(Texture *texture, int i, int j, real *r, real *g = NULL, real *b = NULL, real *a = NULL);-
texture_set_pixel(texture, i, j, ...);sets the color of the$(i, j)$ -th pixel; color values should be between 0.0 and 1.0 -
texture_get_pixel(texture, i, j, ...);gets the color of the$(i, j)$ -th pixel; color values will be between 0.0 and 1.0
void texture_sync_to_GPU(Texture *texture);-
texture_sync_to_GPU(texture);syncs your changes to the texture (typically made withtexture_set_pixel(...);) so everything is up to date when you callmesh_draw(..., texture.name);
// read out an entry of a (1 channel) depth buffer
real current_depth;
texture_get_pixel(depth_buffer, i, j, ¤t_depth);void eg_texture() {
Camera2D camera = { 3.0 };
Texture texture = texture_create("shader toy tribute act", 16, 16, 3);
bool paused = false;
real time = 0.0;
while (cow_begin_frame()) {
camera_move(&camera);
mat4 P = camera_get_P(&camera);
mat4 V = camera_get_V(&camera);
gui_checkbox("paused", &paused, 'p');
if (!paused) {
for (int i = 0; i < texture.height; ++i) {
real v = real(i) / (texture.height - 1);
for (int j = 0; j < texture.width; ++j) {
real u = real(j) / (texture.width - 1);
texture_set_pixel(&texture, i, j, V3(0.5) + 0.5 * V3(cos(time + u), cos(time + v + 2.0), cos(time + u + 4.0)));
}
}
texture_sync_to_GPU(&texture);
time += 0.0167;
}
library.meshes.square.draw(P, V, globals.Identity, {}, texture.name);
}
}basic billboarded text; thank Sean Barrett for stb_easy_font.h!
void text_draw(
mat4 PV,
char *text,
vecX position_World,
vecY color = { 1.0, 1.0, 1.0 },
real font_size_in_pixels = 0,
vec2 nudge_in_pixels = { 0.0, 0.0 },
bool force_draw_on_top = false);-
mat4 PV;is the transform matrix -
char *text;is a NULL-terminated string -
vecX *position_World;is the (top left) position in world coordinates -
vecY color;is the color of the text -
real font_size_in_pixels;is the font size -
vec2 nudge_in_pixels;is how much the text will be nudged in screen coordinates -
bool force_draw_on_top;is whether you want to force the text to be drawn on top of everything else
text_draw(globals.NDC_from_Screen, "<- mouse", globals.mouse_position_Screen);
sound and music; thank Randy Gaul for cute_sound.h!
- does not work on Ubuntu π
- only supports 16-bit wav files
void sound_attach_to_gui();
void sound_play_sound(char *filename);
void sound_loop_music(char *filename);
void sound_stop_all();-
sound_attach_to_gui();adds sound controls to the GUI -
sound_play_sound(filename);plays a sound once -
sound_loop_music(filename);loops a music track forever -
sound_stop_all();stops playing all sounds and music
void eg_sound() {
sound_loop_music("codebase/music.wav");
while (cow_begin_frame()) {
if (gui_button("play sound.wav", ' ')) {
sound_play_sound("codebase/sound.wav");
}
sound_attach_to_gui();
}
}shader wrappers that are easy to use (relatively speaking); if you want to do something avant garde, please read learnopengl.com
Shader shader_create(
char *vertex_shader_source,
int num_vertex_attributes,
char *fragment_shader_source,
char *geometry_shader_source = NULL);- returns a mysterious struct you need to hold onto and pass around
- each source should be a NULL-terminated string; supplying a geometry shader is optional
-
num_vertex_attributesshould be the number oflayout (location = i) in vecX foo;-lines invertex_shader_source
void shader_set_uniform(Shader *shader, char *name, T value);- specifies the
valueof uniform variable namednamefor the shader program corresponding toshader_program_ID - the type
Tofvaluecan bebool,int,real,vec2,vec3, ormat4 - the type must match (i.e. a
vec2in your C++ code should correspond to avec2in your GLSL code); the exceptition is arealin your C++ code should correspond to afloatin your GLSL code
void shader_pass_vertex_attribute(Shader *shader, int num_vertices, vecX *vertex_attribute);- pass a
vertex_attributefor allnum_verticesvertices to the shader - you need to pass attributes in the same order that they are laid out in your vertex shader (see sample code)
- the total number of times you call this function per draw must match the
num_attributesyou supplied when you created the shader
void shader_draw(Shader *shader, int num_triangles, int3 *triangle_indices) ;- draw; hopefully, something shows up π
-
num_trianglesis the number of triangles in the indexed triangle mesh you are drawing -
triangle_indicesis their indices.
void eg_shader() {
char *vertex_shader_source = R""(
#version 330 core
uniform mat4 transform;
layout (location = 0) in vec3 vertex_position;
layout (location = 1) in vec3 vertex_normal;
out vec3 color;
void main() {
color = vertex_normal;
gl_Position = transform * vec4(vertex_position, 1.0);
}
)"";
char *fragment_shader_source = R""(
#version 330 core
in vec3 color;
out vec4 fragColor;
void main() {
fragColor = vec4(color, 1.0);
}
)"";
Shader shader = shader_create(vertex_shader_source, 2, fragment_shader_source);
IndexedTriangleMesh3D mesh = meshlib.mesh_bunny;
int num_vertices = mesh.num_vertices;
vec3 *vertex_positions = mesh.vertex_positions;
vec3 *vertex_normals = mesh.vertex_normals;
int num_triangles = mesh.num_triangles;
int3 *triangle_indices = mesh.triangle_indices;
Camera3D camera = { 5.0 };
while (cow_begin_frame()) {
camera_move(&camera);
shader_set_uniform(&shader, "transform", camera_get_PV(&camera));
shader_pass_vertex_attribute(&shader, num_vertices, vertex_positions);
shader_pass_vertex_attribute(&shader, num_vertices, vertex_normals);
shader_draw(&shader, num_triangles, triangle_indices);
}
}some little wrappers around GLFW
vec2 window_get_size();
void window_set_size(vec2 size);
void window_set_position(vec2 position);
void window_set_clear_color(real r, real g, real b, real a = 1.0);
void window_set_clear_color(vec3 rgb, real a = 1.0);
void window_set_floating(bool floating);
void window_set_decorated(bool decorated);- the
sizeof the window is in pixels - the
positionof the window is in pixels - the window is cleared to the (internally stored) clear color as part of
cow_begin_frame() - a "floating" window is always above any other windows
- a "decorated" window has a top ribbon with the window title and buttons to minimize, maximize, and close
void window_pointer_lock();
void window_pointer_unlock();
bool window_pointer_is_locked();-
window_pointer_lock();"eats" your mouse cursor (useful for FPS games) untilwindow_point_unlock();is called
screen capture; thanks Jon Olick for jo_mpeg.h!--and Wladislav Artsimovich for the C port :3
- press
?for hotkey and tweaks - captures are automatically saved to the recordings directory inside the codebase
- e.g. VLC can play the captures
smol and slow linalg library
the snail types vec2, vec3, vec4, mat2, mat3, mat4 are all plain old data structs (with some fancy pants operator overloading and things)
therefore, code like this
vec2 a = { 1.0, 2.0 };
vec2 b = a + { 5.0, 3.0 }; // does NOT compilewill trigger compile errors like this
! error C2059: syntax error: '{'
! error C2143: syntax error: missing ';' before...you must use a "constructor" if you are both creating and using a snail type on one line
vec2 a = { 1.0, 2.0 };
vec2 b = a + V2(5.0, 3.0); // does compile :)π‘ if this section didn't make 100% sense, i recommend using V2(...), V3(...), and friends every single time you create a snail vector
// nothing wrong with this :)
vec2 a = V2(1.0, 2.0);
vec2 b = a + V2(5.0, 3.0);
the entries of a vecX v can be accessed in multiple ways
v.x, v.y, v.y, v.w // dot accessors
v[0], v[1], v[2], v[3] // square bracket accessorsthe entries of a matX M can be accessed like this
M(row, column)β if you #define SNAIL_I_SOLEMNLY_SWEAR_I_AM_UP_TO_NO_GOOD before including snail.cpp you can also access like this
v.r, v.g, v.b, v.a; v.xy; v.xyz; v.data[0], v.data[1], ...
M.data[0], M.data[1], ...typical
vec2 V2(real x, real y);
vec3 V3(real x, real y, real z);
vec4 V4(real x, real y, real z, real w);
mat2 M2(real xx, real xy, real yx, real yy);
mat3 M3(...);
mat4 M4(...);note: matrix constructors expect entries in row-major order
go up a dimension
vec3 V3(Vec<2> xy, real z);
vec4 V4(Vec<3> xyz, real w);all entries are the same
vec2 V2(real s);
vec3 V3(real s);
vec4 V4(real s);horizontal stack (i.e., specify columns)
mat2 hstack(vec2 col0, vec2 col1);
mat3 hstack(vec3 col0, vec3 col1, vec3 col2);
mat4 hstack(vec4 col0, vec4 col1, vec4 col2, vec4 col3);homogeneous transform
mat4 M4_Identity();
mat4 M4_Translation(real x, real y, real z = 0.0);
mat4 M4_Translation(vec2 xy);
mat4 M4_Translation(vec3 xyz);
mat4 M4_Scaling(real x, real y, real z = 1.0);
mat4 M4_Scaling(real s);
mat4 M4_Scaling(vec2 xy);
mat4 M4_Scaling(vec3 xyz);
mat4 M4_RotationAboutXAxis(real angle);
mat4 M4_RotationAboutYAxis(real angle);
mat4 M4_RotationAboutZAxis(real angle);
mat4 M4_RotationAxisAngle(vec3 axis, real angle);special
mat4 M4_xyzo(vec3 x, vec3 y, vec3 z, vec3 o);vec3 a = ...;
vec3 b = ...;
real r = ...;
vec3 c = r * a;
c += b;mat3 A = ...;
vec3 x = ...;
vec3 b = A * x; // NOT the same as x * Asolve
$\mathbf{x} \leftarrow \mathbf{A}^{-1} \mathbf{b}$
mat3 A = ...;
vec3 b = ...;
vec3 x = inverse(A) * b;real dot(vecX a, vecX b);
matX outer(vecX a, vecX b);
real cross(vec2 a, vec2 b); // "cross product"
vec3 cross(vec3 a, vec3 b); // actual cross product-
dot(a, b)returns$\mathbf{a} \cdot \mathbf{b}=\mathbf{a}^T \mathbf{b}$ -
outer(a, b)returns$\mathbf{a} \mathbf{b}^T$ -
cross(a, b)returns$\mathbf{a} \times \mathbf{b}$ ; forvec2's assumes a$0$ $z$ -component and returns the$z$ -component of the result
real norm(vecX a);
real squaredNorm(vecX a);
vecX normalized(vecX a);-
norm(a)returns$|\mathbf{a}|$ , i.e. the length of$\mathbf{a}$ -
squaredNorm(a)returns$|\mathbf{a}|^2$ -
normalized(a)returns$\hat{\mathbf{a}} = \mathbf{a} / |\mathbf{a}|$
vecX transformPoint(mat4 M, vecX p);
vecX transformVector(mat4 M, vecX v);
vecX transformNormal(mat4 M, vecX n);-
pcan be avec3or avec2; in thevec2case we temporarily augment{ p.x, p.y }into{ p.x, p.y, 0.0 }; the same is true ofvandn -
transformPoint(M, p)performs
and divides by the
-
transformVector(M, v)performs
-
transformNormal(M, n)performs
vecX cwiseAbs(vecX a);
vecX cwiseMin(vecX a, vecX b);
vecX cwiseMax(vecX a, vecX b);
vecX cwiseProduct(vecX a, vecX b);matX inverse(matX M);
matX transpose(matX M);
real determinant(matX M);pprint(vecX a);
pprint(matX M);int2 and int3 are similar to vec2 and vec3, e.g.,
int3 foo = { 0, 1, 2 };
foo[1] = 3;however,
- they store int's instead of real's
- they have no fun functions defined for them
- their "dot accessors" are
foo.i,foo.j,foo.kinstead offoo.x,foo.y,foo.z