nuclear@2: #include nuclear@2: #include nuclear@27: #include nuclear@2: #include nuclear@2: #include nuclear@37: #ifndef _MSC_VER nuclear@37: #include nuclear@37: #else nuclear@37: #include nuclear@37: #endif nuclear@2: #include "erebus.h" nuclear@17: #include "erebus_impl.h" nuclear@4: #include "scene.h" nuclear@4: #include "geomobj.h" nuclear@5: #include "rt.h" nuclear@2: nuclear@8: #define INF_SAMPLES (INT_MAX / 2) nuclear@8: nuclear@2: using namespace std::chrono; nuclear@2: nuclear@26: static void render_block(struct erebus *ctx, Block blk); nuclear@8: static void render_pixel(struct erebus *ctx, int x, int y, int sample); nuclear@2: nuclear@2: static std::mt19937 rnd_gen; nuclear@2: nuclear@2: extern "C" { nuclear@2: nuclear@2: struct erebus *erb_init(void) nuclear@2: { nuclear@31: struct erebus *ctx = 0; nuclear@2: try { nuclear@2: ctx = new struct erebus; nuclear@2: } nuclear@2: catch(...) { nuclear@2: return 0; nuclear@2: } nuclear@2: nuclear@10: rnd_gen.seed(time(0)); nuclear@10: nuclear@40: ctx->rendering = false; nuclear@4: ctx->scn = 0; nuclear@2: ctx->cur_time = 0; nuclear@26: ctx->cur_frame = 0; nuclear@32: ctx->tpool = 0; nuclear@8: nuclear@17: erb_setoptf(ctx, ERB_OPT_GAMMA, 2.2); nuclear@38: erb_setopti(ctx, ERB_OPT_MAX_ITER, 8); nuclear@17: erb_setopti(ctx, ERB_OPT_MAX_SAMPLES, INF_SAMPLES); nuclear@17: erb_setopti(ctx, ERB_OPT_NUM_THREADS, -1); nuclear@15: nuclear@15: ctx->dbg_nodesel = -1; nuclear@2: return ctx; nuclear@2: } nuclear@2: nuclear@2: void erb_destroy(struct erebus *ctx) nuclear@2: { nuclear@31: if(ctx) { nuclear@31: // make sure the threadpool stops BEFORE destroying the framebuffers etc in ctx nuclear@31: delete ctx->tpool; nuclear@31: delete ctx; nuclear@31: } nuclear@2: } nuclear@2: nuclear@40: void erb_clear(struct erebus *ctx) nuclear@40: { nuclear@40: erb_end_frame(ctx); nuclear@40: ctx->scn->clear(); nuclear@40: } nuclear@40: nuclear@2: void erb_setopti(struct erebus *ctx, enum erb_option opt, int val) nuclear@2: { nuclear@17: ctx->options[opt].ival = val; nuclear@17: ctx->options[opt].type = Option::Type::INT; nuclear@2: } nuclear@17: nuclear@2: void erb_setoptf(struct erebus *ctx, enum erb_option opt, float val) nuclear@2: { nuclear@17: ctx->options[opt].fval = val; nuclear@17: ctx->options[opt].type = Option::Type::FLOAT; nuclear@2: } nuclear@17: nuclear@2: void erb_setoptfv(struct erebus *ctx, enum erb_option opt, float *vec) nuclear@2: { nuclear@2: for(int i=0; i<4; i++) { nuclear@17: ctx->options[opt].vval[i] = vec[i]; nuclear@2: } nuclear@17: ctx->options[opt].type = Option::Type::VEC; nuclear@2: } nuclear@2: nuclear@2: int erb_getopti(struct erebus *ctx, enum erb_option opt) nuclear@2: { nuclear@17: switch(ctx->options[opt].type) { nuclear@17: case Option::Type::INT: nuclear@17: return ctx->options[opt].ival; nuclear@17: case Option::Type::FLOAT: nuclear@17: return (int)ctx->options[opt].fval; nuclear@17: case Option::Type::VEC: nuclear@17: return (int)ctx->options[opt].vval.x; nuclear@17: } nuclear@17: return 0; // can't happen nuclear@2: } nuclear@17: nuclear@2: float erb_getoptf(struct erebus *ctx, enum erb_option opt) nuclear@2: { nuclear@17: switch(ctx->options[opt].type) { nuclear@17: case Option::Type::INT: nuclear@17: return (float)ctx->options[opt].ival; nuclear@17: case Option::Type::FLOAT: nuclear@17: return ctx->options[opt].fval; nuclear@17: case Option::Type::VEC: nuclear@17: return ctx->options[opt].vval.x; nuclear@17: } nuclear@17: return 0.0f; // can't happen nuclear@2: } nuclear@17: nuclear@2: float *erb_getoptfv(struct erebus *ctx, enum erb_option opt) nuclear@2: { nuclear@17: switch(ctx->options[opt].type) { nuclear@17: case Option::Type::INT: nuclear@17: { nuclear@17: int ival = ctx->options[opt].ival; nuclear@17: ctx->options[opt].vval = Vector4(ival, ival, ival, ival); nuclear@17: } nuclear@17: break; nuclear@17: case Option::Type::FLOAT: nuclear@17: { nuclear@17: float fval = ctx->options[opt].fval; nuclear@17: ctx->options[opt].vval = Vector4(fval, fval, fval, fval); nuclear@17: } nuclear@17: default: nuclear@17: break; nuclear@17: } nuclear@17: nuclear@17: return &ctx->options[opt].vval.x; nuclear@2: } nuclear@2: nuclear@2: float *erb_get_framebuffer(struct erebus *ctx) nuclear@2: { nuclear@2: return ctx->fbimg.get_pixels(); nuclear@2: } nuclear@2: nuclear@2: void erb_begin_frame(struct erebus *ctx, long ms) nuclear@2: { nuclear@32: if(!ctx->tpool) { nuclear@32: int num_threads = erb_getopti(ctx, ERB_OPT_NUM_THREADS); nuclear@32: ctx->tpool = new ThreadPool(num_threads); nuclear@32: } nuclear@34: ctx->tpool->clear_work(); // remove any previously pending jobs nuclear@32: nuclear@26: ++ctx->cur_frame; nuclear@26: ctx->cur_sample = 0; nuclear@2: ctx->cur_time = ms; nuclear@4: nuclear@17: int xsz = erb_getopti(ctx, ERB_OPT_WIDTH); nuclear@17: int ysz = erb_getopti(ctx, ERB_OPT_HEIGHT); nuclear@4: nuclear@26: if(!ctx->fbimg.get_pixels() || ctx->fbimg.get_width() != xsz || ctx->fbimg.get_height() < ysz) { nuclear@26: ctx->fbimg.create(xsz, ysz); nuclear@26: ctx->accum.create(xsz, ysz); nuclear@26: } else { nuclear@26: ctx->fbimg.clear(); nuclear@26: ctx->accum.clear(); nuclear@26: } nuclear@15: nuclear@17: ctx->inv_gamma = 1.0f / erb_getoptf(ctx, ERB_OPT_GAMMA); nuclear@26: nuclear@26: ctx->scn->update(ctx->cur_time); nuclear@40: ctx->rendering = true; nuclear@40: } nuclear@40: nuclear@40: void erb_end_frame(struct erebus *ctx) nuclear@40: { nuclear@40: if(ctx->tpool) { nuclear@40: ctx->tpool->clear_work(); nuclear@40: ctx->tpool->wait(); nuclear@40: } nuclear@40: ctx->rendering = false; nuclear@2: } nuclear@2: nuclear@2: int erb_render(struct erebus *ctx, long timeout) nuclear@2: { nuclear@2: return erb_render_rect(ctx, 0, 0, ctx->fbimg.get_width(), ctx->fbimg.get_height(), timeout); nuclear@2: } nuclear@2: nuclear@26: #define BLKSZ 32 nuclear@26: nuclear@2: int erb_render_rect(struct erebus *ctx, int x, int y, int width, int height, long timeout) nuclear@2: { nuclear@40: if(!ctx->rendering) { nuclear@40: return 0; nuclear@40: } nuclear@40: nuclear@31: while(ctx->tpool->pending()) { nuclear@26: if(timeout > 0) { nuclear@31: long wait_interval = ctx->tpool->wait(timeout); nuclear@26: timeout -= wait_interval; nuclear@26: } else { nuclear@26: return 1; nuclear@26: } nuclear@26: } nuclear@26: nuclear@2: if(!width || !height) return -1; nuclear@2: nuclear@26: int startx = x; nuclear@26: int endx = x + width; nuclear@26: int endy = y + height; nuclear@26: nuclear@26: while(y < endy) { nuclear@26: x = startx; nuclear@26: while(x < endx) { nuclear@26: Block blk; nuclear@26: blk.x = x; nuclear@26: blk.y = y; nuclear@26: blk.width = std::min(BLKSZ, endx - x); nuclear@26: blk.height = std::min(BLKSZ, endy - y); nuclear@26: blk.sample = ctx->cur_sample; nuclear@26: blk.frame = ctx->cur_frame; nuclear@26: nuclear@31: ctx->tpool->add_work(std::bind(render_block, ctx, blk)); nuclear@26: nuclear@26: x += BLKSZ; nuclear@26: } nuclear@26: y += BLKSZ; nuclear@2: } nuclear@2: nuclear@26: ++ctx->cur_sample; nuclear@31: ctx->tpool->wait(timeout); // wait for completion nuclear@26: return ctx->cur_sample > erb_getopti(ctx, ERB_OPT_MAX_SAMPLES) ? 0 : 1; nuclear@26: } nuclear@4: nuclear@2: nuclear@2: int erb_get_progress(struct erebus *ctx) nuclear@2: { nuclear@32: struct erb_render_status st; nuclear@32: if(erb_get_status(ctx, &st) == -1) { nuclear@32: return 0; nuclear@32: } nuclear@32: return st.progress_percent; nuclear@32: } nuclear@32: nuclear@32: int erb_get_status(struct erebus *ctx, struct erb_render_status *stat) nuclear@32: { nuclear@32: long pending = ctx->tpool->pending(); nuclear@34: nuclear@32: int xsz = ctx->fbimg.get_width(); nuclear@32: int ysz = ctx->fbimg.get_height(); nuclear@32: int xblocks = (xsz + BLKSZ - 1) / BLKSZ; nuclear@32: int yblocks = (ysz + BLKSZ - 1) / BLKSZ; nuclear@32: long num_blocks = xblocks * yblocks; nuclear@32: nuclear@32: stat->frames = stat->max_frames = 0; // TODO nuclear@32: nuclear@32: stat->blocks = num_blocks - pending; nuclear@32: stat->max_blocks = num_blocks; nuclear@32: nuclear@32: stat->samples = ctx->cur_sample ? ctx->cur_sample - 1 : 0; nuclear@32: if((stat->max_samples = erb_getopti(ctx, ERB_OPT_MAX_SAMPLES)) == INF_SAMPLES) { nuclear@32: stat->max_samples = stat->samples; nuclear@32: nuclear@34: if(stat->max_blocks) { nuclear@34: stat->progress_percent = 100 * stat->blocks / stat->max_blocks; nuclear@34: } else { nuclear@34: stat->progress_percent = 0; nuclear@34: } nuclear@32: } else { nuclear@34: if(stat->max_samples) { nuclear@34: stat->progress_percent = 100 * stat->samples / stat->max_samples; nuclear@34: } else { nuclear@34: stat->progress_percent = 0; nuclear@34: } nuclear@32: } nuclear@32: return 0; nuclear@2: } nuclear@2: nuclear@2: int erb_load_scene(struct erebus *ctx, const char *fname) nuclear@2: { nuclear@37: if(!ctx->scn) { nuclear@37: ctx->scn = new Scene; nuclear@37: } nuclear@2: nuclear@19: if(!ctx->scn->load(fname)) { nuclear@19: return -1; nuclear@19: } nuclear@4: return 0; nuclear@2: } nuclear@2: nuclear@37: int erb_proc_cmd(struct erebus *ctx, const char *cmd) nuclear@37: { nuclear@37: if(!ctx->scn) { nuclear@37: ctx->scn = new Scene; nuclear@37: } nuclear@37: nuclear@37: char *buf = (char*)alloca(strlen(cmd) + 1); nuclear@37: strcpy(buf, cmd); nuclear@37: nuclear@37: std::vector args; nuclear@37: char *tok; nuclear@37: while((tok = strtok(buf, " \t\v\n\r"))) { nuclear@37: buf = 0; nuclear@37: args.push_back(tok); nuclear@37: } nuclear@37: args.push_back(0); nuclear@37: nuclear@37: if(!ctx->scn->proc_cmd(args.size() - 1, &args[0])) { nuclear@37: return -1; nuclear@37: } nuclear@37: return 0; nuclear@37: } nuclear@37: nuclear@9: bool erb_input_keyboard(struct erebus *ctx, int key, bool pressed) nuclear@9: { nuclear@9: if(!ctx) return false; nuclear@15: if((int)ctx->keystate.size() <= key) { nuclear@15: ctx->keystate.resize(key < 256 ? 256 : key + 1); nuclear@15: } nuclear@9: nuclear@9: ctx->keystate[key] = pressed; nuclear@10: nuclear@10: if(pressed) { nuclear@10: switch(key) { nuclear@15: case '.': nuclear@15: { nuclear@15: int node_count = ctx->scn->get_node_count(); nuclear@15: if(node_count && ++ctx->dbg_nodesel >= node_count) { nuclear@15: ctx->dbg_nodesel = 0; nuclear@15: } nuclear@15: printf("selected node: %d\n", ctx->dbg_nodesel); nuclear@15: } nuclear@15: break; nuclear@15: nuclear@15: case ',': nuclear@15: { nuclear@15: int node_count = ctx->scn->get_node_count(); nuclear@15: if(node_count && --ctx->dbg_nodesel < 0) { nuclear@15: ctx->dbg_nodesel = node_count - 1; nuclear@15: } nuclear@15: printf("selected node: %d\n", ctx->dbg_nodesel); nuclear@15: } nuclear@15: break; nuclear@15: nuclear@10: case '=': nuclear@10: case '-': nuclear@15: case '0': nuclear@15: if(ctx->dbg_nodesel != -1) { nuclear@15: SceneNode *node = ctx->scn->get_node(ctx->dbg_nodesel); nuclear@15: Vector3 s = node->get_scaling(); nuclear@15: switch(key) { nuclear@15: case '=': nuclear@15: node->set_scaling(s * 1.1); nuclear@15: break; nuclear@15: case '-': nuclear@15: node->set_scaling(s * 0.9); nuclear@15: break; nuclear@15: case '0': nuclear@15: node->set_scaling(Vector3(1, 1, 1)); nuclear@15: break; nuclear@15: } nuclear@15: } nuclear@15: erb_begin_frame(ctx, 0); nuclear@15: return true; nuclear@10: } nuclear@10: } nuclear@9: return false; nuclear@9: } nuclear@9: nuclear@9: bool erb_input_mouse_button(struct erebus *ctx, int bn, bool pressed, int x, int y) nuclear@9: { nuclear@9: if(!ctx) return false; nuclear@15: if((int)ctx->bnstate.size() <= bn) { nuclear@15: ctx->bnstate.resize(bn < 32 ? 32 : bn + 1); nuclear@15: } nuclear@9: nuclear@9: ctx->bnstate[bn] = pressed; nuclear@9: ctx->mouse_pos[0] = x; nuclear@9: ctx->mouse_pos[1] = y; nuclear@10: return false; nuclear@9: } nuclear@9: nuclear@10: bool erb_input_mouse_motion(struct erebus *ctx, int x, int y) nuclear@10: { nuclear@18: bool res = false; nuclear@18: nuclear@18: if(!ctx) return res; nuclear@18: nuclear@18: int dx = x - ctx->mouse_pos[0]; nuclear@18: int dy = y - ctx->mouse_pos[1]; nuclear@18: nuclear@18: if(dx || dy) { nuclear@18: TargetCamera *cam = (TargetCamera*)ctx->scn->get_active_camera(); nuclear@18: if(cam && ctx->bnstate[0]) { nuclear@18: Vector3 cpos = cam->get_position(); nuclear@18: float mag = cpos.length(); nuclear@18: nuclear@19: float theta = atan2(cpos.z / mag, cpos.x / mag) - DEG_TO_RAD(dx * 0.5); nuclear@19: float phi = acos(cpos.y / mag) - DEG_TO_RAD(dy * 0.5); nuclear@18: nuclear@18: if(phi < 0) phi = 0; nuclear@18: if(phi > M_PI) phi = M_PI; nuclear@18: nuclear@18: cpos.x = cos(theta) * sin(phi) * mag; nuclear@18: cpos.y = cos(phi) * mag; nuclear@18: cpos.z = sin(theta) * sin(phi) * mag; nuclear@18: cam->set_position(cpos); nuclear@18: nuclear@18: erb_begin_frame(ctx, 0); nuclear@18: res = true; nuclear@18: } nuclear@18: } nuclear@10: nuclear@10: ctx->mouse_pos[0] = x; nuclear@10: ctx->mouse_pos[1] = y; nuclear@18: return res; nuclear@10: } nuclear@10: nuclear@10: bool erb_input_6dof_button(struct erebus *ctx, int bn, bool pressed) nuclear@10: { nuclear@10: if(!ctx) return false; nuclear@10: return false; nuclear@10: } nuclear@10: nuclear@10: bool erb_input_6dof_motion(struct erebus *ctx, float x, float y, float z) nuclear@10: { nuclear@10: if(!ctx) return false; nuclear@10: return false; nuclear@10: } nuclear@9: nuclear@9: nuclear@2: } // extern "C" nuclear@2: nuclear@2: float randf(float low, float high) nuclear@2: { nuclear@2: std::uniform_real_distribution unirnd(low, high); nuclear@2: return unirnd(rnd_gen); nuclear@2: } nuclear@2: nuclear@26: static void render_block(struct erebus *ctx, Block blk) nuclear@26: { nuclear@26: if(blk.frame < ctx->cur_frame) { nuclear@26: return; // skip stale blocks nuclear@26: } nuclear@26: nuclear@26: for(int i=0; iscn->get_active_camera(); nuclear@5: if(!cam) return; nuclear@5: nuclear@5: int xsz = ctx->fbimg.get_width(); nuclear@5: int ysz = ctx->fbimg.get_height(); nuclear@8: int offs = (y * xsz + x) * 4; nuclear@5: nuclear@8: float *pix = ctx->fbimg.get_pixels() + offs; nuclear@8: float *accum = ctx->accum.get_pixels() + offs; nuclear@8: nuclear@8: Ray ray = cam->get_primary_ray(x, y, xsz, ysz, sample); nuclear@17: Color c = ray_trace(ctx, ray, 0); nuclear@8: accum[0] += c.x; nuclear@8: accum[1] += c.y; nuclear@8: accum[2] += c.z; nuclear@8: accum[3] += c.w; nuclear@8: nuclear@8: float inv_samples = 1.0f / (float)(sample + 1); nuclear@17: pix[0] = pow(accum[0] * inv_samples, ctx->inv_gamma); nuclear@17: pix[1] = pow(accum[1] * inv_samples, ctx->inv_gamma); nuclear@17: pix[2] = pow(accum[2] * inv_samples, ctx->inv_gamma); nuclear@8: pix[3] = accum[3] * inv_samples; nuclear@2: }