diff --git a/src/canvas.cpp b/src/canvas.cpp index 83a5d1c..d9cc904 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -272,77 +272,149 @@ void Canvas::stroke_draw_mix(const glm::vec2& bb_min, const glm::vec2& bb_sz) gl.restore(); } -void Canvas::stroke_draw() + +std::array, 6> Canvas::stroke_draw_project(std::array& B) { - if (!(m_current_stroke && m_current_stroke->has_sample())) + // intersect P with the current face to clip diverging points from the plane + auto unp_vp = zw(m_box); + auto unp_inv = glm::inverse(m_proj * m_mv); + std::array, 6> ret; + for (int i = 0; i < 6; i++) { - //stroke_draw_mix({ 0,0 }, { m_mixer.getWidth(), m_mixer.getHeight() }); - return; + auto P = poly_intersect(std::data(B), std::data(B) + 4, m_plane_shape[i]); + glm::mat4 plane_camera = glm::lookAt(m_plane_origin[i], m_plane_normal[i], m_plane_tangent[i]); + int intersections = 0; + 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::vec4 plane_local = plane_camera * glm::vec4(hit, 1); + + //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; + intersections++; + } + else + { + break; + } + } + if (intersections >= 3) + ret[i] = P; + } + return ret; +} + +void Canvas::stroke_draw_samples(int i, std::vector& P) +{ + if (!ShaderManager::ext_framebuffer_fetch) + { + glActiveTexture(GL_TEXTURE1); + m_tex[i].bind(); // bg, copy of framebuffer (copied before drawing) } - m_dirty = true; + 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; - GLint vp[4]; - GLfloat cc[4]; - glGetIntegerv(GL_VIEWPORT, vp); - glGetFloatv(GL_COLOR_CLEAR_VALUE, cc); - - float zoom = m_node->root()->m_zoom; + 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)); + if (!ShaderManager::ext_framebuffer_fetch) + { + glCopyTexSubImage2D(GL_TEXTURE_2D, 0, tex_pos.x, tex_pos.y, + tex_pos.x, tex_pos.y, tex_sz.x, tex_sz.y); + } + 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))); + + 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(); + + if (!ShaderManager::ext_framebuffer_fetch) + { + glActiveTexture(GL_TEXTURE1); + m_tex[i].unbind(); + } +} + +std::vector Canvas::stroke_draw_compute() +{ + std::vector ret; const auto& m_brush = m_current_stroke->m_brush; auto samples = m_current_stroke->compute_samples(); - auto& tex = *m_brush->m_tip_texture; - auto ortho_proj = glm::ortho(0.f, (float)m_width, 0.f, (float)m_height, -1.f, 1.f); - - std::vector B{ + std::array 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); - if (m_brush->m_stencil_texture) - m_brush->m_stencil_texture->bind(); - else - glBindTexture(GL_TEXTURE_2D, 0); - - glActiveTexture(GL_TEXTURE3); - m_mixer.bindTexture(); - - glDisable(GL_BLEND); - ShaderManager::use(kShader::Stroke); - ShaderManager::u_int(kShaderUniform::Tex, 0); // brush - if (!ShaderManager::ext_framebuffer_fetch) - ShaderManager::u_int(kShaderUniform::TexBG, 1); // bg - 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 (!s.valid()) continue; + ret.emplace_back(); + auto& f = ret.back(); + if (m_mixer_idle) { m_mixer_sample = s; @@ -377,154 +449,93 @@ void Canvas::stroke_draw() B[j].pos = glm::vec4(xy(s.pos) + off[j] * glm::orientate2(-s.angle), 1, 1); B[j].uvs2 = p / mixer_sz; } - - if (m_brush->m_tip_mix > 0.f) - { - stroke_draw_mix(mixer_bb_min, mixer_bb_max - mixer_bb_min); - } - - 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; - - // intersect P with the current face to clip diverging points from the plane - auto P = poly_intersect(B, m_plane_shape[i]); - - glm::mat4 plane_camera = glm::lookAt(m_plane_origin[i], m_plane_normal[i], m_plane_tangent[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::vec4 plane_local = plane_camera * glm::vec4(hit, 1); - - //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) - continue; - - m_dirty_face[i] = true; - - m_tmp[i].bindFramebuffer(); - - if (!ShaderManager::ext_framebuffer_fetch) - { - 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)); - if (!ShaderManager::ext_framebuffer_fetch) - { - glCopyTexSubImage2D(GL_TEXTURE_2D, 0, tex_pos.x, tex_pos.y, - tex_pos.x, tex_pos.y, tex_sz.x, tex_sz.y); - } - - 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(); - - if (!ShaderManager::ext_framebuffer_fetch) - { - glActiveTexture(GL_TEXTURE1); - m_tex[i].unbind(); - } - - m_tmp[i].unbindFramebuffer(); - } + + f.m_mixer_rect = { mixer_bb_min, mixer_bb_max - mixer_bb_min }; + f.col = glm::vec4(s.col, m_brush->m_tip_color.a); + f.pressure = s.flow; + f.shapes = stroke_draw_project(B); m_mixer_sample = s; } + return ret; +} + +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); + const auto& m_brush = m_current_stroke->m_brush; + auto& tex = *m_brush->m_tip_texture; + auto ortho_proj = glm::ortho(0.f, (float)m_width, 0.f, (float)m_height, -1.f, 1.f); + + 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); + if (m_brush->m_stencil_texture) + m_brush->m_stencil_texture->bind(); + else + glBindTexture(GL_TEXTURE_2D, 0); + + glActiveTexture(GL_TEXTURE3); + m_mixer.bindTexture(); + glDisable(GL_BLEND); + ShaderManager::use(kShader::Stroke); + ShaderManager::u_int(kShaderUniform::Tex, 0); // brush + if (!ShaderManager::ext_framebuffer_fetch) + ShaderManager::u_int(kShaderUniform::TexBG, 1); // bg + 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); + ShaderManager::u_mat4(kShaderUniform::MVP, ortho_proj); + + auto frames = stroke_draw_compute(); + for (auto& f : frames) + { + if (m_brush->m_tip_mix > 0.f) + { + stroke_draw_mix(xy(f.m_mixer_rect), zw(f.m_mixer_rect)); + } + + //ShaderManager::use(kShader::Stroke); + ShaderManager::u_vec4(kShaderUniform::Col, f.col); + ShaderManager::u_float(kShaderUniform::Alpha, f.pressure); + for (int i = 0; i < 6; i++) + { + auto& P = f.shapes[i]; + if (P.size() < 3) + continue; + m_dirty_face[i] = true; + m_tmp[i].bindFramebuffer(); + stroke_draw_samples(i, P); + m_tmp[i].unbindFramebuffer(); + } + } glActiveTexture(GL_TEXTURE2); if (m_brush->m_stencil_texture) diff --git a/src/canvas.h b/src/canvas.h index e6bd564..db85bc2 100644 --- a/src/canvas.h +++ b/src/canvas.h @@ -126,6 +126,14 @@ struct PPIHeader class Canvas { + struct StrokeFrame + { + glm::vec4 col; + float pressure; + std::array, 6> shapes; + glm::vec4 m_mixer_rect; + }; + public: Plane m_plane; Plane m_plane_brush; @@ -224,6 +232,9 @@ public: void stroke_start(glm::vec3 point, float pressure, const std::shared_ptr& brush); void stroke_update(glm::vec3 point, float pressure); void stroke_draw_mix(const glm::vec2& bb_min, const glm::vec2& bb_sz); + std::array, 6> stroke_draw_project(std::array& B); + void stroke_draw_samples(int i, std::vector& P); + std::vector stroke_draw_compute(); void stroke_draw(); void stroke_end(); void stroke_cancel(); diff --git a/src/util.cpp b/src/util.cpp index e7ed2e0..8ae1895 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -99,11 +99,11 @@ bool point_side(glm::vec2 a, glm::vec2 b, glm::vec2 p) // a is a convex polygon // a and b are a list of non repeating points // returns the resulting intersection polygon points -std::vector poly_intersect(const std::vector& poly, const std::vector& clip) +std::vector poly_intersect(const vertex_t* poly_begin, const vertex_t* poly_end, const std::vector& clip) { // implementing the Sutherland-Hodgman algorithm // see https://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm - std::vector ret = poly; + std::vector ret(poly_begin, poly_end); for (int i = 0; i < clip.size(); i++) { std::vector tmp; diff --git a/src/util.h b/src/util.h index 48332a1..c1efd59 100644 --- a/src/util.h +++ b/src/util.h @@ -53,7 +53,7 @@ bool ray_intersect(glm::vec3 ray_origin, glm::vec3 ray_dir, glm::vec3 plane_orig bool segments_intersect(const glm::vec2& p0a, const glm::vec2& p0b, const glm::vec2& p1a, const glm::vec2& p1b, glm::vec2& out_pt, glm::vec2& out_hit_uv); bool point_side(glm::vec2 a, glm::vec2 b, glm::vec2 p); -std::vector poly_intersect(const std::vector& poly, const std::vector& clip); +std::vector poly_intersect(const vertex_t* poly_begin, const vertex_t* poly_end, const std::vector& clip); std::vector poly_intersect(const std::vector& poly, const std::vector& clip); std::vector poly_clip_near(const std::vector& poly, float near_plane_distance); glm::vec4 rand_color();