diff --git a/sources/etx/render/host/scene_loader.cxx b/sources/etx/render/host/scene_loader.cxx index bc69ca9..ab0d0b3 100644 --- a/sources/etx/render/host/scene_loader.cxx +++ b/sources/etx/render/host/scene_loader.cxx @@ -19,6 +19,8 @@ #include #include +#include + namespace etx { Pointer spectrums() { @@ -75,6 +77,10 @@ struct SceneRepresentationImpl { std::vector materials; std::vector emitters; + std::string json_file_name; + std::string obj_file_name; + std::string mtl_file_name; + ImagePool images; MediumPool mediums; @@ -341,6 +347,10 @@ float get_camera_fov(const Camera& camera) { return 2.0f * atanf(camera.tan_half_fov) * 180.0f / kPi; } +float get_camera_focal_length(const Camera& camera) { + return 0.5f * kFilmSize / camera.tan_half_fov; +} + float fov_to_focal_length(float fov) { return 0.5f * kFilmSize / tanf(0.5f * fov); } @@ -434,13 +444,84 @@ float3 json_to_f3(json_t* a) { return result; } +json_t* json_float3_to_array(const float3& v) { + json_t* j = json_array(); + json_array_append(j, json_real(v.x)); + json_array_append(j, json_real(v.y)); + json_array_append(j, json_real(v.z)); + return j; +} + +json_t* json_uint2_to_array(const uint2& v) { + json_t* j = json_array(); + json_array_append(j, json_integer(v.x)); + json_array_append(j, json_integer(v.y)); + return j; +} + +void SceneRepresentation::write_materials(const char* filename) { + FILE* fout = fopen(filename, "w"); + if (fout == nullptr) { + log::error("Failed to write materials file: %s", filename); + return; + } + + for (const auto& mmap : _private->material_mapping) { + const auto& material = _private->scene.materials[mmap.second]; + + // TODO : support anisotripic roughness + fprintf(fout, "newmtl %s\n", mmap.first.c_str()); + fprintf(fout, "material class %s\n", material_class_to_string(material.cls)); + fprintf(fout, "Pr %.3f\n", sqrtf(0.5f * (sqr(material.roughness.x) + sqr(material.roughness.y)))); + fprintf(fout, "\n"); + } + + fclose(fout); +} + +void SceneRepresentation::save_to_file(const char* filename) { + if (_private->obj_file_name.empty()) + return; + + FILE* fout = fopen(filename, "w"); + if (fout == nullptr) + return; + + auto materials_file = _private->obj_file_name + ".materials"; + auto relative_obj_file = std::filesystem::relative(_private->obj_file_name, std::filesystem::path(filename).parent_path()).string(); + auto relative_mtl_file = std::filesystem::relative(materials_file, std::filesystem::path(filename).parent_path()).string(); + write_materials(materials_file.c_str()); + + auto j = json_object(); + json_object_set(j, "geometry", json_string(relative_obj_file.c_str())); + json_object_set(j, "materials", json_string(relative_mtl_file.c_str())); + + { + auto camera = json_object(); + json_object_set(camera, "viewport", json_uint2_to_array(_private->scene.camera.image_size)); + json_object_set(camera, "origin", json_float3_to_array(_private->scene.camera.position)); + json_object_set(camera, "target", json_float3_to_array(_private->scene.camera.position + _private->scene.camera.direction)); + json_object_set(camera, "up", json_float3_to_array(_private->scene.camera.up)); + json_object_set(camera, "lens-radius", json_real(_private->scene.camera.lens_radius)); + json_object_set(camera, "focal-distance", json_real(_private->scene.camera.focal_distance)); + json_object_set(camera, "focal-length", json_real(get_camera_focal_length(_private->scene.camera))); + json_object_set(j, "camera", camera); + } + + json_dumpf(j, fout, JSON_INDENT(2)); + fclose(fout); + + json_decref(j); +} + bool SceneRepresentation::load_from_file(const char* filename, uint32_t options) { _private->cleanup(); uint32_t load_result = SceneRepresentationImpl::LoadFailed; - std::string file_to_load = filename; - std::string material_file = {}; + _private->json_file_name = {}; + _private->mtl_file_name = {}; + _private->obj_file_name = filename; char base_folder[2048] = {}; get_file_folder(filename, base_folder, sizeof(base_folder)); @@ -480,14 +561,14 @@ bool SceneRepresentation::load_from_file(const char* filename, uint32_t options) json_decref(js); return false; } - file_to_load = std::string(base_folder) + json_string_value(js_value); + _private->obj_file_name = std::string(base_folder) + json_string_value(js_value); } else if (strcmp(key, "materials") == 0) { if (json_is_string(js_value) == false) { log::error("`materials` in scene description should be a string (file name)"); json_decref(js); return false; } - material_file = json_string_value(js_value); + _private->mtl_file_name = json_string_value(js_value); } else if (strcmp(key, "camera") == 0) { if (json_is_object(js_value) == false) { log::error("`camera` in scene description should be an object"); @@ -522,11 +603,12 @@ bool SceneRepresentation::load_from_file(const char* filename, uint32_t options) } } json_decref(js); + _private->json_file_name = filename; } - auto ext = get_file_ext(file_to_load.c_str()); + auto ext = get_file_ext(_private->obj_file_name.c_str()); if (strcmp(ext, ".obj") == 0) { - load_result = _private->load_from_obj(file_to_load.c_str(), material_file.c_str()); + load_result = _private->load_from_obj(_private->obj_file_name.c_str(), _private->mtl_file_name.c_str()); } if ((load_result & SceneRepresentationImpl::LoadSucceeded) == 0) { @@ -539,7 +621,7 @@ bool SceneRepresentation::load_from_file(const char* filename, uint32_t options) } cam.cls = camera_cls; if (use_focal_len) { - camera_fov = 0.5f * focal_length_to_fov(camera_focal_len) * 180.0f / kPi; + camera_fov = focal_length_to_fov(camera_focal_len) * 180.0f / kPi; } update_camera(cam, camera_pos, camera_view, camera_up, viewport, camera_fov); } @@ -676,20 +758,20 @@ Material::Class material_string_to_class(const char* s) { void material_class_to_string(Material::Class cls, const char** str) { static const char* names[] = { - "Diffuse", - "Plastic", - "Conductor", - "Dielectric", - "Thinfilm", - "Mirror", - "Boundary", - "Coating", - "Velvet", - "Subsurface", - "Undefined", + "diffuse", + "plastic", + "conductor", + "dielectric", + "thinfilm", + "mirror", + "boundary", + "coating", + "velvet", + "subsurface", + "undefined", }; static_assert(sizeof(names) / sizeof(names[0]) == uint32_t(Material::Class::Count) + 1); - *str = cls < Material::Class::Count ? names[uint32_t(cls)] : "Undefined"; + *str = cls < Material::Class::Count ? names[uint32_t(cls)] : "undefined"; } const char* material_class_to_string(Material::Class cls) { diff --git a/sources/etx/render/host/scene_loader.hxx b/sources/etx/render/host/scene_loader.hxx index 6e29dea..946fae5 100644 --- a/sources/etx/render/host/scene_loader.hxx +++ b/sources/etx/render/host/scene_loader.hxx @@ -20,6 +20,8 @@ struct SceneRepresentation { ~SceneRepresentation(); bool load_from_file(const char* filename, uint32_t options); + void save_to_file(const char* filename); + void write_materials(const char* filename); Scene& mutable_scene(); Scene* mutable_scene_pointer(); @@ -38,6 +40,7 @@ struct SceneRepresentation { Camera build_camera(const float3& origin, const float3& target, const float3& up, const uint2& viewport, float fov, float lens_radius, float focal_distance); void update_camera(Camera& camera, const float3& origin, const float3& target, const float3& up, const uint2& viewport, float fov); float get_camera_fov(const Camera& camera); +float get_camera_focal_length(const Camera& camera); float fov_to_focal_length(float fov); float focal_length_to_fov(float focal_len); diff --git a/sources/raytracer/app.cxx b/sources/raytracer/app.cxx index f151275..a3f7c3c 100644 --- a/sources/raytracer/app.cxx +++ b/sources/raytracer/app.cxx @@ -32,6 +32,7 @@ void RTApplication::init() { ui.callbacks.reference_image_selected = std::bind(&RTApplication::on_referenece_image_selected, this, std::placeholders::_1); ui.callbacks.save_image_selected = std::bind(&RTApplication::on_save_image_selected, this, std::placeholders::_1, std::placeholders::_2); ui.callbacks.scene_file_selected = std::bind(&RTApplication::on_scene_file_selected, this, std::placeholders::_1); + ui.callbacks.save_scene_file_selected = std::bind(&RTApplication::on_save_scene_file_selected, this, std::placeholders::_1); ui.callbacks.integrator_selected = std::bind(&RTApplication::on_integrator_selected, this, std::placeholders::_1); ui.callbacks.preview_selected = std::bind(&RTApplication::on_preview_selected, this); ui.callbacks.run_selected = std::bind(&RTApplication::on_run_selected, this); @@ -186,6 +187,11 @@ void RTApplication::load_scene_file(const std::string& file_name, uint32_t optio } } +void RTApplication::save_scene_file(const std::string& file_name) { + log::info("Saving %s..", file_name.c_str()); + scene.save_to_file(file_name.c_str()); +} + void RTApplication::on_referenece_image_selected(std::string file_name) { log::warning("Loading reference image %s...", file_name.c_str()); @@ -271,6 +277,13 @@ void RTApplication::on_scene_file_selected(std::string file_name) { load_scene_file(file_name, SceneRepresentation::LoadEverything, false); } +void RTApplication::on_save_scene_file_selected(std::string file_name) { + if (strlen(get_file_ext(file_name.c_str())) == 0) { + file_name += ".json"; + } + save_scene_file(file_name); +} + void RTApplication::on_integrator_selected(Integrator* i) { if (_current_integrator == i) { return; diff --git a/sources/raytracer/app.hxx b/sources/raytracer/app.hxx index 0674385..f77d880 100644 --- a/sources/raytracer/app.hxx +++ b/sources/raytracer/app.hxx @@ -29,12 +29,15 @@ struct RTApplication { void frame(); void cleanup(); void process_event(const sapp_event*); - void load_scene_file(const std::string&, uint32_t options, bool start_rendering); private: + void load_scene_file(const std::string&, uint32_t options, bool start_rendering); + void save_scene_file(const std::string&); + void on_referenece_image_selected(std::string); void on_save_image_selected(std::string, SaveImageMode); void on_scene_file_selected(std::string); + void on_save_scene_file_selected(std::string); void on_integrator_selected(Integrator*); void on_preview_selected(); void on_run_selected(); diff --git a/sources/raytracer/ui.cxx b/sources/raytracer/ui.cxx index 0ca4e8d..048b14c 100644 --- a/sources/raytracer/ui.cxx +++ b/sources/raytracer/ui.cxx @@ -167,7 +167,8 @@ void UI::build(double dt, const char* status) { if (igMenuItemEx("Reload Materials", nullptr, "Ctrl+M", false, false)) { } igSeparator(); - if (igMenuItemEx("Save...", nullptr, nullptr, false, false)) { + if (igMenuItemEx("Save...", nullptr, nullptr, false, true)) { + save_scene_file(); } igEndMenu(); } @@ -391,7 +392,7 @@ void UI::build(double dt, const char* status) { float3 pos = camera.position; float3 target = camera.position + camera.direction; - float focal_len = fov_to_focal_length(2.0f * get_camera_fov(camera) * kPi / 180.0f); + float focal_len = get_camera_focal_length(camera); igText("Lens size"); changed = changed || igDragFloat("##lens", &camera.lens_radius, 0.01f, 0.0f, 2.0, "%.3f", ImGuiSliderFlags_None); @@ -403,7 +404,7 @@ void UI::build(double dt, const char* status) { if (changed && callbacks.camera_changed) { camera.lens_radius = fmaxf(camera.lens_radius, 0.0f); camera.focal_distance = fmaxf(camera.focal_distance, 0.0f); - update_camera(camera, pos, target, float3{0.0f, 1.0f, 0.0f}, camera.image_size, 0.5f * focal_length_to_fov(focal_len) * 180.0f / kPi); + update_camera(camera, pos, target, float3{0.0f, 1.0f, 0.0f}, camera.image_size, focal_length_to_fov(focal_len) * 180.0f / kPi); callbacks.camera_changed(); } } else { @@ -567,6 +568,13 @@ void UI::select_scene_file() { } } +void UI::save_scene_file() { + auto selected_file = save_file({"Scene description", "*.json"}); + if ((selected_file.empty() == false) && callbacks.save_scene_file_selected) { + callbacks.save_scene_file_selected(selected_file); + } +} + void UI::save_image(SaveImageMode mode) { auto selected_file = save_file({ mode == SaveImageMode::TonemappedLDR ? "PNG images" : "EXR images", diff --git a/sources/raytracer/ui.hxx b/sources/raytracer/ui.hxx index 598a540..42191ac 100644 --- a/sources/raytracer/ui.hxx +++ b/sources/raytracer/ui.hxx @@ -38,6 +38,7 @@ struct UI { std::function reference_image_selected; std::function save_image_selected; std::function scene_file_selected; + std::function save_scene_file_selected; std::function integrator_selected; std::function stop_selected; std::function preview_selected; @@ -56,6 +57,7 @@ struct UI { private: bool build_options(Options&); void select_scene_file(); + void save_scene_file(); void save_image(SaveImageMode mode); void load_image(); bool build_material(Material&);