Files
panopainter/engine/canvas.cpp

2063 lines
68 KiB
C++

#include "pch.h"
#include "log.h"
#include "canvas.h"
#include "app.h"
#include "node_progress_bar.h"
#include <thread>
#ifdef __APPLE__
#include <Foundation/Foundation.h>
#import <Photos/Photos.h>
#endif
ui::Canvas* ui::Canvas::I;
std::vector<CanvasMode*> ui::Canvas::modes[] = {
{ new CanvasModePen, new CanvasModeBasicCamera },
{ new CanvasModePen, new CanvasModeBasicCamera },
{ new CanvasModeLine, new CanvasModeBasicCamera },
{ new CanvasModeCamera, new CanvasModeBasicCamera },
{ new CanvasModeGrid, new CanvasModeBasicCamera },
{ new CanvasModeFill, new CanvasModeBasicCamera },
{ new CanvasModeMaskFree, new CanvasModeBasicCamera },
{ new CanvasModeMaskLine, new CanvasModeBasicCamera },
};
glm::vec3 ui::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 ui::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 ui::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
};
glm::mat4 ui::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 ui::Canvas::pick_start()
{
for (int i = 0; i < 6; i++)
m_pick_ready[i] = false;
}
void ui::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(ui::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)
{
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 ui::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 ui::Canvas::pick_end()
{
for (int i = 0; i < 6; i++)
m_pick_data[i].release();
}
void ui::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;
App::I.title_update();
}
void ui::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);
}
ui::ActionStroke* ui::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 ui::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 ui::Canvas::stroke_cancel()
{
if (!m_current_stroke)
return;
m_current_stroke = nullptr;
m_show_tmp = false;
}
void ui::Canvas::stroke_draw_mix()
{
m_mixer.bindFramebuffer();
m_mixer.clear({ 1, 1, 1, 1 });
glViewport(0, 0, m_mixer.getWidth(), m_mixer.getHeight());
glDisable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
m_sampler_bg.bind(0);
auto layer_index = m_current_layer_idx;
for (int plane_index = 0; plane_index < 6; plane_index++)
{
if (m_layers[layer_index].m_opacity == .0f)
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 = m_proj * m_mv *
m_plane_transform[plane_index] *
//glm::scale(glm::vec3(1, -1, 1)) *
glm::translate(glm::vec3(0, 0, -1));
ui::ShaderManager::use(kShader::TextureAlpha);
ui::ShaderManager::u_int(kShaderUniform::Tex, 0);
ui::ShaderManager::u_float(kShaderUniform::Alpha, m_layers[layer_index].m_opacity);
ui::ShaderManager::u_int(kShaderUniform::Highlight, false);
ui::ShaderManager::u_mat4(kShaderUniform::MVP, plane_mvp_z);
glActiveTexture(GL_TEXTURE0);
//glDisable(GL_BLEND);
m_layers[layer_index].m_rtt[plane_index].bindTexture();
m_node->m_face_plane.draw_fill();
m_layers[layer_index].m_rtt[plane_index].unbindTexture();
m_tmp[plane_index].bindTexture();
m_node->m_face_plane.draw_fill();
m_tmp[plane_index].unbindTexture();
}
m_sampler_bg.unbind();
m_mixer.unbindFramebuffer();
}
void ui::Canvas::stroke_draw()
{
if (!(m_current_stroke && m_current_stroke->has_sample()))
return;
m_dirty = true;
GLint vp[4];
GLfloat cc[4];
glGetIntegerv(GL_VIEWPORT, vp);
glGetFloatv(GL_COLOR_CLEAR_VALUE, cc);
stroke_draw_mix();
glViewport(0, 0, m_width, m_height);
auto ortho_proj = glm::ortho(0.f, (float)m_width, 0.f, (float)m_height, -1.f, 1.f);
auto m_brush = m_current_stroke->m_brush;
auto samples = m_current_stroke->compute_samples();
auto& tex = TextureManager::get(m_brush.m_tex_id);
auto& stencil = TextureManager::get(const_hash("data/paper.jpg"));
glActiveTexture(GL_TEXTURE0);
tex.bind();
m_sampler_brush.bind(0);
m_sampler_bg.bind(1);
m_sampler_stencil.bind(2);
m_sampler_mix.bind(3);
glActiveTexture(GL_TEXTURE2);
stencil.bind();
glActiveTexture(GL_TEXTURE3);
m_mixer.bindTexture();
glDisable(GL_BLEND);
ShaderManager::use(ui::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_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);
for (const auto& s : samples)
{
if (m_mixer_idle)
{
m_mixer_sample = s;
m_mixer_idle = false;
}
static glm::vec2 UV2[4];
for (int j = 0; j < 4; j++)
{
glm::vec2 dx(m_mixer_sample.size * 0.5f, 0), dy(0, m_mixer_sample.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
};
UV2[j] = (m_mixer_sample.pos + off[j]) / glm::vec2(m_mixer.getWidth(), m_mixer.getHeight()) * m_mixer_scale;
UV2[j].y = 1 - UV2[j].y;
}
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;
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
};
static glm::vec4 P[4];
int intersected = 0;
int inside = 0;
for (int j = 0; j < 4; j++)
{
glm::vec3 ray_origin, ray_dir;
point_unproject(s.pos + off[j] * glm::orientate2(-s.angle), { 0, 0, zw(m_box) }, m_mv, m_proj, ray_origin, ray_dir);
glm::vec3 hit;
if (ray_intersect(ray_origin, ray_dir, m_plane_origin[i], m_plane_normal[i], m_plane_tangent[i], hit))
{
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].x = -(plane_local.x * 0.5f - 0.5f) * m_width;
P[j].y = (plane_local.y * 0.5f + 0.5f) * m_height;
intersected++;
}
else
{
// if (i==0)
// LOG("no intersection with plane %d", i);
break;
}
}
if (intersected < 4 || 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 < 4; j++)
{
bb_min = glm::max({ 0, 0 }, glm::min(bb_min, xy(P[j])));
bb_max = glm::min({ m_width, m_height }, glm::max(bb_max, xy(P[j])));
}
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::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);
m_plane_brush.update_vertices(P, nullptr, UV2);
m_plane_brush.draw_fill();
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 ui::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;
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))
{
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 ui::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;
glm::vec2 fb_pos;
if (ray_intersect(ray_origin, ray_dir, m_plane_origin[plane_id],
m_plane_normal[plane_id], m_plane_tangent[plane_id], hit))
{
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;
}
bool ui::Canvas::ray_intersect(glm::vec3 ray_origin, glm::vec3 ray_dir, glm::vec3 plane_origin,
glm::vec3 plane_normal, glm::vec3 plane_tangent, glm::vec3& out_hit)
{
float den = glm::dot(ray_dir, plane_normal);
if (den == 0)
return false; // no intersection
float num = glm::dot(plane_origin - ray_origin, plane_normal);
float t = num / den;
if (t > 0)
out_hit = ray_origin + ray_dir * t;
else
// negative intersection
return false;
return true;
};
void ui::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 ui::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)
{
ui::ShaderManager::use(kShader::CompErase);
ui::ShaderManager::u_int(kShaderUniform::Tex, 0);
ui::ShaderManager::u_int(kShaderUniform::TexStroke, 1);
//ui::ShaderManager::u_int(kShaderUniform::TexMask, 2);
ui::ShaderManager::u_float(kShaderUniform::Alpha, m_current_stroke->m_brush.m_tip_opacity);
ShaderManager::u_mat4(kShaderUniform::MVP, glm::ortho(-.5f, .5f, -.5f, .5f, -1.f, 1.f));
glActiveTexture(GL_TEXTURE0);
m_tex2[i].bind();
glActiveTexture(GL_TEXTURE1);
m_tmp[i].bindTexture();
glActiveTexture(GL_TEXTURE2);
m_smask.m_rtt[i].bindTexture();
m_plane.draw_fill();
m_smask.m_rtt[i].unbindTexture();
glActiveTexture(GL_TEXTURE1);
m_tmp[i].unbindTexture();
glActiveTexture(GL_TEXTURE0);
m_tex2[i].unbind();
}
else
{
auto& paper = TextureManager::get(const_hash("data/paper.jpg"));
ui::ShaderManager::use(kShader::CompDraw);
ui::ShaderManager::u_int(kShaderUniform::Tex, 0);
ui::ShaderManager::u_int(kShaderUniform::TexStroke, 1);
ui::ShaderManager::u_int(kShaderUniform::TexMask, 2);
ui::ShaderManager::u_float(kShaderUniform::Alpha, m_current_stroke->m_brush.m_tip_opacity);
ui::ShaderManager::u_int(kShaderUniform::Mask, m_smask_active);
ui::ShaderManager::u_int(kShaderUniform::BlendMode, m_current_stroke->m_brush.m_blend_mode);
ui::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(ui::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 ui::Canvas::stroke_update(glm::vec2 point, float pressure)
{
m_current_stroke->add_point(point, pressure);
}
void ui::Canvas::stroke_start(glm::vec2 point, float pressure, const ui::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 ui::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 ui::Canvas::layer_remove(int idx) // m_order index
{
int n = m_order[idx];
for (auto& i : m_order)
if (i > n)
i--;
m_layers.erase(m_layers.begin() + n);
m_order.erase(m_order.begin() + idx);
m_current_layer_idx = m_order[std::min<int>(m_layers.size() - 1, idx)];
}
void ui::Canvas::layer_order(int idx, int pos) // m_order index
{
std::swap(m_order[idx], m_order[pos]);
}
void ui::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);
{
ui::ShaderManager::use(kShader::CompDraw);
ui::ShaderManager::u_int(kShaderUniform::Tex, 0); // dest
ui::ShaderManager::u_int(kShaderUniform::TexStroke, 1); // source
ui::ShaderManager::u_float(kShaderUniform::Alpha, m_layers[source_idx].m_opacity);
ui::ShaderManager::u_int(kShaderUniform::Lock, false);
ui::ShaderManager::u_int(kShaderUniform::BlendMode, 0); // TODO: defaulted to normal, change to layer blend mode when implemented
ui::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 ui::Canvas::resize(int width, int height)
{
m_width = width;
m_height = height;
for (int i = 0; i < 6; i++)
{
#if __IOS__
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.create(width, height, "");
}
}
bool ui::Canvas::create(int width, int height)
{
m_width = width;
m_height = height;
for (int i = 0; i < 6; i++)
{
#if __IOS__
m_tmp[i].create(width, height, -1, GL_RGBA8);
m_tex[i].create(width, height, GL_RGBA8);
#else
m_tmp[i].create(width, height, -1, GL_RGBA32F);
m_tex[i].create(width, height, GL_RGBA32F);
#endif
m_tex2[i].create(width, height, GL_RGBA8); // TODO: destroy before recreating
}
m_sampler.create(GL_NEAREST);
m_sampler_brush.create();
m_sampler_brush.set_filter(GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR);
m_sampler_bg.create(GL_NEAREST);
m_sampler_mask.create(GL_LINEAR);
m_sampler_stencil.create(GL_LINEAR, GL_REPEAT);
m_sampler_mix.create(GL_LINEAR, GL_REPEAT);
m_plane.create<1>(1, 1);
m_plane_brush.create<1>(1, 1);
m_mesh.create();
m_brush_mix.create(8, 8);
for (auto& l : m_layers)
{
l.create(width, height, "");
}
m_smask.create(width*2, height*2, "mask");
m_smask.clear({1, 1, 1, 1});
m_unsaved = true;
return true;
}
void ui::Canvas::snapshot_save(std::string data_path)
{
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(data_path);
}
void ui::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();
LOG("RESTORE SNAPSHOT complete");
}
void ui::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 ui::Canvas::import_equirectangular(std::string file_path)
{
std::thread t(&ui::Canvas::import_equirectangular_thread, this, file_path);
t.detach();
}
void ui::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, 1, 4, 0, 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;
ui::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(ui::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);
ui::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(ui::kShader::Texture);
ShaderManager::u_int(kShaderUniform::Tex, 0);
ShaderManager::u_mat4(kShaderUniform::MVP, proj * camera *
glm::eulerAngleY(glm::radians(-90.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 ui::Canvas::export_equirectangular(std::string file_path)
{
if (!App::I.check_license())
return;
std::thread t(&ui::Canvas::export_equirectangular_thread, this, file_path);
t.detach();
}
void ui::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);
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;
for (int i = 0; i < 6; i++)
{
ShaderManager::use(ui::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));
m_tmp[i].bindFramebuffer();
m_tmp[i].clear({ 1, 1, 1, 1 });
glActiveTexture(GL_TEXTURE0); // TODO: maybe remove this line
m_sampler_mask.bind(0);
for (auto layer_index : m_order)
{
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_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();
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();
}
}
//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();
ui::ShaderManager::use(kShader::Equirect);
ui::ShaderManager::u_mat4(kShaderUniform::MVP, glm::ortho(-.5f, .5f, -.5f, .5f, -1.f, 1.f));
ui::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 ui::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 ui::Canvas::export_anim(std::string data_path)
{
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 });
ui::ShaderManager::use(kShader::Checkerboard);
ui::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(ui::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 });
ui::ShaderManager::use(kShader::Equirect);
ui::ShaderManager::u_mat4(kShaderUniform::MVP, glm::ortho(-.5f, .5f, -.5f, .5f, -1.f, 1.f));
ui::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", data_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 ui::Canvas::export_cubes(std::string data_path)
{
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", data_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", data_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 ui::Canvas::project_save(std::function<void()> on_complete)
{
if (App::I.check_license())
{
std::thread t([=] {
project_save_thread(App::I.data_path + "/" + App::I.doc_name + ".pano");
if (on_complete)
on_complete();
});
t.detach();
}
}
void ui::Canvas::project_save(std::string file_path, std::function<void()> on_complete)
{
if (App::I.check_license())
{
std::thread t([=] {
project_save_thread(file_path);
if (on_complete)
on_complete();
});
t.detach();
}
}
void ui::Canvas::project_save_thread(std::string file_path)
{
gl_state gl;
// already saved, nothing to do
if (!m_unsaved)
return;
// static char name[128];
// sprintf(name, "%s/latlong.pano", data_path.c_str());
FILE* fp = fopen(file_path.c_str(), "wb");
if (!fp)
{
LOG("cannot write project to %s", file_path.c_str());
return;
}
// load thumbnail
App::I.async_start();
Image thumb = thumbnail_generate(128, 128);
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.width, sizeof(int), 1, fp);
fwrite(&thumb.height, sizeof(int), 1, fp);
fwrite(&thumb.comp, sizeof(int), 1, fp);
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);
int name_len = (int)m_layers[i].m_name.size();
fwrite(&name_len, sizeof(int), 1, fp);
fwrite(m_layers[i].m_name.data(), name_len, 1, fp);
App::I.async_start();
auto snap = m_layers[i].snapshot(file_path);
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 = 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);
LOG("project saved to %s", file_path.c_str());
// App::I.upload(data_path);
// LOG("uploaded");
App::I.async_start();
pb->destroy();
App::I.async_update();
App::I.async_end();
m_unsaved = false;
}
void ui::Canvas::project_open(std::string file_path, std::function<void()> on_complete)
{
std::thread t([=] {
project_open_thread(file_path);
if (on_complete)
on_complete();
});
t.detach();
}
void ui::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; // should probably return a bool
}
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();
}
LOG("skip thumb");
// skip thumbnail
Image thumb;
fread(&thumb.width, sizeof(int), 1, fp);
fread(&thumb.height, sizeof(int), 1, fp);
fread(&thumb.comp, sizeof(int), 1, fp);
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();
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);
int name_len;
fread(&name_len, sizeof(int), 1, fp);
std::string name(name_len, '\0');
fread((char*)name.data(), name_len, 1, fp);
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.emplace_back();
tmp_layers.back().create(m_width, m_height, name.c_str());
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());
if (App::I.layout.m_loaded)
{
App::I.async_start();
pb->destroy();
gl.save();
App::I.async_update();
gl.restore();
App::I.async_end();
}
m_unsaved = false;
}
ui::Image ui::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();
fb.clear({ 1, 1, 1, 1 });
ui::Plane m_face_plane;
m_face_plane.create<1>(2, 2);
// 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);
glEnable(GL_BLEND);
for (int plane_index = 0; plane_index < 6; plane_index++)
{
auto plane_mvp = proj * m_mv * m_plane_transform[plane_index] * glm::translate(glm::vec3(0, 0, -1));
ui::ShaderManager::use(kShader::Checkerboard);
ui::ShaderManager::u_mat4(kShaderUniform::MVP, plane_mvp);
m_face_plane.draw_fill();
ui::ShaderManager::use(kShader::TextureAlpha);
ui::ShaderManager::u_int(kShaderUniform::Tex, 0);
ui::ShaderManager::u_int(kShaderUniform::Highlight, false);
ui::ShaderManager::u_mat4(kShaderUniform::MVP, plane_mvp);
for (auto layer_index : m_order)
{
ui::ShaderManager::u_float(kShaderUniform::Alpha, m_layers[layer_index].m_opacity);
m_layers[layer_index].m_rtt[plane_index].bindTexture();
m_face_plane.draw_fill();
m_layers[layer_index].m_rtt[plane_index].unbindTexture();
}
}
fb.unbindFramebuffer();
// read the rendered image
ui::Image image;
image.create(w, h);
fb.readTextureData((uint8_t*)image.data());
fb.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 std::move(image);
}
ui::Image ui::Canvas::thumbnail_read(std::string data_path)
{
// static char name[128];
// sprintf(name, "%s/latlong.pano", data_path.c_str());
FILE* fp = fopen(data_path.c_str(), "rb");
if (!fp)
{
LOG("cannot read project %s", data_path.c_str());
return {}; // return empty image
}
Image thumb;
fread(&thumb.width, sizeof(int), 1, fp);
fread(&thumb.height, sizeof(int), 1, fp);
fread(&thumb.comp, sizeof(int), 1, fp);
thumb.create();
fread((uint8_t*)thumb.data(), thumb.size(), 1, fp);
fclose(fp);
LOG("project thumbnail read from %s", data_path.c_str());
return std::move(thumb);
}
void ui::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);
glm::mat4 proj = glm::perspective(glm::radians(90.f), 1.f, .1f, 100.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();
observer(plane_camera, proj, i);
layer.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);
}
void ui::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 ui::Canvas::project2Dpoints(std::vector<ui::Shape::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);
}
}
std::vector<ui::Shape::vertex_t> ui::Canvas::triangulate(const std::vector<std::shared_ptr<p2t::Point>>& points)
{
struct Segment
{
std::shared_ptr<p2t::Point> a = nullptr;
std::shared_ptr<p2t::Point> b = nullptr;
Segment* prev = nullptr;
std::shared_ptr<Segment> next = nullptr;
bool end = false;
};
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 == *other->a || *node->a == *other->b ||
*node->b == *other->a || *node->b == *other->b)
{
other = other->end ? nullptr : other->next;
continue;
}
glm::vec2 s0a(node->a->x, node->a->y);
glm::vec2 s0b(node->b->x, node->b->y);
glm::vec2 s1a(other->a->x, other->a->y);
glm::vec2 s1b(other->b->x, other->b->y);
glm::vec2 is;
if (segments_intersect(s0a, s0b, s1a, s1b, is))
{
auto p = std::make_shared<p2t::Point>(is.x, is.y);
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<ui::Shape::vertex_t> vertices;
for (auto poly : polys)
{
std::vector<p2t::Point*> outline;
node = poly;
while (node)
{
outline.push_back(new p2t::Point(*node->a));
auto current = node;
node = node->end ? nullptr : node->next;
current->next = nullptr;
}
LOG("poly %ld", outline.size());
if (outline.size() > 2)
{
p2t::CDT* cdt = new p2t::CDT(outline);
cdt->Triangulate();
auto tr = cdt->GetTriangles();
for (auto t : tr)
{
ui::Shape::vertex_t vertex;
for (int i = 0; i < 3; i++)
{
auto p = t->GetPoint(i);
vertex.pos = glm::vec4(p->x, p->y, 0, 1);
vertices.push_back(vertex);
}
}
delete cdt;
}
for (auto p : outline)
delete p;
}
return vertices;
}
///////////////////////////////////////////////////////////////////////////////////////////
void ui::Layer::destroy()
{
for (int i = 0; i < 6; i++)
m_rtt[i].destroy();
}
void ui::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];
m_rtt[i].recreate(); // TODO: this should not be recreated here! Sorry I messed up with this, just quick fix DON'T SHIP!!
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);
}
}
ui::Layer::Snapshot ui::Layer::snapshot(std::string data_path)
{
Snapshot snap;
static int counter = 0;
LOG("errno = %d", errno);
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);
LOG("snapshot face %d - %d bytes (%dx%d)", i, (int)box_sz.x * (int)box_sz.y * 4, (int)box_sz.x, (int)box_sz.y);
static char name[128];
sprintf(name, "%s/Layer%d-%d.png", data_path.c_str(), counter, i);
//int ret = stbi_write_png(name, m_rtt[i].getWidth(), m_rtt[i].getHeight(), 4, snap.image[i].get(), m_rtt[i].stride());
}
counter++;
return std::move(snap);
}
void ui::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 ui::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;
}