nuclear@2: #include nuclear@2: #include nuclear@2: #include nuclear@2: #include nuclear@2: #include "erebus.h" nuclear@2: #include "vmath/vector.h" nuclear@2: #include "image.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@2: struct Rect { nuclear@2: int x, y, width, height; nuclear@2: nuclear@2: bool operator ==(const Rect &r) { return memcmp(this, &r, sizeof r) == 0; } nuclear@2: bool operator !=(const Rect &r) { return memcmp(this, &r, sizeof r) != 0; } nuclear@2: }; nuclear@2: nuclear@2: #define INVALID_RECT Rect{0, 0, 0, 0} nuclear@2: nuclear@2: struct erebus { nuclear@4: Scene *scn; nuclear@4: nuclear@2: Image fbimg; nuclear@8: Image accum; // sample accumulator per pixel nuclear@2: Vector4 options[ERB_NUM_OPTIONS]; nuclear@2: nuclear@2: // render state nuclear@2: long cur_time; nuclear@2: int cur_pixel_x, cur_pixel_y; nuclear@2: Rect cur_rect; nuclear@8: int cur_sample; nuclear@9: nuclear@9: // interactive input nuclear@9: std::vector keystate; nuclear@9: std::vector bnstate; nuclear@9: int mouse_pos[2]; nuclear@2: }; nuclear@2: 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@2: struct erebus *ctx; nuclear@2: try { nuclear@2: ctx = new struct erebus; nuclear@2: } nuclear@2: catch(...) { nuclear@2: return 0; nuclear@2: } nuclear@2: nuclear@4: ctx->scn = 0; nuclear@2: ctx->cur_time = 0; nuclear@2: ctx->cur_rect = INVALID_RECT; nuclear@8: nuclear@8: ctx->options[ERB_OPT_MAX_SAMPLES].x = (float)INF_SAMPLES; nuclear@2: return ctx; nuclear@2: } nuclear@2: nuclear@2: void erb_destroy(struct erebus *ctx) nuclear@2: { nuclear@2: delete ctx; nuclear@2: } nuclear@2: nuclear@2: void erb_setopti(struct erebus *ctx, enum erb_option opt, int val) nuclear@2: { nuclear@2: ctx->options[opt].x = val; nuclear@2: } nuclear@2: void erb_setoptf(struct erebus *ctx, enum erb_option opt, float val) nuclear@2: { nuclear@2: ctx->options[opt].x = val; nuclear@2: } 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@2: ctx->options[opt][i] = vec[i]; nuclear@2: } nuclear@2: } nuclear@2: nuclear@2: int erb_getopti(struct erebus *ctx, enum erb_option opt) nuclear@2: { nuclear@2: return ctx->options[opt].x; nuclear@2: } nuclear@2: float erb_getoptf(struct erebus *ctx, enum erb_option opt) nuclear@2: { nuclear@2: return ctx->options[opt].x; nuclear@2: } nuclear@2: float *erb_getoptfv(struct erebus *ctx, enum erb_option opt) nuclear@2: { nuclear@2: return &ctx->options[opt].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@8: printf("starting new frame...\n"); nuclear@2: ctx->cur_time = ms; nuclear@4: nuclear@4: int xsz = ctx->options[ERB_OPT_WIDTH].x; nuclear@4: int ysz = ctx->options[ERB_OPT_HEIGHT].x; nuclear@4: nuclear@4: ctx->fbimg.create(xsz, ysz); nuclear@8: ctx->accum.create(xsz, ysz); 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@2: int erb_render_rect(struct erebus *ctx, int x, int y, int width, int height, long timeout) nuclear@2: { nuclear@2: if(!width || !height) return -1; nuclear@2: nuclear@2: Rect rect{x, y, width, height}; nuclear@2: if(ctx->cur_rect != rect) { nuclear@8: // starting a new rendering apparently nuclear@2: ctx->cur_rect = rect; nuclear@2: ctx->cur_pixel_x = x; nuclear@2: ctx->cur_pixel_y = y; nuclear@8: ctx->cur_sample = 0; nuclear@2: } nuclear@2: nuclear@4: ctx->scn->update(); nuclear@4: nuclear@8: int max_samples = ctx->options[ERB_OPT_MAX_SAMPLES].x; nuclear@8: nuclear@2: if(timeout > 0) { nuclear@2: auto start_time = steady_clock::now(); nuclear@2: while(duration_cast(steady_clock::now() - start_time).count() < timeout) { nuclear@8: render_pixel(ctx, ctx->cur_pixel_x, ctx->cur_pixel_y, ctx->cur_sample); nuclear@2: nuclear@2: if(++ctx->cur_pixel_x >= ctx->cur_rect.width) { nuclear@8: ctx->cur_pixel_x = ctx->cur_rect.x; nuclear@2: if(++ctx->cur_pixel_y >= ctx->cur_rect.height) { nuclear@8: ctx->cur_pixel_y = ctx->cur_rect.y; nuclear@8: if(++ctx->cur_sample >= max_samples) { nuclear@8: ctx->cur_rect = INVALID_RECT; nuclear@8: return 0; nuclear@8: } nuclear@2: } nuclear@2: } nuclear@2: } nuclear@2: return 1; nuclear@2: } nuclear@2: nuclear@8: if(ctx->options[ERB_OPT_MAX_SAMPLES].x == (float)INF_SAMPLES) { nuclear@8: max_samples = 128; nuclear@8: } nuclear@8: nuclear@2: for(int i=0; iscn; nuclear@4: ctx->scn = new Scene; nuclear@2: nuclear@4: // XXX for now just create a test scene here nuclear@4: Sphere *sph = new Sphere; nuclear@8: sph->mtl.set_attrib("albedo", Color(1.0, 0.3, 0.2)); nuclear@4: SceneNode *sph_node = new SceneNode(sph); nuclear@4: ctx->scn->add_object(sph); nuclear@4: ctx->scn->add_node(sph_node); nuclear@4: nuclear@8: sph = new Sphere; nuclear@8: sph->mtl.set_attrib("albedo", Color(0.3, 0.4, 1.0)); nuclear@8: sph_node = new SceneNode(sph); nuclear@8: sph_node->set_position(Vector3(0, -3.0, 0)); nuclear@8: //sph_node->set_scaling(Vector3(4.0, 4.0, 4.0) * 0.3); nuclear@8: ctx->scn->add_object(sph); nuclear@8: ctx->scn->add_node(sph_node); nuclear@8: nuclear@8: Sphere *lt = new Sphere; nuclear@8: lt->mtl.set_attrib("emissive", Color(10, 10, 10)); nuclear@8: SceneNode *lt_node = new SceneNode(lt); nuclear@8: lt_node->set_position(Vector3(-15, 15, -10)); nuclear@8: lt_node->set_scaling(Vector3(5, 5, 5)); nuclear@8: ctx->scn->add_object(lt); nuclear@8: ctx->scn->add_node(lt_node); nuclear@8: nuclear@8: TargetCamera *cam = new TargetCamera(Vector3(0, 4, -8), Vector3(0, 0, 0)); nuclear@4: //ctx->scn->add_object(cam); nuclear@4: ctx->scn->use_camera(cam); nuclear@4: nuclear@4: return 0; nuclear@2: } nuclear@2: nuclear@9: bool erb_input_keyboard(struct erebus *ctx, int key, bool pressed) nuclear@9: { nuclear@9: if(!ctx) return false; nuclear@9: if(ctx->keystate.size() <= key) ctx->keystate.resize(key < 256 ? 256 : key + 1); nuclear@9: nuclear@9: ctx->keystate[key] = pressed; 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@9: if(ctx->bnstate.size() <= bn) ctx->bnstate.resize(bn < 32 ? 32 : bn + 1); nuclear@9: nuclear@9: ctx->bnstate[bn] = pressed; nuclear@9: ctx->mouse_pos[0] = x; nuclear@9: ctx->mouse_pos[1] = y; nuclear@9: } nuclear@9: nuclear@9: bool erb_input_mouse_motion(struct erebus *ctx, int x, int y); nuclear@9: bool erb_input_6dof_button(struct erebus *ctx, int bn, bool pressed); nuclear@9: bool erb_input_6dof_motion(struct erebus *ctx, float x, float y, float z); 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@8: static void render_pixel(struct erebus *ctx, int x, int y, int sample) nuclear@2: { nuclear@5: Camera *cam = ctx->scn->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@8: Color c = ray_trace(ray, ctx->scn, 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@8: pix[0] = accum[0] * inv_samples; nuclear@8: pix[1] = accum[1] * inv_samples; nuclear@8: pix[2] = accum[2] * inv_samples; nuclear@8: pix[3] = accum[3] * inv_samples; nuclear@2: }