#include "pch.h" #include "log.h" #include "node_panel_grid.h" #include "canvas.h" #include "app.h" #include "image.h" #include "util.h" Node* NodePanelGrid::clone_instantiate() const { return new NodePanelGrid(); } void NodePanelGrid::clone_finalize(Node* dest) const { NodePanelGrid* n = static_cast(dest); n->init_controls(); } void NodePanelGrid::init() { init_template_file("data/dialogs/panel-grid.xml", "tpl-panel-grid"); init_controls(); } void NodePanelGrid::init_controls() { m_groud_opacity = find("grid-ground-opacity"); m_groud_value = find("grid-ground-value"); m_groud_resolution = find("grid-ground-resolution"); m_groud_offset = find("grid-ground-offset"); m_hm_preview = find("grid-heightmap-preview"); m_hm_load = find("grid-heightmap-load"); m_hm_clear = find("grid-heightmap-clear"); m_hm_reload = find("grid-heightmap-reload"); m_hm_wireframe = find("grid-heightmap-wireframe"); m_hm_thickness = find("grid-heightmap-thickness"); m_hm_height = find("grid-heightmap-height"); m_hm_lyaw = find("grid-heightmap-lyaw"); m_hm_lpitch = find("grid-heightmap-lpitch"); m_hm_ambient = find("grid-heightmap-ambient"); m_hm_radius = find("grid-heightmap-radius"); m_hm_shading = find("grid-heightmap-shading"); m_hm_texres = find("grid-heightmap-texres"); m_hm_samples = find("grid-heightmap-samples"); m_render = find("grid-render"); m_commit = find("grid-commit"); m_hm_preview->SetHeight(0); m_hm_plane.create(1, 1, 100); m_hm_preview_nav = m_hm_preview->add_child(); m_hm_preview_nav->SetWidthP(100); m_hm_preview_nav->SetHeightP(100); //m_hm_height->on_value_changed = update_hm; m_groud_resolution->on_select = [this](Node* target, int index) { if (m_hm_image.data()) m_hm_plane.create(1, 1, m_hm_image, get_resolution(), get_height()); else m_hm_plane.create(1, 1, 100 * get_resolution()); m_rt_dirty = true; LOG("resolution index %d", index); }; m_hm_height->on_value_final = [this](Node* target, float v) { if (m_hm_image.data()) m_hm_plane.create(1, 1, m_hm_image, get_resolution(), get_height()); m_rt_dirty = true; LOG("height value %f", v); }; m_hm_shading->on_select = [this](Node*, int index) { m_shade_mode = (ShadeMode)index; }; m_hm_load->on_click = [this](Node*) { App::I->pick_image([this](std::string path) { Image img; if (img.load_file(path)) { m_file_path = path; m_hm_image = img.resize(128, 128); m_hm_preview->tex.create(m_hm_image); m_hm_preview->tex.create_mipmaps(); auto sz = m_hm_preview->tex.size(); m_hm_preview->SetAspectRatio(sz.x / sz.y); m_hm_plane.create(1, 1, m_hm_image, get_resolution(), get_height()); m_hm_preview->SetHeight(100); if (m_groud_opacity->get_value() == 0.f) m_groud_opacity->set_value(1.f); m_rt_dirty = true; } }); }; m_hm_clear->on_click = [this](Node*) { m_hm_plane.create(1, 1, 100 * get_resolution()); m_hm_image.destroy(); m_hm_preview->tex.destroy(); m_hm_preview->SetHeight(0); }; m_hm_reload->on_click = [this](Node*) { Image img; if (img.load_file(m_file_path)) { m_hm_image = img.resize(128, 128); m_hm_preview->tex.create(m_hm_image); m_hm_preview->tex.create_mipmaps(); auto sz = m_hm_preview->tex.size(); m_hm_preview->SetAspectRatio(sz.x / sz.y); m_hm_plane.create(1, 1, m_hm_image, get_resolution(), get_height()); m_hm_preview->SetHeight(100); m_rt_dirty = true; } }; m_render->on_click = [this](Node*) { if (ShaderManager::ext_float32 || ShaderManager::ext_float16) { std::thread([this] { bake_uvs(); m_hm_shading->set_index(3); m_shade_mode = ShadeMode::Textured; }).detach(); } else { App::I->message_box("Rendering failed", "Your hardware does not support lightmap rendering."); } }; m_commit->on_click = [this](Node*) { Canvas::I->draw_objects([this](const glm::mat4& camera, const glm::mat4& proj, int i) { draw_heightmap(proj, camera, true); }); m_groud_opacity->set_value(0); }; m_hm_texres->on_select = [this](Node*, int index) { int texres = get_texres(); if (texres == m_texture.size().x) return; #if defined(__GLES__) m_texture.create(texres, texres); m_texture.create_mipmaps(); #else // get the texture data and resize it Image img; img.create(m_texture.size().x, m_texture.size().y); App::I->render_task([&] { m_texture.bind(); glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, img.m_data.get()); m_texture.unbind(); }); Image resized = img.resize(texres, texres); m_texture.create(resized); m_texture.create_mipmaps(); #endif // }; int rexres = get_texres(); m_texture.create(rexres, rexres); m_sampler_mipmap.create(); m_sampler_mipmap.set_filter(GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR); m_sampler_linear.create(); m_plane.create<1>(1, 1); TextureManager::load("data/sun.png"); } int NodePanelGrid::get_samples() const { return atoi(m_hm_samples->m_items[m_hm_samples->m_current_index].c_str()); } int NodePanelGrid::get_texres() const { return atoi(m_hm_texres->m_items[m_hm_texres->m_current_index].c_str()); } float NodePanelGrid::get_radius() const { return m_hm_radius->get_value() * 0.5f + 0.01f; } float NodePanelGrid::get_ambient() const { return glm::pow(m_hm_ambient->get_value(), 3); } float NodePanelGrid::get_thickness() const { return glm::lerp(m_line_range[0], m_line_range[1], m_hm_thickness->get_value()); } float NodePanelGrid::get_resolution() const { return atof(m_groud_resolution->m_items[m_groud_resolution->m_current_index].c_str()); } float NodePanelGrid::get_height() const { return -glm::pow(m_hm_height->get_value() - 0.5f, 3.f) * 10.f; } float NodePanelGrid::get_offset() const { return glm::pow(m_groud_offset->get_value() - 0.5f, 3); } void NodePanelGrid::draw_heightmap(const glm::mat4& proj, const glm::mat4& camera, bool commit) const { assert(App::I->is_render_thread()); if (m_groud_opacity->get_value() > 0.f) { bool depth = glIsEnabled(GL_DEPTH_TEST); glEnable(GL_DEPTH_TEST); glClear(GL_DEPTH_BUFFER_BIT); auto nav = m_hm_image.m_data ? -(m_hm_preview_nav->m_value - 0.5f) : glm::vec2(0); auto mvp = proj * camera * glm::scale(glm::vec3(100)) * glm::translate(glm::vec3(0, get_offset(), 0)) * glm::translate(glm::vec3(nav.x, get_offset(), nav.y)) ; auto light_yaw = m_hm_lyaw->get_value() * glm::pi() * 2.f; auto light_pitch = m_hm_lpitch->get_value() * 5; auto light_pos = glm::vec3(sinf(light_yaw) + nav.x, light_pitch + get_offset(), cosf(light_yaw) + nav.y); auto light_dir = glm::normalize(light_pos); glDisable(GL_BLEND); // DRAW SOLID if (m_hm_image.m_data) { if (m_shade_mode == ShadeMode::Solid) { glDisable(GL_BLEND); ShaderManager::use(kShader::Lambert); ShaderManager::u_mat4(kShaderUniform::MVP, mvp); ShaderManager::u_vec3(kShaderUniform::LightDir, light_dir); ShaderManager::u_float(kShaderUniform::Ambient, get_ambient()); m_hm_plane.draw_fill(); } else if (m_shade_mode == ShadeMode::Flat) { ShaderManager::use(kShader::Color); ShaderManager::u_vec4(kShaderUniform::Col, glm::vec4( glm::vec3(1.f - m_groud_value->get_value()), m_groud_opacity->get_value() )); ShaderManager::u_mat4(kShaderUniform::MVP, mvp); m_hm_plane.draw_fill(); } else if(m_shade_mode == ShadeMode::Textured) { ShaderManager::use(kShader::LambertLightmap); ShaderManager::u_mat4(kShaderUniform::MVP, mvp); ShaderManager::u_vec3(kShaderUniform::LightDir, light_dir); ShaderManager::u_float(kShaderUniform::Ambient, get_ambient()); ShaderManager::u_int(kShaderUniform::Tex, 0); m_sampler_mipmap.bind(0); glActiveTexture(GL_TEXTURE0); m_texture.bind(); m_hm_plane.draw_fill(); m_texture.unbind(); } } // DRAW GRIDS auto wire_alpha = m_hm_image.m_data ? m_hm_wireframe->get_value() : 1.f; if (wire_alpha > 0.f) { glEnable(GL_BLEND); ShaderManager::use(kShader::Color); ShaderManager::u_vec4(kShaderUniform::Col, glm::vec4( glm::vec3(m_groud_value->get_value()), m_groud_opacity->get_value() * wire_alpha )); ShaderManager::u_mat4(kShaderUniform::MVP, mvp); if (m_hm_image.m_data && m_shade_mode == ShadeMode::Transparent) { glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); m_hm_plane.draw_fill(); glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); } m_hm_plane.draw_stroke(); } // DRAW SUN SPHERE if (!commit) { auto c = proj * camera * glm::translate(light_pos) * glm::vec4(0, 0, 0, 1); if (c.w > 0) { auto p2d = xy(c) / c.z; GLint vp[4]; glGetIntegerv(GL_VIEWPORT, vp); auto aspect_ratio = (float)vp[3] / (float)vp[2]; glEnable(GL_BLEND); glDisable(GL_DEPTH_TEST); ShaderManager::use(kShader::Texture); ShaderManager::u_int(kShaderUniform::Tex, 0); ShaderManager::u_mat4(kShaderUniform::MVP, glm::ortho(-1.f, 1.f, -1.f, 1.f) * //glm::scale(glm::vec3(100)) * glm::translate(glm::vec3(p2d, 0)) * glm::scale(glm::vec3(.1f * aspect_ratio, .1f, 1.f))); glActiveTexture(GL_TEXTURE0); m_sampler_linear.bind(0); constexpr auto sun_tex = const_hash("data/sun.png"); TextureManager::get(sun_tex).bind(); m_plane.draw_fill(); } } depth ? glEnable(GL_DEPTH_TEST) : glDisable(GL_DEPTH_TEST); } } kEventResult NodePanelGrid::handle_event(Event* e) { switch (e->m_type) { case kEventType::MouseUpL: if (!m_mouse_inside) { mouse_release(); m_parent->remove_child(this); if (on_popup_close) on_popup_close(this); } break; default: return kEventResult::Available; break; } return kEventResult::Available; } void NodePanelGrid::bake_uvs() { if (!m_hm_image.m_data) return; RTT fb; if (ShaderManager::ext_float32) { fb.create(m_texture.size().x, m_texture.size().y, -1, GL_RGBA32F); } else if (ShaderManager::ext_float16) { fb.create(m_texture.size().x, m_texture.size().y, -1, GL_RGBA16F); } std::unique_ptr data_nor; std::unique_ptr data_pos; App::I->render_task([&]{ fb.bindFramebuffer(); fb.clear({ 1, 0, 0, 1 }); glDisable(GL_BLEND); glDisable(GL_DEPTH_TEST); glViewport(0, 0, fb.getWidth(), fb.getHeight()); ShaderManager::use(kShader::BakeUV); ShaderManager::u_mat4(kShaderUniform::MVP, glm::mat4(1)); // bake normal ShaderManager::u_int(kShaderUniform::Mode, 0); m_hm_plane.draw_fill(); data_nor.reset(fb.readTextureDataFloat()); //stbi_write_png("bake-nor.png", fb.getWidth(), fb.getHeight(), 4, fb.readTextureData(), 0); // bake position ShaderManager::u_int(kShaderUniform::Mode, 1); m_hm_plane.draw_fill(); data_pos.reset(fb.readTextureDataFloat()); //stbi_write_png("bake-pos.png", fb.getWidth(), fb.getHeight(), 4, fb.readTextureData(), 0); fb.unbindFramebuffer(); fb.destroy(); }); auto pb = App::I->show_progress("Lightmap Rendering"); if (m_rt_dirty) { nanort::BVHBuildOptions build_options; // Use default option build_options.cache_bbox = false; nanort::TriangleMesh triangle_mesh(reinterpret_cast(m_hm_plane.vertices.data()), m_hm_plane.idx.data(), sizeof(vertex_t)); nanort::TriangleSAHPred triangle_pred(reinterpret_cast(m_hm_plane.vertices.data()), m_hm_plane.idx.data(), sizeof(vertex_t)); bool ret = m_rt_accel.Build(static_cast(m_hm_plane.idx.size() / 3), triangle_mesh, triangle_pred, build_options); if (!ret) return; m_rt_dirty = false; } auto light_yaw = m_hm_lyaw->get_value() * glm::pi() * 2.f; auto light_pitch = m_hm_lpitch->get_value() * 5; auto light_pos = glm::vec3(sinf(light_yaw), light_pitch + get_offset(), cosf(light_yaw)); auto light_dir = glm::normalize(light_pos); std::atomic_int pb_value(0); auto data_out = std::make_unique(fb.getWidth() * fb.getHeight() * 4); const auto samples = get_samples(); const auto radius = get_radius(); std::thread worker([&] { BT_SetTerminate(); __block float* d_pos = data_pos.get(); __block float* d_nor = data_nor.get(); __block glm::u8vec4* d_out = reinterpret_cast(data_out.get()); #if _WIN32 concurrency::parallel_for(int(0), fb.getHeight(), [&](int y) #elif __IOS__ || __OSX__ dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_apply((size_t)fb.getHeight(), queue, ^ (size_t y) #else parallel_for(fb.getHeight(), [&](int y) #endif { for (int x = 0; x < fb.getWidth(); x++) { int i = (int)y * fb.getHeight() + x; auto nor = glm::make_vec3(&d_nor[i * 4]); auto pos = glm::make_vec3(&d_pos[i * 4]); auto& out = d_out[i]; if (glm::dot(nor, light_dir) <= 0.f) { out = { 50, 50, 50, 255 }; continue; } int hit = 0; for (int s = 0; s < samples; s++) { auto dir = glm::normalize(light_dir + glm::sphericalRand(radius)); nanort::Ray ray; ray.org[0] = pos.x;// + nor.x * 0.005; ray.org[1] = pos.y;// + nor.y * 0.005; ray.org[2] = pos.z;// + nor.z * 0.005; ray.dir[0] = dir.x; ray.dir[1] = dir.y; ray.dir[2] = dir.z; float kFar = 2000.0; ray.min_t = 0.005f; ray.max_t = kFar; nanort::TriangleIntersector<> triangle_intersector(reinterpret_cast(m_hm_plane.vertices.data()), m_hm_plane.idx.data(), sizeof(vertex_t)); nanort::TriangleIntersection<> isect; hit += m_rt_accel.Traverse(ray, triangle_intersector, &isect); } out = glm::lerp(glm::vec4(255, 255, 255, 255), glm::vec4(50, 50, 50, 255), hit / (float)samples); } pb_value++; }); } ); while (pb_value < fb.getHeight()) { pb->set_progress((float)pb_value / (float)fb.getHeight()); std::this_thread::sleep_for(std::chrono::milliseconds(100)); } worker.join(); pb->destroy(); //stbi_write_jpg("bake-out.jpg", fb.getWidth(), fb.getHeight(), 4, data_out.get(), 75); m_texture.update(data_out.get()); m_texture.create_mipmaps(); App::I->async_redraw(); } /////////////////////////////////////////////////////////////////////////////// Node* NodeHeightmapOverlay::clone_instantiate() const { return new NodeHeightmapOverlay(); } void NodeHeightmapOverlay::clone_finalize(Node* dest) const { auto n = (NodeHeightmapOverlay*)dest; n->m_picker = (NodeBorder*)n->m_children[0].get(); } void NodeHeightmapOverlay::init() { m_picker = new NodeBorder; m_picker->SetSize({ 10, 10 }); m_picker->SetPositioning(YGPositionTypeAbsolute); m_picker->SetPosition(0, 0); m_picker->m_border_color = glm::vec4(0, 0, 0, 1); m_picker->m_thinkness = 1; m_picker->m_color = glm::vec4(1, 1, 1, 0.25f); add_child(m_picker); } void NodeHeightmapOverlay::set_value(float x, float y) { auto sz = m_size; auto pos = glm::clamp(glm::vec2(x, y) * sz, { 0, 0 }, sz); //m_picker->SetPosition(pos - m_picker->GetSize() * .5f); m_value = pos / glm::max({ 1,1 }, sz); // avoid div0 if (on_value_changed) on_value_changed(this, m_value); } kEventResult NodeHeightmapOverlay::handle_event(Event* e) { NodeBorder::handle_event(e); switch (e->m_type) { case kEventType::MouseDownL: { m_old_value = m_value; dragging = true; mouse_capture(); auto sz = m_size; auto pos = glm::clamp(((MouseEvent*)e)->m_pos - m_pos, { 0, 0 }, sz); m_picker->SetPosition(pos - m_picker->GetSize() * .5f); m_value = pos / glm::max({ 1,1 }, sz); // avoid div0 if (on_value_changed) on_value_changed(this, m_value); } break; case kEventType::MouseUpL: mouse_release(); dragging = false; break; case kEventType::MouseMove: if (dragging) { auto sz = m_size; auto pos = glm::clamp(((MouseEvent*)e)->m_pos - m_pos, { 0, 0 }, sz); m_picker->SetPosition(pos - m_picker->GetSize() * .5f); m_value = pos / glm::max({ 1,1 }, sz); // avoid div0 if (on_value_changed) on_value_changed(this, m_value); } break; case kEventType::MouseCancel: mouse_release(); dragging = false; m_value = m_old_value; set_value(m_value.x, m_value.y); if (on_value_changed) on_value_changed(this, m_value); break; default: return kEventResult::Available; break; } return kEventResult::Consumed; } void NodeHeightmapOverlay::draw() { auto sz = m_size; auto pos = glm::clamp(m_value * sz, { 0, 0 }, sz); m_picker->SetPosition(pos - m_picker->GetSize() * .5f); } void NodeHeightmapOverlay::handle_resize(glm::vec2 old_size, glm::vec2 new_size, float zoom) { NodeBorder::handle_resize(old_size, new_size, zoom); set_value(m_value.x, m_value.y); }