summaryrefslogtreecommitdiff
path: root/src/render.c
diff options
context:
space:
mode:
authorLizzy Fleckenstein <lizzy@vlhl.dev>2026-04-12 20:57:06 +0200
committerLizzy Fleckenstein <lizzy@vlhl.dev>2026-04-12 20:59:39 +0200
commite5af28536bfb0f4c9131df56d2009ba5196f5e3a (patch)
tree3ab928f961a1ccd8440b070d7b57f79146457e8c /src/render.c
downloadanimtool-e5af28536bfb0f4c9131df56d2009ba5196f5e3a.tar.xz
init
Diffstat (limited to 'src/render.c')
-rw-r--r--src/render.c191
1 files changed, 191 insertions, 0 deletions
diff --git a/src/render.c b/src/render.c
new file mode 100644
index 0000000..9c7d796
--- /dev/null
+++ b/src/render.c
@@ -0,0 +1,191 @@
+#include <math.h>
+#include <time.h>
+#include <unistd.h>
+#include "lib/linmath.h"
+#include "render.h"
+
+typedef arraybuf(struct draw_call) draw_calls;
+
+struct render_state {
+ size_t n_frame;
+ double time;
+ struct timespec start_ts;
+ draw_calls calls_buf;
+ rgba_data render_buffer;
+};
+
+static void render_surface(struct draw_surface *dsurf, struct surface *surf, double time)
+{
+ uint32_t color = round(expr_eval(&surf->color, time));
+ for (size_t i = 0; i < 3; i++)
+ dsurf->color[i] = (float) ((color >> ((3-i-1)*8)) & 0xff) / 255.0;
+ if ((dsurf->texture.present = surf->texture.present))
+ dsurf->texture.data = surf->texture.data.id;
+}
+
+#define deg2rad(X) ((X)*M_PI/180.0)
+
+static void render_node(struct node *node, mat4x4 model, draw_calls *calls, double time)
+{
+ switch (node->type) {
+ case NODE_TRANSFORM: {
+ float vals[TRANSFORM_OPS_N][3] = {
+ [TR_TRANSLATE] = { 0.0, 0.0, 0.0 },
+ [TR_SCALE] = { 1.0, 1.0, 1.0 },
+ [TR_ROTATE] = { 0.0, 0.0, 0.0 },
+ };
+ for (enum transform_op op = 0; op < TRANSFORM_OPS_N; op++)
+ for (size_t i = 0; i < 3; i++)
+ if (node->transform.has_op[op][i])
+ vals[op][i] = expr_eval(&node->transform.op[op][i], time);
+
+ mat4x4 trans;
+ mat4x4_identity(trans);
+ mat4x4_translate(trans, vals[TR_TRANSLATE][0], vals[TR_TRANSLATE][1], vals[TR_TRANSLATE][2]);
+ mat4x4_scale_aniso(trans, trans, vals[TR_SCALE][0], vals[TR_SCALE][1], vals[TR_SCALE][2]);
+ mat4x4_rotate_X(trans, trans, deg2rad(vals[TR_ROTATE][0]));
+ mat4x4_rotate_Y(trans, trans, deg2rad(vals[TR_ROTATE][1]));
+ mat4x4_rotate_Z(trans, trans, deg2rad(vals[TR_ROTATE][2]));
+
+ mat4x4 new_model;
+ mat4x4_mul(new_model, model, trans);
+
+ for (size_t i = 0; i < node->transform.children.len; i++)
+ render_node(&node->transform.children.ptr[i], new_model, calls, time);
+ break;
+ }
+ case NODE_CAMERA: {
+ vec4 pos, up, origin = { 0.0, 0.0, 0.0, 1.0 }, origin_up = { 0.0, 1.0, 0.0, 1.0 };
+ mat4x4_mul_vec4(pos, model, origin);
+ if (node->camera.roll)
+ mat4x4_mul_vec4(up, model, origin_up);
+ else
+ vec4_dup(up, origin_up);
+ vec3_dup(node->camera.pos, pos);
+ vec3_norm(node->camera.up, up);
+ break;
+ }
+ case NODE_OBJECT: {
+ arraybuf_grow(calls, 1);
+ struct draw_call *call = &calls->ptr[calls->len++];
+ call->mesh = node->object.mesh.id;
+ render_surface(&call->surface, &node->object.surface, time);
+ mat4x4_dup(call->model, model);
+ break;
+ }
+ case NODE_BONE: {
+ vec4 pos, origin = { 0.0, 0.0, 0.0, 1.0 };
+ mat4x4_mul_vec4(pos, model, origin);
+ vec3_dup(node->bone.pos, pos);
+ }
+ case NODE_LIGHT:
+ // TODO: light
+ break;
+ }
+}
+
+static void eval_pos(struct pos *pos, float out[3], double time)
+{
+ switch (pos->type) {
+ case POS_ABSOLUTE:
+ for (size_t i = 0; i < 3; i++)
+ out[i] = expr_eval(&pos->absolute[i], time);
+ break;
+ case POS_ATTACHED:
+ vec3_dup(out, pos->attach->pos);
+ break;
+ }
+}
+
+static void render_frame(struct draw_frame *frame, struct scene *scene, draw_calls *calls_buf, double time)
+{
+ mat4x4 model;
+ mat4x4_identity(model);
+ calls_buf->len = 0;
+ render_node(&scene->root, model, calls_buf, time);
+
+ mat4x4 view;
+ vec3 target;
+ eval_pos(&scene->camera->target, target, time);
+ mat4x4_look_at(view, scene->camera->pos, target, scene->camera->up);
+
+ mat4x4 proj;
+ switch (scene->proj.type) {
+ case PROJ_PERSPECTIVE: {
+ float fov = deg2rad(expr_eval(&scene->proj.perspective.fov, time));
+ mat4x4_perspective(proj, fov, (float) scene->size[0] / scene->size[1],
+ scene->proj.perspective.planes[0], scene->proj.perspective.planes[1]);
+ break;
+ }
+ case PROJ_ORTHO: {
+ // TODO: aspect scaling?
+ float *planes = scene->proj.ortho.planes;
+ mat4x4_ortho(proj, planes[0], planes[1], planes[2], planes[3], planes[4], planes[5]);
+ break;
+ }
+ }
+
+ array_assign(&frame->calls, *calls_buf);
+ if ((frame->bg.present = scene->bg.present))
+ render_surface(&frame->bg.data, &scene->bg.data, time);
+ mat4x4_mul(frame->view_proj, proj, view);
+}
+
+static void render_seq(size_t n_inst, struct seq_inst inst[n_inst], struct scene *scene, struct draw_backend *draw, struct nut_writer *out, struct render_state *state)
+{
+ for (size_t i = 0; i < n_inst; i++) {
+ switch (inst[i].type) {
+ case SEQ_TIME: {
+ double target = state->time + inst[i].time;
+ double time;
+ while ((time = (double) state->n_frame / scene->fps) < target) {
+ if (scene->live.present) {
+ struct timespec ts_now;
+ clock_gettime(CLOCK_REALTIME, &ts_now);
+ double e_time = (double)
+ (ts_now.tv_nsec - state->start_ts.tv_nsec) / 1000000000.0 +
+ (ts_now.tv_sec - state->start_ts.tv_sec);
+ double ahead = time - e_time - scene->live.data;
+ if (ahead > 0.0)
+ usleep(ahead * 1000000.0);
+ }
+
+ struct draw_frame frame;
+ render_frame(&frame, scene, &state->calls_buf, time);
+ draw->draw_frame(&frame, state->render_buffer);
+ nut_write_frame(out, state->render_buffer);
+ state->n_frame++;
+ }
+ state->time = target;
+ break;
+ }
+ case SEQ_REP:
+ for (size_t n = 0; inst[i].rep.times == SEQ_TIMES_INF || n < inst[i].rep.times; n++)
+ render_seq(inst[i].rep.children.len, inst[i].rep.children.ptr, scene, draw, out, state);
+ break;
+ case SEQ_POV:
+ scene->camera = inst[i].pov;
+ break;
+ }
+ }
+}
+
+void render_scene(struct scene *scene, struct draw_backend *draw, struct nut_writer *out)
+{
+ struct render_state state = {};
+ state.time = 0.0;
+ state.render_buffer = malloc(4 * scene->size[0] * scene->size[1]);
+ clock_gettime(CLOCK_REALTIME, &state.start_ts);
+
+ if (scene->seq.len) {
+ render_seq(scene->seq.len, scene->seq.ptr, scene, draw, out, &state);
+ } else {
+ struct seq_inst seq;
+ seq.type = SEQ_TIME;
+ seq.time = 1.0;
+ render_seq(1, &seq, scene, draw, out, &state);
+ }
+
+ free(state.render_buffer);
+ free(state.calls_buf.ptr);
+}