Commit: af4c8ec39796779c9fd0b93b87f8b31d216a0037
Parent: 5e2feb594a335bc7e8a834e8c559ef829862d213
Author: Randy Palamar
Date:   Sat, 24 May 2025 15:10:25 -0600
add camera and projection matrices
Diffstat:
5 files changed, 177 insertions(+), 30 deletions(-)
diff --git a/common.c b/common.c
@@ -4,7 +4,9 @@
 #include "GLFW/glfw3.h"
 
 #define BG_CLEAR_COLOUR    (v4){{0.12, 0.1, 0.1, 1}}
-#define RENDER_TARGET_SIZE 720, 720
+#define RENDER_TARGET_SIZE 1024, 1024
+
+#define CAMERA_ELEVATION_ANGLE 30.0f
 
 /* NOTE(rnp): for video output we will render a full rotation in this much time at the
  * the specified frame rate */
@@ -12,10 +14,13 @@
 #define OUTPUT_FRAME_RATE      30.0f
 #define OUTPUT_BG_CLEAR_COLOUR (v4){{0.0, 0.2, 0.0, 1}}
 
-#define VIEWER_RENDER_DYNAMIC_RANGE_LOC (0)
-#define VIEWER_RENDER_THRESHOLD_LOC     (1)
-#define VIEWER_RENDER_GAMMA_LOC         (2)
-#define VIEWER_RENDER_LOG_SCALE_LOC     (3)
+#define MODEL_RENDER_MODEL_MATRIX_LOC  (0)
+#define MODEL_RENDER_VIEW_MATRIX_LOC   (1)
+#define MODEL_RENDER_PROJ_MATRIX_LOC   (2)
+#define MODEL_RENDER_LOG_SCALE_LOC     (3)
+#define MODEL_RENDER_DYNAMIC_RANGE_LOC (4)
+#define MODEL_RENDER_THRESHOLD_LOC     (5)
+#define MODEL_RENDER_GAMMA_LOC         (6)
 
 typedef struct {
 	v3  normal;
@@ -144,41 +149,60 @@ function FILE_WATCH_CALLBACK_FN(reload_shader)
 }
 
 function RenderModel
-load_render_model(Arena arena, c8 *positions_file_name, c8 *indices_file_name)
+load_render_model(Arena arena, c8 *positions_file_name, c8 *indices_file_name, c8 *normals_file_name)
 {
 	RenderModel result = {0};
 
 	str8 positions = os_read_whole_file(&arena, positions_file_name);
+	str8 normals   = os_read_whole_file(&arena, normals_file_name);
 	str8 indices   = os_read_whole_file(&arena, indices_file_name);
 
 	result.elements = indices.len / sizeof(u16);
 
-	s32 buffer_size = positions.len + indices.len;
+	s32 buffer_size = positions.len + indices.len + normals.len;
 
-	result.elements_offset = positions.len;
+	s32 el_offset = positions.len + normals.len;
+	result.elements_offset = el_offset;
 
 	glCreateBuffers(1, &result.buffer);
 	glNamedBufferStorage(result.buffer, buffer_size, 0, GL_DYNAMIC_STORAGE_BIT);
 	glNamedBufferSubData(result.buffer, 0,             positions.len, positions.data);
-	glNamedBufferSubData(result.buffer, positions.len, indices.len,   indices.data);
+	glNamedBufferSubData(result.buffer, positions.len, normals.len,   normals.data);
+	glNamedBufferSubData(result.buffer, el_offset,     indices.len,   indices.data);
 
 	glCreateVertexArrays(1, &result.vao);
-	glVertexArrayVertexBuffer(result.vao, 0, result.buffer, 0, 3 * sizeof(f32));
+	glVertexArrayVertexBuffer(result.vao, 0, result.buffer, 0,             3 * sizeof(f32));
+	glVertexArrayVertexBuffer(result.vao, 1, result.buffer, positions.len, 3 * sizeof(f32));
 	glVertexArrayElementBuffer(result.vao, result.buffer);
 
 	glEnableVertexArrayAttrib(result.vao, 0);
-	glVertexArrayAttribFormat(result.vao,  0, 3, GL_FLOAT, 0, 0);
+	glEnableVertexArrayAttrib(result.vao, 1);
+
+	glVertexArrayAttribFormat(result.vao, 0, 3, GL_FLOAT, 0, 0);
+	glVertexArrayAttribFormat(result.vao, 1, 3, GL_FLOAT, 0, positions.len);
+
 	glVertexArrayAttribBinding(result.vao, 0, 0);
+	glVertexArrayAttribBinding(result.vao, 1, 0);
 
 	return result;
 }
 
 function void
