rev |
line source |
nuclear@2
|
1 #include <string.h>
|
nuclear@2
|
2 #include <limits.h>
|
nuclear@27
|
3 #include <algorithm>
|
nuclear@2
|
4 #include <chrono>
|
nuclear@2
|
5 #include <random>
|
nuclear@2
|
6 #include "erebus.h"
|
nuclear@17
|
7 #include "erebus_impl.h"
|
nuclear@4
|
8 #include "scene.h"
|
nuclear@4
|
9 #include "geomobj.h"
|
nuclear@5
|
10 #include "rt.h"
|
nuclear@2
|
11
|
nuclear@8
|
12 #define INF_SAMPLES (INT_MAX / 2)
|
nuclear@8
|
13
|
nuclear@2
|
14 using namespace std::chrono;
|
nuclear@2
|
15
|
nuclear@26
|
16 static void render_block(struct erebus *ctx, Block blk);
|
nuclear@8
|
17 static void render_pixel(struct erebus *ctx, int x, int y, int sample);
|
nuclear@2
|
18
|
nuclear@2
|
19 static std::mt19937 rnd_gen;
|
nuclear@2
|
20
|
nuclear@2
|
21 extern "C" {
|
nuclear@2
|
22
|
nuclear@2
|
23 struct erebus *erb_init(void)
|
nuclear@2
|
24 {
|
nuclear@31
|
25 struct erebus *ctx = 0;
|
nuclear@2
|
26 try {
|
nuclear@2
|
27 ctx = new struct erebus;
|
nuclear@2
|
28 }
|
nuclear@2
|
29 catch(...) {
|
nuclear@2
|
30 return 0;
|
nuclear@2
|
31 }
|
nuclear@2
|
32
|
nuclear@10
|
33 rnd_gen.seed(time(0));
|
nuclear@10
|
34
|
nuclear@4
|
35 ctx->scn = 0;
|
nuclear@2
|
36 ctx->cur_time = 0;
|
nuclear@26
|
37 ctx->cur_frame = 0;
|
nuclear@32
|
38 ctx->tpool = 0;
|
nuclear@8
|
39
|
nuclear@17
|
40 erb_setoptf(ctx, ERB_OPT_GAMMA, 2.2);
|
nuclear@17
|
41 erb_setopti(ctx, ERB_OPT_MAX_ITER, 6);
|
nuclear@17
|
42 erb_setopti(ctx, ERB_OPT_MAX_SAMPLES, INF_SAMPLES);
|
nuclear@17
|
43 erb_setopti(ctx, ERB_OPT_NUM_THREADS, -1);
|
nuclear@15
|
44
|
nuclear@15
|
45 ctx->dbg_nodesel = -1;
|
nuclear@2
|
46 return ctx;
|
nuclear@2
|
47 }
|
nuclear@2
|
48
|
nuclear@2
|
49 void erb_destroy(struct erebus *ctx)
|
nuclear@2
|
50 {
|
nuclear@31
|
51 if(ctx) {
|
nuclear@31
|
52 // make sure the threadpool stops BEFORE destroying the framebuffers etc in ctx
|
nuclear@31
|
53 delete ctx->tpool;
|
nuclear@31
|
54 delete ctx;
|
nuclear@31
|
55 }
|
nuclear@2
|
56 }
|
nuclear@2
|
57
|
nuclear@2
|
58 void erb_setopti(struct erebus *ctx, enum erb_option opt, int val)
|
nuclear@2
|
59 {
|
nuclear@17
|
60 ctx->options[opt].ival = val;
|
nuclear@17
|
61 ctx->options[opt].type = Option::Type::INT;
|
nuclear@2
|
62 }
|
nuclear@17
|
63
|
nuclear@2
|
64 void erb_setoptf(struct erebus *ctx, enum erb_option opt, float val)
|
nuclear@2
|
65 {
|
nuclear@17
|
66 ctx->options[opt].fval = val;
|
nuclear@17
|
67 ctx->options[opt].type = Option::Type::FLOAT;
|
nuclear@2
|
68 }
|
nuclear@17
|
69
|
nuclear@2
|
70 void erb_setoptfv(struct erebus *ctx, enum erb_option opt, float *vec)
|
nuclear@2
|
71 {
|
nuclear@2
|
72 for(int i=0; i<4; i++) {
|
nuclear@17
|
73 ctx->options[opt].vval[i] = vec[i];
|
nuclear@2
|
74 }
|
nuclear@17
|
75 ctx->options[opt].type = Option::Type::VEC;
|
nuclear@2
|
76 }
|
nuclear@2
|
77
|
nuclear@2
|
78 int erb_getopti(struct erebus *ctx, enum erb_option opt)
|
nuclear@2
|
79 {
|
nuclear@17
|
80 switch(ctx->options[opt].type) {
|
nuclear@17
|
81 case Option::Type::INT:
|
nuclear@17
|
82 return ctx->options[opt].ival;
|
nuclear@17
|
83 case Option::Type::FLOAT:
|
nuclear@17
|
84 return (int)ctx->options[opt].fval;
|
nuclear@17
|
85 case Option::Type::VEC:
|
nuclear@17
|
86 return (int)ctx->options[opt].vval.x;
|
nuclear@17
|
87 }
|
nuclear@17
|
88 return 0; // can't happen
|
nuclear@2
|
89 }
|
nuclear@17
|
90
|
nuclear@2
|
91 float erb_getoptf(struct erebus *ctx, enum erb_option opt)
|
nuclear@2
|
92 {
|
nuclear@17
|
93 switch(ctx->options[opt].type) {
|
nuclear@17
|
94 case Option::Type::INT:
|
nuclear@17
|
95 return (float)ctx->options[opt].ival;
|
nuclear@17
|
96 case Option::Type::FLOAT:
|
nuclear@17
|
97 return ctx->options[opt].fval;
|
nuclear@17
|
98 case Option::Type::VEC:
|
nuclear@17
|
99 return ctx->options[opt].vval.x;
|
nuclear@17
|
100 }
|
nuclear@17
|
101 return 0.0f; // can't happen
|
nuclear@2
|
102 }
|
nuclear@17
|
103
|
nuclear@2
|
104 float *erb_getoptfv(struct erebus *ctx, enum erb_option opt)
|
nuclear@2
|
105 {
|
nuclear@17
|
106 switch(ctx->options[opt].type) {
|
nuclear@17
|
107 case Option::Type::INT:
|
nuclear@17
|
108 {
|
nuclear@17
|
109 int ival = ctx->options[opt].ival;
|
nuclear@17
|
110 ctx->options[opt].vval = Vector4(ival, ival, ival, ival);
|
nuclear@17
|
111 }
|
nuclear@17
|
112 break;
|
nuclear@17
|
113 case Option::Type::FLOAT:
|
nuclear@17
|
114 {
|
nuclear@17
|
115 float fval = ctx->options[opt].fval;
|
nuclear@17
|
116 ctx->options[opt].vval = Vector4(fval, fval, fval, fval);
|
nuclear@17
|
117 }
|
nuclear@17
|
118 default:
|
nuclear@17
|
119 break;
|
nuclear@17
|
120 }
|
nuclear@17
|
121
|
nuclear@17
|
122 return &ctx->options[opt].vval.x;
|
nuclear@2
|
123 }
|
nuclear@2
|
124
|
nuclear@2
|
125 float *erb_get_framebuffer(struct erebus *ctx)
|
nuclear@2
|
126 {
|
nuclear@2
|
127 return ctx->fbimg.get_pixels();
|
nuclear@2
|
128 }
|
nuclear@2
|
129
|
nuclear@2
|
130 void erb_begin_frame(struct erebus *ctx, long ms)
|
nuclear@2
|
131 {
|
nuclear@32
|
132 if(!ctx->tpool) {
|
nuclear@32
|
133 int num_threads = erb_getopti(ctx, ERB_OPT_NUM_THREADS);
|
nuclear@32
|
134 ctx->tpool = new ThreadPool(num_threads);
|
nuclear@32
|
135 }
|
nuclear@32
|
136
|
nuclear@26
|
137 ++ctx->cur_frame;
|
nuclear@26
|
138 ctx->cur_sample = 0;
|
nuclear@2
|
139 ctx->cur_time = ms;
|
nuclear@4
|
140
|
nuclear@17
|
141 int xsz = erb_getopti(ctx, ERB_OPT_WIDTH);
|
nuclear@17
|
142 int ysz = erb_getopti(ctx, ERB_OPT_HEIGHT);
|
nuclear@4
|
143
|
nuclear@26
|
144 if(!ctx->fbimg.get_pixels() || ctx->fbimg.get_width() != xsz || ctx->fbimg.get_height() < ysz) {
|
nuclear@26
|
145 ctx->fbimg.create(xsz, ysz);
|
nuclear@26
|
146 ctx->accum.create(xsz, ysz);
|
nuclear@26
|
147 } else {
|
nuclear@26
|
148 ctx->fbimg.clear();
|
nuclear@26
|
149 ctx->accum.clear();
|
nuclear@26
|
150 }
|
nuclear@15
|
151
|
nuclear@17
|
152 ctx->inv_gamma = 1.0f / erb_getoptf(ctx, ERB_OPT_GAMMA);
|
nuclear@26
|
153
|
nuclear@26
|
154 ctx->scn->update(ctx->cur_time);
|
nuclear@2
|
155 }
|
nuclear@2
|
156
|
nuclear@2
|
157 int erb_render(struct erebus *ctx, long timeout)
|
nuclear@2
|
158 {
|
nuclear@2
|
159 return erb_render_rect(ctx, 0, 0, ctx->fbimg.get_width(), ctx->fbimg.get_height(), timeout);
|
nuclear@2
|
160 }
|
nuclear@2
|
161
|
nuclear@26
|
162 #define BLKSZ 32
|
nuclear@26
|
163
|
nuclear@2
|
164 int erb_render_rect(struct erebus *ctx, int x, int y, int width, int height, long timeout)
|
nuclear@2
|
165 {
|
nuclear@31
|
166 while(ctx->tpool->pending()) {
|
nuclear@26
|
167 if(timeout > 0) {
|
nuclear@31
|
168 long wait_interval = ctx->tpool->wait(timeout);
|
nuclear@26
|
169 timeout -= wait_interval;
|
nuclear@26
|
170 } else {
|
nuclear@26
|
171 return 1;
|
nuclear@26
|
172 }
|
nuclear@26
|
173 }
|
nuclear@26
|
174
|
nuclear@2
|
175 if(!width || !height) return -1;
|
nuclear@2
|
176
|
nuclear@26
|
177 int startx = x;
|
nuclear@26
|
178 int endx = x + width;
|
nuclear@26
|
179 int endy = y + height;
|
nuclear@26
|
180
|
nuclear@26
|
181 while(y < endy) {
|
nuclear@26
|
182 x = startx;
|
nuclear@26
|
183 while(x < endx) {
|
nuclear@26
|
184 Block blk;
|
nuclear@26
|
185 blk.x = x;
|
nuclear@26
|
186 blk.y = y;
|
nuclear@26
|
187 blk.width = std::min(BLKSZ, endx - x);
|
nuclear@26
|
188 blk.height = std::min(BLKSZ, endy - y);
|
nuclear@26
|
189 blk.sample = ctx->cur_sample;
|
nuclear@26
|
190 blk.frame = ctx->cur_frame;
|
nuclear@26
|
191
|
nuclear@31
|
192 ctx->tpool->add_work(std::bind(render_block, ctx, blk));
|
nuclear@26
|
193
|
nuclear@26
|
194 x += BLKSZ;
|
nuclear@26
|
195 }
|
nuclear@26
|
196 y += BLKSZ;
|
nuclear@2
|
197 }
|
nuclear@2
|
198
|
nuclear@26
|
199 ++ctx->cur_sample;
|
nuclear@31
|
200 ctx->tpool->wait(timeout); // wait for completion
|
nuclear@26
|
201 return ctx->cur_sample > erb_getopti(ctx, ERB_OPT_MAX_SAMPLES) ? 0 : 1;
|
nuclear@26
|
202 }
|
nuclear@4
|
203
|
nuclear@2
|
204
|
nuclear@2
|
205 int erb_get_progress(struct erebus *ctx)
|
nuclear@2
|
206 {
|
nuclear@32
|
207 struct erb_render_status st;
|
nuclear@32
|
208 if(erb_get_status(ctx, &st) == -1) {
|
nuclear@32
|
209 return 0;
|
nuclear@32
|
210 }
|
nuclear@32
|
211 return st.progress_percent;
|
nuclear@32
|
212 }
|
nuclear@32
|
213
|
nuclear@32
|
214 int erb_get_status(struct erebus *ctx, struct erb_render_status *stat)
|
nuclear@32
|
215 {
|
nuclear@32
|
216 long pending = ctx->tpool->pending();
|
nuclear@32
|
217 if(!pending) {
|
nuclear@32
|
218 return -1;
|
nuclear@32
|
219 }
|
nuclear@32
|
220 int xsz = ctx->fbimg.get_width();
|
nuclear@32
|
221 int ysz = ctx->fbimg.get_height();
|
nuclear@32
|
222 int xblocks = (xsz + BLKSZ - 1) / BLKSZ;
|
nuclear@32
|
223 int yblocks = (ysz + BLKSZ - 1) / BLKSZ;
|
nuclear@32
|
224 long num_blocks = xblocks * yblocks;
|
nuclear@32
|
225
|
nuclear@32
|
226 stat->frames = stat->max_frames = 0; // TODO
|
nuclear@32
|
227
|
nuclear@32
|
228 stat->blocks = num_blocks - pending;
|
nuclear@32
|
229 stat->max_blocks = num_blocks;
|
nuclear@32
|
230
|
nuclear@32
|
231 stat->samples = ctx->cur_sample ? ctx->cur_sample - 1 : 0;
|
nuclear@32
|
232 if((stat->max_samples = erb_getopti(ctx, ERB_OPT_MAX_SAMPLES)) == INF_SAMPLES) {
|
nuclear@32
|
233 stat->max_samples = stat->samples;
|
nuclear@32
|
234
|
nuclear@32
|
235 stat->progress_percent = 100 * stat->blocks / stat->max_blocks;
|
nuclear@32
|
236 } else {
|
nuclear@32
|
237 stat->progress_percent = 100 * stat->samples / stat->max_samples;
|
nuclear@32
|
238 }
|
nuclear@32
|
239 return 0;
|
nuclear@2
|
240 }
|
nuclear@2
|
241
|
nuclear@2
|
242 int erb_load_scene(struct erebus *ctx, const char *fname)
|
nuclear@2
|
243 {
|
nuclear@4
|
244 delete ctx->scn;
|
nuclear@4
|
245 ctx->scn = new Scene;
|
nuclear@2
|
246
|
nuclear@19
|
247 if(!ctx->scn->load(fname)) {
|
nuclear@19
|
248 return -1;
|
nuclear@19
|
249 }
|
nuclear@4
|
250 return 0;
|
nuclear@2
|
251 }
|
nuclear@2
|
252
|
nuclear@9
|
253 bool erb_input_keyboard(struct erebus *ctx, int key, bool pressed)
|
nuclear@9
|
254 {
|
nuclear@9
|
255 if(!ctx) return false;
|
nuclear@15
|
256 if((int)ctx->keystate.size() <= key) {
|
nuclear@15
|
257 ctx->keystate.resize(key < 256 ? 256 : key + 1);
|
nuclear@15
|
258 }
|
nuclear@9
|
259
|
nuclear@9
|
260 ctx->keystate[key] = pressed;
|
nuclear@10
|
261
|
nuclear@10
|
262 if(pressed) {
|
nuclear@10
|
263 switch(key) {
|
nuclear@15
|
264 case '.':
|
nuclear@15
|
265 {
|
nuclear@15
|
266 int node_count = ctx->scn->get_node_count();
|
nuclear@15
|
267 if(node_count && ++ctx->dbg_nodesel >= node_count) {
|
nuclear@15
|
268 ctx->dbg_nodesel = 0;
|
nuclear@15
|
269 }
|
nuclear@15
|
270 printf("selected node: %d\n", ctx->dbg_nodesel);
|
nuclear@15
|
271 }
|
nuclear@15
|
272 break;
|
nuclear@15
|
273
|
nuclear@15
|
274 case ',':
|
nuclear@15
|
275 {
|
nuclear@15
|
276 int node_count = ctx->scn->get_node_count();
|
nuclear@15
|
277 if(node_count && --ctx->dbg_nodesel < 0) {
|
nuclear@15
|
278 ctx->dbg_nodesel = node_count - 1;
|
nuclear@15
|
279 }
|
nuclear@15
|
280 printf("selected node: %d\n", ctx->dbg_nodesel);
|
nuclear@15
|
281 }
|
nuclear@15
|
282 break;
|
nuclear@15
|
283
|
nuclear@10
|
284 case '=':
|
nuclear@10
|
285 case '-':
|
nuclear@15
|
286 case '0':
|
nuclear@15
|
287 if(ctx->dbg_nodesel != -1) {
|
nuclear@15
|
288 SceneNode *node = ctx->scn->get_node(ctx->dbg_nodesel);
|
nuclear@15
|
289 Vector3 s = node->get_scaling();
|
nuclear@15
|
290 switch(key) {
|
nuclear@15
|
291 case '=':
|
nuclear@15
|
292 node->set_scaling(s * 1.1);
|
nuclear@15
|
293 break;
|
nuclear@15
|
294 case '-':
|
nuclear@15
|
295 node->set_scaling(s * 0.9);
|
nuclear@15
|
296 break;
|
nuclear@15
|
297 case '0':
|
nuclear@15
|
298 node->set_scaling(Vector3(1, 1, 1));
|
nuclear@15
|
299 break;
|
nuclear@15
|
300 }
|
nuclear@15
|
301 }
|
nuclear@15
|
302 erb_begin_frame(ctx, 0);
|
nuclear@15
|
303 return true;
|
nuclear@10
|
304 }
|
nuclear@10
|
305 }
|
nuclear@9
|
306 return false;
|
nuclear@9
|
307 }
|
nuclear@9
|
308
|
nuclear@9
|
309 bool erb_input_mouse_button(struct erebus *ctx, int bn, bool pressed, int x, int y)
|
nuclear@9
|
310 {
|
nuclear@9
|
311 if(!ctx) return false;
|
nuclear@15
|
312 if((int)ctx->bnstate.size() <= bn) {
|
nuclear@15
|
313 ctx->bnstate.resize(bn < 32 ? 32 : bn + 1);
|
nuclear@15
|
314 }
|
nuclear@9
|
315
|
nuclear@9
|
316 ctx->bnstate[bn] = pressed;
|
nuclear@9
|
317 ctx->mouse_pos[0] = x;
|
nuclear@9
|
318 ctx->mouse_pos[1] = y;
|
nuclear@10
|
319 return false;
|
nuclear@9
|
320 }
|
nuclear@9
|
321
|
nuclear@10
|
322 bool erb_input_mouse_motion(struct erebus *ctx, int x, int y)
|
nuclear@10
|
323 {
|
nuclear@18
|
324 bool res = false;
|
nuclear@18
|
325
|
nuclear@18
|
326 if(!ctx) return res;
|
nuclear@18
|
327
|
nuclear@18
|
328 int dx = x - ctx->mouse_pos[0];
|
nuclear@18
|
329 int dy = y - ctx->mouse_pos[1];
|
nuclear@18
|
330
|
nuclear@18
|
331 if(dx || dy) {
|
nuclear@18
|
332 TargetCamera *cam = (TargetCamera*)ctx->scn->get_active_camera();
|
nuclear@18
|
333 if(cam && ctx->bnstate[0]) {
|
nuclear@18
|
334 Vector3 cpos = cam->get_position();
|
nuclear@18
|
335 float mag = cpos.length();
|
nuclear@18
|
336
|
nuclear@19
|
337 float theta = atan2(cpos.z / mag, cpos.x / mag) - DEG_TO_RAD(dx * 0.5);
|
nuclear@19
|
338 float phi = acos(cpos.y / mag) - DEG_TO_RAD(dy * 0.5);
|
nuclear@18
|
339
|
nuclear@18
|
340 if(phi < 0) phi = 0;
|
nuclear@18
|
341 if(phi > M_PI) phi = M_PI;
|
nuclear@18
|
342
|
nuclear@18
|
343 cpos.x = cos(theta) * sin(phi) * mag;
|
nuclear@18
|
344 cpos.y = cos(phi) * mag;
|
nuclear@18
|
345 cpos.z = sin(theta) * sin(phi) * mag;
|
nuclear@18
|
346 cam->set_position(cpos);
|
nuclear@18
|
347
|
nuclear@18
|
348 erb_begin_frame(ctx, 0);
|
nuclear@18
|
349 res = true;
|
nuclear@18
|
350 }
|
nuclear@18
|
351 }
|
nuclear@10
|
352
|
nuclear@10
|
353 ctx->mouse_pos[0] = x;
|
nuclear@10
|
354 ctx->mouse_pos[1] = y;
|
nuclear@18
|
355 return res;
|
nuclear@10
|
356 }
|
nuclear@10
|
357
|
nuclear@10
|
358 bool erb_input_6dof_button(struct erebus *ctx, int bn, bool pressed)
|
nuclear@10
|
359 {
|
nuclear@10
|
360 if(!ctx) return false;
|
nuclear@10
|
361 return false;
|
nuclear@10
|
362 }
|
nuclear@10
|
363
|
nuclear@10
|
364 bool erb_input_6dof_motion(struct erebus *ctx, float x, float y, float z)
|
nuclear@10
|
365 {
|
nuclear@10
|
366 if(!ctx) return false;
|
nuclear@10
|
367 return false;
|
nuclear@10
|
368 }
|
nuclear@9
|
369
|
nuclear@9
|
370
|
nuclear@2
|
371 } // extern "C"
|
nuclear@2
|
372
|
nuclear@2
|
373 float randf(float low, float high)
|
nuclear@2
|
374 {
|
nuclear@2
|
375 std::uniform_real_distribution<float> unirnd(low, high);
|
nuclear@2
|
376 return unirnd(rnd_gen);
|
nuclear@2
|
377 }
|
nuclear@2
|
378
|
nuclear@26
|
379 static void render_block(struct erebus *ctx, Block blk)
|
nuclear@26
|
380 {
|
nuclear@26
|
381 if(blk.frame < ctx->cur_frame) {
|
nuclear@26
|
382 return; // skip stale blocks
|
nuclear@26
|
383 }
|
nuclear@26
|
384
|
nuclear@26
|
385 for(int i=0; i<blk.height; i++) {
|
nuclear@26
|
386 for(int j=0; j<blk.width; j++) {
|
nuclear@26
|
387 render_pixel(ctx, blk.x + j, blk.y + i, blk.sample);
|
nuclear@26
|
388 }
|
nuclear@26
|
389 }
|
nuclear@26
|
390 }
|
nuclear@26
|
391
|
nuclear@8
|
392 static void render_pixel(struct erebus *ctx, int x, int y, int sample)
|
nuclear@2
|
393 {
|
nuclear@5
|
394 Camera *cam = ctx->scn->get_active_camera();
|
nuclear@5
|
395 if(!cam) return;
|
nuclear@5
|
396
|
nuclear@5
|
397 int xsz = ctx->fbimg.get_width();
|
nuclear@5
|
398 int ysz = ctx->fbimg.get_height();
|
nuclear@8
|
399 int offs = (y * xsz + x) * 4;
|
nuclear@5
|
400
|
nuclear@8
|
401 float *pix = ctx->fbimg.get_pixels() + offs;
|
nuclear@8
|
402 float *accum = ctx->accum.get_pixels() + offs;
|
nuclear@8
|
403
|
nuclear@8
|
404 Ray ray = cam->get_primary_ray(x, y, xsz, ysz, sample);
|
nuclear@17
|
405 Color c = ray_trace(ctx, ray, 0);
|
nuclear@8
|
406 accum[0] += c.x;
|
nuclear@8
|
407 accum[1] += c.y;
|
nuclear@8
|
408 accum[2] += c.z;
|
nuclear@8
|
409 accum[3] += c.w;
|
nuclear@8
|
410
|
nuclear@8
|
411 float inv_samples = 1.0f / (float)(sample + 1);
|
nuclear@17
|
412 pix[0] = pow(accum[0] * inv_samples, ctx->inv_gamma);
|
nuclear@17
|
413 pix[1] = pow(accum[1] * inv_samples, ctx->inv_gamma);
|
nuclear@17
|
414 pix[2] = pow(accum[2] * inv_samples, ctx->inv_gamma);
|
nuclear@8
|
415 pix[3] = accum[3] * inv_samples;
|
nuclear@2
|
416 }
|