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