+scroll_callback(GLFWwindow *window, f64 x, f64 y)
+{
+	ViewerContext *ctx  = glfwGetWindowUserPointer(window);
+	ctx->camera_radius += 0.2 * y;
+}
+
+function void
 key_callback(GLFWwindow *window, s32 key, s32 scancode, s32 action, s32 modifiers)
 {
 	ViewerContext *ctx = glfwGetWindowUserPointer(window);
 	if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
 		ctx->should_exit = 1;
+
+	ctx->camera_angle += (key == GLFW_KEY_W && action != GLFW_RELEASE) * 5 * PI / 180.0f;
+	ctx->camera_angle -= (key == GLFW_KEY_S && action != GLFW_RELEASE) * 5 * PI / 180.0f;
 }
 
 function void
@@ -191,7 +215,9 @@ fb_callback(GLFWwindow *window, s32 w, s32 h)
 function void
 init_viewer(ViewerContext *ctx)
 {
-	ctx->window_size  = (sv2){.w = 640, .h = 640};
+	ctx->window_size   = (sv2){.w = 640, .h = 640};
+	ctx->camera_radius = 5;
+	ctx->camera_angle  = CAMERA_ELEVATION_ANGLE * PI / 180.0f;
 
 	if (!glfwInit()) os_fatal(str8("failed to start glfw\n"));
 
@@ -205,6 +231,7 @@ init_viewer(ViewerContext *ctx)
 	glfwSwapInterval(1);
 
 	glfwSetKeyCallback(ctx->window, key_callback);
+	glfwSetScrollCallback(ctx->window, scroll_callback);
 	glfwSetFramebufferSizeCallback(ctx->window, fb_callback);
 
 	if (!gladLoadGL(glfwGetProcAddress)) os_fatal(str8("failed to load glad\n"));
@@ -218,6 +245,8 @@ init_viewer(ViewerContext *ctx)
 	glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
 #endif
 
+	glEnable(GL_DEPTH_TEST);
+
 	RenderContext *rc = &ctx->model_render_context;
 
 	RenderTarget *rt = &ctx->output_target;
@@ -236,25 +265,48 @@ init_viewer(ViewerContext *ctx)
 	"#version 460 core\n"
 	"\n"
 	"layout(location = 0) in vec3 v_position;\n"
+	"layout(location = 1) in vec3 v_normal;\n"
+	"\n"
+	"layout(location = 0) out vec3 f_normal;\n"
+	"\n"
+	"layout(location = " str(MODEL_RENDER_MODEL_MATRIX_LOC) ") uniform mat4 u_model;\n"
+	"layout(location = " str(MODEL_RENDER_VIEW_MATRIX_LOC)  ") uniform mat4 u_view;\n"
+	"layout(location = " str(MODEL_RENDER_PROJ_MATRIX_LOC)  ") uniform mat4 u_projection;\n"
+	"\n"
 	"\n"
 	"void main()\n"
 	"{\n"
-	"\tgl_Position = vec4(0.5 * v_position, 1);\n"
+	//"\tf_normal    = normalize(mat3(u_model) * v_normal);\n"
+	"\tf_normal    = v_normal;\n"
+	"\tgl_Position = u_projection * u_view * u_model * vec4(v_position, 1);\n"
 	"}\n");
 
 	model_rc->fragment_header = str8(""
 	"#version 460 core\n\n"
+	"layout(location = 0) in  vec3 normal;\n\n"
 	"layout(location = 0) out vec4 out_colour;\n\n"
-	"layout(location = " str(VIEWER_RENDER_DYNAMIC_RANGE_LOC) ") uniform float u_db_cutoff = 60;\n"
-	"layout(location = " str(VIEWER_RENDER_THRESHOLD_LOC)     ") uniform float u_threshold = 40;\n"
-	"layout(location = " str(VIEWER_RENDER_GAMMA_LOC)         ") uniform float u_gamma     = 1;\n"
-	"layout(location = " str(VIEWER_RENDER_LOG_SCALE_LOC)     ") uniform bool  u_log_scale;\n"
+	"layout(location = " str(MODEL_RENDER_DYNAMIC_RANGE_LOC) ") uniform float u_db_cutoff = 60;\n"
+	"layout(location = " str(MODEL_RENDER_THRESHOLD_LOC)     ") uniform float u_threshold = 40;\n"
+	"layout(location = " str(MODEL_RENDER_GAMMA_LOC)         ") uniform float u_gamma     = 1;\n"
+	"layout(location = " str(MODEL_RENDER_LOG_SCALE_LOC)     ") uniform bool  u_log_scale;\n"
 	"\n#line 1\n");
 
 	str8 render_model = str8("render_model.frag.glsl");
 	reload_shader(&ctx->os, render_model, (sptr)model_rc, ctx->arena);
 	os_add_file_watch(&ctx->os, &ctx->arena, render_model, reload_shader, (sptr)model_rc);
 
