2819 lines
96 KiB
C++
2819 lines
96 KiB
C++
#include "pch.h"
|
|
#include "log.h"
|
|
#include "canvas.h"
|
|
#include "app.h"
|
|
#include "texture.h"
|
|
#include "node_progress_bar.h"
|
|
#include <thread>
|
|
#include <algorithm>
|
|
#include <numeric>
|
|
|
|
#ifdef __APPLE__
|
|
#include <Foundation/Foundation.h>
|
|
#import "objc_utils.h"
|
|
#elif __WEB__
|
|
void webgl_sync();
|
|
#endif
|
|
|
|
|
|
Canvas* Canvas::I;
|
|
std::vector<CanvasMode*> Canvas::modes[] = {
|
|
{ new CanvasModePen, new CanvasModeBasicCamera }, // brush
|
|
{ new CanvasModePen, new CanvasModeBasicCamera }, // eraser
|
|
{ new CanvasModeLine, new CanvasModeBasicCamera }, // line
|
|
{ new CanvasModeCamera, new CanvasModeBasicCamera }, // parallax
|
|
{ new CanvasModeGrid, new CanvasModeBasicCamera }, // grids
|
|
{ new CanvasModeTransform, new CanvasModeBasicCamera }, // import
|
|
{ new CanvasModeTransform, new CanvasModeBasicCamera }, // cut
|
|
{ new CanvasModeTransform, new CanvasModeBasicCamera }, // copy
|
|
{ new CanvasModeFill, new CanvasModeBasicCamera }, // fill
|
|
{ new CanvasModeMaskFree, new CanvasModeBasicCamera }, // mask-free
|
|
{ new CanvasModeMaskLine, new CanvasModeBasicCamera }, // mask-poly
|
|
{ new CanvasModeFloodFill, new CanvasModeBasicCamera }, // flood-fill
|
|
};
|
|
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;
|
|
|
|
App::I->render_task([this, plane]
|
|
{
|
|
std::array<bool, 6> faces{ false };
|
|
faces[plane] = true;
|
|
draw_merge(true, faces);
|
|
|
|
int i = plane;
|
|
m_layers_merge.rtt(i).bindFramebuffer();
|
|
if (!m_pick_data[plane])
|
|
m_pick_data[plane] = std::make_unique<glm::u8vec4[]>(m_width * m_height);
|
|
glReadPixels(0, 0, m_width, m_height, GL_RGBA, GL_UNSIGNED_BYTE, m_pick_data[plane].get());
|
|
m_layers_merge.rtt(i).unbindFramebuffer();
|
|
});
|
|
|
|
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}*/)
|
|
{
|
|
auto a = new ActionLayerClear;
|
|
a->m_layer = m_layers[m_current_layer_idx];
|
|
a->m_frame = layer().m_frame_index;
|
|
a->m_snap = std::make_shared<Layer::Snapshot>(a->m_layer->snapshot());
|
|
a->m_color = c;
|
|
ActionManager::add(a);
|
|
|
|
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::stroke_end()
|
|
{
|
|
if (!m_current_stroke)
|
|
return;
|
|
if (m_current_stroke->has_sample())
|
|
{
|
|
m_commit_delayed = true;
|
|
}
|
|
else
|
|
{
|
|
m_show_tmp = false;
|
|
stroke_commit();
|
|
m_current_stroke = nullptr;
|
|
}
|
|
}
|
|
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)
|
|
{
|
|
gl_state gl;
|
|
gl.save();
|
|
|
|
m_mixer.bindFramebuffer();
|
|
|
|
glViewport(0, 0, m_mixer.getWidth(), m_mixer.getHeight());
|
|
glDisable(GL_DEPTH_TEST);
|
|
glEnable(GL_SCISSOR_TEST);
|
|
glDisable(GL_BLEND);
|
|
|
|
glScissor(bb_min.x, bb_min.y, bb_sz.x, bb_sz.y);
|
|
|
|
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]->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));
|
|
|
|
m_sampler.bind(0);
|
|
m_sampler.bind(1);
|
|
m_sampler.bind(2);
|
|
const auto& b = m_current_stroke->m_brush;
|
|
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::TexPattern, 3);
|
|
ShaderManager::u_float(kShaderUniform::Alpha, 1);
|
|
ShaderManager::u_int(kShaderUniform::Lock, false/*m_layers[layer_index]->m_alpha_locked*/);
|
|
ShaderManager::u_int(kShaderUniform::Mask, false/*m_smask_active*/);
|
|
ShaderManager::u_int(kShaderUniform::UseFragcoord, false);
|
|
ShaderManager::u_int(kShaderUniform::UseDual, false);
|
|
ShaderManager::u_int(kShaderUniform::UsePattern, false);
|
|
ShaderManager::u_int(kShaderUniform::BlendMode, b->m_blend_mode);
|
|
ShaderManager::u_mat4(kShaderUniform::MVP, plane_mvp_z);
|
|
glActiveTexture(GL_TEXTURE0);
|
|
m_layers[layer_index]->rtt(plane_index).bindTexture();
|
|
glActiveTexture(GL_TEXTURE1);
|
|
m_tmp[plane_index].bindTexture();
|
|
glActiveTexture(GL_TEXTURE2);
|
|
m_smask.rtt(plane_index).bindTexture();
|
|
m_node->m_face_plane.draw_fill();
|
|
m_smask.rtt(plane_index).unbindTexture();
|
|
glActiveTexture(GL_TEXTURE1);
|
|
m_tmp[plane_index].unbindTexture();
|
|
glActiveTexture(GL_TEXTURE0);
|
|
m_layers[layer_index]->rtt(plane_index).unbindTexture();
|
|
}
|
|
m_sampler.unbind();
|
|
m_mixer.unbindFramebuffer();
|
|
|
|
gl.restore();
|
|
}
|
|
|
|
std::array<std::vector<vertex_t>, 6> Canvas::stroke_draw_project(std::array<vertex_t, 4>& B, bool project_3d /*= false*/, glm::mat4 mv /*= glm::mat4(1)*/) const
|
|
{
|
|
// intersect P with the current face to clip diverging points from the plane
|
|
const auto unp_vp = zw(m_box);
|
|
const auto unp_inv = glm::inverse(m_proj * m_mv);
|
|
std::array<std::vector<vertex_t>, 6> ret;
|
|
for (int i = 0; i < 6; i++)
|
|
{
|
|
struct ray_t {
|
|
glm::vec3 o;
|
|
glm::vec3 d;
|
|
vertex_t v;
|
|
ray_t(glm::vec3 o, glm::vec3 d, vertex_t v) : o(o), d(d), v(v) { }
|
|
};
|
|
std::vector<ray_t> rays;
|
|
if (project_3d)
|
|
{
|
|
rays.reserve(B.size());
|
|
for (auto const& b : B)
|
|
rays.emplace_back(glm::vec3(0), b.pos, b);
|
|
}
|
|
else
|
|
{
|
|
auto P = poly_intersect(B.data(), B.data() + 4, m_plane_shape[i]);
|
|
rays.reserve(P.size());
|
|
for (auto const& p : P)
|
|
{
|
|
glm::vec3 ray_origin, ray_dir;
|
|
auto clip_space = glm::vec2(p.pos.x, unp_vp.y - p.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);
|
|
rays.emplace_back(ray_origin, ray_dir, p);
|
|
}
|
|
}
|
|
glm::mat4 plane_camera = glm::lookAt(m_plane_origin[i], m_plane_normal[i], m_plane_tangent[i]);
|
|
std::vector<vertex_t> face_ret;
|
|
face_ret.reserve(rays.size());
|
|
for (auto const& r : rays)
|
|
{
|
|
glm::vec3 hit;
|
|
float hit_t;
|
|
if (ray_intersect(r.o, r.d, 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);
|
|
vertex_t v;
|
|
v.pos.x = -(plane_local.x * 0.5f - 0.5f) * m_width;
|
|
v.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 = mv * glm::vec4(hit, 1);
|
|
v.pos.z = 0;
|
|
v.pos.w = hit_cam.z;
|
|
v.uvs = r.v.uvs * hit_cam.z;
|
|
v.uvs2 = r.v.uvs2 * hit_cam.z;
|
|
face_ret.emplace_back(v);
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
if (face_ret.size() >= 3)
|
|
ret[i] = std::move(face_ret);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
glm::vec4 Canvas::stroke_draw_samples(int i, std::vector<vertex_t>& P)
|
|
{
|
|
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);
|
|
}
|
|
|
|
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(), (int)P.size());
|
|
}
|
|
else
|
|
{
|
|
P = triangulate_simple(P);
|
|
m_brush_shape.update_vertices(P.data(), (int)P.size());
|
|
}
|
|
m_brush_shape.draw_fill();
|
|
|
|
if (!ShaderManager::ext_framebuffer_fetch)
|
|
{
|
|
glActiveTexture(GL_TEXTURE1);
|
|
m_tex[i].unbind();
|
|
}
|
|
|
|
return glm::vec4(tex_pos, tex_pos + tex_sz);
|
|
}
|
|
|
|
std::vector<Canvas::StrokeFrame> Canvas::stroke_draw_compute(Stroke& stroke) const
|
|
{
|
|
std::vector<StrokeFrame> ret;
|
|
StrokeSample prev = stroke.m_prev_sample;
|
|
auto samples = stroke.compute_samples();
|
|
std::array<vertex_t, 4> 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} },
|
|
};
|
|
for (const auto& s : samples)
|
|
{
|
|
if (!s.valid())
|
|
continue;
|
|
|
|
ret.emplace_back();
|
|
auto& f = ret.back();
|
|
|
|
glm::vec2 dx_mix(prev.size * 0.5f, 0), dy_mix(0, prev.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
|
|
};
|
|
|
|
glm::vec2 mixer_sz(App::I->width, App::I->height);
|
|
glm::vec2 mixer_bb_min(mixer_sz);
|
|
glm::vec2 mixer_bb_max(0, 0);
|
|
for (int j = 0; j < 4; j++)
|
|
{
|
|
auto p = (xy(prev.pos) + App::I->zoom * s.scale * off_mix[j] * glm::orientate2(-s.angle));
|
|
mixer_bb_min = glm::max({ 0, 0 }, glm::min(mixer_bb_min, p));
|
|
mixer_bb_max = glm::min(mixer_sz, glm::max(mixer_bb_max, p));
|
|
|
|
if (s.pos.z == 0.f)
|
|
{
|
|
B[j].pos = glm::vec4(xy(s.pos) + App::I->zoom * s.scale * off[j] * glm::orientate2(-s.angle) - glm::vec2(0, 1), 1, 1);
|
|
}
|
|
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);
|
|
B[j].pos = glm::vec4(s.pos + off_3d, 1.f);
|
|
}
|
|
B[j].uvs2 = p / mixer_sz;
|
|
}
|
|
|
|
f.m_mixer_rect = glm::vec4(glm::floor(mixer_bb_min), glm::ceil(mixer_bb_max - mixer_bb_min)) / App::I->zoom;
|
|
f.col = glm::vec4(s.col, 1);
|
|
f.flow = s.flow;
|
|
f.opacity = s.opacity;
|
|
if (s.pos.z == 0.f)
|
|
{
|
|
f.shapes = stroke_draw_project(B, false, m_mv);
|
|
}
|
|
else
|
|
{
|
|
auto m = glm::lookAt({ 0, 0, 0 }, s.pos, { 0, 1, 0 });
|
|
f.shapes = stroke_draw_project(B, true, m);
|
|
}
|
|
|
|
prev = 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;
|
|
std::array<bool, 6> merge_faces;
|
|
|
|
GLint vp[4];
|
|
GLfloat cc[4];
|
|
glGetIntegerv(GL_VIEWPORT, vp);
|
|
glGetFloatv(GL_COLOR_CLEAR_VALUE, cc);
|
|
|
|
const auto& brush = m_current_stroke->m_brush;
|
|
const auto& dual_brush = m_dual_stroke->m_brush;
|
|
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);
|
|
|
|
m_sampler_brush.bind(0);
|
|
m_sampler_nearest.bind(1);
|
|
m_sampler_stencil.bind(2);
|
|
m_sampler.bind(3);
|
|
//m_sampler_linear.bind(5);
|
|
|
|
glm::vec2 patt_scale = glm::vec2(brush->m_pattern_scale);
|
|
if (brush->m_pattern_flipx) patt_scale.x *= -1.f;
|
|
if (brush->m_pattern_flipy) patt_scale.y *= -1.f;
|
|
|
|
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::TexPattern, 2); // pattern
|
|
ShaderManager::u_int(kShaderUniform::TexMix, 3); // mixer
|
|
//ShaderManager::u_int(kShaderUniform::TexMixA, 4); // mixer
|
|
ShaderManager::u_float(kShaderUniform::Opacity, brush->m_tip_opacity);
|
|
ShaderManager::u_vec2(kShaderUniform::Resolution, { m_width, m_height });
|
|
ShaderManager::u_vec2(kShaderUniform::PatternScale, patt_scale);
|
|
ShaderManager::u_float(kShaderUniform::PatternInvert, brush->m_pattern_invert);
|
|
ShaderManager::u_float(kShaderUniform::PatternBright, brush->m_pattern_brightness);
|
|
ShaderManager::u_float(kShaderUniform::PatternContrast, brush->m_pattern_contrast);
|
|
ShaderManager::u_float(kShaderUniform::PatternDepth, brush->m_pattern_depth);
|
|
ShaderManager::u_int(kShaderUniform::PatternBlendMode, brush->m_pattern_blend_mode);
|
|
ShaderManager::u_vec2(kShaderUniform::PatternOffset, m_pattern_offset);
|
|
ShaderManager::u_int(kShaderUniform::UsePattern, brush->m_pattern_enabled && brush->m_pattern_eachsample);
|
|
ShaderManager::u_float(kShaderUniform::MixAlpha, brush->m_tip_mix);
|
|
ShaderManager::u_float(kShaderUniform::Wet, brush->m_tip_wet);
|
|
ShaderManager::u_float(kShaderUniform::Noise, brush->m_tip_noise);
|
|
ShaderManager::u_mat4(kShaderUniform::MVP, ortho_proj);
|
|
|
|
// DRAW MAIN BRUSH
|
|
|
|
glActiveTexture(GL_TEXTURE0);
|
|
brush->m_tip_texture->bind();
|
|
glActiveTexture(GL_TEXTURE2);
|
|
brush->m_pattern_texture ?
|
|
brush->m_pattern_texture->bind() :
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
|
glActiveTexture(GL_TEXTURE3);
|
|
m_mixer.bindTexture();
|
|
|
|
auto frames = stroke_draw_compute(*m_current_stroke);
|
|
|
|
std::array<glm::vec4, 6> box_face = SIXPLETTE(glm::vec4(m_size, 0, 0));
|
|
std::array<bool, 6> box_dirty = SIXPLETTE(false);
|
|
glm::vec4 pad_color;
|
|
for (auto& f : frames)
|
|
{
|
|
if (brush->m_tip_mix > 0.f)
|
|
{
|
|
stroke_draw_mix(xy(f.m_mixer_rect), zw(f.m_mixer_rect));
|
|
}
|
|
|
|
for (int i = 0; i < 6; i++)
|
|
{
|
|
auto& P = f.shapes[i];
|
|
if (P.size() < 3)
|
|
continue;
|
|
m_dirty_face[i] = true;
|
|
merge_faces[i] = true;
|
|
box_dirty[i] = true;
|
|
|
|
m_tmp[i].bindFramebuffer();
|
|
|
|
ShaderManager::use(kShader::Stroke);
|
|
ShaderManager::u_vec4(kShaderUniform::Col, f.col);
|
|
ShaderManager::u_float(kShaderUniform::Alpha, f.flow);
|
|
ShaderManager::u_float(kShaderUniform::Opacity, f.opacity);
|
|
auto box_sample = stroke_draw_samples(i, P);
|
|
|
|
m_tmp[i].unbindFramebuffer();
|
|
|
|
m_dirty_box[i] = glm::clamp(box_union(m_dirty_box[i], box_sample), glm::vec4(0), glm::vec4(m_width));
|
|
box_face[i] = box_union(box_face[i], box_sample);
|
|
// TODO: maybe average color?
|
|
pad_color = f.col;
|
|
}
|
|
}
|
|
|
|
glActiveTexture(GL_TEXTURE3);
|
|
m_mixer.unbindTexture();
|
|
glActiveTexture(GL_TEXTURE0);
|
|
brush->m_tip_texture->unbind();
|
|
|
|
// pad stroke
|
|
// In order to mitigate color bleeding at the edge of shapes in transparent layers
|
|
// we need to fill the area around the stroke with the same color because by default
|
|
// the transparent area may have black or other undefined color.
|
|
// This step is only useful for previewing the stroke because on commit the dilate
|
|
// algorithm fixes this issue.
|
|
// NOTE: at the moment this works on the whole canvas, but it can be optimized
|
|
// to only affect the current dirty box. In this case it may be necessary to do this
|
|
// work on documents that doesn't have the padding, so on document loading.
|
|
ShaderManager::use(kShader::StrokePad);
|
|
ShaderManager::u_vec4(kShaderUniform::Col, pad_color);
|
|
if (!ShaderManager::ext_framebuffer_fetch)
|
|
{
|
|
glActiveTexture(GL_TEXTURE1);
|
|
ShaderManager::u_int(kShaderUniform::TexBG, 1);
|
|
}
|
|
for (int i = 0; i < 6; i++)
|
|
{
|
|
if (!box_dirty[i])
|
|
continue;
|
|
const auto& b = box_face[i];
|
|
glm::vec2 box_size = zw(b) - xy(b);
|
|
glm::vec2 pad = { 20, 20 }; // pixels padding
|
|
glm::vec4 pad_box = {
|
|
glm::max({0, 0}, xy(b) - pad) * 2.f / m_size - 1.f,
|
|
glm::min(m_size, zw(b) + pad) * 2.f / m_size - 1.f
|
|
};
|
|
// B(xw)--(zw)C box
|
|
// | // | coordinates
|
|
// A(xy)--(zy)D mapping
|
|
std::array<vertex_t, 6> pad_quad = {
|
|
vertex_t({pad_box.x, pad_box.y}), // A
|
|
vertex_t({pad_box.x, pad_box.w}), // B
|
|
vertex_t({pad_box.z, pad_box.w}), // C
|
|
vertex_t({pad_box.x, pad_box.y}), // A
|
|
vertex_t({pad_box.z, pad_box.w}), // C
|
|
vertex_t({pad_box.z, pad_box.y}), // D
|
|
};
|
|
m_brush_shape.update_vertices(pad_quad.data(), pad_quad.size());
|
|
|
|
m_tmp[i].bindFramebuffer();
|
|
if (!ShaderManager::ext_framebuffer_fetch)
|
|
{
|
|
glm::vec2 o = glm::max({0, 0}, xy(b) - pad);
|
|
glm::vec2 sz = glm::min(m_size, zw(b) + pad) - o;
|
|
m_tex[i].bind();
|
|
if (sz.x > 0 && sz.y > 0)
|
|
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, o.x, o.y, o.x, o.y, sz.x, sz.y);
|
|
}
|
|
m_brush_shape.draw_fill();
|
|
m_tmp[i].unbindFramebuffer();
|
|
}
|
|
if (!ShaderManager::ext_framebuffer_fetch)
|
|
{
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
|
}
|
|
|
|
// DRAW DUAL BRUSH
|
|
|
|
if (brush->m_dual_enabled)
|
|
{
|
|
ShaderManager::use(kShader::Stroke);
|
|
ShaderManager::u_int(kShaderUniform::UsePattern, false);
|
|
ShaderManager::u_float(kShaderUniform::MixAlpha, 0);
|
|
ShaderManager::u_float(kShaderUniform::Wet, 0);
|
|
ShaderManager::u_float(kShaderUniform::Noise, 0);
|
|
|
|
glActiveTexture(GL_TEXTURE0);
|
|
dual_brush->m_tip_texture ?
|
|
dual_brush->m_tip_texture->bind() :
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
|
auto frames_dual = stroke_draw_compute(*m_dual_stroke);
|
|
for (auto& f : frames_dual)
|
|
{
|
|
ShaderManager::u_vec4(kShaderUniform::Col, f.col);
|
|
ShaderManager::u_float(kShaderUniform::Alpha, f.flow);
|
|
ShaderManager::u_float(kShaderUniform::Opacity, f.opacity);
|
|
for (int i = 0; i < 6; i++)
|
|
{
|
|
auto& P = f.shapes[i];
|
|
if (P.size() < 3)
|
|
continue;
|
|
m_tmp_dual[i].bindFramebuffer();
|
|
auto box_sample = stroke_draw_samples(i, P);
|
|
m_tmp_dual[i].unbindFramebuffer();
|
|
|
|
// this mode overflows the main brush boundries
|
|
if (brush->m_dual_blend_mode == 0)
|
|
m_dirty_box[i] = glm::clamp(box_union(m_dirty_box[i], box_sample), glm::vec4(0), glm::vec4(m_width));
|
|
}
|
|
}
|
|
}
|
|
|
|
m_sampler_brush.unbind();
|
|
m_sampler_nearest.unbind();
|
|
m_sampler_stencil.unbind();
|
|
|
|
glViewport(vp[0], vp[1], vp[2], vp[3]);
|
|
glClearColor(cc[0], cc[1], cc[2], cc[3]);
|
|
|
|
if (m_commit_delayed)
|
|
{
|
|
m_show_tmp = false;
|
|
m_commit_delayed = false;
|
|
stroke_commit();
|
|
m_current_stroke = nullptr;
|
|
}
|
|
}
|
|
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)
|
|
{
|
|
point_unproject(loc, { 0, 0, zw(m_box) }, m_mv, m_proj, ray_origin, ray_dir);
|
|
glm::vec3 hit;
|
|
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_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);
|
|
|
|
const auto& b = m_current_stroke->m_brush;
|
|
|
|
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]->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<uint8_t[]>(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]->box(i);
|
|
action->m_old_dirty[i] = m_layers[m_current_layer_idx]->face(i);
|
|
|
|
if (!m_layers[m_current_layer_idx]->m_alpha_locked)
|
|
{
|
|
auto& lbox = m_layers[m_current_layer_idx]->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]->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_nearest.bind(1);
|
|
m_sampler.bind(2);
|
|
m_sampler.bind(3);
|
|
m_sampler_stencil.bind(4);
|
|
if (m_current_mode == 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_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.rtt(i).bindTexture();
|
|
m_plane.draw_fill();
|
|
m_smask.rtt(i).unbindTexture();
|
|
glActiveTexture(GL_TEXTURE1);
|
|
m_tmp[i].unbindTexture();
|
|
glActiveTexture(GL_TEXTURE0);
|
|
m_tex2[i].unbind();
|
|
}
|
|
else
|
|
{
|
|
glm::vec2 patt_scale = glm::vec2(b->m_pattern_scale);
|
|
if (b->m_pattern_flipx) patt_scale.x *= -1.f;
|
|
if (b->m_pattern_flipy) patt_scale.y *= -1.f;
|
|
|
|
ShaderManager::use(kShader::CompDraw);
|
|
ShaderManager::u_int(kShaderUniform::Tex, 0);
|
|
ShaderManager::u_int(kShaderUniform::TexStroke, 1);
|
|
ShaderManager::u_int(kShaderUniform::TexMask, 2);
|
|
ShaderManager::u_int(kShaderUniform::TexDual, 3);
|
|
ShaderManager::u_int(kShaderUniform::TexPattern, 4);
|
|
ShaderManager::u_vec2(kShaderUniform::Resolution, m_size);
|
|
ShaderManager::u_float(kShaderUniform::Alpha, 1);
|
|
ShaderManager::u_int(kShaderUniform::Mask, m_smask_active);
|
|
ShaderManager::u_int(kShaderUniform::Lock, m_layers[m_current_layer_idx]->m_alpha_locked);
|
|
ShaderManager::u_int(kShaderUniform::UseFragcoord, false);
|
|
ShaderManager::u_int(kShaderUniform::BlendMode, b->m_blend_mode);
|
|
ShaderManager::u_mat4(kShaderUniform::MVP, glm::ortho(-.5f, .5f, -.5f, .5f, -1.f, 1.f));
|
|
ShaderManager::u_int(kShaderUniform::UseDual, b->m_dual_enabled);
|
|
ShaderManager::u_int(kShaderUniform::DualBlendMode, b->m_dual_blend_mode);
|
|
ShaderManager::u_float(kShaderUniform::DualAlpha, b->m_dual_opacity);
|
|
ShaderManager::u_int(kShaderUniform::UsePattern, b->m_pattern_enabled && !b->m_pattern_eachsample);
|
|
ShaderManager::u_vec2(kShaderUniform::PatternScale, patt_scale);
|
|
ShaderManager::u_float(kShaderUniform::PatternInvert, b->m_pattern_invert);
|
|
ShaderManager::u_float(kShaderUniform::PatternBright, b->m_pattern_brightness);
|
|
ShaderManager::u_float(kShaderUniform::PatternContrast, b->m_pattern_contrast);
|
|
ShaderManager::u_float(kShaderUniform::PatternDepth, b->m_pattern_depth);
|
|
ShaderManager::u_int(kShaderUniform::PatternBlendMode, b->m_pattern_blend_mode);
|
|
ShaderManager::u_vec2(kShaderUniform::PatternOffset, m_pattern_offset);
|
|
|
|
glActiveTexture(GL_TEXTURE0);
|
|
m_tex2[i].bind();
|
|
glActiveTexture(GL_TEXTURE1);
|
|
m_tmp[i].bindTexture();
|
|
glActiveTexture(GL_TEXTURE2);
|
|
m_smask.rtt(i).bindTexture();
|
|
glActiveTexture(GL_TEXTURE3);
|
|
if (b->m_dual_enabled)
|
|
m_tmp_dual[i].bindTexture();
|
|
glActiveTexture(GL_TEXTURE4);
|
|
b->m_pattern_texture ?
|
|
b->m_pattern_texture->bind() :
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
|
m_plane.draw_fill();
|
|
glActiveTexture(GL_TEXTURE3);
|
|
if (b->m_dual_enabled)
|
|
m_tmp_dual[i].unbindTexture();
|
|
glActiveTexture(GL_TEXTURE2);
|
|
m_smask.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, b->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();
|
|
// }
|
|
|
|
// Dilate borders to avoid interpolation bleeding
|
|
ShaderManager::use(kShader::StrokeDilate);
|
|
ShaderManager::u_mat4(kShaderUniform::MVP, glm::ortho(-.5f, .5f, -.5f, .5f, -1.f, 1.f));
|
|
ShaderManager::u_int(kShaderUniform::TexBG, 0);
|
|
glActiveTexture(GL_TEXTURE0);
|
|
m_tex2[i].bind();
|
|
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, m_width, m_height);
|
|
m_plane.draw_fill();
|
|
|
|
m_layers[m_current_layer_idx]->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_frame_idx = layer().m_frame_index;
|
|
action->m_canvas = this;
|
|
//action->m_stroke = std::move(m_current_stroke);
|
|
ActionManager::add(action);
|
|
}
|
|
|
|
void Canvas::draw_merge(bool draw_checkerboard, std::array<bool, 6> faces /*= SIXPLETTE(false)*/)
|
|
{
|
|
assert(App::I->is_render_thread());
|
|
|
|
glViewport(0, 0, m_width, m_height);
|
|
auto ortho = glm::ortho<float>(-0.5f, 0.5f, -0.5f, 0.5f, -1.f, 1.f);
|
|
const auto& b = m_current_stroke->m_brush;
|
|
|
|
// check if any layer use blend, otherwise draw directly on main framebuffer
|
|
bool use_blend = false;
|
|
for (auto& l : m_layers)
|
|
{
|
|
use_blend |= l->m_blend_mode != 0;
|
|
}
|
|
if (Canvas::I->m_current_stroke)
|
|
use_blend |= Canvas::I->m_current_stroke->m_brush->m_blend_mode != 0;
|
|
|
|
// if not using shader blend, use gl rasterizer blend
|
|
glDisable(GL_DEPTH_TEST);
|
|
|
|
for (int plane_index = 0; plane_index < 6; plane_index++)
|
|
{
|
|
if (!faces[plane_index])
|
|
continue;
|
|
|
|
m_layers_merge.rtt(plane_index).bindFramebuffer();
|
|
m_layers_merge.rtt(plane_index).clear({ 1, 1, 1, 0 });
|
|
|
|
if (use_blend)
|
|
{
|
|
glDisable(GL_BLEND);
|
|
m_layers_merge.rtt(plane_index).clear();
|
|
}
|
|
else
|
|
{
|
|
if (draw_checkerboard)
|
|
{
|
|
ShaderManager::use(kShader::Checkerboard);
|
|
ShaderManager::u_int(kShaderUniform::Colorize, false);
|
|
ShaderManager::u_mat4(kShaderUniform::MVP, ortho);
|
|
m_plane.draw_fill();
|
|
}
|
|
glEnable(GL_BLEND);
|
|
}
|
|
|
|
for (int layer_index = 0; layer_index < m_layers.size(); layer_index++)
|
|
{
|
|
if (!(m_show_tmp && m_current_layer_idx == layer_index) &&
|
|
(!m_layers[layer_index]->m_visible ||
|
|
m_layers[layer_index]->m_opacity == .0f ||
|
|
!m_layers[layer_index]->face(plane_index)))
|
|
continue;
|
|
|
|
if (use_blend)
|
|
{
|
|
m_merge_rtt.bindFramebuffer();
|
|
m_merge_rtt.clear();
|
|
}
|
|
|
|
if (m_current_stroke && m_current_mode == kCanvasMode::Erase && m_show_tmp && m_current_layer_idx == layer_index)
|
|
{
|
|
m_sampler.bind(0);
|
|
m_sampler.bind(1);
|
|
m_sampler.bind(2);
|
|
|
|
ShaderManager::use(kShader::CompErase);
|
|
ShaderManager::u_int(kShaderUniform::Tex, 0);
|
|
ShaderManager::u_int(kShaderUniform::TexStroke, 1);
|
|
ShaderManager::u_int(kShaderUniform::TexMask, 2);
|
|
//ShaderManager::u_vec2(kShaderUniform::Resolution, zw(m_box) / zoom);
|
|
ShaderManager::u_float(kShaderUniform::Alpha, m_layers[layer_index]->m_opacity);
|
|
//ShaderManager::u_int(kShaderUniform::Lock, m_layers[layer_index]->m_alpha_locked);
|
|
ShaderManager::u_int(kShaderUniform::Mask, m_smask_active);
|
|
ShaderManager::u_mat4(kShaderUniform::MVP, ortho);
|
|
glActiveTexture(GL_TEXTURE0);
|
|
m_layers[layer_index]->rtt(plane_index).bindTexture();
|
|
glActiveTexture(GL_TEXTURE1);
|
|
m_tmp[plane_index].bindTexture();
|
|
glActiveTexture(GL_TEXTURE2);
|
|
m_smask.rtt(plane_index).bindTexture();
|
|
m_plane.draw_fill();
|
|
m_smask.rtt(plane_index).unbindTexture();
|
|
glActiveTexture(GL_TEXTURE1);
|
|
m_tmp[plane_index].unbindTexture();
|
|
glActiveTexture(GL_TEXTURE0);
|
|
m_layers[layer_index]->rtt(plane_index).unbindTexture();
|
|
}
|
|
else if (m_current_stroke && m_show_tmp && m_current_layer_idx == layer_index)
|
|
{
|
|
m_sampler.bind(0);
|
|
m_sampler.bind(1);
|
|
m_sampler.bind(2);
|
|
m_sampler.bind(3);
|
|
m_sampler_stencil.bind(4);
|
|
|
|
glm::vec2 patt_scale = glm::vec2(b->m_pattern_scale);
|
|
if (b->m_pattern_flipx) patt_scale.x *= -1.f;
|
|
if (b->m_pattern_flipy) patt_scale.y *= -1.f;
|
|
|
|
ShaderManager::use(kShader::CompDraw);
|
|
ShaderManager::u_int(kShaderUniform::Tex, 0);
|
|
ShaderManager::u_int(kShaderUniform::TexStroke, 1);
|
|
ShaderManager::u_int(kShaderUniform::TexMask, 2);
|
|
ShaderManager::u_int(kShaderUniform::TexDual, 3);
|
|
ShaderManager::u_vec2(kShaderUniform::Resolution, Canvas::I->m_size);
|
|
ShaderManager::u_int(kShaderUniform::TexPattern, 4);
|
|
ShaderManager::u_float(kShaderUniform::Alpha, m_layers[layer_index]->m_opacity);
|
|
ShaderManager::u_int(kShaderUniform::Lock, m_layers[layer_index]->m_alpha_locked);
|
|
ShaderManager::u_int(kShaderUniform::Mask, m_smask_active);
|
|
ShaderManager::u_int(kShaderUniform::UseFragcoord, false);
|
|
ShaderManager::u_int(kShaderUniform::BlendMode, b->m_blend_mode);
|
|
ShaderManager::u_mat4(kShaderUniform::MVP, ortho);
|
|
ShaderManager::u_int(kShaderUniform::UseDual, b->m_dual_enabled);
|
|
ShaderManager::u_int(kShaderUniform::DualBlendMode, b->m_dual_blend_mode);
|
|
ShaderManager::u_int(kShaderUniform::UsePattern, b->m_pattern_enabled && !b->m_pattern_eachsample);
|
|
ShaderManager::u_vec2(kShaderUniform::PatternScale, patt_scale);
|
|
ShaderManager::u_float(kShaderUniform::PatternInvert, b->m_pattern_invert);
|
|
ShaderManager::u_float(kShaderUniform::PatternBright, b->m_pattern_brightness);
|
|
ShaderManager::u_float(kShaderUniform::PatternContrast, b->m_pattern_contrast);
|
|
ShaderManager::u_float(kShaderUniform::PatternDepth, b->m_pattern_depth);
|
|
ShaderManager::u_int(kShaderUniform::PatternBlendMode, b->m_pattern_blend_mode);
|
|
ShaderManager::u_vec2(kShaderUniform::PatternOffset, Canvas::I->m_pattern_offset);
|
|
ShaderManager::u_float(kShaderUniform::DualAlpha, b->m_dual_opacity);
|
|
|
|
glActiveTexture(GL_TEXTURE0);
|
|
m_layers[layer_index]->rtt(plane_index).bindTexture();
|
|
glActiveTexture(GL_TEXTURE1);
|
|
m_tmp[plane_index].bindTexture();
|
|
glActiveTexture(GL_TEXTURE2);
|
|
m_smask.rtt(plane_index).bindTexture();
|
|
glActiveTexture(GL_TEXTURE3);
|
|
if (b->m_dual_enabled)
|
|
m_tmp_dual[plane_index].bindTexture();
|
|
glActiveTexture(GL_TEXTURE4);
|
|
b->m_pattern_texture ?
|
|
b->m_pattern_texture->bind() :
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
|
m_plane.draw_fill();
|
|
glActiveTexture(GL_TEXTURE3);
|
|
if (b->m_dual_enabled)
|
|
m_tmp_dual[plane_index].unbindTexture();
|
|
glActiveTexture(GL_TEXTURE2);
|
|
m_smask.rtt(plane_index).unbindTexture();
|
|
glActiveTexture(GL_TEXTURE1);
|
|
m_tmp[plane_index].unbindTexture();
|
|
glActiveTexture(GL_TEXTURE0);
|
|
m_layers[layer_index]->rtt(plane_index).unbindTexture();
|
|
}
|
|
else
|
|
{
|
|
m_cam_fov < 20.f ? m_sampler_nearest.bind(0) : m_sampler.bind(0);
|
|
ShaderManager::use(kShader::TextureAlpha);
|
|
ShaderManager::u_int(kShaderUniform::Tex, 0);
|
|
ShaderManager::u_float(kShaderUniform::Alpha, m_layers[layer_index]->m_opacity);
|
|
ShaderManager::u_int(kShaderUniform::Highlight, m_layers[layer_index]->m_hightlight);
|
|
ShaderManager::u_mat4(kShaderUniform::MVP, ortho);
|
|
|
|
glActiveTexture(GL_TEXTURE0);
|
|
m_layers[layer_index]->rtt(plane_index).bindTexture();
|
|
m_plane.draw_fill();
|
|
m_layers[layer_index]->rtt(plane_index).unbindTexture();
|
|
}
|
|
|
|
if (use_blend)
|
|
{
|
|
m_merge_rtt.unbindFramebuffer();
|
|
}
|
|
|
|
// draw the blended
|
|
if (use_blend)
|
|
{
|
|
m_sampler.bind(0);
|
|
m_sampler.bind(2);
|
|
|
|
ShaderManager::use(kShader::TextureBlend);
|
|
ShaderManager::u_int(kShaderUniform::Tex, 0);
|
|
ShaderManager::u_int(kShaderUniform::BlendMode, m_layers[layer_index]->m_blend_mode);
|
|
ShaderManager::u_float(kShaderUniform::Alpha, 1.f);
|
|
ShaderManager::u_mat4(kShaderUniform::MVP, ortho);
|
|
if (!ShaderManager::ext_framebuffer_fetch)
|
|
{
|
|
m_sampler.bind(2);
|
|
ShaderManager::u_int(kShaderUniform::TexBG, 2);
|
|
}
|
|
|
|
glActiveTexture(GL_TEXTURE0);
|
|
m_merge_rtt.bindTexture();
|
|
if (!ShaderManager::ext_framebuffer_fetch)
|
|
{
|
|
glActiveTexture(GL_TEXTURE2);
|
|
m_merge_tex.bind();
|
|
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, m_width, m_height);
|
|
}
|
|
|
|
m_plane.draw_fill();
|
|
|
|
if (!ShaderManager::ext_framebuffer_fetch)
|
|
{
|
|
glActiveTexture(GL_TEXTURE2);
|
|
m_merge_tex.unbind();
|
|
}
|
|
glActiveTexture(GL_TEXTURE0);
|
|
m_merge_rtt.unbindTexture();
|
|
}
|
|
}
|
|
|
|
glActiveTexture(GL_TEXTURE2);
|
|
m_merge_tex.bind();
|
|
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, m_width, m_height);
|
|
|
|
// draw the grid behind the layers using a temporary copy
|
|
if (use_blend)
|
|
{
|
|
glEnable(GL_BLEND);
|
|
|
|
//draw the grid
|
|
if (draw_checkerboard)
|
|
{
|
|
ShaderManager::use(kShader::Checkerboard);
|
|
ShaderManager::u_int(kShaderUniform::Colorize, false);
|
|
ShaderManager::u_mat4(kShaderUniform::MVP, ortho);
|
|
m_plane.draw_fill();
|
|
}
|
|
|
|
// draw the layers
|
|
m_sampler.bind(0);
|
|
glActiveTexture(GL_TEXTURE0);
|
|
m_merge_tex.bind();
|
|
ShaderManager::use(kShader::Texture);
|
|
ShaderManager::u_int(kShaderUniform::Tex, 0);
|
|
ShaderManager::u_mat4(kShaderUniform::MVP, ortho);
|
|
m_plane.draw_fill();
|
|
m_merge_tex.unbind();
|
|
}
|
|
|
|
m_layers_merge.rtt(plane_index).unbindFramebuffer();
|
|
}
|
|
}
|
|
|
|
void Canvas::stroke_update(glm::vec3 point, float pressure)
|
|
{
|
|
m_current_stroke->add_point(point, pressure);
|
|
if (m_dual_stroke)
|
|
m_dual_stroke->add_point(point, pressure);
|
|
}
|
|
void Canvas::stroke_start(glm::vec3 point, float pressure)
|
|
{
|
|
assert(App::I->is_render_thread());
|
|
|
|
// need to commit this now before starting a new stroke
|
|
if (m_current_stroke && m_commit_delayed)
|
|
{
|
|
m_show_tmp = false;
|
|
m_commit_delayed = false;
|
|
stroke_commit();
|
|
m_current_stroke = nullptr;
|
|
m_dual_stroke = nullptr;
|
|
}
|
|
|
|
m_pattern_offset = m_current_brush->m_pattern_rand_offset ?
|
|
glm::vec2((rand()%1000)*0.001f, (rand()%1000)*0.001f) :
|
|
glm::vec2(0);
|
|
|
|
m_current_stroke = std::make_unique<Stroke>();
|
|
m_current_stroke->m_camera.rot = m_cam_rot;
|
|
m_current_stroke->m_camera.fov = m_cam_fov;
|
|
if (m_current_mode == kCanvasMode::Line)
|
|
m_current_stroke->m_filter_points = false;
|
|
m_current_stroke->randomize_prng();
|
|
m_current_stroke->start(m_current_brush);
|
|
m_current_stroke->add_point(point, pressure);
|
|
|
|
// Generate a brush for the dual-brush
|
|
if (m_current_brush->m_dual_enabled)
|
|
{
|
|
auto dual_brush = std::make_shared<Brush>();
|
|
dual_brush->m_tip_flow = m_current_brush->m_dual_flow;
|
|
dual_brush->m_tip_opacity = m_current_brush->m_dual_opacity;
|
|
dual_brush->m_tip_flipx = m_current_brush->m_dual_flipx;
|
|
dual_brush->m_tip_flipy = m_current_brush->m_dual_flipy;
|
|
dual_brush->m_tip_invert = m_current_brush->m_dual_invert;
|
|
dual_brush->m_blend_mode = m_current_brush->m_dual_blend_mode;
|
|
dual_brush->m_tip_randflipx = m_current_brush->m_dual_randflip;
|
|
dual_brush->m_tip_randflipy = m_current_brush->m_dual_randflip;
|
|
dual_brush->m_tip_size = m_current_brush->m_dual_size * m_current_brush->m_tip_size;
|
|
dual_brush->m_tip_spacing = m_current_brush->m_dual_spacing;
|
|
dual_brush->m_jitter_scatter = m_current_brush->m_dual_scatter;
|
|
dual_brush->m_jitter_scatter_bothaxis = m_current_brush->m_dual_scatter_bothaxis;
|
|
dual_brush->m_jitter_angle = m_current_brush->m_dual_rotate;
|
|
dual_brush->m_tip_texture = m_current_brush->m_dual_texture;
|
|
dual_brush->m_tip_aspect = m_current_brush->m_dual_aspect;
|
|
m_dual_stroke = std::make_unique<Stroke>();
|
|
m_dual_stroke->m_camera.rot = m_cam_rot;
|
|
m_dual_stroke->m_camera.fov = m_cam_fov;
|
|
m_dual_stroke->start(dual_brush);
|
|
m_dual_stroke->add_point(point, pressure);
|
|
}
|
|
|
|
auto const& l = m_layers[m_current_layer_idx];
|
|
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;
|
|
|
|
m_tmp[i].bindFramebuffer();
|
|
m_tmp[i].clear({ 0, 0, 0, 0 });
|
|
m_tmp[i].unbindFramebuffer();
|
|
|
|
if (m_current_brush->m_dual_enabled)
|
|
{
|
|
m_tmp_dual[i].bindFramebuffer();
|
|
m_tmp_dual[i].clear({ 0, 0, 0, 0 });
|
|
m_tmp_dual[i].unbindFramebuffer();
|
|
}
|
|
}
|
|
m_show_tmp = true;
|
|
}
|
|
void Canvas::layer_add(std::string name, std::shared_ptr<Layer> layer /*= nullptr*/, int index /*= 0*/)
|
|
{
|
|
LOG("canvas layer_add %s", name.c_str());
|
|
int idx = (int)m_layers.size();
|
|
if (layer)
|
|
{
|
|
m_layers.insert(m_layers.begin() + index, layer);
|
|
}
|
|
else
|
|
{
|
|
m_layers.insert(m_layers.begin() + index, std::make_unique<Layer>());
|
|
m_layers[index]->create(m_width, m_height, name);
|
|
}
|
|
m_current_layer_idx = index;
|
|
}
|
|
|
|
std::shared_ptr<Layer> Canvas::layer_with_id(uint32_t id)
|
|
{
|
|
auto layer = std::find_if(m_layers.begin(), m_layers.end(),
|
|
[id](const auto& l) { return l->id == id; });
|
|
if (layer != m_layers.end())
|
|
return *layer;
|
|
return nullptr;
|
|
}
|
|
|
|
void Canvas::layer_remove(int idx) // m_order index
|
|
{
|
|
LOG("canvas layer_remove %d", idx);
|
|
//m_layers[n]->destroy();
|
|
m_layers.erase(m_layers.begin() + idx);
|
|
m_current_layer_idx = std::min<int>((int)m_layers.size() - 1, idx);
|
|
}
|
|
void Canvas::layer_order(int idx, int pos) // m_order index
|
|
{
|
|
std::swap(m_layers[idx], m_layers[pos]);
|
|
}
|
|
void Canvas::layer_merge(int source_idx, int dest_idx) // m_layer index
|
|
{
|
|
m_dirty = false;
|
|
|
|
App::I->render_task([&]
|
|
{
|
|
// 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]->face(i))
|
|
continue; // no stroke on this face, skip it
|
|
|
|
m_layers[dest_idx]->rtt(i).bindFramebuffer();
|
|
|
|
auto& lbox = m_layers[dest_idx]->box(i);
|
|
lbox = glm::vec4(
|
|
glm::min(xy(m_layers[source_idx]->box(i)), xy(lbox)),
|
|
glm::max(zw(m_layers[source_idx]->box(i)), zw(lbox))
|
|
);
|
|
m_layers[dest_idx]->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_nearest.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_float(kShaderUniform::PatternAlpha, 0);
|
|
ShaderManager::u_float(kShaderUniform::Alpha, m_layers[source_idx]->m_opacity);
|
|
ShaderManager::u_int(kShaderUniform::Lock, false);
|
|
ShaderManager::u_int(kShaderUniform::UseFragcoord, 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));
|
|
ShaderManager::u_int(kShaderUniform::UseDual, false);
|
|
ShaderManager::u_int(kShaderUniform::UsePattern, false);
|
|
|
|
glActiveTexture(GL_TEXTURE0);
|
|
m_tex2[i].bind();
|
|
glActiveTexture(GL_TEXTURE1);
|
|
m_layers[source_idx]->rtt(i).bindTexture();
|
|
m_plane.draw_fill();
|
|
m_layers[source_idx]->rtt(i).unbindTexture();
|
|
glActiveTexture(GL_TEXTURE0);
|
|
m_tex2[i].unbind();
|
|
}
|
|
|
|
m_layers[dest_idx]->rtt(i).unbindFramebuffer();
|
|
}
|
|
});
|
|
}
|
|
|
|
int Canvas::anim_duration() const noexcept
|
|
{
|
|
int frames = 1;
|
|
for (auto& l : m_layers)
|
|
frames = glm::max(frames, l->total_duration());
|
|
return frames;
|
|
}
|
|
|
|
void Canvas::anim_goto_frame(int frame) noexcept
|
|
{
|
|
m_anim_frame = frame;
|
|
for (auto& l : m_layers)
|
|
l->goto_frame(frame);
|
|
}
|
|
|
|
void Canvas::anim_goto_next() noexcept
|
|
{
|
|
anim_goto_frame((m_anim_frame + 1) % anim_duration());
|
|
}
|
|
|
|
void Canvas::flood_fill(int layer, int plane, std::vector<glm::ivec2> pos, FloodData& plane_data,
|
|
float threshold, glm::vec4 dest_color, std::unique_ptr<glm::vec4>& source_color)
|
|
{
|
|
struct adj_t
|
|
{
|
|
int plane;
|
|
bool flipx;
|
|
bool flipy;
|
|
bool flipcoord;
|
|
adj_t(int plane, bool flipx, bool flipy, int flipcoord) :
|
|
plane(plane), flipx(flipx), flipy(flipy), flipcoord(flipcoord) { }
|
|
glm::ivec2 compute(glm::ivec2 p, glm::ivec2 sz) const
|
|
{
|
|
glm::ivec2 ret;
|
|
ret[flipcoord] = flipx ? sz.x - p.x : p.x;
|
|
ret[1 - flipcoord] = flipy ? sz.y - p.y : p.y;
|
|
return ret;
|
|
}
|
|
};
|
|
|
|
LOG("flood_fill plane %d", plane);
|
|
|
|
auto& rtt = m_layers[layer]->rtt(plane);
|
|
auto sz = rtt.getSize();
|
|
|
|
if (!plane_data.mask[plane])
|
|
{
|
|
plane_data.mask[plane] = std::make_unique<bool[]>((size_t)sz.x * sz.y);
|
|
plane_data.rgb[plane] = std::unique_ptr<glm::u8vec4[]>(
|
|
reinterpret_cast<glm::u8vec4*>(m_layers[layer]->rtt(plane).readTextureData()));
|
|
plane_data.bb[plane] = { sz.x, sz.y, 0, 0 };
|
|
plane_data.dirty[plane] = false;
|
|
plane_data.layer = m_layers[layer];
|
|
}
|
|
auto& mask = plane_data.mask[plane];
|
|
auto& rgb = plane_data.rgb[plane];
|
|
|
|
if (!source_color)
|
|
source_color = std::make_unique<glm::vec4>(rgb[pos.back().y * sz.x + pos.back().x]);
|
|
const glm::vec4 c = *source_color;
|
|
|
|
std::array<std::vector<glm::ivec2>, 4> edges;
|
|
static const std::array<adj_t, 4> adj[6] = {
|
|
// front
|
|
{
|
|
adj_t(3, 1, 0, 0),
|
|
adj_t(4, 1, 1, 0),
|
|
adj_t(1, 0, 0, 0),
|
|
adj_t(5, 1, 0, 0),
|
|
},
|
|
// right
|
|
{
|
|
adj_t(0, 1, 0, 0),
|
|
adj_t(4, 1, 0, 1),
|
|
adj_t(2, 0, 0, 0),
|
|
adj_t(5, 0, 0, 1),
|
|
},
|
|
// back
|
|
{
|
|
adj_t(1, 1, 0, 0),
|
|
adj_t(4, 0, 0, 0),
|
|
adj_t(3, 0, 0, 0),
|
|
adj_t(5, 0, 1, 0),
|
|
},
|
|
// left
|
|
{
|
|
adj_t(2, 1, 0, 0),
|
|
adj_t(4, 0, 1, 1),
|
|
adj_t(0, 0, 0, 0),
|
|
adj_t(5, 1, 1, 1),
|
|
},
|
|
// top
|
|
{
|
|
adj_t(1, 1, 1, 1),
|
|
adj_t(0, 1, 1, 0),
|
|
adj_t(3, 1, 0, 1),
|
|
adj_t(2, 0, 1, 0),
|
|
},
|
|
// bottom
|
|
{
|
|
adj_t(1, 0, 0, 1),
|
|
adj_t(2, 0, 0, 0),
|
|
adj_t(3, 0, 1, 1),
|
|
adj_t(0, 1, 0, 0),
|
|
},
|
|
};
|
|
|
|
auto test = [&](glm::ivec2 p, bool set_color) -> bool
|
|
{
|
|
int i = p.y * sz.x + p.x;
|
|
if (p.x < 0)
|
|
{
|
|
edges[0].push_back(adj[plane][0].compute({ -p.x, p.y }, sz));
|
|
return false;
|
|
}
|
|
else if (p.x >= sz.x)
|
|
{
|
|
edges[2].push_back(adj[plane][2].compute({ sz.x - p.x + 1, p.y }, sz));
|
|
return false;
|
|
}
|
|
else if (p.y < 0)
|
|
{
|
|
edges[3].push_back(adj[plane][3].compute({ p.x, -p.y }, sz));
|
|
return false;
|
|
}
|
|
else if (p.y >= sz.y)
|
|
{
|
|
edges[1].push_back(adj[plane][1].compute({ p.x, sz.y - p.y + 1 }, sz));
|
|
return false;
|
|
}
|
|
if (!mask[i])
|
|
{
|
|
if (c.a == 0 && glm::abs(rgb[i].a - c.a) < threshold ||
|
|
c.a > 0 && rgb[i].a > 0 && glm::distance(glm::vec3(c), glm::vec3(rgb[i])) < threshold)
|
|
{
|
|
if (set_color)
|
|
{
|
|
mask[i] = true;
|
|
rgb[i] = dest_color * 255.f;
|
|
plane_data.dirty[plane] = true;
|
|
glm::vec2 bb_min = glm::min((glm::vec2)p, xy(plane_data.bb[plane]));
|
|
// add 1 pixel to the end because 1 pixel has min(0) and max(1)
|
|
glm::vec2 bb_max = glm::max((glm::vec2)p + glm::vec2(1), zw(plane_data.bb[plane]));
|
|
plane_data.bb[plane] = { bb_min, bb_max };
|
|
}
|
|
pos.push_back(p);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
while (!pos.empty())
|
|
{
|
|
auto p = pos.back();
|
|
pos.pop_back();
|
|
if(!test(p + glm::ivec2( 0, 0), true))
|
|
continue;
|
|
test(p + glm::ivec2(-1, 0), false);
|
|
test(p + glm::ivec2(1, 0), false);
|
|
test(p + glm::ivec2(0, 1), false);
|
|
test(p + glm::ivec2(0, -1), false);
|
|
//for (int x = -1; x <= 1; x++)
|
|
// for (int y = -1; y <= 1; y++)
|
|
// if (x != 0 && y != 0)
|
|
// test(p + glm::ivec2(x, y), false);
|
|
}
|
|
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
if (!edges[i].empty())
|
|
{
|
|
flood_fill(layer, adj[plane][i].plane, edges[i], plane_data, threshold, dest_color, source_color);
|
|
//LOG("continue to plane %d -> %d", plane, adj[plane][i].plane);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Canvas::FloodData::apply()
|
|
{
|
|
for (int plane = 0; plane < 6; plane++)
|
|
{
|
|
if (!dirty[plane])
|
|
continue;
|
|
auto& rtt = layer->rtt(plane);
|
|
App::I->render_task([&]
|
|
{
|
|
rtt.bindTexture();
|
|
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, rtt.getWidth(), rtt.getHeight(),
|
|
GL_RGBA, GL_UNSIGNED_BYTE, rgb[plane].get());
|
|
rtt.unbindTexture();
|
|
});
|
|
layer->face(plane) = true;
|
|
layer->box(plane) = box_union(layer->box(plane), bb[plane]);
|
|
}
|
|
Canvas::I->m_unsaved = true;
|
|
}
|
|
|
|
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 (ShaderManager::ext_float32_linear)
|
|
{
|
|
m_tmp[i].create(width, height, -1, GL_RGBA32F);
|
|
m_tmp_dual[i].create(width, height, -1, GL_RGBA32F);
|
|
}
|
|
else if (ShaderManager::ext_float16)
|
|
{
|
|
m_tmp[i].create(width, height, -1, GL_RGBA16F);
|
|
m_tmp_dual[i].create(width, height, -1, GL_RGBA16F);
|
|
}
|
|
else
|
|
{
|
|
m_tmp[i].create(width, height, -1, GL_RGBA8);
|
|
m_tmp_dual[i].create(width, height, -1, GL_RGBA8);
|
|
}
|
|
m_tex[i].create(width, height, GL_RGBA8);
|
|
m_tex2[i].create(width, height, GL_RGBA8);
|
|
}
|
|
for (auto& l : m_layers)
|
|
l->resize(width, height);
|
|
m_smask.create(width, height, "mask");
|
|
m_layers_merge.resize(width, height);
|
|
m_merge_rtt.create(width, height);
|
|
m_merge_tex.create(width, height);
|
|
m_unsaved = true;
|
|
}
|
|
|
|
void Canvas::destroy()
|
|
{
|
|
for (int i = 0; i < 6; i++)
|
|
{
|
|
m_tmp[i].destroy();
|
|
m_tmp_dual[i].destroy();
|
|
m_tex[i].destroy();
|
|
m_tex2[i].destroy();
|
|
}
|
|
for (auto& l : m_layers)
|
|
l->destroy();
|
|
m_smask.destroy();
|
|
m_mixer.destroy();
|
|
m_layers_merge.destroy();
|
|
m_merge_rtt.destroy();
|
|
m_merge_tex.destroy();
|
|
}
|
|
|
|
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 (ShaderManager::ext_float32_linear)
|
|
{
|
|
m_tmp[i].create(width, height, -1, GL_RGBA32F);
|
|
m_tmp_dual[i].create(width, height, -1, GL_RGBA32F);
|
|
}
|
|
else if (ShaderManager::ext_float16)
|
|
{
|
|
m_tmp[i].create(width, height, -1, GL_RGBA16F);
|
|
m_tmp_dual[i].create(width, height, -1, GL_RGBA16F);
|
|
}
|
|
else
|
|
{
|
|
m_tmp[i].create(width, height, -1, GL_RGBA8);
|
|
m_tmp_dual[i].create(width, height, -1, GL_RGBA8);
|
|
}
|
|
m_tex[i].create(width, height, GL_RGBA8);
|
|
m_tex2[i].create(width, height, GL_RGBA8);
|
|
}
|
|
#if defined(__GLES__)
|
|
m_sampler_brush.create();
|
|
#else
|
|
m_sampler_brush.create(GL_LINEAR, GL_CLAMP_TO_BORDER);
|
|
#endif
|
|
m_sampler.create(GL_LINEAR);
|
|
m_sampler_nearest.create(GL_NEAREST);
|
|
m_sampler_brush.set_filter(GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR);
|
|
m_sampler_brush.set_border({ 1, 1, 1, 1 });
|
|
m_sampler_stencil.create(GL_LINEAR, GL_REPEAT);
|
|
m_sampler_mix.create(GL_NEAREST, GL_REPEAT);
|
|
m_sampler_linear.create();
|
|
m_plane.create<1>(1, 1);
|
|
m_plane_brush.create<1>(1, 1);
|
|
m_brush_shape.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_layers_merge.create(width, height, "merge");
|
|
m_merge_rtt.create(width, height);
|
|
m_merge_tex.create(width, height);
|
|
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::shared_ptr<Layer> layer /*= nullptr*/)
|
|
{
|
|
if (App::I->check_license())
|
|
{
|
|
std::thread t([=] {
|
|
BT_SetTerminate();
|
|
import_equirectangular_thread(file_path, layer);
|
|
});
|
|
t.detach();
|
|
}
|
|
}
|
|
|
|
void Canvas::import_equirectangular_thread(std::string file_path, std::shared_ptr<Layer> layer /*= nullptr*/, int frame /*= -1*/)
|
|
{
|
|
Image img;
|
|
if (!img.load_file(file_path))
|
|
return;
|
|
|
|
if (!layer)
|
|
layer = m_layers[m_current_layer_idx];
|
|
|
|
if (frame == -1)
|
|
frame = layer->m_frame_index;
|
|
|
|
auto a = new ActionImportEquirect;
|
|
a->m_layer = layer;
|
|
a->m_frame = frame;
|
|
a->m_snap = std::make_shared<Layer::Snapshot>(layer->snapshot(frame));
|
|
a->m_path = file_path;
|
|
ActionManager::add(a);
|
|
|
|
m_unsaved = true;
|
|
|
|
if (img.width == img.height / 6)
|
|
{
|
|
Texture2D tex;
|
|
static const GLint indices[] = { 5, 0, 4, 1, 2, 3 };
|
|
static const GLint formats[] = { GL_RED, GL_RG, GL_RGB, GL_RGBA };
|
|
static const 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) {
|
|
glDisable(GL_DEPTH_TEST);
|
|
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();
|
|
}, frame, false);
|
|
plane.destroy();
|
|
}
|
|
else
|
|
{
|
|
Texture2D tex;
|
|
tex.load_file(file_path);
|
|
Sphere sphere;
|
|
sphere.create<64, 64>(2.f);
|
|
draw_objects([&](const glm::mat4& camera, const glm::mat4& proj, int i) {
|
|
glDisable(GL_DEPTH_TEST);
|
|
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();
|
|
}, frame, false);
|
|
sphere.destroy();
|
|
}
|
|
for (int i = 0; i < 6; i++)
|
|
{
|
|
layer->box(i) = glm::vec4(0, 0, m_width, m_height);
|
|
layer->face(i) = true;
|
|
}
|
|
}
|
|
|
|
void Canvas::export_equirectangular(std::string file_path, std::function<void()> on_complete)
|
|
{
|
|
if (App::I->check_license())
|
|
{
|
|
std::thread t([=] {
|
|
BT_SetTerminate();
|
|
export_equirectangular_thread(file_path);
|
|
if (on_complete)
|
|
on_complete();
|
|
});
|
|
t.detach();
|
|
}
|
|
}
|
|
|
|
void Canvas::export_equirectangular_thread(std::string file_path)
|
|
{
|
|
Image data;
|
|
|
|
App::I->render_task([&]
|
|
{
|
|
draw_merge(false);
|
|
Texture2D equirect = m_layers_merge.gen_equirect();
|
|
data = equirect.get_image();
|
|
});
|
|
|
|
LOG("writing %s", file_path.c_str());
|
|
if (file_path.substr(file_path.size() - 4) == ".jpg")
|
|
{
|
|
data.save_jpg(file_path, 100);
|
|
inject_xmp(file_path);
|
|
}
|
|
else if (file_path.substr(file_path.size() - 4) == ".png")
|
|
{
|
|
data.save_png(file_path);
|
|
}
|
|
|
|
#ifdef __IOS__
|
|
save_image_library(file_path);
|
|
#endif
|
|
}
|
|
|
|
void Canvas::inject_xmp(std::string jpg_path)
|
|
{
|
|
static const char xmp[] =
|
|
"http://ns.adobe.com/xap/1.0/\0" R"(<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
|
|
<x:xmpmeta xmlns:x="adobe:ns:meta/" xmptk="SAMSUNG 360CAM">
|
|
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
|
<rdf:Description rdf:about="" xmlns:GPano="http://ns.google.com/photos/1.0/panorama/">
|
|
<GPano:ProjectionType>equirectangular</GPano:ProjectionType>
|
|
<GPano:UsePanoramaViewer>True</GPano:UsePanoramaViewer>
|
|
<GPano:CroppedAreaLeftPixels>0</GPano:CroppedAreaLeftPixels>
|
|
<GPano:CroppedAreaTopPixels>0</GPano:CroppedAreaTopPixels>
|
|
<GPano:PoseHeadingDegrees>0</GPano:PoseHeadingDegrees>
|
|
<GPano:PosePitchDegrees>0</GPano:PosePitchDegrees>
|
|
<GPano:PoseRollDegrees>0</GPano:PoseRollDegrees>
|
|
<GPano:StitchingSoftware>PanoPainter</GPano:StitchingSoftware>
|
|
</rdf:Description>
|
|
</rdf:RDF>
|
|
</x:xmpmeta>
|
|
<?xpacket end="r"?>)";
|
|
|
|
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_depth(std::string file_name, std::function<void()> on_complete)
|
|
{
|
|
if (App::I->check_license())
|
|
{
|
|
std::thread t([=] {
|
|
export_depth_thread(file_name);
|
|
if (on_complete)
|
|
on_complete();
|
|
});
|
|
t.detach();
|
|
}
|
|
}
|
|
|
|
void Canvas::export_depth_thread(std::string file_name)
|
|
{
|
|
RTT rtt;
|
|
rtt.create(1024, 1024);
|
|
|
|
glm::mat4 proj = glm::perspective(glm::radians(m_cam_fov), (float)rtt.getWidth() / (float)rtt.getHeight(), 0.1f, 100.f);
|
|
glm::mat4 camera = m_cam_rot;
|
|
|
|
App::I->render_task([&]
|
|
{
|
|
draw_merge(false);
|
|
|
|
rtt.bindFramebuffer();
|
|
rtt.clear({ 0, 0, 0, 1 });
|
|
glEnable(GL_BLEND);
|
|
glDisable(GL_DEPTH_TEST);
|
|
glViewport(0, 0, rtt.getWidth(), rtt.getHeight());
|
|
for (int plane_index = 0; plane_index < 6; plane_index++)
|
|
{
|
|
auto plane_mvp_z = proj * camera *
|
|
m_plane_transform[plane_index] *
|
|
glm::translate(glm::vec3(0, 0, -1)) *
|
|
glm::scale(glm::vec3(2));
|
|
|
|
m_sampler.bind(0);
|
|
ShaderManager::use(kShader::TextureAlpha);
|
|
ShaderManager::u_int(kShaderUniform::Tex, 0);
|
|
ShaderManager::u_float(kShaderUniform::Alpha, 1.f);
|
|
ShaderManager::u_int(kShaderUniform::Highlight, false);
|
|
ShaderManager::u_mat4(kShaderUniform::MVP, plane_mvp_z);
|
|
|
|
glActiveTexture(GL_TEXTURE0);
|
|
m_layers_merge.rtt(plane_index).bindTexture();
|
|
m_plane.draw_fill();
|
|
m_layers_merge.rtt(plane_index).unbindTexture();
|
|
}
|
|
rtt.unbindFramebuffer();
|
|
});
|
|
|
|
uint8_t* rgba_data = rtt.readTextureData();
|
|
stbi_flip_vertically_on_write(true);
|
|
std::string path_rgba = App::I->work_path + "/" + file_name + ".png";
|
|
stbi_write_jpg(path_rgba.c_str(), rtt.getWidth(), rtt.getHeight(), 4, rgba_data, 100);
|
|
delete rgba_data;
|
|
|
|
App::I->render_task([&]
|
|
{
|
|
rtt.bindFramebuffer();
|
|
rtt.clear({ 0, 0, 0, 1 });
|
|
glEnable(GL_BLEND);
|
|
glDisable(GL_DEPTH_TEST);
|
|
glViewport(0, 0, rtt.getWidth(), rtt.getHeight());
|
|
for (int layer_index = 0; layer_index < m_layers.size(); layer_index++)
|
|
{
|
|
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]->face(plane_index)))
|
|
continue;
|
|
|
|
auto plane_mvp_z = proj * camera *
|
|
m_plane_transform[plane_index] *
|
|
glm::translate(glm::vec3(0, 0, -1)) *
|
|
glm::scale(glm::vec3(2));
|
|
|
|
m_sampler.bind(0);
|
|
ShaderManager::use(kShader::TextureColorize);
|
|
ShaderManager::u_int(kShaderUniform::Tex, 0);
|
|
ShaderManager::u_vec4(kShaderUniform::Col, { glm::vec3((float)(layer_index + 1) / (float)(m_layers.size() + 1)), 1.f });
|
|
ShaderManager::u_mat4(kShaderUniform::MVP, plane_mvp_z);
|
|
|
|
glActiveTexture(GL_TEXTURE0);
|
|
m_layers[layer_index]->rtt(plane_index).bindTexture();
|
|
m_plane.draw_fill();
|
|
m_layers[layer_index]->rtt(plane_index).unbindTexture();
|
|
}
|
|
}
|
|
rtt.unbindFramebuffer();
|
|
});
|
|
|
|
uint8_t* depth_data = rtt.readTextureData();
|
|
std::string path_depth = App::I->work_path + "/" + file_name + "_depth.png";
|
|
stbi_write_jpg(path_depth.c_str(), rtt.getWidth(), rtt.getHeight(), 4, depth_data, 100);
|
|
delete depth_data;
|
|
stbi_flip_vertically_on_write(false);
|
|
|
|
rtt.destroy();
|
|
}
|
|
|
|
void Canvas::export_layers(std::string path, std::function<void()> on_complete)
|
|
{
|
|
if (App::I->check_license())
|
|
{
|
|
std::thread t([=] {
|
|
BT_SetTerminate();
|
|
export_layers_thread(path);
|
|
if (on_complete)
|
|
on_complete();
|
|
});
|
|
t.detach();
|
|
}
|
|
}
|
|
|
|
void Canvas::export_layers_thread(std::string path)
|
|
{
|
|
auto pb = App::I->show_progress("Export Layers", m_layers.size());
|
|
for (int i = 0; i < m_layers.size(); i++)
|
|
{
|
|
auto l = m_layers[i];
|
|
Image img = l->gen_equirect().get_image();
|
|
img.save_png(fmt::format("{}-layer{:02d}-{}.png", path, i, l->m_name));
|
|
pb->increment();
|
|
}
|
|
pb->destroy();
|
|
}
|
|
|
|
void Canvas::export_cube_faces(std::string file_name, std::function<void()> on_complete)
|
|
{
|
|
if (App::I->check_license())
|
|
{
|
|
std::thread t([=] {
|
|
BT_SetTerminate();
|
|
export_cube_faces_thread(file_name);
|
|
if (on_complete)
|
|
on_complete();
|
|
});
|
|
t.detach();
|
|
}
|
|
}
|
|
|
|
void Canvas::export_cube_faces_thread(std::string file_name)
|
|
{
|
|
#ifdef __OBJC__
|
|
NSMutableArray* files = [NSMutableArray array];
|
|
#endif
|
|
static std::array<const char*, 6> plane_names{ "front", "right", "back", "left", "top", "bottom" };
|
|
|
|
auto pb = App::I->show_progress("Export Cube Faces", 7);
|
|
|
|
App::I->render_task([this] {
|
|
draw_merge(false);
|
|
});
|
|
pb->increment();
|
|
|
|
for (int i = 0; i < 6; i++)
|
|
{
|
|
Image face = m_layers_merge.rtt(i).get_image();
|
|
std::string path = fmt::format("{}/{}-{}.png", App::I->work_path, file_name, plane_names[i]);
|
|
face.save_png(path);
|
|
pb->increment();
|
|
|
|
#ifdef __IOS__
|
|
save_image_library(path);
|
|
#endif
|
|
#ifdef __OBJC__
|
|
[files addObject : [NSString stringWithUTF8String:path.c_str()] ];
|
|
#endif
|
|
}
|
|
|
|
pb->destroy();
|
|
|
|
#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<void(bool)> on_complete)
|
|
{
|
|
if (App::I->check_license())
|
|
{
|
|
std::thread t([=] {
|
|
BT_SetTerminate();
|
|
bool ret = project_save_thread(App::I->doc_path, true);
|
|
if (on_complete)
|
|
on_complete(ret);
|
|
});
|
|
t.detach();
|
|
}
|
|
}
|
|
|
|
void Canvas::project_save(std::string file_path, std::function<void(bool)> on_complete)
|
|
{
|
|
LOG("saving %s", file_path.c_str());
|
|
if (App::I->check_license())
|
|
{
|
|
std::thread t([=] {
|
|
BT_SetTerminate();
|
|
bool ret = project_save_thread(file_path, true);
|
|
if (on_complete)
|
|
on_complete(ret);
|
|
});
|
|
t.detach();
|
|
}
|
|
else
|
|
{
|
|
LOG("no license, no save");
|
|
}
|
|
}
|
|
|
|
bool Canvas::project_save_thread(std::string file_path, bool show_progress)
|
|
{
|
|
// already saved, nothing to do
|
|
if (!m_unsaved && file_path == App::I->doc_path)
|
|
{
|
|
LOG("already saved");
|
|
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";
|
|
|
|
LOG("file name %s", file_name.c_str());
|
|
LOG("tmp path %s", tmp_path.c_str());
|
|
|
|
bool use_tmp = false;
|
|
// check if file already exists
|
|
if ((fp = fopen(file_path.c_str(), "rb")))
|
|
{
|
|
fclose(fp);
|
|
LOG("use tmp file");
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
LOG("save first time");
|
|
|
|
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
|
|
Image thumb = thumbnail_generate(ppi_header.thumb_header.width, ppi_header.thumb_header.height);
|
|
|
|
std::shared_ptr<NodeProgressBar> pb;
|
|
if (show_progress)
|
|
pb = App::I->show_progress("Saving Pano Project");
|
|
|
|
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 n_frames = std::accumulate(m_layers.begin(), m_layers.end(), 0,
|
|
[](int tot, auto& l) { return tot + l->m_frames.size(); });
|
|
if (ppi_header.doc_version.minor >= 3)
|
|
fwrite(&n_frames, sizeof(int), 1, fp);
|
|
|
|
int progress = 0;
|
|
int total = n_frames * 6;
|
|
|
|
for (int i = 0; i < (int)m_layers.size(); i++)
|
|
{
|
|
int n_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);
|
|
|
|
if (ppi_header.doc_version.minor >= 2)
|
|
{
|
|
fwrite(&m_layers[i]->m_blend_mode, sizeof(int), 1, fp);
|
|
fwrite(&m_layers[i]->m_alpha_locked, sizeof(bool), 1, fp);
|
|
fwrite(&m_layers[i]->m_visible, sizeof(bool), 1, fp);
|
|
}
|
|
|
|
int frames = 1;
|
|
if (ppi_header.doc_version.minor >= 3)
|
|
{
|
|
frames = (int)m_layers[i]->m_frames.size();
|
|
fwrite(&frames, sizeof(int), 1, fp);
|
|
}
|
|
|
|
for (int fi = 0; fi < frames; fi++)
|
|
{
|
|
if (ppi_header.doc_version.minor >= 3)
|
|
fwrite(&m_layers[i]->m_frames[fi].m_duration, sizeof(int), 1, fp);
|
|
|
|
m_layers[i]->optimize(fi);
|
|
auto snap = m_layers[i]->snapshot(fi);
|
|
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<uint8_t> compressed;
|
|
auto callback = [](void* context, void* data, int size)
|
|
{
|
|
std::vector<uint8_t>* buffer = static_cast<std::vector<uint8_t>*>(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;
|
|
if (show_progress)
|
|
pb->m_progress->SetWidthP(p);
|
|
LOG("progress: %f", p);
|
|
}
|
|
}
|
|
}
|
|
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;
|
|
#if __WEB__
|
|
webgl_sync();
|
|
#endif
|
|
}
|
|
|
|
if (show_progress)
|
|
pb->destroy();
|
|
App::I->title_update();
|
|
|
|
return success;
|
|
}
|
|
|
|
void Canvas::project_open(std::string file_path, std::function<void(bool)> on_complete)
|
|
{
|
|
std::thread t([=] {
|
|
BT_SetTerminate();
|
|
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;
|
|
}
|
|
|
|
std::shared_ptr<NodeProgressBar> pb;
|
|
if (App::I->layout.m_loaded)
|
|
{
|
|
pb = std::make_shared<NodeProgressBar>();
|
|
pb->set_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);
|
|
}
|
|
|
|
// 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);
|
|
int n_frames = 1;
|
|
if (ppi_header.doc_version.minor >= 3)
|
|
fread(&n_frames, 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_frames * 6;
|
|
|
|
for (auto& l : m_layers)
|
|
l->destroy();
|
|
m_layers.clear();
|
|
//clear_all();
|
|
resize(m_width, m_height);
|
|
|
|
std::vector<std::shared_ptr<Layer>> tmp_layers(n_layers);
|
|
|
|
for (int i = 0; i < n_layers; i++)
|
|
{
|
|
int n_order;
|
|
fread(&n_order, sizeof(int), 1, fp);
|
|
|
|
//if (ppi_header.doc_version.minor > 1)
|
|
// n_order = i;
|
|
|
|
tmp_layers[n_order] = std::make_unique<Layer>();
|
|
auto& layer = tmp_layers[n_order];
|
|
|
|
fread(&layer->m_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);
|
|
|
|
if (ppi_header.doc_version.minor >= 2)
|
|
{
|
|
fread(&layer->m_blend_mode, sizeof(int), 1, fp);
|
|
fread(&layer->m_alpha_locked, sizeof(bool), 1, fp);
|
|
fread(&layer->m_visible, sizeof(bool), 1, fp);
|
|
}
|
|
|
|
int frames = 1;
|
|
if (ppi_header.doc_version.minor >= 3)
|
|
fread(&frames, sizeof(int), 1, fp);
|
|
|
|
layer->create(m_width, m_height, name.c_str());
|
|
|
|
for (int fi = 0; fi < frames; fi++)
|
|
{
|
|
if (fi > 0)
|
|
layer->add_frame();
|
|
if (ppi_header.doc_version.minor >= 3)
|
|
fread(&layer->m_frames[fi].m_duration, sizeof(int), 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<uint8_t> 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)
|
|
{
|
|
pb->m_progress->SetWidthP(p);
|
|
}
|
|
}
|
|
layer->restore(snap, fi);
|
|
}
|
|
}
|
|
|
|
std::swap(tmp_layers, m_layers);
|
|
|
|
fclose(fp);
|
|
LOG("project restore from %s", file_path.c_str());
|
|
|
|
m_current_layer_idx = 0;
|
|
m_current_stroke = nullptr;
|
|
m_dual_stroke = nullptr;
|
|
m_show_tmp = false;
|
|
m_smask_active = false;
|
|
m_smask_mode = 0;
|
|
m_dirty = false;
|
|
m_commit_delayed = false;
|
|
m_dirty_stroke = false;
|
|
memset(m_dirty_face, 0, sizeof(bool) * 6);
|
|
memset(m_pick_ready, 0, sizeof(bool) * 6);
|
|
m_unsaved = false;
|
|
m_newdoc = false;
|
|
if (App::I->layout.m_loaded)
|
|
{
|
|
pb->destroy();
|
|
App::I->title_update();
|
|
App::I->animation->load_layers();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
Image Canvas::thumbnail_generate(int w, int h)
|
|
{
|
|
Image image;
|
|
image.create(w, h);
|
|
|
|
App::I->render_task([this, w, h, &image]
|
|
{
|
|
// 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_mat4(kShaderUniform::MVP, plane_mvp);
|
|
if (!ShaderManager::ext_framebuffer_fetch)
|
|
{
|
|
ShaderManager::u_int(kShaderUniform::TexBG, 2);
|
|
glActiveTexture(GL_TEXTURE2);
|
|
blendtex.bind();
|
|
m_sampler_nearest.bind(2);
|
|
}
|
|
m_sampler_nearest.bind(0); // nearest
|
|
for (int layer_index = 0; layer_index < m_layers.size(); layer_index++)
|
|
{
|
|
if (!m_layers[layer_index]->m_visible ||
|
|
m_layers[layer_index]->m_opacity == 0.f ||
|
|
!m_layers[layer_index]->face(i))
|
|
continue;
|
|
if (!ShaderManager::ext_framebuffer_fetch)
|
|
{
|
|
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]->rtt(i).bindTexture();
|
|
m_face_plane.draw_fill();
|
|
m_layers[layer_index]->rtt(i).unbindTexture();
|
|
}
|
|
|
|
if (!ShaderManager::ext_framebuffer_fetch)
|
|
{
|
|
glActiveTexture(GL_TEXTURE2);
|
|
blendtex.unbind();
|
|
}
|
|
|
|
glActiveTexture(GL_TEXTURE0);
|
|
blendtex.bind();
|
|
// copy the content of the fb before drawing the grid
|
|
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.bind(0); // linear
|
|
m_plane.draw_fill();
|
|
|
|
blendtex.unbind();
|
|
}
|
|
|
|
fb.unbindFramebuffer();
|
|
|
|
// read the rendered image
|
|
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_direct(std::function<void(const glm::mat4& camera, const glm::mat4& proj, int i)> observer, Layer& layer, int frame)
|
|
{
|
|
App::I->render_task([&]
|
|
{
|
|
// 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_COMPONENT24, 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.rtt(i, frame).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.rtt(i, frame).unbindFramebuffer();
|
|
|
|
layer.face(i, frame) = true;
|
|
layer.box(i, frame) = { 0, 0, layer.w, layer.h };
|
|
}
|
|
|
|
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<void(const glm::mat4& camera, const glm::mat4& proj, int i)> observer, Layer& layer, int frame, bool save_history)
|
|
{
|
|
App::I->render_task([&]
|
|
{
|
|
// 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_COMPONENT24, layer.w, layer.h);
|
|
glBindRenderbuffer(GL_RENDERBUFFER, 0);
|
|
|
|
RTT rtt;
|
|
rtt.create(layer.w, layer.h);
|
|
rtt.bindFramebuffer();
|
|
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rboID);
|
|
rtt.unbindFramebuffer();
|
|
|
|
// allocate action to add to history
|
|
ActionStroke* action;
|
|
|
|
if (save_history)
|
|
{
|
|
action = new ActionStroke;
|
|
action->was_saved = !m_unsaved;
|
|
}
|
|
|
|
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]);
|
|
rtt.bindFramebuffer();
|
|
rtt.clear({ 1, 1, 1, 0 });
|
|
observer(plane_camera, proj, i);
|
|
rtt.unbindFramebuffer();
|
|
|
|
glm::vec4 bounds = rtt.calc_bounds();
|
|
|
|
layer.rtt(i, frame).bindFramebuffer();
|
|
|
|
glm::vec2 box_sz = zw(bounds) - xy(bounds);
|
|
bool has_data = box_sz.x > 0 && box_sz.y > 0;
|
|
|
|
if (save_history)
|
|
{
|
|
// save image before commit
|
|
if (has_data)
|
|
{
|
|
action->m_image[i] = std::make_unique<uint8_t[]>(box_sz.x * box_sz.y * 4);
|
|
glReadPixels(bounds.x, bounds.y, box_sz.x, box_sz.y, GL_RGBA, GL_UNSIGNED_BYTE, action->m_image[i].get());
|
|
action->m_box[i] = bounds;
|
|
}
|
|
action->m_old_box[i] = layer.box(i, frame);
|
|
action->m_old_dirty[i] = layer.face(i, frame);
|
|
}
|
|
|
|
// draw the tmp layer into the actual layer
|
|
if (has_data)
|
|
{
|
|
ShaderManager::use(kShader::Texture);
|
|
ShaderManager::u_int(kShaderUniform::Tex, 0);
|
|
ShaderManager::u_mat4(kShaderUniform::MVP, glm::ortho(-0.5f, 0.5f, -0.5f, 0.5f));
|
|
glActiveTexture(GL_TEXTURE0);
|
|
m_sampler_nearest.bind(0);
|
|
rtt.bindTexture();
|
|
m_plane.draw_fill();
|
|
rtt.unbindTexture();
|
|
|
|
layer.face(i, frame) = true;
|
|
layer.box(i, frame) = { glm::min(xy(layer.box(i, frame)), xy(bounds)), glm::max(zw(layer.box(i, frame)), zw(bounds)) };
|
|
}
|
|
|
|
layer.rtt(i, frame).unbindFramebuffer();
|
|
}
|
|
|
|
if (save_history)
|
|
{
|
|
// save history
|
|
action->m_layer_idx = m_current_layer_idx;
|
|
action->m_frame_idx = frame;
|
|
action->m_canvas = this;
|
|
//action->m_stroke = std::move(m_current_stroke);
|
|
ActionManager::add(action);
|
|
}
|
|
|
|
glDeleteRenderbuffers(1, &rboID);
|
|
rtt.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::draw_objects(std::function<void(const glm::mat4& camera, const glm::mat4& proj, int i)> observer, int frame, bool save_history)
|
|
{
|
|
draw_objects(observer, layer(), frame, save_history);
|
|
}
|
|
|
|
void Canvas::project2Dpoints(std::vector<vertex_t>& 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<glm::vec2> Canvas::face_to_shape2D(int plane_index)
|
|
{
|
|
static std::array<glm::vec4, 4> 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<glm::vec3> 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<glm::vec2> 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;
|
|
}
|