#pragma once #include "rtt.h" #include "texture.h" #include "shader.h" #include "shape.h" #include "brush.h" #include "action.h" #include "canvas_modes.h" #include #define CANVAS_RES 1536 class Layer { public: RTT m_rtt[6]; glm::vec4 m_dirty_box[6] = SIXPLETTE(glm::vec4(0)); bool m_dirty_face[6] = SIXPLETTE(false); bool m_visible = true; bool m_alpha_locked = false; float m_opacity = 1.f; bool m_hightlight = false; int m_blend_mode = 0; std::string m_name; int w = 0; int h = 0; struct Snapshot { std::unique_ptr image[6] = SIXPLETTE(0); glm::vec4 m_dirty_box[6] = SIXPLETTE(glm::vec4(0)); bool m_dirty_face[6] = SIXPLETTE(false); int width = 0; int height = 0; void create(int w, int h) { for (int i = 0; i < 6; i++) { image[i] = std::make_unique(w*h*4); std::fill_n(image[i].get(), w*h*4, 0); } } void clear() { for (int i = 0; i < 6; i++) { m_dirty_face[i] = false; m_dirty_box[i] = glm::vec4(0); std::fill_n(image[i].get(), width*height*4, 0); } } void optimize() { for (int i = 0; i < 6; i++) { if (!m_dirty_face[i] || !image[i]) continue; auto data = reinterpret_cast(image[i].get()); glm::ivec2 bbmin(width, height); glm::ivec2 bbmax(0); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { if (data[x + y * width].a > 0) { bbmin = glm::min(bbmin, { x, y }); bbmax = glm::max(bbmax, { x + 1, y + 1 }); } } } glm::vec2 bbsz = bbmax - bbmin; } } }; void resize(int width, int height); bool create(int width, int height, std::string name); void clear(const glm::vec4& c); Snapshot snapshot(); void restore(const Snapshot& snap); void destroy(); void optimize(); }; struct PPIThumb { int width = 128; int height = 128; int comp = 4; bool valid() const { return (width == 128 && height == 128 && comp == 4); } }; struct PPIDocVersion { int major = 0; int minor = 2; }; struct PPISoftVersion { int major = g_version_major; int minor = g_version_minor; int fix = g_version_fix; int build = g_version_build; }; struct PPIHeader { char magic[4]{ 'P', 'P', 'I', 0 }; PPIDocVersion doc_version; PPISoftVersion soft_version; PPIThumb thumb_header; bool valid() { if (strcmp(magic, "PPI") != 0) return false; if (doc_version.major != 0 || (doc_version.minor < 1 && doc_version.minor > 2)) return false; if (!thumb_header.valid()) return false; return true; } }; class Canvas { public: Plane m_plane; Plane m_plane_brush; DynamicShape m_brush_shape; BrushMesh m_mesh; bool m_unsaved = false; bool m_newdoc = true; bool m_dirty = false; bool m_commit_delayed = false; bool m_dirty_stroke = false; std::stack m_camera_stack; static Canvas* I; NodeCanvas* m_node = nullptr; bool m_alpha_lock = false; bool m_touch_lock = false; glm::mat4 m_mv{ 1 }; glm::mat4 m_proj{ 1 }; glm::vec4 m_box{ 0 }; glm::vec4 m_vp{ 0 }; glm::vec2 m_pan{ 0 }; glm::vec2 m_size{ 0 }; int m_width = 0; int m_height = 0; bool m_use_instanced = false; int m_current_layer_idx = 0; std::unique_ptr m_current_stroke; bool m_show_tmp = false; std::vector m_layers; std::vector m_order; std::vector m_plane_shape[6]; // screen space projection of the plane glm::mat4 m_plane_unproject[6] = SIXPLETTE(glm::mat4(1)); glm::vec3 m_plane_dir[6] = SIXPLETTE(glm::vec3(0)); glm::vec4 m_dirty_box[6] = SIXPLETTE(glm::vec4(0)); bool m_dirty_face[6] = SIXPLETTE(false); Layer m_smask; // selection mask bool m_smask_active = false; RTT m_tmp[6]; RTT m_mixer; float m_mixer_scale = 1; StrokeSample m_mixer_sample; bool m_mixer_idle = true; Texture2D m_brush_mix; Texture2D m_tex[6]; Texture2D m_tex2[6]; bool m_pick_ready[6]; std::unique_ptr m_pick_data[6] = SIXPLETTE(nullptr); static glm::vec3 m_plane_origin[6]; static glm::vec3 m_plane_normal[6]; static glm::vec3 m_plane_tangent[6]; static glm::mat4 m_plane_transform[6]; glm::vec2 stencil_offset; Sampler m_sampler; Sampler m_sampler_nearest; Sampler m_sampler_linear; Sampler m_sampler_brush; Sampler m_sampler_bg; Sampler m_sampler_mask; Sampler m_sampler_stencil; Sampler m_sampler_mix; glm::mat4 m_cam_rot = glm::mat4(1); glm::vec3 m_cam_pos{ 0 }; float m_cam_fov = 85; glm::vec2 m_cur_pos; Brush m_current_brush; enum class kCanvasMode { Draw, Erase, Line, Camera, Grid, Transform, Fill, MaskFree, MaskLine, COUNT }; kCanvasMode m_state{ kCanvasMode::Draw }; static std::vector modes[]; std::vector* m_mode = nullptr; kCanvasMode m_current_mode = kCanvasMode::Draw; static void set_mode(kCanvasMode mode) { if (I->m_mode) for (auto& m : *I->m_mode) m->leave(); I->m_mode = &modes[(int)mode]; I->m_state = mode; I->m_current_mode = mode; if (I->m_mode) for (auto& m : *I->m_mode) m->enter(); } std::vector m_layers_snapshot; Canvas() { I = this; } bool create(int width, int height); void resize(int width, int height); void layer_remove(int idx); void layer_add(std::string name); void layer_order(int idx, int pos); void layer_merge(int source_idx, int dest_idx); void stroke_start(glm::vec3 point, float pressure, const Brush& brush); void stroke_update(glm::vec3 point, float pressure); void stroke_draw_mix(const glm::vec2& bb_min, const glm::vec2& bb_sz); void stroke_draw(); void stroke_end(); void stroke_cancel(); void stroke_commit(); void clear(const glm::vec4& color = { 1, 1, 1, 0 }); void clear_all(); void pick_start(); void pick_update(int plane); glm::vec4 pick_get(glm::vec2 canvas_loc); void pick_end(); void snapshot_save(); void snapshot_restore(); void snap_history(const std::vector& planes); class ActionStroke* create_action(int layer); void clear_context(); void import_equirectangular(std::string file_path); void import_equirectangular_thread(std::string file_path); void export_equirectangular(std::string file_path, std::function on_complete = nullptr); void export_equirectangular_thread(std::string file_path); void export_anim(); void export_cubes(); void project_save(std::function on_complete = nullptr); void project_save(std::string file_path, std::function on_complete = nullptr); bool project_save_thread(std::string file_path); void project_open(std::string file_path, std::function on_complete = nullptr); bool project_open_thread(std::string file_path); void inject_xmp(std::string jpg_path); Image thumbnail_generate(int w, int h); Image thumbnail_read(std::string file_path); void draw_objects(std::function); void draw_objects(std::function, Layer& layer); void draw_objects_direct(std::function, Layer& layer); void point_unproject(glm::vec2 loc, glm::vec4 vp, glm::mat4 camera, glm::mat4 proj, glm::vec3 &out_origin, glm::vec3 &out_dir); void point_unproject(glm::vec2 loc, glm::vec3 &out_origin, glm::vec3 &out_dir); glm::vec3 point_trace(glm::vec2 loc); bool 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); bool 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); std::vector triangulate_simple(const std::vector& vertices); std::vector triangulate(const std::vector& points); std::vector triangulate(const std::vector& points); void project2Dpoints(std::vector& vertices); glm::vec3 project2Dpoint(glm::vec2 pt); std::vector face_to_shape2D(int plane_index); void push_camera(); void pop_camera(); CameraData get_camera(); void set_camera(const CameraData& c); }; class ActionStroke : public Action { public: std::unique_ptr m_stroke; std::unique_ptr m_image[6] = SIXPLETTE(nullptr); glm::ivec4 m_old_box[6] = SIXPLETTE(glm::ivec4(0)); bool m_old_dirty[6] = SIXPLETTE(false); glm::ivec4 m_box[6] = SIXPLETTE(glm::ivec4(0)); bool m_dirty[6] = SIXPLETTE(false); bool clear_layer = false; int m_layer_idx; Canvas* m_canvas; virtual void run() override { } virtual Action* get_redo() override { auto redo = m_canvas->create_action(m_layer_idx); return redo; } virtual void undo() override { if (clear_layer) m_canvas->m_layers[m_layer_idx].clear({ 0, 0, 0, 0 }); for (int i = 0; i < 6; i++) { // empty data if (!m_image[i]) continue; m_canvas->m_layers[m_layer_idx].m_dirty_box[i] = m_old_box[i]; m_canvas->m_layers[m_layer_idx].m_dirty_face[i] = m_old_dirty[i]; m_canvas->m_layers[m_layer_idx].m_rtt[i].bindTexture(); glm::vec2 box_sz = zw(m_box[i]) - xy(m_box[i]); glTexSubImage2D(GL_TEXTURE_2D, 0, (int)m_box[i].x, (int)m_box[i].y, (int)box_sz.x, (int)box_sz.y, GL_RGBA, GL_UNSIGNED_BYTE, m_image[i].get()); m_canvas->m_layers[m_layer_idx].m_rtt[i].unbindTexture(); } } virtual size_t memory() override { size_t mem = 0; for (int i = 0; i < 6; i++) { glm::ivec2 sz = zw(m_box[i]) - xy(m_box[i]); mem += sz.x * sz.y * 4 + sizeof(*this); } return mem; } virtual ~ActionStroke() { } };