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