+	/* TODO(rnp): think about a reasonable region (probably min_coord -> max_coord + 10%) */
+	f32 n = 1;
+	f32 f = 20;
+	f32 a = -f / (f - n);
+	f32 b = -f * n / (f - n);
+	m4 projection;
+	projection.c[0] = (v4){{1, 0, 0,  0}};
+	projection.c[1] = (v4){{0, 1, 0,  0}};
+	projection.c[2] = (v4){{0, 0, a, -1}};
+	projection.c[3] = (v4){{0, 0, b,  0}};
+	glProgramUniformMatrix4fv(rc->shader, MODEL_RENDER_PROJ_MATRIX_LOC, 1, 0, projection.E);
+
 	rc = &ctx->overlay_render_context;
 	ShaderReloadContext *overlay_rc = push_struct(&ctx->arena, ShaderReloadContext);
 	overlay_rc->render_context = rc;
@@ -312,19 +364,63 @@ init_viewer(ViewerContext *ctx)
 	os_add_file_watch(&ctx->os, &ctx->arena, render_overlay, reload_shader, (sptr)overlay_rc);
 
 	ctx->unit_cube = load_render_model(ctx->arena, "unit_cube_positions.bin",
-	                                   "unit_cube_indices.bin");
+	                                   "unit_cube_indices.bin", "unit_cube_normals.bin");
+}
+
+function void
+set_camera(u32 program, u32 location, v3 position, v3 normal, v3 orthogonal)
+{
+	v3 right = cross(orthogonal, normal);
+	v3 up    = cross(normal,     right);
+
+	v3 translate;
+	position    = v3_sub((v3){0}, position);
+	translate.x = v3_dot(position, right);
+	translate.y = v3_dot(position, up);
+	translate.z = v3_dot(position, normal);
+
+	m4 transform;
+	transform.c[0] = (v4){{right.x,     up.x,        normal.x,    0}};
+	transform.c[1] = (v4){{right.y,     up.y,        normal.y,    0}};
+	transform.c[2] = (v4){{right.z,     up.z,        normal.z,    0}};
+	transform.c[3] = (v4){{translate.x, translate.y, translate.z, 1}};
+	glProgramUniformMatrix4fv(program, location, 1, 0, transform.E);
 }
 
 function void
-viewer_frame_step(ViewerContext *ctx)
+viewer_frame_step(ViewerContext *ctx, f32 dt)
 {
+	ctx->cycle_t += 0.25 * dt;
+	if (ctx->cycle_t > 1) ctx->cycle_t -= 1;
+
+	f32 angle = ctx->cycle_t * 2 * PI;
+	ctx->camera_position.x =  ctx->camera_radius * cos_f32(angle);
+	ctx->camera_position.z = -ctx->camera_radius * sin_f32(angle);
+	ctx->camera_position.y =  ctx->camera_radius * tan_f32(ctx->camera_angle);
+
 	//////////////
 	// 3D Models
+	f32 one = 1;
 	glBindFramebuffer(GL_FRAMEBUFFER, ctx->output_target.fb);
 	glClearNamedFramebufferfv(ctx->output_target.fb, GL_COLOR, 0, OUTPUT_BG_CLEAR_COLOUR.E);
+	glClearNamedFramebufferfv(ctx->output_target.fb, GL_DEPTH, 0, &one);
 	glViewport(0, 0, ctx->output_target.size.w, ctx->output_target.size.h);
 
 	glUseProgram(ctx->model_render_context.shader);
+
+	v3 camera = ctx->camera_position;
+	set_camera(ctx->model_render_context.shader, MODEL_RENDER_VIEW_MATRIX_LOC,
+	           camera, v3_normalize(v3_sub(camera, (v3){0})), (v3){{0, 1, 0}});
+
+	f32 scale = 2;
+	m4 model_transform;
+	model_transform.c[0] = (v4){{scale, 0,     0,     0}};
+	model_transform.c[1] = (v4){{0,     scale, 0,     0}};
+	model_transform.c[2] = (v4){{0,     0,     scale, 0}};
+	model_transform.c[3] = (v4){{0,     0,     0,     1}};
+	glProgramUniformMatrix4fv(ctx->model_render_context.shader, MODEL_RENDER_MODEL_MATRIX_LOC,
+	                          1, 0, model_transform.E);
+
 	glBindVertexArray(ctx->unit_cube.vao);
 	glDrawElements(GL_TRIANGLES, ctx->unit_cube.elements, GL_UNSIGNED_SHORT,
 	               (void *)ctx->unit_cube.elements_offset);
@@ -333,6 +429,7 @@ viewer_frame_step(ViewerContext *ctx)
 	// UI Overlay
 	glBindFramebuffer(GL_FRAMEBUFFER, 0);
 	glClearNamedFramebufferfv(0, GL_COLOR, 0, BG_CLEAR_COLOUR.E);
+	glClearNamedFramebufferfv(0, GL_DEPTH, 0, &one);
 
 	f32 aspect_ratio = (f32)ctx->output_target.size.w / (f32)ctx->output_target.size.h;
 	sv2 target_size  = ctx->window_size;
diff --git a/main_linux.c b/main_linux.c
@@ -60,10 +60,13 @@ main(void)
 	fds[0].fd     = ctx->os.file_watch_context.handle;
 	fds[0].events = POLLIN;
 
+	f64 last_time = 0;
 	while (!ctx->should_exit) {
 		poll(fds, countof(fds), 0);
 		if (fds[0].revents & POLLIN)
 			dispatch_file_watch_events(&ctx->os, ctx->arena);
-		viewer_frame_step(ctx);
+		f64 now = glfwGetTime();
+		viewer_frame_step(ctx, now - last_time);
+		last_time = now;
 	}
 }
