#include "pch.h" #include "log.h" #include "canvas.h" #include "app.h" #include "texture.h" #include "node_progress_bar.h" #include #ifdef __APPLE__ #include #import #endif Canvas* Canvas::I; std::vector Canvas::modes[] = { { new CanvasModePen, new CanvasModeBasicCamera }, { new CanvasModePen, new CanvasModeBasicCamera }, { new CanvasModeLine, new CanvasModeBasicCamera }, { new CanvasModeCamera, new CanvasModeBasicCamera }, { new CanvasModeGrid, new CanvasModeBasicCamera }, { new CanvasModeTransform, new CanvasModeBasicCamera }, { new CanvasModeFill, new CanvasModeBasicCamera }, { new CanvasModeMaskFree, new CanvasModeBasicCamera }, { new CanvasModeMaskLine, new CanvasModeBasicCamera }, }; glm::vec3 Canvas::m_plane_origin[6] = { { 0, 0,-1}, // front { 1, 0, 0}, // right { 0, 0, 1}, // back {-1, 0, 0}, // left { 0, 1, 0}, // top { 0,-1, 0}, // bottom }; glm::vec3 Canvas::m_plane_normal[6] = { { 0, 0, 1}, // front {-1, 0, 0}, // right { 0, 0,-1}, // back { 1, 0, 0}, // left { 0,-1, 0}, // top { 0, 1, 0}, // bottom }; glm::vec3 Canvas::m_plane_tangent[6] = { {0, 1, 0}, // front {0, 1, 0}, // right {0, 1, 0}, // back {0, 1, 0}, // left {0, 0,-1}, // top {0, 0, 1}, // bottom }; // only rotation glm::mat4 Canvas::m_plane_transform[6] = { glm::lookAt(glm::vec3(), { 0, 0,-1}, {0, 1, 0}), // front glm::lookAt(glm::vec3(), {-1, 0, 0}, {0, 1, 0}), // right glm::lookAt(glm::vec3(), { 0, 0, 1}, {0, 1, 0}), // back glm::lookAt(glm::vec3(), { 1, 0, 0}, {0, 1, 0}), // left glm::lookAt(glm::vec3(), { 0, 1, 0}, {0, 0,-1}), // top glm::lookAt(glm::vec3(), { 0,-1, 0}, {0, 0, 1}), // bottom }; void Canvas::pick_start() { for (int i = 0; i < 6; i++) m_pick_ready[i] = false; } void Canvas::pick_update(int plane) { // check if already updated if (m_pick_ready[plane]) return; // save viewport and clear color states GLint vp[4]; GLfloat cc[4]; glGetIntegerv(GL_VIEWPORT, vp); glGetFloatv(GL_COLOR_CLEAR_VALUE, cc); GLboolean blend = glIsEnabled(GL_BLEND); // prepare common states glViewport(0, 0, m_width, m_height); glEnable(GL_BLEND); int i = plane; m_tmp[i].bindFramebuffer(); m_tmp[i].clear({ 1, 1, 1, 1 }); ShaderManager::use(kShader::TextureAlpha); ShaderManager::u_int(kShaderUniform::Highlight, false); ShaderManager::u_int(kShaderUniform::Tex, 0); ShaderManager::u_mat4(kShaderUniform::MVP, glm::ortho(-.5f, .5f, -.5f, .5f, -1.f, 1.f)); glActiveTexture(GL_TEXTURE0); m_sampler.bind(0); for (auto layer_index : m_order) { if (!m_layers[layer_index].m_visible || m_layers[layer_index].m_opacity == 0.f) continue; ShaderManager::u_float(kShaderUniform::Alpha, m_layers[layer_index].m_opacity); m_layers[layer_index].m_rtt[i].bindTexture(); m_plane.draw_fill(); m_layers[layer_index].m_rtt[i].unbindTexture(); } m_sampler.unbind(); if (!m_pick_data[plane]) m_pick_data[plane] = std::make_unique(m_width*m_height); glReadPixels(0, 0, m_width, m_height, GL_RGBA, GL_UNSIGNED_BYTE, m_pick_data[plane].get()); m_tmp[i].unbindFramebuffer(); // restore viewport and clear color states blend ? glEnable(GL_BLEND) : glDisable(GL_BLEND); glViewport(vp[0], vp[1], vp[2], vp[3]); glClearColor(cc[0], cc[1], cc[2], cc[3]); glActiveTexture(GL_TEXTURE0); m_pick_ready[plane] = true; } glm::vec4 Canvas::pick_get(glm::vec2 canvas_loc) { glm::vec3 ray_origin; glm::vec3 ray_dir; glm::vec3 hit_pos; glm::vec3 hit_normal; glm::vec2 fb_pos; int plane_id; if (point_trace(canvas_loc, ray_origin, ray_dir, hit_pos, fb_pos, hit_normal, plane_id)) { pick_update(plane_id); int i = (int)fb_pos.x + (int)fb_pos.y * m_width; return glm::vec4(m_pick_data[plane_id][i]) / 255.f; } return {0,0,0,1}; } void Canvas::pick_end() { for (int i = 0; i < 6; i++) m_pick_data[i].release(); } void Canvas::clear(const glm::vec4& c/*={0,0,0,1}*/) { snap_history({ 0, 1, 2, 3, 4, 5 }); m_layers[m_current_layer_idx].clear(c); m_unsaved = true; } void Canvas::clear_all() { for (auto& l : m_layers) l.clear({0, 0, 0, 0}); } void Canvas::snap_history(const std::vector& planes) { auto action = new ActionStroke; action->was_saved = !m_unsaved; for (auto i : planes) { if (!m_layers[m_current_layer_idx].m_dirty_face[i]) continue; // no stroke on this face, skip it m_layers[m_current_layer_idx].m_rtt[i].bindFramebuffer(); // save image before commit glm::vec2 box_or = xy(m_layers[m_current_layer_idx].m_dirty_box[i]); glm::vec2 box_sz = zw(m_layers[m_current_layer_idx].m_dirty_box[i]) - xy(m_layers[m_current_layer_idx].m_dirty_box[i]); action->m_image[i] = std::make_unique(box_sz.x * box_sz.y * 4); glReadPixels(box_or.x, box_or.y, box_sz.x, box_sz.y, GL_RGBA, GL_UNSIGNED_BYTE, action->m_image[i].get()); action->m_box[i] = m_layers[m_current_layer_idx].m_dirty_box[i]; action->m_old_box[i] = m_layers[m_current_layer_idx].m_dirty_box[i]; action->m_old_dirty[i] = m_layers[m_current_layer_idx].m_dirty_face[i]; m_layers[m_current_layer_idx].m_rtt[i].unbindFramebuffer(); } // save history action->m_layer_idx = m_current_layer_idx; action->m_canvas = this; action->m_stroke = std::move(m_current_stroke); action->clear_layer = true; ActionManager::add(action); } ActionStroke* Canvas::create_action(int layer) { auto action = new ActionStroke; for (int i = 0; i < 6; i++) { if (!m_layers[layer].m_dirty_face[i]) continue; // no stroke on this face, skip it m_layers[layer].m_rtt[i].bindFramebuffer(); // save image before commit glm::vec2 box_or = xy(m_layers[layer].m_dirty_box[i]); glm::vec2 box_sz = zw(m_layers[layer].m_dirty_box[i]) - xy(m_layers[layer].m_dirty_box[i]); action->m_image[i] = std::make_unique(box_sz.x * box_sz.y * 4); glReadPixels(box_or.x, box_or.y, box_sz.x, box_sz.y, GL_RGBA, GL_UNSIGNED_BYTE, action->m_image[i].get()); action->m_box[i] = m_layers[layer].m_dirty_box[i]; action->m_old_box[i] = m_layers[layer].m_dirty_box[i]; action->m_old_dirty[i] = m_layers[layer].m_dirty_face[i]; m_layers[layer].m_rtt[i].unbindFramebuffer(); } // save history action->m_layer_idx = layer; action->m_canvas = this; action->m_stroke = std::move(m_current_stroke); action->clear_layer = true; return action; } void Canvas::stroke_end() { if (!m_current_stroke) return; if (m_current_stroke->has_sample()) { m_commit_delayed = true; } else { stroke_commit(); m_current_stroke = nullptr; m_show_tmp = false; } } void Canvas::stroke_cancel() { if (!m_current_stroke) return; m_current_stroke = nullptr; m_show_tmp = false; } void Canvas::stroke_draw_mix(const glm::vec2& bb_min, const glm::vec2& bb_sz) { m_mixer.bindFramebuffer(); float zoom = m_node->root()->m_zoom; glViewport(0, 0, m_mixer.getWidth(), m_mixer.getHeight()); glDisable(GL_DEPTH_TEST); glEnable(GL_SCISSOR_TEST); glScissor(bb_min.x, bb_min.y, bb_sz.x, bb_sz.y); m_mixer.clear({ 1, 1, 1, 0 }); m_sampler.bind(0); m_sampler_linear.bind(1); auto layer_index = m_current_layer_idx; for (int plane_index = 0; plane_index < 6; plane_index++) { if (!m_layers[layer_index].m_visible || m_layers[layer_index].m_opacity == .0f || !m_layers[layer_index].m_dirty_face[plane_index]) continue; glm::mat4 proj = glm::perspective(glm::radians(m_cam_fov), (float)m_mixer.getWidth() / m_mixer.getHeight(), 0.1f, 1000.f); auto plane_mvp_z = glm::scale(glm::vec3(1, -1, 1)) * m_proj * m_mv * m_plane_transform[plane_index] * glm::translate(glm::vec3(0, 0, -1)); ShaderManager::use(kShader::TextureAlphaSep); ShaderManager::u_int(kShaderUniform::Tex, 0); ShaderManager::u_int(kShaderUniform::TexA, 1); ShaderManager::u_float(kShaderUniform::Alpha, m_layers[layer_index].m_opacity); ShaderManager::u_int(kShaderUniform::Highlight, false); ShaderManager::u_mat4(kShaderUniform::MVP, plane_mvp_z); glEnable(GL_BLEND); glActiveTexture(GL_TEXTURE0); m_layers[layer_index].m_rtt[plane_index].bindTexture(); glActiveTexture(GL_TEXTURE1); m_layers[layer_index].m_rtt[plane_index].bindTexture(); m_node->m_face_plane.draw_fill(); glActiveTexture(GL_TEXTURE1); m_layers[layer_index].m_rtt[plane_index].unbindTexture(); glActiveTexture(GL_TEXTURE0); m_layers[layer_index].m_rtt[plane_index].unbindTexture(); glEnable(GL_BLEND); // glActiveTexture(GL_TEXTURE0); // m_tmp[plane_index].bindTexture(); // glActiveTexture(GL_TEXTURE1); // m_tmp[plane_index].bindTexture(); // m_node->m_face_plane.draw_fill(); // glActiveTexture(GL_TEXTURE1); // m_tmp[plane_index].unbindTexture(); // glActiveTexture(GL_TEXTURE0); // m_tmp[plane_index].unbindTexture(); m_sampler.bind(0); m_sampler.bind(1); m_sampler.bind(2); auto& paper = TextureManager::get(const_hash("data/paper.jpg")); ShaderManager::use(kShader::CompDraw); ShaderManager::u_int(kShaderUniform::Tex, 0); //ShaderManager::u_int(kShaderUniform::TexA, 0); ShaderManager::u_int(kShaderUniform::TexStroke, 1); ShaderManager::u_int(kShaderUniform::TexMask, 2); //ShaderManager::u_vec2(kShaderUniform::Resolution, m_size); //ShaderManager::u_int(kShaderUniform::TexStencil, 3); ShaderManager::u_float(kShaderUniform::StrokeAlpha, m_current_stroke->m_brush.m_tip_opacity); ShaderManager::u_float(kShaderUniform::Alpha, 1); ShaderManager::u_int(kShaderUniform::Lock, m_layers[layer_index].m_alpha_locked); ShaderManager::u_int(kShaderUniform::Mask, m_smask_active); ShaderManager::u_int(kShaderUniform::UseFragCoordUV2, false); ShaderManager::u_int(kShaderUniform::BlendMode, m_current_stroke->m_brush.m_blend_mode); ShaderManager::u_mat4(kShaderUniform::MVP, plane_mvp_z); glActiveTexture(GL_TEXTURE0); m_layers[layer_index].m_rtt[plane_index].bindTexture(); glActiveTexture(GL_TEXTURE1); m_tmp[plane_index].bindTexture(); glActiveTexture(GL_TEXTURE2); m_smask.m_rtt[plane_index].bindTexture(); glActiveTexture(GL_TEXTURE3); paper.bind(); m_node->m_face_plane.draw_fill(); paper.unbind(); glActiveTexture(GL_TEXTURE2); m_smask.m_rtt[plane_index].unbindTexture(); glActiveTexture(GL_TEXTURE1); m_tmp[plane_index].unbindTexture(); glActiveTexture(GL_TEXTURE0); m_layers[layer_index].m_rtt[plane_index].unbindTexture(); } m_sampler.unbind(); m_mixer.unbindFramebuffer(); glDisable(GL_SCISSOR_TEST); } void Canvas::stroke_draw() { if (!(m_current_stroke && m_current_stroke->has_sample())) { //stroke_draw_mix({ 0,0 }, { m_mixer.getWidth(), m_mixer.getHeight() }); return; } m_dirty = true; GLint vp[4]; GLfloat cc[4]; glGetIntegerv(GL_VIEWPORT, vp); glGetFloatv(GL_COLOR_CLEAR_VALUE, cc); float zoom = m_node->root()->m_zoom; auto m_brush = m_current_stroke->m_brush; auto samples = m_current_stroke->compute_samples(); auto& tex = TextureManager::get(m_brush.m_tex_id); auto& stencil = TextureManager::get(const_hash("data/paper.jpg")); auto ortho_proj = glm::ortho(0.f, (float)m_width, 0.f, (float)m_height, -1.f, 1.f); std::vector B{ vertex_t{ {0, 0, 1, 1}, {0, 0}, {0, 0} }, vertex_t{ {0, 0, 1, 1}, {0, 1}, {0, 1} }, vertex_t{ {0, 0, 1, 1}, {1, 1}, {1, 1} }, vertex_t{ {0, 0, 1, 1}, {1, 0}, {1, 0} }, }; glViewport(0, 0, m_width, m_height); glActiveTexture(GL_TEXTURE0); tex.bind(); m_sampler_brush.bind(0); m_sampler_bg.bind(1); m_sampler_stencil.bind(2); m_sampler.bind(3); //m_sampler_linear.bind(5); glActiveTexture(GL_TEXTURE2); stencil.bind(); glActiveTexture(GL_TEXTURE3); m_mixer.bindTexture(); glDisable(GL_BLEND); ShaderManager::use(kShader::Stroke); ShaderManager::u_int(kShaderUniform::Tex, 0); // brush #ifndef __IOS__ ShaderManager::u_int(kShaderUniform::TexBG, 1); // bg #endif ShaderManager::u_int(kShaderUniform::TexStencil, 2); // stencil ShaderManager::u_int(kShaderUniform::TexMix, 3); // mixer //ShaderManager::u_int(kShaderUniform::TexMixA, 4); // mixer ShaderManager::u_vec2(kShaderUniform::Resolution, { m_width, m_height }); ShaderManager::u_vec2(kShaderUniform::StencilOffset, stencil_offset); ShaderManager::u_float(kShaderUniform::StencilAlpha, m_brush.m_tip_stencil); ShaderManager::u_float(kShaderUniform::MixAlpha, m_brush.m_tip_mix); ShaderManager::u_float(kShaderUniform::Wet, m_brush.m_tip_wet); ShaderManager::u_float(kShaderUniform::Noise, m_brush.m_tip_noise); auto unp_vp = zw(m_box); auto unp_inv = glm::inverse(m_proj * m_mv); for (const auto& s : samples) { if (m_mixer_idle) { m_mixer_sample = s; m_mixer_idle = false; } static glm::vec2 UV2[4]; glm::vec2 dx_mix(m_mixer_sample.size * 0.5f, 0), dy_mix(0, m_mixer_sample.size * 0.5f); glm::vec2 off_mix[4] = { -dx_mix - dy_mix, // A - bottom-left -dx_mix + dy_mix, // B - top-left +dx_mix + dy_mix, // C - top-right +dx_mix - dy_mix, // D - bottom-right }; // P is the initial square centered at the cursor location glm::vec2 dx(s.size * 0.5f, 0), dy(0, s.size * 0.5f); glm::vec2 off[4] = { -dx - dy, // A - bottom-left -dx + dy, // B - top-left +dx + dy, // C - top-right +dx - dy, // D - bottom-right }; auto sz = glm::vec2(m_mixer.getWidth(), m_mixer.getHeight()) * m_mixer_scale; glm::vec2 bb_min(sz); glm::vec2 bb_max(0, 0); for (int j = 0; j < 4; j++) { auto p = (xy(m_mixer_sample.pos) + off_mix[j] * glm::orientate2(-s.angle) + glm::vec2(0, 1)) / zoom; UV2[j] = p / sz; bb_min = glm::max({ 0, 0 }, glm::min(bb_min, p)); bb_max = glm::min(sz, glm::max(bb_max, p)); B[j].pos = glm::vec4(xy(s.pos) + off[j] * glm::orientate2(-s.angle), 1, 1); B[j].uvs2 = UV2[j]; } auto bb_sz = bb_max - bb_min; if (m_brush.m_tip_mix > 0.f) { stroke_draw_mix(bb_min, bb_sz); glViewport(0, 0, m_width, m_height); glActiveTexture(GL_TEXTURE0); tex.bind(); m_sampler_brush.bind(0); m_sampler_bg.bind(1); m_sampler_stencil.bind(2); m_sampler.bind(3); //m_sampler_linear.bind(5); glActiveTexture(GL_TEXTURE2); stencil.bind(); glActiveTexture(GL_TEXTURE3); m_mixer.bindTexture(); glDisable(GL_BLEND); } for (int i = 0; i < 6; i++) { // check if plane is even visible glm::vec4 forward = m_mv * glm::vec4(0, 0, 1, 1); float dot = glm::dot(xyz(forward), m_plane_normal[i]); // TODO: use better threshold than 0.3 // some trigonometric shit, tangent and stuff // if (dot < -0.3f) // continue; int intersected = 0; int inside = 0; // intersect P with the current face to clip diverging points from the plane auto P = poly_intersect(B, m_plane_shape[i]); for (int j = 0; j < P.size(); j++) { glm::vec3 ray_origin, ray_dir; if (s.pos.z == 0) { //point_unproject(P[j].pos, { 0, 0, zw(m_box) }, m_mv, m_proj, ray_origin, ray_dir); auto clip_space = glm::vec2(P[j].pos.x, unp_vp.y - P[j].pos.y - 1.f) / unp_vp * 2.f - 1.f; auto wp0 = unp_inv * glm::vec4(clip_space, 0, 1); auto wp1 = unp_inv * glm::vec4(clip_space, .5, 1); ray_origin = xyz(wp0 / wp0.w); ray_dir = glm::normalize(xyz(wp1 / wp1.w) - ray_origin); } else { auto m = glm::inverse(glm::lookAt({ 0, 0, 0 }, s.pos, { 0, 1, 0 })); glm::vec3 off_3d = m * glm::vec4(off[j], 0, 1); ray_origin = glm::vec3(0); ray_dir = s.pos + off_3d; } glm::vec3 hit; float hit_t; if (ray_intersect(ray_origin, ray_dir, m_plane_origin[i], m_plane_normal[i], m_plane_tangent[i], hit, hit_t)) { glm::mat4 plane_camera = glm::lookAt(m_plane_origin[i], m_plane_normal[i], m_plane_tangent[i]); glm::vec4 plane_local = plane_camera * glm::vec4(hit, 1); if (glm::abs(plane_local.x) < 1.5f && glm::abs(plane_local.y) < 1.5f) { inside++; } //P[j].uvs2 = xy(P[j].pos) / glm::vec2(App::I.width, App::I.height); P[j].pos.x = -(plane_local.x * 0.5f - 0.5f) * m_width; P[j].pos.y = (plane_local.y * 0.5f + 0.5f) * m_height; // Black magic - BEWARE! // interpolation perspective correction, use the current camera projection to correct the interpolation // because the new shape will have z fixed with an ortho projection when drawn to the face // we need to imitate the same perspective as the once in the camera // see: https://www.scratchapixel.com/lessons/3d-basic-rendering/rasterization-practical-implementation/perspective-correct-interpolation-vertex-attributes auto hit_cam = m_mv * glm::vec4(hit, 1); P[j].pos.z = 0; P[j].pos.w = hit_cam.z; P[j].uvs *= hit_cam.z; P[j].uvs2 *= hit_cam.z; intersected++; } else { break; } } if (intersected < 3 || inside == 0) continue; m_dirty_face[i] = true; m_tmp[i].bindFramebuffer(); glActiveTexture(GL_TEXTURE1); m_tex[i].bind(); // bg, copy of framebuffer (copied before drawing) glm::vec2 bb_min(m_width, m_height); glm::vec2 bb_max(0, 0); for (int j = 0; j < P.size(); j++) { bb_min = glm::max({ 0, 0 }, glm::min(bb_min, xy(P[j].pos))); bb_max = glm::min({ m_width, m_height }, glm::max(bb_max, xy(P[j].pos))); } auto bb_sz = bb_max - bb_min; glm::vec2 pad(1); glm::ivec2 tex_pos = glm::clamp(glm::floor(bb_min) - pad , { 0, 0 }, { m_width, m_height }); glm::ivec2 tex_sz = glm::clamp(glm::ceil(bb_sz ) + pad*2.f, { 0, 0 }, (glm::vec2)(glm::ivec2(m_width, m_height) - tex_pos)); #ifndef __IOS__ glCopyTexSubImage2D(GL_TEXTURE_2D, 0, tex_pos.x, tex_pos.y, tex_pos.x, tex_pos.y, tex_sz.x, tex_sz.y); #endif m_dirty_box[i] = glm::vec4( glm::min(xy(m_dirty_box[i]), (glm::vec2)tex_pos), glm::max(zw(m_dirty_box[i]), (glm::vec2)(tex_pos + tex_sz)) ); ShaderManager::use(kShader::Stroke); ShaderManager::u_mat4(kShaderUniform::MVP, ortho_proj); ShaderManager::u_vec4(kShaderUniform::Col, glm::vec4(s.col, m_brush.m_tip_color.a)); ShaderManager::u_float(kShaderUniform::Alpha, s.flow); if (P.size() == 4) { static vertex_t rect[6]; rect[0] = P[0]; rect[1] = P[1]; rect[2] = P[2]; rect[3] = P[0]; rect[4] = P[2]; rect[5] = P[3]; m_brush_shape.update_vertices(rect, 6); } else if (P.size() == 3) { m_brush_shape.update_vertices(P.data(), P.size()); } else { P = triangulate_simple(P); m_brush_shape.update_vertices(P.data(), P.size()); } m_brush_shape.draw_fill(); /* // draw sample wireframe std::vector lines; for (int vi = 0; vi < P.size(); vi += 3) { auto a = P[vi]; auto b = P[(vi + 1) % P.size()]; auto c = P[(vi + 2) % P.size()]; a.pos.z = b.pos.z = c.pos.z = 0; lines.push_back(a); lines.push_back(b); lines.push_back(b); lines.push_back(c); lines.push_back(c); lines.push_back(a); } ShaderManager::use(kShader::Color); ShaderManager::u_vec4(kShaderUniform::Col, { s.col, 1 }); ShaderManager::u_mat4(kShaderUniform::MVP, ortho_proj); m_brush_shape.update_vertices(lines.data(), lines.size()); m_brush_shape.draw_stroke(); */ glActiveTexture(GL_TEXTURE1); m_tex[i].unbind(); m_tmp[i].unbindFramebuffer(); } m_mixer_sample = s; } glDisable(GL_BLEND); glActiveTexture(GL_TEXTURE2); stencil.unbind(); glActiveTexture(GL_TEXTURE3); m_mixer.unbindTexture(); glActiveTexture(GL_TEXTURE0); m_sampler_brush.unbind(); m_sampler_bg.unbind(); m_sampler_stencil.unbind(); tex.unbind(); glViewport(vp[0], vp[1], vp[2], vp[3]); glClearColor(cc[0], cc[1], cc[2], cc[3]); if (m_commit_delayed) { stroke_commit(); m_current_stroke = nullptr; m_show_tmp = false; m_commit_delayed = false; } } bool Canvas::point_trace(glm::vec2 loc, glm::vec3& ray_origin, glm::vec3& ray_dir, glm::vec3& hit_pos, glm::vec2& fb_pos, glm::vec3& hit_normal, int& out_plane_id) { point_unproject(loc, { 0, 0, zw(m_box) }, m_mv, m_proj, ray_origin, ray_dir); glm::vec3 hit; float hit_t; for (int i = 0; i < 6; i++) { if (ray_intersect(ray_origin, ray_dir, m_plane_origin[i], m_plane_normal[i], m_plane_tangent[i], hit, hit_t)) { glm::mat4 plane_camera = glm::lookAt(m_plane_origin[i], m_plane_normal[i], m_plane_tangent[i]); glm::vec4 plane_local = plane_camera * glm::vec4(hit, 1); if (glm::abs(plane_local.x) < 1.f && glm::abs(plane_local.y) < 1.f) { fb_pos.x = -(plane_local.x * 0.5f - 0.5f) * m_width; fb_pos.y = (plane_local.y * 0.5f + 0.5f) * m_height; hit_pos = hit; hit_normal = m_plane_normal[i]; out_plane_id = i; return true; } else continue; } else continue; } return false; } /* bool Canvas::point_trace_plane(glm::vec2 loc, glm::vec3& hit_pos, glm::vec2& hit_fb_pos, int plane_id) { auto ln = (loc / zw(m_box)) * 2.f - 1.f; auto p = m_plane_unproject[plane_id] * glm::vec4(ln, 1, 1); if (p.w <= 0) { return true; } return false; } */ bool Canvas::point_trace_plane(glm::vec2 loc, glm::vec3& ray_origin, glm::vec3& ray_dir, glm::vec3& hit_pos, glm::vec3& hit_normal, glm::vec2& hit_fb_pos, int plane_id) { /* auto ln = (loc / zw(m_box)) * 2.f - 1.f; auto p = m_plane_unproject[plane_id] * glm::vec4(ln, 1, 1); if (p.w <= 0) { return true; } */ point_unproject(loc, { 0, 0, zw(m_box) }, m_mv, m_proj, ray_origin, ray_dir); glm::vec3 hit; glm::vec2 fb_pos; float hit_t; if (ray_intersect(ray_origin, ray_dir, m_plane_origin[plane_id], m_plane_normal[plane_id], m_plane_tangent[plane_id], hit, hit_t)) { glm::mat4 plane_camera = glm::lookAt(m_plane_origin[plane_id], m_plane_normal[plane_id], m_plane_tangent[plane_id]); glm::vec4 plane_local = plane_camera * glm::vec4(hit, 1); hit_pos = hit; hit_normal = m_plane_normal[plane_id]; hit_fb_pos.x = -(plane_local.x * 0.5f - 0.5f); hit_fb_pos.y = (plane_local.y * 0.5f + 0.5f); return true; } return false; } void Canvas::point_unproject(glm::vec2 loc, glm::vec4 vp, glm::mat4 camera, glm::mat4 proj, glm::vec3& out_origin, glm::vec3& out_dir) { auto clip_space = glm::vec2(loc.x, vp.w - loc.y - 1.f) / zw(vp) * 2.f - 1.f; auto inv = glm::inverse(proj * camera); auto wp0 = inv * glm::vec4(clip_space, 0, 1); auto wp1 = inv * glm::vec4(clip_space, .5, 1); out_origin = xyz(wp0 / wp0.w); out_dir = glm::normalize(xyz(wp1 / wp1.w) - out_origin); }; void Canvas::point_unproject(glm::vec2 loc, glm::vec3& out_origin, glm::vec3& out_dir) { auto clip_space = glm::vec2(loc.x, m_vp.w - loc.y - 1.f) / zw(m_vp) * 2.f - 1.f; auto inv = glm::inverse(m_proj * m_mv); auto wp0 = inv * glm::vec4(clip_space, 0, 1); auto wp1 = inv * glm::vec4(clip_space, .5, 1); out_origin = xyz(wp0 / wp0.w); out_dir = glm::normalize(xyz(wp1 / wp1.w) - out_origin); } glm::vec3 Canvas::point_trace(glm::vec2 loc) { glm::vec3 ray_origin; glm::vec3 ray_dir; glm::vec3 hit_pos; glm::vec3 hit_normal; glm::vec2 fb_pos; int plane_id; if (point_trace(loc, ray_origin, ray_dir, hit_pos, fb_pos, hit_normal, plane_id)) return hit_pos; return glm::vec3(0); } void Canvas::stroke_commit() { if (!m_dirty || m_layers.empty()) return; m_mixer_idle = true; m_dirty = false; m_dirty_stroke = true; // new stroke ready for timelapse capture App::I.redraw = true; // save viewport and clear color states GLint vp[4]; GLfloat cc[4]; glGetIntegerv(GL_VIEWPORT, vp); glGetFloatv(GL_COLOR_CLEAR_VALUE, cc); GLboolean blend = glIsEnabled(GL_BLEND); // allocate action to add to history auto action = new ActionStroke; action->was_saved = !m_unsaved; m_unsaved = true; App::I.title_update(); // prepare common states glViewport(0, 0, m_width, m_height); glDisable(GL_BLEND); for (int i = 0; i < 6; i++) { //m_dirty_box[i] = glm::vec4(0, 0, m_width, m_height); // reset bounding box if (!m_dirty_face[i]) continue; // no stroke on this face, skip it m_layers[m_current_layer_idx].m_rtt[i].bindFramebuffer(); // save image before commit glm::vec2 box_sz = zw(m_dirty_box[i]) - xy(m_dirty_box[i]); action->m_image[i] = std::make_unique(box_sz.x * box_sz.y * 4); glReadPixels(m_dirty_box[i].x, m_dirty_box[i].y, box_sz.x, box_sz.y, GL_RGBA, GL_UNSIGNED_BYTE, action->m_image[i].get()); action->m_box[i] = m_dirty_box[i]; action->m_old_box[i] = m_layers[m_current_layer_idx].m_dirty_box[i]; action->m_old_dirty[i] = m_layers[m_current_layer_idx].m_dirty_face[i]; auto& lbox = m_layers[m_current_layer_idx].m_dirty_box[i]; lbox = glm::vec4( glm::min(xy(m_dirty_box[i]), xy(lbox)), glm::max(zw(m_dirty_box[i]), zw(lbox)) ); m_layers[m_current_layer_idx].m_dirty_face[i] = true; // copy to tmp2 for layer blending glActiveTexture(GL_TEXTURE0); m_tex2[i].bind(); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, m_width, m_height); m_tex2[i].unbind(); m_tmp[i].bindTexture(); glActiveTexture(GL_TEXTURE1); m_tex2[i].bind(); m_sampler.bind(0); m_sampler_bg.bind(1); m_sampler_mask.bind(2); m_sampler_stencil.bind(3); if (m_state == kCanvasMode::Erase) { ShaderManager::use(kShader::CompErase); ShaderManager::u_int(kShaderUniform::Tex, 0); ShaderManager::u_int(kShaderUniform::TexStroke, 1); ShaderManager::u_int(kShaderUniform::TexMask, 2); ShaderManager::u_int(kShaderUniform::Mask, m_smask_active); ShaderManager::u_int(kShaderUniform::UseFragCoordUV2, false); ShaderManager::u_float(kShaderUniform::StrokeAlpha, m_current_stroke->m_brush.m_tip_opacity); ShaderManager::u_float(kShaderUniform::Alpha, 1); ShaderManager::u_mat4(kShaderUniform::MVP, glm::ortho(-.5f, .5f, -.5f, .5f, -1.f, 1.f)); glActiveTexture(GL_TEXTURE0); m_tex2[i].bind(); glActiveTexture(GL_TEXTURE1); m_tmp[i].bindTexture(); glActiveTexture(GL_TEXTURE2); m_smask.m_rtt[i].bindTexture(); m_plane.draw_fill(); m_smask.m_rtt[i].unbindTexture(); glActiveTexture(GL_TEXTURE1); m_tmp[i].unbindTexture(); glActiveTexture(GL_TEXTURE0); m_tex2[i].unbind(); } else { auto& paper = TextureManager::get(const_hash("data/paper.jpg")); ShaderManager::use(kShader::CompDraw); ShaderManager::u_int(kShaderUniform::Tex, 0); ShaderManager::u_int(kShaderUniform::TexStroke, 1); ShaderManager::u_int(kShaderUniform::TexMask, 2); //ShaderManager::u_vec2(kShaderUniform::Resolution, m_size); ShaderManager::u_float(kShaderUniform::StrokeAlpha, m_current_stroke->m_brush.m_tip_opacity); ShaderManager::u_float(kShaderUniform::Alpha, 1); ShaderManager::u_int(kShaderUniform::Mask, m_smask_active); ShaderManager::u_int(kShaderUniform::UseFragCoordUV2, false); ShaderManager::u_int(kShaderUniform::BlendMode, m_current_stroke->m_brush.m_blend_mode); ShaderManager::u_mat4(kShaderUniform::MVP, glm::ortho(-.5f, .5f, -.5f, .5f, -1.f, 1.f)); glActiveTexture(GL_TEXTURE0); m_tex2[i].bind(); glActiveTexture(GL_TEXTURE1); m_tmp[i].bindTexture(); glActiveTexture(GL_TEXTURE2); m_smask.m_rtt[i].bindTexture(); glActiveTexture(GL_TEXTURE3); paper.bind(); m_plane.draw_fill(); paper.unbind(); glActiveTexture(GL_TEXTURE2); m_smask.m_rtt[i].unbindTexture(); glActiveTexture(GL_TEXTURE1); m_tmp[i].unbindTexture(); glActiveTexture(GL_TEXTURE0); m_tex2[i].unbind(); } // else // { // ShaderManager::use(kShader::StrokeLayer); // ShaderManager::u_int(kShaderUniform::TexBG, 1); // ShaderManager::u_int(kShaderUniform::Lock, m_layers[m_current_layer_idx].m_alpha_locked); // ShaderManager::u_float(kShaderUniform::Alpha, m_current_stroke->m_brush.m_tip_opacity); // // ShaderManager::u_int(kShaderUniform::Tex, 0); // ShaderManager::u_mat4(kShaderUniform::MVP, glm::ortho(-.5f, .5f, -.5f, .5f, -1.f, 1.f)); // m_plane.draw_fill(); // m_sampler.unbind(); // m_sampler_bg.unbind(); // m_tex2[i].unbind(); // m_tmp[i].unbindTexture(); // } m_layers[m_current_layer_idx].m_rtt[i].unbindFramebuffer(); } // restore viewport and clear color states blend ? glEnable(GL_BLEND) : glDisable(GL_BLEND); glViewport(vp[0], vp[1], vp[2], vp[3]); glClearColor(cc[0], cc[1], cc[2], cc[3]); glActiveTexture(GL_TEXTURE0); // save history action->m_layer_idx = m_current_layer_idx; action->m_canvas = this; action->m_stroke = std::move(m_current_stroke); ActionManager::add(action); } void Canvas::stroke_update(glm::vec3 point, float pressure) { m_current_stroke->add_point(point, pressure); } void Canvas::stroke_start(glm::vec3 point, float pressure, const Brush& brush) { // need to commit this now before starting a new stroke if (m_current_stroke && m_commit_delayed) { stroke_commit(); m_current_stroke = nullptr; m_show_tmp = false; m_commit_delayed = false; } stencil_offset = glm::vec2((rand()%1000)*0.001f, (rand()%1000)*0.001f); m_current_stroke = std::make_unique(); m_current_stroke->m_camera.rot = m_cam_rot; m_current_stroke->m_camera.fov = m_cam_fov; m_current_stroke->start(brush); m_current_stroke->add_point(point, pressure); for (int i = 0; i < 6; i++) { m_dirty_box[i] = glm::vec4(m_width, m_height, 0, 0); // reset bounding box m_dirty_face[i] = false; // if (m_state == kCanvasMode::Erase || m_layers[m_current_layer_idx].m_alpha_locked) // { // m_layers[m_current_layer_idx].m_rtt[i].bindFramebuffer(); // m_tmp[i].bindTexture(); // glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, m_width, m_height); // m_tmp[i].unbindTexture(); // m_layers[m_current_layer_idx].m_rtt[i].unbindFramebuffer(); // } // else { m_tmp[i].bindFramebuffer(); m_tmp[i].clear({ 0, 0, 0, 0 }); m_tmp[i].unbindFramebuffer(); } } m_show_tmp = true; } void Canvas::layer_add(std::string name) { int idx = (int)m_layers.size(); m_layers.emplace_back(); m_layers.back().create(m_width, m_height, name); m_order.push_back(idx); m_current_layer_idx = idx; } void Canvas::layer_remove(int idx) // m_order index { int n = m_order[idx]; for (auto& i : m_order) if (i > n) i--; m_layers.erase(m_layers.begin() + n); m_order.erase(m_order.begin() + idx); m_current_layer_idx = m_order[std::min((int)m_layers.size() - 1, idx)]; } void Canvas::layer_order(int idx, int pos) // m_order index { std::swap(m_order[idx], m_order[pos]); } void Canvas::layer_merge(int source_idx, int dest_idx) // m_layer index { m_dirty = false; // save viewport and clear color states GLint vp[4]; GLfloat cc[4]; glGetIntegerv(GL_VIEWPORT, vp); glGetFloatv(GL_COLOR_CLEAR_VALUE, cc); GLboolean blend = glIsEnabled(GL_BLEND); // allocate action to add to history auto action = new ActionStroke; // prepare common states glViewport(0, 0, m_width, m_height); glDisable(GL_BLEND); for (int i = 0; i < 6; i++) { if (!m_layers[source_idx].m_dirty_face[i]) continue; // no stroke on this face, skip it m_layers[dest_idx].m_rtt[i].bindFramebuffer(); /* // save image before commit glm::vec2 box_sz = m_dirty_box[i].zw() - m_dirty_box[i].xy(); action->m_image[i] = std::make_unique(box_sz.x * box_sz.y * 4); glReadPixels(m_dirty_box[i].x, m_dirty_box[i].y, box_sz.x, box_sz.y, GL_RGBA, GL_UNSIGNED_BYTE, action->m_image[i].get()); action->m_box[i] = m_dirty_box[i]; action->m_old_box[i] = m_layers[m_current_layer_idx].m_dirty_box[i]; action->m_old_dirty[i] = m_layers[m_current_layer_idx].m_dirty_face[i]; */ auto& lbox = m_layers[dest_idx].m_dirty_box[i]; lbox = glm::vec4( glm::min(xy(m_layers[source_idx].m_dirty_box[i]), xy(lbox)), glm::max(zw(m_layers[source_idx].m_dirty_box[i]), zw(lbox)) ); m_layers[dest_idx].m_dirty_face[i] = true; // copy to tmp2 for layer blending glActiveTexture(GL_TEXTURE0); m_tex2[i].bind(); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, m_width, m_height); m_tex2[i].unbind(); m_sampler.bind(0); m_sampler_bg.bind(1); { ShaderManager::use(kShader::CompDraw); ShaderManager::u_int(kShaderUniform::Tex, 0); // dest ShaderManager::u_int(kShaderUniform::TexStroke, 1); // source //ShaderManager::u_vec2(kShaderUniform::Resolution, m_size); ShaderManager::u_int(kShaderUniform::UseFragCoordUV2, false); ShaderManager::u_float(kShaderUniform::StrokeAlpha, 1); ShaderManager::u_float(kShaderUniform::Alpha, m_layers[source_idx].m_opacity); ShaderManager::u_int(kShaderUniform::Lock, false); ShaderManager::u_int(kShaderUniform::BlendMode, m_layers[source_idx].m_blend_mode); ShaderManager::u_mat4(kShaderUniform::MVP, glm::ortho(-.5f, .5f, -.5f, .5f, -1.f, 1.f)); glActiveTexture(GL_TEXTURE0); m_tex2[i].bind(); glActiveTexture(GL_TEXTURE1); m_layers[source_idx].m_rtt[i].bindTexture(); m_plane.draw_fill(); m_layers[source_idx].m_rtt[i].unbindTexture(); glActiveTexture(GL_TEXTURE0); m_tex2[i].unbind(); } m_layers[dest_idx].m_rtt[i].unbindFramebuffer(); } // restore viewport and clear color states blend ? glEnable(GL_BLEND) : glDisable(GL_BLEND); glViewport(vp[0], vp[1], vp[2], vp[3]); glClearColor(cc[0], cc[1], cc[2], cc[3]); glActiveTexture(GL_TEXTURE0); /* // save history action->m_layer_idx = m_current_layer_idx; action->m_canvas = this; action->m_stroke = std::move(m_current_stroke); ActionManager::add(action); */ } void Canvas::resize(int width, int height) { m_width = width; m_height = height; m_size = { width, height }; for (int i = 0; i < 6; i++) { #if defined(__IOS__) || defined(__ANDROID__) m_tmp[i].create(width, height, -1, GL_RGBA8); m_tex[i].create(width, height, GL_RGBA8); #else m_tmp[i].create(width, height, -1, GL_RGBA32F); m_tex[i].create(width, height, GL_RGBA32F); #endif m_tex2[i].create(width, height, GL_RGBA8); } for (auto& l : m_layers) l.resize(width, height); m_smask.create(width, height, "mask"); m_unsaved = true; } bool Canvas::create(int width, int height) { m_width = width; m_height = height; m_size = { width, height }; for (int i = 0; i < 6; i++) { #if defined(__IOS__) || defined(__ANDROID__) m_tmp[i].create(width, height, -1, GL_RGBA8); m_tex[i].create(width, height, GL_RGBA8); #else m_tmp[i].create(width, height, -1, GL_RGBA32F); m_tex[i].create(width, height, GL_RGBA32F); #endif m_tex2[i].create(width, height, GL_RGBA8); // TODO: destroy before recreating } m_sampler.create(GL_NEAREST); m_sampler.create(GL_LINEAR); m_sampler_brush.create(); m_sampler_brush.set_filter(GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR); m_sampler_bg.create(GL_NEAREST); m_sampler_mask.create(GL_LINEAR); m_sampler_stencil.create(GL_LINEAR, GL_REPEAT); m_sampler_mix.create(GL_NEAREST, GL_REPEAT); m_plane.create<1>(1, 1); m_plane_brush.create<1>(1, 1); m_brush_shape.create(); m_mesh.create(); m_brush_mix.create(8, 8); for (auto& l : m_layers) { l.create(width, height, ""); } m_smask.create(width, height, "mask"); //m_smask.clear({1, 1, 1, 1}); m_unsaved = true; return true; } void Canvas::snapshot_save() { LOG("SAVE SNAPSHOT"); m_layers_snapshot.clear(); m_layers_snapshot.resize(m_layers.size()); for (int i = 0; i < m_layers.size(); i++) m_layers_snapshot[i] = m_layers[i].snapshot(); } void Canvas::snapshot_restore() { LOG("RESTORE SNAPSHOT"); for (int i = 0; i < m_layers.size(); i++) m_layers[i].restore(m_layers_snapshot[i]); m_layers_snapshot.clear(); } void Canvas::clear_context() { LOG("Canvas CLEAR CONTEXT"); for (auto& layer : m_layers) layer.destroy(); for (int i = 0; i < 6; i++) { m_tmp[i].destroy(); m_tex[i].destroy(); m_tex2[i].destroy(); } }; void Canvas::import_equirectangular(std::string file_path) { std::thread t(&Canvas::import_equirectangular_thread, this, file_path); t.detach(); } void Canvas::import_equirectangular_thread(std::string file_path) { App::I.async_start(); gl_state gl; gl.save(); snap_history({0,1,2,3,4,5}); m_unsaved = true; Image img; img.load_file(file_path); if (img.width == img.height / 6) { Texture2D tex; static GLint indices[] = { 5, 0, 4, 1, 2, 3 }; static GLint formats[] = { GL_RED, GL_RG, GL_RGB, GL_RGBA }; static GLint iformats[] = { GL_R8, GL_RG8, GL_RGB8, GL_RGBA8 }; tex.create(img.width, img.width, iformats[img.comp - 1], formats[img.comp - 1]); int stride = img.width * img.width * img.comp; Plane plane; plane.create<1>(2, 2); draw_objects([&](const glm::mat4& camera, const glm::mat4& proj, int i) { tex.update(img.m_data.get() + indices[i] * stride); m_sampler.bind(0); glActiveTexture(GL_TEXTURE0); tex.bind(); ShaderManager::use(kShader::Texture); ShaderManager::u_int(kShaderUniform::Tex, 0); ShaderManager::u_mat4(kShaderUniform::MVP, glm::scale(glm::vec3(-1, -1, 1))); plane.draw_fill(); tex.unbind(); m_sampler.unbind(); }); } else { Texture2D tex; tex.load_file(file_path); Sphere sphere; glDisable(GL_DEPTH_TEST); sphere.create<64, 64>(2.f); draw_objects([&](const glm::mat4& camera, const glm::mat4& proj, int i) { m_sampler.bind(0); glActiveTexture(GL_TEXTURE0); tex.bind(); ShaderManager::use(kShader::Texture); ShaderManager::u_int(kShaderUniform::Tex, 0); ShaderManager::u_mat4(kShaderUniform::MVP, proj * camera * glm::eulerAngleY(glm::radians(180.f)) * glm::scale(glm::vec3(1, -1, 1))); sphere.draw_fill(); tex.unbind(); m_sampler.unbind(); }); } for (int i = 0; i < 6; i++) { m_layers[m_current_layer_idx].m_dirty_box[i] = glm::vec4(0, 0, m_width, m_height); m_layers[m_current_layer_idx].m_dirty_face[i] = true; } App::I.async_update(); gl.restore(); App::I.async_end(); } void Canvas::export_equirectangular(std::string file_path, std::function on_complete) { if (App::I.check_license()) { std::thread t([=] { export_equirectangular_thread(file_path); if (on_complete) on_complete(); }); t.detach(); } } void Canvas::export_equirectangular_thread(std::string file_path) { gl_state gl; App::I.async_start(); std::shared_ptr pb; if (App::I.layout.m_loaded) { pb = std::make_shared(); pb->m_manager = &App::I.layout; pb->init(); pb->create(); pb->loaded(); pb->m_progress->SetWidthP(0); pb->m_title->set_text("Export Pano Image"); App::I.layout[App::I.main_id]->add_child(pb); App::I.async_update(); } // save viewport and clear color states GLint vp[4]; GLfloat cc[4]; glGetIntegerv(GL_VIEWPORT, vp); glGetFloatv(GL_COLOR_CLEAR_VALUE, cc); GLboolean blend = glIsEnabled(GL_BLEND); // prepare common states glViewport(0, 0, m_width, m_height); glEnable(GL_BLEND); RTT m_latlong; m_latlong.create(m_width * 4, m_height * 2); // NOTE: w and h must be equal to make sense GLuint cube_id; glGenTextures(1, &cube_id); glBindTexture(GL_TEXTURE_CUBE_MAP, cube_id); for (GLuint i = 0; i < 6; i++) glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGBA8, m_width, m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); static int faces[]{ GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, // front GL_TEXTURE_CUBE_MAP_NEGATIVE_X, // right GL_TEXTURE_CUBE_MAP_POSITIVE_Z, // back GL_TEXTURE_CUBE_MAP_POSITIVE_X, // left GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, // top GL_TEXTURE_CUBE_MAP_POSITIVE_Y, // bottom }; int progress = 0; int total = 6 + 2; Texture2D face; face.create(m_width, m_height); for (int i = 0; i < 6; i++) { // prepare common states glViewport(0, 0, m_width, m_height); glDisable(GL_BLEND); ShaderManager::use(kShader::TextureBlend); ShaderManager::u_int(kShaderUniform::Tex, 0); ShaderManager::u_int(kShaderUniform::TexA, 1); ShaderManager::u_int(kShaderUniform::TexBG, 2); ShaderManager::u_mat4(kShaderUniform::MVP, glm::ortho(-.5f, .5f, -.5f, .5f, -1.f, 1.f)); m_tmp[i].bindFramebuffer(); // clear transparent not to mess with blending modes m_tmp[i].clear({ 1, 1, 1, 0 }); glActiveTexture(GL_TEXTURE2); face.bind(); m_sampler_bg.bind(0); // nearest m_sampler_mask.bind(1); // linear m_sampler_bg.bind(2); for (auto layer_index : m_order) { if (!m_layers[layer_index].m_visible || m_layers[layer_index].m_opacity == 0.f || !m_layers[layer_index].m_dirty_face[i]) continue; glActiveTexture(GL_TEXTURE2); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, m_width, m_height); ShaderManager::u_int(kShaderUniform::BlendMode, m_layers[layer_index].m_blend_mode); ShaderManager::u_float(kShaderUniform::Alpha, m_layers[layer_index].m_opacity); glActiveTexture(GL_TEXTURE0); m_layers[layer_index].m_rtt[i].bindTexture(); glActiveTexture(GL_TEXTURE1); m_layers[layer_index].m_rtt[i].bindTexture(); m_plane.draw_fill(); m_layers[layer_index].m_rtt[i].unbindTexture(); glActiveTexture(GL_TEXTURE0); m_layers[layer_index].m_rtt[i].unbindTexture(); } glActiveTexture(GL_TEXTURE2); face.unbind(); // now blend with the background glEnable(GL_BLEND); ShaderManager::use(kShader::Texture); ShaderManager::u_int(kShaderUniform::Tex, 0); ShaderManager::u_mat4(kShaderUniform::MVP, glm::ortho(-.5f, .5f, -.5f, .5f, -1.f, 1.f)); m_sampler_mask.bind(0); // linear glActiveTexture(GL_TEXTURE0); face.bind(); // copy the framebuffer before clearing to white glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, m_width, m_height); m_tmp[i].clear({ 1, 1, 1, 1 }); m_plane.draw_fill(); face.unbind(); // copy result to cubemap glBindTexture(GL_TEXTURE_CUBE_MAP, cube_id); glCopyTexImage2D(faces[i], 0, GL_RGBA8, 0, 0, m_width, m_height, 0); glBindTexture(GL_TEXTURE_CUBE_MAP, 0); m_tmp[i].unbindFramebuffer(); progress++; float p = (float)progress / total * 100.f; LOG("progress: %f", p); if (App::I.layout.m_loaded) { pb->m_progress->SetWidthP(p); gl.save(); App::I.async_update(); gl.restore(); } } face.destroy(); //auto data = std::make_unique(m_tmp[0].bytes()); //for (int i = 0; i < 1; i++) //{ // m_tmp[i].readTextureData(data.get()); // static char name[128]; // sprintf(name, "%s/Face%d.png", data_path.c_str(), i); // LOG("writing %s", name); // int ret = stbi_write_png(name, m_tmp[i].getWidth(), m_tmp[i].getHeight(), 4, data.get(), m_tmp[i].stride()); //} glDisable(GL_BLEND); glViewport(0, 0, m_latlong.getWidth(), m_latlong.getHeight()); glActiveTexture(GL_TEXTURE0); m_latlong.bindFramebuffer(); ShaderManager::use(kShader::Equirect); ShaderManager::u_mat4(kShaderUniform::MVP, glm::ortho(-.5f, .5f, -.5f, .5f, -1.f, 1.f)); ShaderManager::u_int(kShaderUniform::Tex, 0); glBindTexture(GL_TEXTURE_CUBE_MAP, cube_id); m_sampler_mask.bind(0); m_plane.draw_fill(); m_sampler_mask.unbind(); glBindTexture(GL_TEXTURE_CUBE_MAP, 0); m_latlong.unbindFramebuffer(); { auto latlong_data = std::make_unique(m_latlong.bytes()); m_latlong.readTextureData(latlong_data.get()); { progress++; float p = (float)progress / total * 100.f; LOG("progress: %f", p); if (App::I.layout.m_loaded) { pb->m_progress->SetWidthP(p); gl.save(); App::I.async_update(); gl.restore(); } } LOG("writing %s", file_path.c_str()); jpge::params params; params.m_quality = 100; bool saved = jpge::compress_image_to_jpeg_file(file_path.c_str(), m_latlong.getWidth(), m_latlong.getHeight(), 4, latlong_data.get(), params); inject_xmp(file_path.c_str()); { progress++; float p = (float)progress / total * 100.f; LOG("progress: %f", p); if (App::I.layout.m_loaded) { pb->m_progress->SetWidthP(p); gl.save(); App::I.async_update(); gl.restore(); } } //int ret = stbi_write_png(name, m_latlong.getWidth(), m_latlong.getHeight(), 4, latlong_data.get(), m_latlong.stride()); #ifdef __IOS__ [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{ NSURL* url = [NSURL fileURLWithPath:[NSString stringWithUTF8String:file_path.c_str()]]; PHAssetChangeRequest *changeRequest = [PHAssetChangeRequest creationRequestForAssetFromImageAtFileURL:url]; changeRequest.creationDate = [NSDate date]; } completionHandler:^(BOOL success, NSError *error) { if (success) { NSLog(@"successfully saved"); } else { NSLog(@"error saving to photos: %@", error); } }]; #endif } glDeleteTextures(1, &cube_id); m_latlong.destroy(); // restore viewport and clear color states blend ? glEnable(GL_BLEND) : glDisable(GL_BLEND); glViewport(vp[0], vp[1], vp[2], vp[3]); glClearColor(cc[0], cc[1], cc[2], cc[3]); glActiveTexture(GL_TEXTURE0); if (App::I.layout.m_loaded) { pb->destroy(); App::I.async_update(); } App::I.async_end(); } void Canvas::inject_xmp(std::string jpg_path) { static const char xmp[] = "http://ns.adobe.com/xap/1.0/\0" R"( equirectangular True 0 0 0 0 0 PanoPainter )"; FILE* fp = fopen(jpg_path.c_str(), "rb"); fseek(fp, 0, SEEK_END); long len = ftell(fp); fseek(fp, 0, SEEK_SET); unsigned char* jpeg_data = (unsigned char*)malloc(len); fread(jpeg_data, len, 1, fp); fclose(fp); fp = fopen(jpg_path.c_str(), "wb"); int i = 0; while (i < len && !(jpeg_data[i] == 0xff && jpeg_data[i + 1] == 0xd8)) i++; i += 2; unsigned char* xmp_section = (unsigned char*)malloc(sizeof(xmp) + 4); xmp_section[0] = 0xff; xmp_section[1] = 0xe1; xmp_section[2] = ((int)sizeof(xmp) + 2) >> 8; xmp_section[3] = ((int)sizeof(xmp) + 2) >> 0; memcpy(xmp_section + 4, xmp, sizeof(xmp)); fwrite(jpeg_data, 1, i, fp); fwrite(xmp_section, 1, sizeof(xmp) + 4, fp); fwrite(jpeg_data + i, 1, len - i, fp); fclose(fp); } void Canvas::export_anim() { if (!App::I.check_license()) return; // save viewport and clear color states GLint vp[4]; GLfloat cc[4]; glGetIntegerv(GL_VIEWPORT, vp); glGetFloatv(GL_COLOR_CLEAR_VALUE, cc); GLboolean blend = glIsEnabled(GL_BLEND); // prepare common states glViewport(0, 0, m_width, m_height); RTT m_latlong; m_latlong.create(m_width * 4, m_height * 2); // NOTE: w and h must be equal to make sense GLuint cube_id; glGenTextures(1, &cube_id); glBindTexture(GL_TEXTURE_CUBE_MAP, cube_id); for (GLuint i = 0; i < 6; i++) glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGBA8, m_width, m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); int faces[]{ GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, // front GL_TEXTURE_CUBE_MAP_NEGATIVE_X, // right GL_TEXTURE_CUBE_MAP_POSITIVE_Z, // back GL_TEXTURE_CUBE_MAP_POSITIVE_X, // left GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, // top GL_TEXTURE_CUBE_MAP_POSITIVE_Y, // bottom }; int seq = 0; for (auto layer_index : m_order) { glViewport(0, 0, m_width, m_height); for (int i = 0; i < 6; i++) { m_tmp[i].bindFramebuffer(); if (seq == 0) { m_tmp[i].clear({ 1, 1, 1, 1 }); ShaderManager::use(kShader::Checkerboard); ShaderManager::u_mat4(kShaderUniform::MVP, glm::ortho(-.5f, .5f, -.5f, .5f, -1.f, 1.f)); m_plane.draw_fill(); glEnable(GL_BLEND); } else { m_tmp[i].clear({ 1, 1, 1, 0 }); glDisable(GL_BLEND); } glActiveTexture(GL_TEXTURE0); ShaderManager::use(kShader::TextureAlpha); ShaderManager::u_float(kShaderUniform::Alpha, 1); ShaderManager::u_int(kShaderUniform::Highlight, false); ShaderManager::u_int(kShaderUniform::Tex, 0); ShaderManager::u_mat4(kShaderUniform::MVP, glm::ortho(-.5f, .5f, -.5f, .5f, -1.f, 1.f)); m_sampler_mask.bind(0); m_layers[layer_index].m_rtt[i].bindTexture(); m_plane.draw_fill(); m_layers[layer_index].m_rtt[i].unbindTexture(); m_sampler_mask.unbind(); // copy result to cubemap glBindTexture(GL_TEXTURE_CUBE_MAP, cube_id); glCopyTexImage2D(faces[i], 0, GL_RGBA8, 0, 0, m_width, m_height, 0); glBindTexture(GL_TEXTURE_CUBE_MAP, 0); m_tmp[i].unbindFramebuffer(); } glViewport(0, 0, m_latlong.getWidth(), m_latlong.getHeight()); glActiveTexture(GL_TEXTURE0); m_latlong.bindFramebuffer(); m_latlong.clear({ 1, 1, 1, 0 }); ShaderManager::use(kShader::Equirect); ShaderManager::u_mat4(kShaderUniform::MVP, glm::ortho(-.5f, .5f, -.5f, .5f, -1.f, 1.f)); ShaderManager::u_int(kShaderUniform::Tex, 0); glBindTexture(GL_TEXTURE_CUBE_MAP, cube_id); glDisable(GL_BLEND); m_sampler_mask.bind(0); m_plane.draw_fill(); m_sampler_mask.unbind(); glBindTexture(GL_TEXTURE_CUBE_MAP, 0); m_latlong.unbindFramebuffer(); { auto latlong_data = std::make_unique(m_latlong.bytes()); m_latlong.readTextureData(latlong_data.get()); static char name[128]; sprintf(name, "%s/latlong-frame%02d.png", App::I.work_path.c_str(), seq); seq++; LOG("writing %s", name); int ret = stbi_write_png(name, m_latlong.getWidth(), m_latlong.getHeight(), 4, latlong_data.get(), m_latlong.stride()); //jpge::params params; //params.m_quality = 100; //bool saved = jpge::compress_image_to_jpeg_file(name, m_latlong.getWidth(), m_latlong.getHeight(), 4, latlong_data.get(), params); } } glDeleteTextures(1, &cube_id); m_latlong.destroy(); // restore viewport and clear color states blend ? glEnable(GL_BLEND) : glDisable(GL_BLEND); glViewport(vp[0], vp[1], vp[2], vp[3]); glClearColor(cc[0], cc[1], cc[2], cc[3]); glActiveTexture(GL_TEXTURE0); } void Canvas::export_cubes() { if (!App::I.check_license()) return; #ifdef __OBJC__ NSMutableArray* files = [NSMutableArray array]; #endif std::array names { "pz", "px", "nz", "nx", "py", "ny" }; const int stride = m_width * 4; auto buffer = std::make_unique(m_width * m_height * 4); auto flipped = std::make_unique(m_width * m_height * 4); for (int layer = 0; layer < m_order.size(); layer++) { for (int plane = 0; plane < 6; plane++) { auto& l = m_layers[m_order[layer]]; l.m_rtt[plane].bindFramebuffer(); glReadPixels(0, 0, m_width, m_height, GL_RGBA, GL_UNSIGNED_BYTE, buffer.get()); l.m_rtt[plane].unbindFramebuffer(); if (plane < 4) { for (int y = 0; y < m_height; y++) { int y_rev = m_height - y - 1; std::copy_n(buffer.get() + y * stride, stride, flipped.get() + y_rev * stride); } std::swap(buffer, flipped); } else { for (int y = 0; y < m_height; y++) { auto src = (glm::u8vec4*)(buffer.get() + y * stride); auto dst = (glm::u8vec4*)(flipped.get() + y * stride); for (int x = 0; x < m_width; x++) { int x_rev = m_width - x - 1; dst[x_rev] = src[x]; } //std::copy_backward(src + stride, src, dst + stride); } std::swap(buffer, flipped); } static char name[128]; sprintf(name, "%s-%02d-%d.png", App::I.work_path.c_str(), layer, plane); int ret = stbi_write_png(name, m_width, m_height, 4, buffer.get(), 0); #ifdef __IOS__ [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{ NSURL* url = [NSURL fileURLWithPath : [NSString stringWithUTF8String : name]]; PHAssetChangeRequest *changeRequest = [PHAssetChangeRequest creationRequestForAssetFromImageAtFileURL : url]; changeRequest.creationDate = [NSDate date]; } completionHandler: ^ (BOOL success, NSError *error) { if (success) { NSLog(@"successfully saved"); } else { NSLog(@"error saving to photos : %@", error); } }]; #endif #ifdef __OBJC__ [files addObject:[NSString stringWithUTF8String : name]]; #endif } } #ifdef __OBJC__ static char name[128]; sprintf(name, "%s.zip", App::I.work_path.c_str()); auto zip_path = [NSString stringWithUTF8String : name]; //[SSZipArchive createZipFileAtPath:zip_path withFilesAtPaths:files]; for (NSString* f : files) [[NSFileManager defaultManager] removeItemAtPath:f error:nil]; #endif } void Canvas::project_save(std::function on_complete) { if (App::I.check_license()) { std::thread t([=] { bool ret = project_save_thread(App::I.doc_path); if (on_complete) on_complete(ret); }); t.detach(); } } void Canvas::project_save(std::string file_path, std::function on_complete) { if (App::I.check_license()) { std::thread t([=] { bool ret = project_save_thread(file_path); if (on_complete) on_complete(ret); }); t.detach(); } } bool Canvas::project_save_thread(std::string file_path) { gl_state gl; // already saved, nothing to do if (!m_unsaved && file_path == App::I.doc_path) return true; // static char name[128]; // sprintf(name, "%s/latlong.ppi", data_path.c_str()); FILE* fp; auto start = file_path.rfind('/') + 1; std::string file_name = file_path.substr(start, file_path.length() - start - strlen(".ppi")); std::string tmp_path = App::I.data_path + '/' + file_name + ".tmp.ppi"; bool use_tmp = false; // check if file already exists if ((fp = fopen(file_path.c_str(), "rb"))) { fclose(fp); // use tmp file for writing use_tmp = true; if (!(fp = fopen(tmp_path.c_str(), "wb"))) { LOG("cannot write tmp project to %s", tmp_path.c_str()); use_tmp = false; } } if (!fp) { // write directly to the new file if (!(fp = fopen(file_path.c_str(), "wb"))) { LOG("cannot write project to %s", file_path.c_str()); return false; } LOG("unsafe mode saving directly to %s", file_path.c_str()); } PPIHeader ppi_header; fwrite(&ppi_header, sizeof(PPIHeader), 1, fp); // load thumbnail App::I.async_start(); Image thumb = thumbnail_generate(ppi_header.thumb_header.width, ppi_header.thumb_header.height); auto pb = std::make_shared(); pb->m_manager = &App::I.layout; pb->init(); pb->create(); pb->loaded(); pb->m_progress->SetWidthP(0); pb->m_title->set_text("Saving Pano Project"); App::I.layout[App::I.main_id]->add_child(pb); App::I.async_update(); App::I.async_end(); thumb.flip(); fwrite(thumb.data(), thumb.size(), 1, fp); fwrite(&m_width, sizeof(int), 1, fp); fwrite(&m_height, sizeof(int), 1, fp); int n_layers = (int)m_layers.size(); fwrite(&n_layers, sizeof(int), 1, fp); int progress = 0; int total = (int)m_layers.size() * 6; for (int i = 0; i < (int)m_layers.size(); i++) { int n_order = m_order[i]; fwrite(&n_order, sizeof(int), 1, fp); float layer_alpha = m_layers[i].m_opacity; fwrite(&layer_alpha, sizeof(float), 1, fp); int name_len = (int)m_layers[i].m_name.size(); fwrite(&name_len, sizeof(int), 1, fp); fwrite(m_layers[i].m_name.data(), name_len, 1, fp); App::I.async_start(); auto snap = m_layers[i].snapshot(); App::I.async_update(); App::I.async_end(); for (int plane_index = 0; plane_index < 6; plane_index++) { int has_data = snap.m_dirty_face[plane_index] ? 1 : 0; fwrite(&has_data, sizeof(int), 1, fp); if (has_data) { glm::ivec4 b = snap.m_dirty_box[plane_index]; glm::vec2 sz = zw(b) - xy(b); int box[4] = { b.x, b.y, b.z, b.w }; fwrite(&box, sizeof(box), 1, fp); std::vector compressed; auto callback = [](void *context, void *data, int size) { std::vector* buffer = static_cast*>(context); buffer->insert(buffer->end(), (uint8_t*)data, (uint8_t*)data + size); }; int ret = stbi_write_png_to_func(callback, &compressed, sz.x, sz.y, 4, snap.image[plane_index].get(), sz.x * 4); int data_size = (int)compressed.size(); fwrite(&data_size, sizeof(int), 1, fp); fwrite(compressed.data(), 1, compressed.size(), fp); } progress++; float p = (float)progress / total * 100.f; App::I.async_start(); pb->m_progress->SetWidthP(p); LOG("progress: %f", p); App::I.async_update(); App::I.async_end(); } } fclose(fp); bool success = false; if (use_tmp) { LOG("project saved tmp to %s", tmp_path.c_str()); LOG("swapping to %s", file_path.c_str()); if (std::remove(file_path.c_str()) == 0) { if (std::rename(tmp_path.c_str(), file_path.c_str()) == 0) { success = true; LOG("tmp file swapped succesfully"); } else { success = false; LOG("tmp file NOT swapped, original removed"); } } else { success = false; LOG("could not remove %s", file_path.c_str()); } } else { success = true; LOG("project saved to %s", file_path.c_str()); } if (success) { m_unsaved = false; m_newdoc = false; } App::I.async_start(); pb->destroy(); App::I.title_update(); App::I.async_update(); App::I.async_end(); return success; } void Canvas::project_open(std::string file_path, std::function on_complete) { std::thread t([=] { bool result = project_open_thread(file_path); if (on_complete) on_complete(result); }); t.detach(); } bool Canvas::project_open_thread(std::string file_path) { FILE* fp = fopen(file_path.c_str(), "rb"); if (!fp) { LOG("cannot write project to %s", file_path.c_str()); return false; // should probably return a bool } PPIHeader ppi_header; fread(&ppi_header, sizeof(PPIHeader), 1, fp); if (!ppi_header.valid()) { LOG("INVALID PPI HEADER"); return false; } gl_state gl; std::shared_ptr pb; if (App::I.layout.m_loaded) { App::I.async_start(); pb = std::make_shared(); pb->m_manager = &App::I.layout; pb->init(); pb->create(); pb->loaded(); pb->m_progress->SetWidthP(0); pb->m_title->set_text("Opening Pano Project"); App::I.layout[App::I.main_id]->add_child(pb); gl.save(); App::I.async_update(); gl.restore(); App::I.async_end(); } // skip thumbnail Image thumb; thumb.width = ppi_header.thumb_header.width; thumb.height = ppi_header.thumb_header.height; thumb.comp = ppi_header.thumb_header.comp; fseek(fp, thumb.size(), SEEK_CUR); fread(&m_width, sizeof(int), 1, fp); fread(&m_height, sizeof(int), 1, fp); int n_layers = 0; fread(&n_layers, sizeof(int), 1, fp); const int bytes = m_width * m_height * 4; Layer::Snapshot snap; snap.create(m_width, m_height); // allocate single data, no box should be bigger int progress = 0; int total = n_layers * 6; App::I.async_start(); for (auto& l : m_layers) l.destroy(); m_layers.clear(); m_order.clear(); //clear_all(); resize(m_width, m_height); App::I.async_end(); std::vector tmp_order; std::vector tmp_layers; for (int i = 0; i < n_layers; i++) { int n_order; fread(&n_order, sizeof(int), 1, fp); float layer_opacity; fread(&layer_opacity, sizeof(float), 1, fp); int name_len; fread(&name_len, sizeof(int), 1, fp); std::string name(name_len, '\0'); fread((char*)name.data(), name_len, 1, fp); snap.clear(); for (int plane_index = 0; plane_index < 6; plane_index++) { int has_data; fread(&has_data, sizeof(int), 1, fp); snap.m_dirty_face[plane_index] = has_data; if (has_data) { int b[4]; fread(&b, sizeof(b), 1, fp); snap.m_dirty_box[plane_index] = glm::vec4(b[0], b[1], b[2], b[3]); glm::vec2 sz = zw(snap.m_dirty_box[plane_index]) - xy(snap.m_dirty_box[plane_index]); int data_size; fread(&data_size, sizeof(int), 1, fp); std::vector compressed(data_size); fread(compressed.data(), 1, data_size, fp); int imgw, imgh, imgc; uint8_t* rgba = stbi_load_from_memory(compressed.data(), data_size, &imgw, &imgh, &imgc, 4); if (rgba) { std::copy(rgba, rgba + (imgw*imgh * 4), snap.image[plane_index].get()); delete rgba; } } progress++; float p = (float)progress / total * 100.f; LOG("progress: %f", p); if (App::I.layout.m_loaded) { App::I.async_start(); pb->m_progress->SetWidthP(p); gl.save(); App::I.async_update(); gl.restore(); App::I.async_end(); } } App::I.async_start(); tmp_layers.emplace_back(); tmp_layers.back().m_opacity = layer_opacity; tmp_layers.back().create(m_width, m_height, name.c_str()); tmp_layers.back().clear({0, 0, 0, 0}); tmp_layers.back().restore(snap); tmp_order.push_back(n_order); App::I.async_end(); } std::swap(tmp_order, m_order); std::swap(tmp_layers, m_layers); fclose(fp); LOG("project restore from %s", file_path.c_str()); m_unsaved = false; m_newdoc = false; if (App::I.layout.m_loaded) { App::I.async_start(); pb->destroy(); gl.save(); App::I.async_update(); gl.restore(); App::I.title_update(); App::I.async_end(); } return true; } Image Canvas::thumbnail_generate(int w, int h) { // save viewport and clear color states GLint vp[4]; GLfloat cc[4]; glGetIntegerv(GL_VIEWPORT, vp); glGetFloatv(GL_COLOR_CLEAR_VALUE, cc); GLboolean blend = glIsEnabled(GL_BLEND); // prepare common states glViewport(0, 0, w, h); RTT fb; fb.create(w, h); fb.bindFramebuffer(); Plane m_face_plane; m_face_plane.create<1>(2, 2); Texture2D blendtex; blendtex.create(w, h); // recalculate because of different aspect ratio than the m_proj matrix glm::mat4 proj = glm::perspective(glm::radians(m_cam_fov), (float)w / (float)h, 0.1f, 1000.f); fb.clear({ 1, 1, 1, 0 }); for (int i = 0; i < 6; i++) { glDisable(GL_BLEND); auto plane_mvp = proj * m_mv * m_plane_transform[i] * glm::translate(glm::vec3(0, 0, -1)); ShaderManager::use(kShader::TextureBlend); ShaderManager::u_int(kShaderUniform::Tex, 0); ShaderManager::u_int(kShaderUniform::TexA, 1); ShaderManager::u_int(kShaderUniform::TexBG, 2); ShaderManager::u_mat4(kShaderUniform::MVP, plane_mvp); glActiveTexture(GL_TEXTURE2); blendtex.bind(); m_sampler_bg.bind(0); // nearest m_sampler_mask.bind(1); // linear m_sampler_bg.bind(2); for (auto layer_index : m_order) { if (!m_layers[layer_index].m_visible || m_layers[layer_index].m_opacity == 0.f || !m_layers[layer_index].m_dirty_face[i]) continue; glActiveTexture(GL_TEXTURE2); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, w, h); ShaderManager::u_int(kShaderUniform::BlendMode, m_layers[layer_index].m_blend_mode); ShaderManager::u_float(kShaderUniform::Alpha, m_layers[layer_index].m_opacity); glActiveTexture(GL_TEXTURE0); m_layers[layer_index].m_rtt[i].bindTexture(); glActiveTexture(GL_TEXTURE1); m_layers[layer_index].m_rtt[i].bindTexture(); m_face_plane.draw_fill(); m_layers[layer_index].m_rtt[i].unbindTexture(); glActiveTexture(GL_TEXTURE0); m_layers[layer_index].m_rtt[i].unbindTexture(); } // copy the framebuffer before clearing to white glActiveTexture(GL_TEXTURE2); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, w, h); // draw the grid ShaderManager::use(kShader::Checkerboard); ShaderManager::u_mat4(kShaderUniform::MVP, plane_mvp); m_face_plane.draw_fill(); // now blend with the background glEnable(GL_BLEND); ShaderManager::use(kShader::Texture); ShaderManager::u_int(kShaderUniform::Tex, 0); ShaderManager::u_mat4(kShaderUniform::MVP, glm::ortho(-.5f, .5f, -.5f, .5f, -1.f, 1.f)); m_sampler_mask.bind(0); // linear glActiveTexture(GL_TEXTURE0); blendtex.bind(); m_plane.draw_fill(); blendtex.unbind(); } fb.unbindFramebuffer(); // read the rendered image Image image; image.create(w, h); fb.readTextureData((uint8_t*)image.data()); fb.destroy(); blendtex.destroy(); // restore viewport and clear color states blend ? glEnable(GL_BLEND) : glDisable(GL_BLEND); glViewport(vp[0], vp[1], vp[2], vp[3]); glClearColor(cc[0], cc[1], cc[2], cc[3]); glActiveTexture(GL_TEXTURE0); return image; } Image Canvas::thumbnail_read(std::string file_path) { // static char name[128]; // sprintf(name, "%s/latlong.ppi", data_path.c_str()); FILE* fp = fopen(file_path.c_str(), "rb"); if (!fp) { LOG("cannot read project %s", file_path.c_str()); return {}; // return empty image } PPIHeader ppi_header; fread(&ppi_header, sizeof(PPIHeader), 1, fp); if (!ppi_header.valid()) return {}; Image thumb; thumb.width = ppi_header.thumb_header.width; thumb.height = ppi_header.thumb_header.height; thumb.comp = ppi_header.thumb_header.comp; thumb.create(); fread((uint8_t*)thumb.data(), thumb.size(), 1, fp); fclose(fp); LOG("project thumbnail read from %s", file_path.c_str()); return thumb; } void Canvas::draw_objects(std::function observer, Layer& layer) { // save viewport and clear color states GLint vp[4]; GLfloat cc[4]; glGetIntegerv(GL_VIEWPORT, vp); glGetFloatv(GL_COLOR_CLEAR_VALUE, cc); GLboolean blend = glIsEnabled(GL_BLEND); // prepare common states glViewport(0, 0, layer.w, layer.h); glDisable(GL_BLEND); GLuint rboID; glGenRenderbuffers(1, &rboID); glBindRenderbuffer(GL_RENDERBUFFER, rboID); glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, layer.w, layer.h); glBindRenderbuffer(GL_RENDERBUFFER, 0); glm::mat4 proj = glm::perspective(glm::radians(90.f), 1.f, .01f, 1000.f); for (int i = 0; i < 6; i++) { glm::mat4 plane_camera = glm::lookAt(glm::vec3(0), m_plane_origin[i], m_plane_tangent[i]); layer.m_rtt[i].bindFramebuffer(); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rboID); observer(plane_camera, proj, i); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, 0); layer.m_rtt[i].unbindFramebuffer(); } glDeleteRenderbuffers(1, &rboID); // restore viewport and clear color states blend ? glEnable(GL_BLEND) : glDisable(GL_BLEND); glViewport(vp[0], vp[1], vp[2], vp[3]); glClearColor(cc[0], cc[1], cc[2], cc[3]); glActiveTexture(GL_TEXTURE0); } void Canvas::draw_objects(std::function observer) { draw_objects(observer, m_layers[m_current_layer_idx]); } void Canvas::project2Dpoints(std::vector& vertices) { for (auto& p : vertices) { glm::vec3 ro, rd, hit_o, hit_d; glm::vec2 hit_fb; int plane_id; if (point_trace(p.pos, ro, rd, hit_o, hit_fb, hit_d, plane_id)) p.pos = glm::vec4(hit_o, 1); } } glm::vec3 Canvas::project2Dpoint(glm::vec2 pt) { glm::vec3 ro, rd, hit_o, hit_d; glm::vec2 hit_fb; int plane_id; if (point_trace(pt, ro, rd, hit_o, hit_fb, hit_d, plane_id)) return glm::vec4(hit_o, 1); return glm::vec3(0); } // return the 2d shape of the faces based on the current camera // this can be used for screen space shapes clipping std::vector Canvas::face_to_shape2D(int plane_index) { static std::array corners{ glm::vec4(-1.f, +1.f, -1.f, 1.f), // A top-left glm::vec4(+1.f, +1.f, -1.f, 1.f), // B top-right glm::vec4(+1.f, -1.f, -1.f, 1.f), // C bottom-right glm::vec4(-1.f, -1.f, -1.f, 1.f), // D bottom-left }; // compute points in camera space std::vector pt_cam; for (auto c : corners) { auto pt_world = m_plane_transform[plane_index] * c; pt_cam.push_back(m_mv * pt_world); } // clip at near plane pt_cam = poly_clip_near(pt_cam, 0.01); // compute windows space std::vector points; for (auto p : pt_cam) { auto pt_clip = m_proj * glm::vec4(p, 1); pt_clip = pt_clip / pt_clip.w; glm::vec2 pt_screen = (glm::vec2(pt_clip) * 0.5f + 0.5f) * zw(m_vp); pt_screen.y = m_vp.w - pt_screen.y - 1; points.push_back(pt_screen); } return points; } void Canvas::push_camera() { m_camera_stack.push(get_camera()); } void Canvas::pop_camera() { if (!m_camera_stack.empty()) { set_camera(m_camera_stack.top()); m_camera_stack.pop(); } } CameraData Canvas::get_camera() { CameraData c; c.m_box = m_box; c.m_mv = m_mv; c.m_pan = m_pan; std::copy_n(m_plane_dir, 6, c.m_plane_dir); std::copy_n(m_plane_unproject, 6, c.m_plane_unproject); c.m_proj = m_proj; c.m_vp = m_vp; return c; } void Canvas::set_camera(const CameraData& c) { m_box = c.m_box; m_mv = c.m_mv; m_pan = c.m_pan; std::copy_n(c.m_plane_dir, 6, m_plane_dir); std::copy_n(c.m_plane_unproject, 6, m_plane_unproject); m_proj = c.m_proj; m_vp = c.m_vp; } std::vector Canvas::triangulate_simple(const std::vector& vertices) { std::vector ret; std::vector points(vertices.size()); std::vector points_ptr(vertices.size()); for (size_t i = 0; i < vertices.size(); i++) { points[i] = { vertices[i].pos.x, vertices[i].pos.y }; points_ptr[i] = &points[i]; } auto cdt = std::make_unique(points_ptr); cdt->Triangulate(); auto tr = cdt->GetTriangles(); for (auto t : tr) { vertex_t vertex; for (int i = 0; i < 3; i++) { auto index = std::distance(points.data(), t->GetPoint(i)); ret.push_back(vertices[index]); } } return ret; } std::vector Canvas::triangulate(const std::vector& points) { std::vector tmp; for (auto pt : points) tmp.push_back(pt); return triangulate(tmp); } std::vector Canvas::triangulate(const std::vector& points) { struct Segment { const vertex_t* a = nullptr; const vertex_t* b = nullptr; Segment* prev = nullptr; std::shared_ptr next = nullptr; bool end = false; }; std::vector> new_points; std::shared_ptr root = std::make_shared(); std::shared_ptr node = root; for (int i = 0; i < points.size(); i++) { node->a = &points[i]; if (i == points.size() - 1) { node->b = &points[0]; node->next = root; node->end = true; root->prev = node.get(); } else { node->b = &points[i + 1]; node->next = std::make_shared(); node->next->prev = node.get(); } node = node->next; } node = root; std::stack> todo; std::vector> polys; todo.push(root); while (!todo.empty()) { node = todo.top(); todo.pop(); polys.push_back(node); while (node) { std::shared_ptr other = node->next; while (other) { if (node->a->pos == other->a->pos || node->a->pos == other->b->pos || node->b->pos == other->a->pos || node->b->pos == other->b->pos) { other = other->end ? nullptr : other->next; continue; } glm::vec2 s0a(node->a->pos); glm::vec2 s0b(node->b->pos); glm::vec2 s1a(other->a->pos); glm::vec2 s1b(other->b->pos); glm::vec2 hit_uv; glm::vec2 is; if (segments_intersect(s0a, s0b, s1a, s1b, is, hit_uv)) { new_points.push_back(std::make_unique()); auto p = new_points.back().get(); p->pos = glm::lerp(node->a->pos, node->b->pos, hit_uv.x); p->uvs = glm::lerp(node->a->uvs, node->b->uvs, hit_uv.x); p->uvs2 = glm::lerp(node->a->uvs2, node->b->uvs2, hit_uv.x); auto poly_root = std::make_shared(); poly_root->a = p; poly_root->b = node->b; poly_root->next = node->next; todo.push(poly_root); other->a = p; node->b = p; auto poly_end = std::make_shared(); poly_end->a = other->prev->b; poly_end->b = p; poly_end->end = true; poly_end->prev = other->prev; other->prev->next = poly_end; other->prev = node.get(); node->next = other; break; } other = other->end ? nullptr : other->next; } node = node->end ? nullptr : node->next; } } std::vector ret; for (auto poly : polys) { std::vector outline; node = poly; while (node) { if (outline.empty() || // if empty insert right away outline.back() != node->a && // insert only if different than the last post (outline.front() != node->a || !node->end)) // if is the end check against the first one { outline.push_back(node->a); } auto current = node; node = node->end ? nullptr : node->next; current->next = nullptr; } if (outline.size() > 2) { std::vector points(outline.size()); std::vector points_ptr(outline.size()); for (size_t i = 0; i < outline.size(); i++) { points[i] = { outline[i]->pos.x, outline[i]->pos.y }; points_ptr[i] = &points[i]; } p2t::CDT* cdt = new p2t::CDT(points_ptr); // TODO: remove duplicates cdt->Triangulate(); auto tr = cdt->GetTriangles(); for (auto t : tr) { for (int i = 0; i < 3; i++) { auto index = std::distance(points.data(), t->GetPoint(i)); ret.push_back(*outline[index]); } } } } return ret; } /////////////////////////////////////////////////////////////////////////////////////////// void Layer::destroy() { for (int i = 0; i < 6; i++) m_rtt[i].destroy(); } void Layer::restore(const Snapshot& snap) { //clear({ 0, 0, 0, 0 }); for (int i = 0; i < 6; i++) { if (snap.image[i] == nullptr || snap.m_dirty_face[i] == false) continue; m_dirty_box[i] = snap.m_dirty_box[i]; m_dirty_face[i] = snap.m_dirty_face[i]; // TODO: this should not be recreated here! // Sorry I messed up with this, // it's just a quick fix DON'T SHIP!! //m_rtt[i].recreate(); m_rtt[i].bindTexture(); glm::vec2 box_sz = zw(m_dirty_box[i]) - xy(m_dirty_box[i]); glTexSubImage2D(GL_TEXTURE_2D, 0, m_dirty_box[i].x, m_dirty_box[i].y, box_sz.x, box_sz.y, GL_RGBA, GL_UNSIGNED_BYTE, snap.image[i].get()); m_rtt[i].unbindTexture(); LOG("restore face %d - %d bytes (%dx%d)", i, (int)box_sz.x * (int)box_sz.y * 4, (int)box_sz.x, (int)box_sz.y); } } Layer::Snapshot Layer::snapshot() { Snapshot snap; static int counter = 0; glBindTexture(GL_TEXTURE_2D, 0); //glBindFramebuffer(GL_FRAMEBUFFER, 0); for (int i = 0; i < 6; i++) { snap.m_dirty_box[i] = m_dirty_box[i]; snap.m_dirty_face[i] = m_dirty_face[i]; if (!m_dirty_face[i]) continue; snap.image[i] = std::make_unique(m_rtt[i].bytes()); glReadBuffer(GL_BACK); m_rtt[i].bindFramebuffer(); glm::vec2 box_sz = zw(m_dirty_box[i]) - xy(m_dirty_box[i]); glReadPixels(m_dirty_box[i].x, m_dirty_box[i].y, box_sz.x, box_sz.y, GL_RGBA, GL_UNSIGNED_BYTE, snap.image[i].get()); m_rtt[i].unbindFramebuffer(); //glReadBuffer(GL_NONE); } counter++; return snap; } void Layer::clear(const glm::vec4& c) { // push clear color state GLfloat cc[4]; glGetFloatv(GL_COLOR_CLEAR_VALUE, cc); glClearColor(c.r, c.g, c.b, c.a); bool erase = (c.a == 0.f); for (int i = 0; i < 6; i++) { m_rtt[i].bindFramebuffer(); glClear(GL_COLOR_BUFFER_BIT); m_rtt[i].unbindFramebuffer(); if (erase) { m_dirty_box[i] = glm::vec4(w, h, 0, 0); // reset bounding box m_dirty_face[i] = false; } else { m_dirty_box[i] = glm::vec4(0, 0, w, h); // reset bounding box m_dirty_face[i] = true; } } // restore clear color state glClearColor(cc[0], cc[1], cc[2], cc[3]); } bool Layer::create(int width, int height, std::string name) { m_name = name; w = width; h = height; for (int i = 0; i < 6; i++) { m_rtt[i].create(width, height); m_rtt[i].bindFramebuffer(); m_rtt[i].clear(); m_rtt[i].unbindFramebuffer(); m_dirty_box[i] = glm::vec4(w, h, 0, 0); // reset bounding box m_dirty_face[i] = false; } return true; } void Layer::resize(int width, int height) { glm::vec2 ratio = glm::vec2(width, height) / glm::vec2(w, h); w = width; h = height; for (int i = 0; i < 6; i++) { m_rtt[i].resize(width, height); m_dirty_box[i] = m_dirty_box[i] * glm::vec4(ratio, ratio); //m_dirty_face[i] = true; } }