summaryrefslogtreecommitdiff
path: root/src/render.c
blob: fe0c200d153f19a12a37459bfa7c91d59469a503 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
#include <math.h>
#include <time.h>
#include <unistd.h>
#include "lib/linmath.h"
#include "render.h"

#define deg2rad(X) ((X)*M_PI/180.0)

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;
}

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) {
                            uint64_t nanos = ahead * 1000000000.0;
                            struct timespec ts_sleep;
                            ts_sleep.tv_nsec = nanos % 1000000000;
                            ts_sleep.tv_sec = nanos / 1000000000;
                            nanosleep(&ts_sleep, nullptr);
                        }
                    }
                
                    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);
}