diff --git a/render_model.frag.glsl b/render_model.frag.glsl
@@ -36,5 +36,5 @@ void main()
 	//v_out_colour = vec4(hsv2rgb(vec3(360 * smp, 0.8, 0.95)), 1);
 	#endif
 
-	out_colour = vec4(1);
+	out_colour = vec4(abs(normal), 1);
 }
diff --git a/util.c b/util.c
@@ -80,6 +80,53 @@ da_reserve_(Arena *a, void *data, sz *capacity, sz needed, sz align, sz size)
 	return data;
 }
 
+function sv2
+sv2_sub(sv2 a, sv2 b)
+{
+	sv2 result;
+	result.x = a.x - b.x;
+	result.y = a.y - b.y;
+	return result;
+}
+
+function v3
+v3_sub(v3 a, v3 b)
+{
+	v3 result;
+	result.x = a.x - b.x;
+	result.y = a.y - b.y;
+	result.z = a.z - b.z;
+	return result;
+}
+
+function f32
+v3_dot(v3 a, v3 b)
+{
+	f32 result = a.x * b.x + a.y * b.y + a.z * b.z;
+	return result;
+}
+
+function v3
+v3_normalize(v3 a)
+{
+	f32 scale   = sqrt_f32(1 / v3_dot(a, a));
+	v3  result  = a;
+	result.x   *= scale;
+	result.y   *= scale;
+	result.z   *= scale;
+	return result;
+}
+
+function v3
+cross(v3 a, v3 b)
+{
+	v3 result;
+	result.x = a.y * b.z - a.z * b.y;
+	result.y = a.z * b.x - a.x * b.z;
+	result.z = a.x * b.y - a.y * b.x;
+	return result;
+}
+
 function u32
 utf8_encode(u8 *out, u32 cp)
 {
@@ -407,15 +454,6 @@ parse_f64(str8 s)
 	return result;
 }
 
-function sv2
-sv2_sub(sv2 a, sv2 b)
-{
-	sv2 result;
-	result.x = a.x - b.x;
-	result.y = a.y - b.y;
-	return result;
-}
-
 function FileWatchDirectory *
 lookup_file_watch_directory(FileWatchContext *ctx, u64 hash)
 {
diff --git a/util.h b/util.h
@@ -37,6 +37,9 @@
 /* TODO(rnp): msvc probably won't build this but there are other things preventing that as well */
 #define sqrt_f32(a)     __builtin_sqrtf(a)
 #define atan2_f32(y, x) __builtin_atan2f(y, x)
+#define sin_f32(x)      __builtin_sinf(x)
+#define cos_f32(x)      __builtin_cosf(x)
+#define tan_f32(x)      __builtin_tanf(x)
 
 #if ARCH_ARM64
   /* TODO? debuggers just loop here forever and need a manual PC increment (step over) */
@@ -111,6 +114,8 @@
 #define U32_MAX          (0xFFFFFFFFUL)
 #define F32_INFINITY     (__builtin_inff())
 
+#define PI (3.14159265358979323846)
+
 #define INVALID_FILE (-1)
 
 typedef char      c8;
@@ -294,6 +299,10 @@ typedef struct {
 
 	sv2 window_size;
 
+	f32 cycle_t;
+	f32 camera_angle;
+	f32 camera_radius;
+	v3  camera_position;
 	b32 should_exit;
 
 	void *window;