Files
panopainter/src/canvas.cpp
2019-09-21 00:02:48 +02:00

2912 lines
98 KiB
C++

#include "pch.h"
#include "log.h"
#include "canvas.h"
#include "app.h"
#include "texture.h"
#include "node_progress_bar.h"
#include <thread>
#ifdef __APPLE__
#include <Foundation/Foundation.h>
#import "objc_utils.h"
#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.m_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.m_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_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]->m_dirty_face[plane_index])
continue;
//glm::mat4 proj = glm::perspective(glm::radians(m_cam_fov), (float)m_mixer.getWidth() / m_mixer.getHeight(), 0.1f, 1000.f);
auto plane_mvp_z =
glm::scale(glm::vec3(1, -1, 1)) *
m_proj * m_mv *
m_plane_transform[plane_index] *
glm::translate(glm::vec3(0, 0, -1));
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]->m_rtt[plane_index].bindTexture();
glActiveTexture(GL_TEXTURE1);
m_tmp[plane_index].bindTexture();
glActiveTexture(GL_TEXTURE2);
m_smask.m_rtt[plane_index].bindTexture();
m_node->m_face_plane.draw_fill();
m_smask.m_rtt[plane_index].unbindTexture();
glActiveTexture(GL_TEXTURE1);
m_tmp[plane_index].unbindTexture();
glActiveTexture(GL_TEXTURE0);
m_layers[layer_index]->m_rtt[plane_index].unbindTexture();
}
m_sampler.unbind();
m_mixer.unbindFramebuffer();
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]->m_rtt[i].bindFramebuffer();
// save image before commit
glm::vec2 box_sz = zw(m_dirty_box[i]) - xy(m_dirty_box[i]);
action->m_image[i] = std::make_unique<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]->m_dirty_box[i];
action->m_old_dirty[i] = m_layers[m_current_layer_idx]->m_dirty_face[i];
if (!m_layers[m_current_layer_idx]->m_alpha_locked)
{
auto& lbox = m_layers[m_current_layer_idx]->m_dirty_box[i];
lbox = glm::vec4(
glm::min(xy(m_dirty_box[i]), xy(lbox)),
glm::max(zw(m_dirty_box[i]), zw(lbox))
);
}
m_layers[m_current_layer_idx]->m_dirty_face[i] = true;
// copy to tmp2 for layer blending
glActiveTexture(GL_TEXTURE0);
m_tex2[i].bind();
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, m_width, m_height);
m_tex2[i].unbind();
m_tmp[i].bindTexture();
glActiveTexture(GL_TEXTURE1);
m_tex2[i].bind();
m_sampler.bind(0);
m_sampler_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.m_rtt[i].bindTexture();
m_plane.draw_fill();
m_smask.m_rtt[i].unbindTexture();
glActiveTexture(GL_TEXTURE1);
m_tmp[i].unbindTexture();
glActiveTexture(GL_TEXTURE0);
m_tex2[i].unbind();
}
else
{
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.m_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.m_rtt[i].unbindTexture();
glActiveTexture(GL_TEXTURE1);
m_tmp[i].unbindTexture();
glActiveTexture(GL_TEXTURE0);
m_tex2[i].unbind();
}
// else
// {
// ShaderManager::use(kShader::StrokeLayer);
// ShaderManager::u_int(kShaderUniform::TexBG, 1);
// ShaderManager::u_int(kShaderUniform::Lock, m_layers[m_current_layer_idx]->m_alpha_locked);
// ShaderManager::u_float(kShaderUniform::Alpha, 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]->m_rtt[i].unbindFramebuffer();
}
// restore viewport and clear color states
blend ? glEnable(GL_BLEND) : glDisable(GL_BLEND);
glViewport(vp[0], vp[1], vp[2], vp[3]);
glClearColor(cc[0], cc[1], cc[2], cc[3]);
glActiveTexture(GL_TEXTURE0);
// save history
action->m_layer_idx = m_current_layer_idx;
action->m_canvas = this;
//action->m_stroke = std::move(m_current_stroke);
ActionManager::add(action);
}
void Canvas::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.m_rtt[plane_index].bindFramebuffer();
m_layers_merge.m_rtt[plane_index].clear({ 1, 1, 1, 0 });
if (use_blend)
{
glDisable(GL_BLEND);
m_layers_merge.m_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]->m_dirty_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]->m_rtt[plane_index].bindTexture();
glActiveTexture(GL_TEXTURE1);
m_tmp[plane_index].bindTexture();
glActiveTexture(GL_TEXTURE2);
m_smask.m_rtt[plane_index].bindTexture();
m_plane.draw_fill();
m_smask.m_rtt[plane_index].unbindTexture();
glActiveTexture(GL_TEXTURE1);
m_tmp[plane_index].unbindTexture();
glActiveTexture(GL_TEXTURE0);
m_layers[layer_index]->m_rtt[plane_index].unbindTexture();
}
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]->m_rtt[plane_index].bindTexture();
glActiveTexture(GL_TEXTURE1);
m_tmp[plane_index].bindTexture();
glActiveTexture(GL_TEXTURE2);
m_smask.m_rtt[plane_index].bindTexture();
glActiveTexture(GL_TEXTURE3);
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.m_rtt[plane_index].unbindTexture();
glActiveTexture(GL_TEXTURE1);
m_tmp[plane_index].unbindTexture();
glActiveTexture(GL_TEXTURE0);
m_layers[layer_index]->m_rtt[plane_index].unbindTexture();
}
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]->m_rtt[plane_index].bindTexture();
m_plane.draw_fill();
m_layers[layer_index]->m_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.m_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;
}
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]->m_dirty_face[i])
continue; // no stroke on this face, skip it
m_layers[dest_idx]->m_rtt[i].bindFramebuffer();
auto& lbox = m_layers[dest_idx]->m_dirty_box[i];
lbox = glm::vec4(
glm::min(xy(m_layers[source_idx]->m_dirty_box[i]), xy(lbox)),
glm::max(zw(m_layers[source_idx]->m_dirty_box[i]), zw(lbox))
);
m_layers[dest_idx]->m_dirty_face[i] = true;
// copy to tmp2 for layer blending
glActiveTexture(GL_TEXTURE0);
m_tex2[i].bind();
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, m_width, m_height);
m_tex2[i].unbind();
m_sampler.bind(0);
m_sampler_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]->m_rtt[i].bindTexture();
m_plane.draw_fill();
m_layers[source_idx]->m_rtt[i].unbindTexture();
glActiveTexture(GL_TEXTURE0);
m_tex2[i].unbind();
}
m_layers[dest_idx]->m_rtt[i].unbindFramebuffer();
}
});
}
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]->m_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]->m_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->m_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->m_dirty_face[plane] = true;
layer->m_dirty_box[plane] = box_union(layer->m_dirty_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(__IOS__) || defined(__ANDROID__)
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*/)
{
Image img;
if (!img.load_file(file_path))
return;
if (!layer)
layer = m_layers[m_current_layer_idx];
auto a = new ActionImportEquirect;
a->m_layer = layer;
a->m_snap = std::make_shared<Layer::Snapshot>(layer->snapshot());
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();
});
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();
});
sphere.destroy();
}
for (int i = 0; i < 6; i++)
{
layer->m_dirty_box[i] = glm::vec4(0, 0, m_width, m_height);
layer->m_dirty_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.m_rtt[plane_index].bindTexture();
m_plane.draw_fill();
m_layers_merge.m_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]->m_dirty_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]->m_rtt[plane_index].bindTexture();
m_plane.draw_fill();
m_layers[layer_index]->m_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.m_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 progress = 0;
int total = (int)m_layers.size() * 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 > 1)
{
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);
}
m_layers[i]->optimize();
auto snap = m_layers[i]->snapshot();
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 (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->m_manager = &App::I->layout;
pb->init();
pb->create();
pb->loaded();
pb->m_progress->SetWidthP(0);
pb->m_title->set_text("Opening Pano Project");
App::I->layout[App::I->main_id]->add_child(pb);
}
// skip thumbnail
Image thumb;
thumb.width = ppi_header.thumb_header.width;
thumb.height = ppi_header.thumb_header.height;
thumb.comp = ppi_header.thumb_header.comp;
fseek(fp, thumb.size(), SEEK_CUR);
fread(&m_width, sizeof(int), 1, fp);
fread(&m_height, sizeof(int), 1, fp);
int n_layers = 0;
fread(&n_layers, sizeof(int), 1, fp);
const int bytes = m_width * m_height * 4;
Layer::Snapshot snap;
snap.create(m_width, m_height); // allocate single data, no box should be bigger
int progress = 0;
int total = n_layers * 6;
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 > 1)
{
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);
}
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->create(m_width, m_height, name.c_str());
layer->restore(snap);
}
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();
}
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]->m_dirty_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]->m_rtt[i].bindTexture();
m_face_plane.draw_fill();
m_layers[layer_index]->m_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)
{
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.m_rtt[i].bindFramebuffer();
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rboID);
observer(plane_camera, proj, i);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, 0);
layer.m_rtt[i].unbindFramebuffer();
layer.m_dirty_face[i] = true;
layer.m_dirty_box[i] = { 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)
{
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
auto 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.m_rtt[i].bindFramebuffer();
// save image before commit
glm::vec2 box_sz = zw(bounds) - xy(bounds);
bool has_data = box_sz.x > 0 && box_sz.y > 0;
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.m_dirty_box[i];
action->m_old_dirty[i] = layer.m_dirty_face[i];
// 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.m_dirty_face[i] = true;
layer.m_dirty_box[i] = { glm::min(xy(layer.m_dirty_box[i]), xy(bounds)), glm::max(zw(layer.m_dirty_box[i]), zw(bounds)) };
}
layer.m_rtt[i].unbindFramebuffer();
}
// save history
action->m_layer_idx = m_current_layer_idx;
action->m_canvas = this;
//action->m_stroke = std::move(m_current_stroke);
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)
{
draw_objects(observer, *m_layers[m_current_layer_idx]);
}
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;
}
///////////////////////////////////////////////////////////////////////////////////////////
void Layer::destroy()
{
for (int i = 0; i < 6; i++)
m_rtt[i].destroy();
}
void Layer::optimize()
{
int saved_bytes = 0;
for (int i = 0; i < 6; i++)
{
if (!m_dirty_face[i])
continue;
auto data = std::unique_ptr<glm::u8vec4[]>(reinterpret_cast<glm::u8vec4*>(m_rtt[i].readTextureData()));
glm::ivec2 bbmin(w,h);
glm::ivec2 bbmax(0);
for (int y = 0; y < h; y++)
{
for (int x = 0; x < w; x++)
{
if (data[x + y * w].a > 0)
{
bbmin = glm::min(bbmin, { x, y });
bbmax = glm::max(bbmax, { x + 1, y + 1 });
}
}
}
glm::vec2 bbsz = bbmax - bbmin;
glm::vec2 old_size = zw(m_dirty_box[i]) - xy(m_dirty_box[i]);
glm::vec2 diff;
if (bbsz.x <= 0 || bbmax.y <= 0)
{
m_dirty_face[i] = false;
m_dirty_box[i] = glm::vec4(0);
diff = old_size;
}
else
{
m_dirty_box[i] = { bbmin, bbmax };
diff = old_size - bbsz;
}
saved_bytes += (int)(diff.x * diff.y * 4);
}
LOG("optimized %d bytes", saved_bytes);
}
void Layer::restore(const Snapshot& snap)
{
clear({ 0, 0, 0, 0 });
for (int i = 0; i < 6; i++)
{
if (snap.image[i] == nullptr || snap.m_dirty_face[i] == false || box_area(snap.m_dirty_box[i]) <= 0)
{
m_dirty_box[i] = glm::vec4(snap.width, snap.height, 0, 0);
m_dirty_face[i] = false;
continue;
}
m_dirty_box[i] = snap.m_dirty_box[i];
m_dirty_face[i] = snap.m_dirty_face[i];
// TODO: this should not be recreated here!
// Sorry I messed up with this,
// it's just a quick fix DON'T SHIP!!
//m_rtt[i].recreate();
App::I->render_task_async([this,i,&snap]
{
m_rtt[i].bindTexture();
glm::vec2 box_sz = zw(m_dirty_box[i]) - xy(m_dirty_box[i]);
glTexSubImage2D(GL_TEXTURE_2D, 0,
m_dirty_box[i].x, m_dirty_box[i].y,
box_sz.x, box_sz.y, GL_RGBA, GL_UNSIGNED_BYTE,
snap.image[i].get());
m_rtt[i].unbindTexture();
LOG("restore face %d - %d bytes (%dx%d)", i,
(int)box_sz.x * (int)box_sz.y * 4, (int)box_sz.x, (int)box_sz.y);
});
}
App::I->render_sync();
}
Layer::Snapshot Layer::snapshot(std::array<glm::vec4, 6> * dirty_box /*= nullptr*/, std::array<bool, 6> * dirty_face /*= nullptr*/)
{
Snapshot snap;
snap.width = w;
snap.height = h;
for (int i = 0; i < 6; i++)
{
snap.m_dirty_box[i] = dirty_box ? dirty_box->at(i) : m_dirty_box[i];
snap.m_dirty_face[i] = dirty_face ? dirty_face->at(i) : m_dirty_face[i];
if (!snap.m_dirty_face[i])
continue;
snap.image[i] = std::make_unique<uint8_t[]>(m_rtt[i].bytes());
App::I->render_task_async([this,i,&snap]
{
m_rtt[i].bindFramebuffer();
glm::vec2 box_sz = zw(snap.m_dirty_box[i]) - xy(snap.m_dirty_box[i]);
glReadPixels(snap.m_dirty_box[i].x, snap.m_dirty_box[i].y,
box_sz.x, box_sz.y, GL_RGBA, GL_UNSIGNED_BYTE, snap.image[i].get());
m_rtt[i].unbindFramebuffer();
});
}
App::I->render_sync();
return snap;
}
void Layer::clear(const glm::vec4& c)
{
App::I->render_task([&]
{
// push clear color state
GLfloat cc[4];
glGetFloatv(GL_COLOR_CLEAR_VALUE, cc);
glClearColor(c.r, c.g, c.b, c.a);
bool erase = (c.a == 0.f);
for (int i = 0; i < 6; i++)
{
m_rtt[i].bindFramebuffer();
glClear(GL_COLOR_BUFFER_BIT);
m_rtt[i].unbindFramebuffer();
if (erase)
{
m_dirty_box[i] = glm::vec4(w, h, 0, 0); // reset bounding box
m_dirty_face[i] = false;
}
else
{
m_dirty_box[i] = glm::vec4(0, 0, w, h); // reset bounding box
m_dirty_face[i] = true;
}
}
// restore clear color state
glClearColor(cc[0], cc[1], cc[2], cc[3]);
});
}
bool Layer::create(int width, int height, std::string name)
{
m_name = name;
w = width;
h = height;
App::I->render_task([&]
{
for (int i = 0; i < 6; i++)
{
m_rtt[i].create(width, height);
m_rtt[i].bindFramebuffer();
m_rtt[i].clear();
m_rtt[i].unbindFramebuffer();
m_dirty_box[i] = glm::vec4(w, h, 0, 0); // reset bounding box
m_dirty_face[i] = false;
}
});
return true;
}
void Layer::resize(int width, int height)
{
glm::vec2 ratio = glm::vec2(width, height) / glm::vec2(w, h);
w = width;
h = height;
for (int i = 0; i < 6; i++)
{
m_rtt[i].resize(width, height);
m_dirty_box[i] = m_dirty_box[i] * glm::vec4(ratio, ratio);
//m_dirty_face[i] = true;
}
}