#include "pch.h"

#include <cstdint>

#include "log.h"
#include "canvas_modes.h"
#include "layout.h"
#include "canvas.h"
#include "shader.h"
#include "node_canvas.h"
#include "app.h"
#include "util.h"
#include "renderer_gl/opengl_capabilities.h"

NodeCanvas* CanvasMode::node;

namespace {

void set_active_texture_unit(std::uint32_t unit_index)
{
    glActiveTexture(pp::renderer::gl::active_texture_unit(unit_index));
}

}

void CanvasModeBasicCamera::on_MouseEvent(MouseEvent* me, glm::vec2& loc)
{
    switch (me->m_type)
    {
        case kEventType::MouseDownL:
//             if (Canvas::I->m_touch_lock && me->m_source == kEventSource::Touch)
//             {
//                 m_draggingR = true;
//                 m_dragR_start = me->m_pos;
//                 m_pan_start = Canvas::I->m_pan;
//                 node->mouse_capture();
//             }
            if (App::I->keys[(int)kKey::KeySpacebar])
            {
                m_draggingL = true;
                m_pan_start = Canvas::I->m_pan;
                m_dragL_start = me->m_pos;
                node->mouse_capture();
            }
            break;
        case kEventType::MouseUpL:
//             if (Canvas::I->m_touch_lock && me->m_source == kEventSource::Touch)
//             {
//                 m_draggingR = false;
//                 node->mouse_release();
//             }
            if (m_draggingL)
            {
                m_draggingL = false;
                node->mouse_release();
            }
            break;
        case kEventType::MouseDownR:
            if (App::I->keys[(int)kKey::KeyAlt])
                break;
            m_zooming = App::I->keys[(int)kKey::KeyCtrl];
            m_draggingR = true;
            m_dragR_start = me->m_pos;
            m_pan_start = Canvas::I->m_pan;
            m_fov_start = Canvas::I->m_cam_fov;
            node->mouse_capture();
            break;
        case kEventType::MouseUpR:
            m_draggingR = false;
            node->mouse_release();
            break;
        case kEventType::MouseMove:
            if (m_draggingR)
            {
                if (m_zooming)
                {
                    Canvas::I->m_cam_fov = glm::clamp(m_fov_start - (me->m_pos.x - m_dragR_start.x) * 0.05f, 
                        Canvas::I->m_cam_fov_min, Canvas::I->m_cam_fov_max);
                }
                else
                {
                    auto dir = (App::I->has_vr && App::I->vr_active) ? glm::vec2(1, 1) : glm::vec2(-1, -1);
                    Canvas::I->m_pan = m_pan_start + (me->m_pos - m_dragR_start) * dir * (Canvas::I->m_cam_fov / 85.f);
                    auto angle = Canvas::I->m_pan * 0.003f;
                    Canvas::I->m_cam_rot = glm::eulerAngleXY(angle.y, angle.x);
                }
            }
            if (m_draggingL)
            {
                Canvas::I->m_pan = m_pan_start + (me->m_pos - m_dragL_start) * glm::vec2(-1, -1) * (Canvas::I->m_cam_fov / 85.f);
                auto angle = Canvas::I->m_pan * 0.003f;
                Canvas::I->m_cam_rot = glm::eulerAngleXY(angle.y, angle.x);
            }
            break;
        case kEventType::MouseScroll:
            m_zoom_canvas += me->m_scroll_delta * 0.1f;
            Canvas::I->m_cam_fov = glm::clamp(Canvas::I->m_cam_fov - me->m_scroll_delta * 2.0f,
                Canvas::I->m_cam_fov_min, Canvas::I->m_cam_fov_max);
            //App::I->brush_update(true, true);
            break;
        case kEventType::MouseCancel:
            m_draggingR = false;
            node->mouse_release();
            break;
        default:
            break;
    }
}

void CanvasModeBasicCamera::on_GestureEvent(GestureEvent* ge)
{
    switch (ge->m_type)
    {
        case kEventType::GestureStart:
            m_pan_start = Canvas::I->m_pan;
            m_zoom_start = m_zoom_canvas;
            m_camera_fov = Canvas::I->m_cam_fov;
            break;
        case kEventType::GestureMove:
        {
            Canvas::I->m_pan = m_pan_start + ge->m_pos_delta * glm::vec2(-1, -1) * 0.3f * (Canvas::I->m_cam_fov / 85.f);
            Canvas::I->m_cam_fov = glm::clamp(m_camera_fov - ge->m_distance_delta * .05f,
                Canvas::I->m_cam_fov_min, Canvas::I->m_cam_fov_max);
            auto angle = Canvas::I->m_pan * 0.003f;
            Canvas::I->m_cam_rot = glm::eulerAngleXY(angle.y, angle.x);
            //App::I->brush_update(true, true);
            break;
        }
        default:
            break;
    }
}

////////////////////////////////////////////////////////////////////

void CanvasModePen::on_GestureEvent(GestureEvent* ge)
{
    m_draw_tip = false;
}

void CanvasModePen::on_MouseEvent(MouseEvent* me, glm::vec2& loc)
{
#if defined(__IOS__)
    m_draw_tip = (me->m_source == kEventSource::Mouse && me->m_type != kEventType::MouseUpL);
#else
    m_draw_tip = (me->m_source == kEventSource::Mouse || me->m_source == kEventSource::Stylus);
#endif
    m_draw_outline = true;

    if (Canvas::I->m_touch_lock && me->m_source == kEventSource::Touch)
        return;

#if _WIN32
    // curve https://www.wolframalpha.com/input/?i=plot+(1-(x-1)%5E2)%5E2+from+x%3D0+to+1
    auto curve = [](float x, float max) { return x > max ? 1.f : glm::pow(1.f - glm::pow(x / max - 1.f, 2.f), 2.f); };
    me->m_pressure = curve(me->m_pressure, 0.95f);
#endif

    switch (me->m_type)
    {
        case kEventType::MouseDownL:
            if (!App::I->keys[(int)kKey::KeySpacebar])
            {
                if (App::I->keys[(int)kKey::KeyAlt] || m_picking)
                {
                    m_picking = true;
                    Canvas::I->pick_start();
                    glm::vec4 pix = Canvas::I->pick_get(loc);
                    Canvas::I->m_current_brush->m_tip_color = pix;
                    App::I->brush_update(true, false);
                }
                else
                {
                    App::I->render_task_async([loc, pr = me->m_pressure]
                    {
                        Canvas::I->stroke_start({ loc, 0 }, pr);
                    });
                    m_drawing = true;
                }
                m_dragging = true;
                node->mouse_capture();
            }
            break;
        case kEventType::MouseUpL:
            if (m_dragging && !m_picking)
            {
                node->mouse_release();
                App::I->render_task_async([]
                {
                    Canvas::I->stroke_end();
                });
            }
            if (m_dragging && m_picking)
            {
                node->mouse_release();
                glm::vec4 pix = Canvas::I->pick_get(loc);
                Canvas::I->m_current_brush->m_tip_color = pix;
                App::I->brush_update(true, false);
                Canvas::I->pick_end();
            }
            m_drawing = false;
            m_dragging = false;
            m_picking = false;
            break;
        case kEventType::MouseDownR:
            if (App::I->keys[(int)kKey::KeyAlt])
            {
                m_resizing = true;
                m_dragging = true;
                m_size_pos_start = m_cur_pos;
                auto curve = App::I->stroke->m_curves[App::I->stroke->m_tip_size];
                m_size_value_start = curve.to_slider(Canvas::I->m_current_brush->m_tip_size);
                node->mouse_capture();
            }
            break;
        case kEventType::MouseUpR:
            if (m_dragging && m_resizing)
            {
                node->mouse_release();
                m_dragging = false;
                m_resizing = false;
            }
            break;
        case kEventType::MouseMove:
            if (m_dragging && !m_picking && !m_resizing)
            {
                App::I->render_task_async([loc, pr=me->m_pressure]
                {
                    Canvas::I->stroke_update({ loc, 0 }, pr);
                });
            }
            if (m_dragging && m_picking)
            {
                glm::vec4 pix = Canvas::I->pick_get(loc);
                Canvas::I->m_current_brush->m_tip_color = pix;
                App::I->brush_update(true, false);
            }
            if (m_dragging && m_resizing)
            {
                auto diff = m_cur_pos - m_size_pos_start;
                auto curve = App::I->stroke->m_curves[App::I->stroke->m_tip_size];
                Canvas::I->m_current_brush->m_tip_size = glm::max(curve.to_value(m_size_value_start + diff.x * 0.001f), 0.001f);
                App::I->brush_update(true, true);
            }
            m_cur_pos = loc;
            break;
        case kEventType::MouseCancel:
            if (m_dragging)
            {
                App::I->render_task_async([]
                {
                    Canvas::I->stroke_cancel();
                });
                m_dragging = false;
                node->mouse_release();
            }
            if (m_picking)
                m_picking = false;
            if (m_resizing)
                m_resizing = false;
            break;
        default:
            break;
    }
}

void CanvasModePen::on_Draw(const glm::mat4& ortho, const glm::mat4& proj, const glm::mat4& camera)
{
    if (m_draw_tip)
    {
        const auto& brush = Canvas::I->m_current_brush;
        auto pos = m_resizing ? m_size_pos_start : m_cur_pos;
        //if (App::I->keys[(int)kKey::KeyAlt] && !m_resizing)
        //    pos.x = pos.x - brush->m_tip_size * .5f;
        ShaderManager::use(kShader::StrokePreview);
        ShaderManager::u_int(kShaderUniform::Tex, 0);
        ShaderManager::u_float(kShaderUniform::Alpha, brush->m_tip_flow * brush->m_tip_opacity);
        float tip_scale_fix = 1.f / glm::tan(glm::radians(Canvas::I->m_cam_fov * 0.5f));
        float tip_angle = brush->m_tip_angle * (float)(M_PI * 2.0);
        glm::vec2 tip_scale = App::I->zoom * brush->m_tip_scale *
            glm::vec2(brush->m_tip_size * tip_scale_fix) * 
            glm::vec2(brush->m_tip_flipx ? -1 : 1, brush->m_tip_flipy ? -1.f : 1.f) * 
            glm::vec2((brush->m_tip_aspect <= 0.5 ? brush->m_tip_aspect * 2.f : 1.f),
                      (brush->m_tip_aspect > 0.5  ? 1.f - (brush->m_tip_aspect - .5f) * 2.f : 1.f));
        glm::vec2 tip_offset = glm::vec2(0);
        auto tip_color = glm::vec4(glm::vec3(brush->m_tip_color), 1.f);
        if (Canvas::I->m_current_stroke)
        {
            const auto& s = Canvas::I->m_current_stroke->m_prev_sample;
            if (s.size > 0.f)
            {
                tip_scale = App::I->zoom * (brush->m_tip_size * tip_scale_fix) * s.scale;
                tip_angle = s.angle;
                tip_offset = s.pos - s.origin;
                tip_color = glm::vec4(s.col, s.flow);
            }
        }
        glm::u8vec4 pixel;
        GLint fb_width = App::I->width;
        GLint fb_height = App::I->height;
        if (node->m_density != 1.f)
        {
            fb_width = node->m_rtt.getWidth();
            fb_height = node->m_rtt.getHeight();
        }
        glReadPixels((pos.x / App::I->width) * fb_width, 
            ((App::I->height - pos.y - 1) / App::I->height) * fb_height,
            1, 1,
            pp::renderer::gl::rgba_pixel_format(),
            pp::renderer::gl::unsigned_byte_component_type(),
            &pixel);
        bool outline = glm::min(tip_scale.x, tip_scale.y) < 20 || m_resizing ? false : m_draw_outline;
        ShaderManager::u_int(kShaderUniform::DrawOutline, outline);
        ShaderManager::u_vec4(kShaderUniform::Col, outline ? glm::vec4(1.f - glm::vec3(pixel) / 255.f, 1.f) : tip_color);
        ShaderManager::u_mat4(kShaderUniform::MVP,
            glm::scale(glm::vec3(1, -1, 1)) *
            ortho *
            glm::translate(glm::vec3(pos + tip_offset, 0)) *
            glm::eulerAngleZ(tip_angle) *
            glm::scale(glm::vec3(tip_scale, 1))
        );
        bool blend = glIsEnabled(pp::renderer::gl::blend_state());
        glEnable(pp::renderer::gl::blend_state());
        set_active_texture_unit(0);
        auto& tex = *brush->m_tip_texture;
        tex.bind();
        Canvas::I->m_sampler_brush.bind(0);
        Canvas::I->m_plane.draw_fill();
        tex.unbind();
        if (!blend) glDisable(pp::renderer::gl::blend_state());
    }
}

void CanvasModePen::leave(kCanvasMode next)
{
}

void CanvasModePen::enter(kCanvasMode prev)
{
    m_cur_pos = Canvas::I->m_cur_pos;
}

////////////////////////////////////////////////////////////////////

void CanvasModeLine::on_MouseEvent(MouseEvent* me, glm::vec2& loc)
{
#if defined(__IOS__)
    m_draw_tip = (me->m_source == kEventSource::Mouse && me->m_type != kEventType::MouseUpL);
#else
    m_draw_tip = (me->m_source == kEventSource::Mouse || me->m_source == kEventSource::Stylus);
#endif
    if (Canvas::I->m_touch_lock && me->m_source == kEventSource::Touch)
        return;
    switch (me->m_type)
    {
        case kEventType::MouseDownL:
            node->mouse_capture();
            m_dragging = true;
            m_drag_start = loc;
            m_drag_pos = loc;
            break;
        case kEventType::MouseUpL:
            node->mouse_release();
            if (m_dragging)
            {
                App::I->render_task_async([=]
                {
                    Canvas::I->stroke_start({ m_drag_start, 0 }, 1.f);
                    Canvas::I->stroke_update({ m_drag_pos, 0 }, 1.f);
                    Canvas::I->stroke_draw();
                    Canvas::I->stroke_end();
                });
            }
            m_dragging = false;
            break;
        case kEventType::MouseMove:
            if (m_dragging)
                m_drag_pos = loc;
            m_cur_pos = loc;
            break;
        case kEventType::MouseCancel:
            node->mouse_release();
            m_dragging = false;
            break;
        default:
            break;
    }
}

void CanvasModeLine::on_Draw(const glm::mat4& ortho, const glm::mat4& proj, const glm::mat4& camera)
{
    if (m_dragging)
    {
        ShaderManager::use(kShader::Color);
        ShaderManager::u_mat4(kShaderUniform::MVP, ortho);
        ShaderManager::u_vec4(kShaderUniform::Col, Canvas::I->m_current_brush->m_tip_color);
        static glm::vec4 AB[2];
        AB[0] = { m_drag_start, 0, 1 };
        AB[1] = { m_drag_pos, 0, 1 };
        AB[0].y = Canvas::I->m_box.w - AB[0].y - 1; // invert Y
        AB[1].y = Canvas::I->m_box.w - AB[1].y - 1; // invert Y
        m_line.update_vertices(AB);
        m_line.draw_stroke();
    }
    else if (m_draw_tip)
    {
        const auto& brush = Canvas::I->m_current_brush;
        ShaderManager::use(kShader::StrokePreview);
        ShaderManager::u_int(kShaderUniform::Tex, 0);
        ShaderManager::u_float(kShaderUniform::Alpha, brush->m_tip_flow * brush->m_tip_opacity);
        float tip_scale_fix = 1.f / glm::tan(glm::radians(Canvas::I->m_cam_fov * 0.5f));
        float tip_angle = brush->m_tip_angle * (float)(M_PI * 2.0);
        glm::vec2 tip_scale = App::I->zoom * brush->m_tip_scale *
            glm::vec2(brush->m_tip_size * tip_scale_fix) *
            glm::vec2(brush->m_tip_flipx ? -1 : 1, brush->m_tip_flipy ? -1.f : 1.f) *
            glm::vec2((brush->m_tip_aspect <= 0.5 ? brush->m_tip_aspect * 2.f : 1.f),
            (brush->m_tip_aspect > 0.5 ? 1.f - (brush->m_tip_aspect - .5f) * 2.f : 1.f));
        auto tip_color = glm::vec4(glm::vec3(brush->m_tip_color), 1.f);
        ShaderManager::u_int(kShaderUniform::DrawOutline, false);
        ShaderManager::u_vec4(kShaderUniform::Col, tip_color);
        ShaderManager::u_mat4(kShaderUniform::MVP,
            glm::scale(glm::vec3(1, -1, 1)) *
            ortho *
            glm::translate(glm::vec3(m_cur_pos, 0)) *
            glm::eulerAngleZ(tip_angle) *
            glm::scale(glm::vec3(tip_scale, 1))
        );
        bool blend = glIsEnabled(pp::renderer::gl::blend_state());
        glEnable(pp::renderer::gl::blend_state());
        set_active_texture_unit(0);
        auto& tex = *brush->m_tip_texture;
        tex.bind();
        Canvas::I->m_sampler_brush.bind(0);
        Canvas::I->m_plane.draw_fill();
        tex.unbind();
        if (!blend) glDisable(pp::renderer::gl::blend_state());
    }
}

void CanvasModeLine::init()
{
    m_line.create();
}

void CanvasModeLine::enter(kCanvasMode prev)
{
    m_cur_pos = Canvas::I->m_cur_pos;
}

void CanvasModeLine::leave(kCanvasMode next)
{
}

////////////////////////////////////////////////////////////////////

void CanvasModeCamera::on_MouseEvent(MouseEvent* me, glm::vec2& loc)
{
    if (Canvas::I->m_touch_lock && me->m_source == kEventSource::Touch)
        return;
    switch (me->m_type)
    {
    case kEventType::MouseDownR:
        Canvas::I->m_cam_pos = { 0, 0, 0 };
        break;
    case kEventType::MouseDownL:
        m_dragging = true;
        m_drag_start = me->m_pos;
        m_pos_start = xy(Canvas::I->m_cam_pos);
        node->mouse_capture();
        break;
    case kEventType::MouseUpL:
        m_dragging = false;
        node->mouse_release();
        Canvas::I->m_cam_pos = { 0, 0, 0 };
        break;
    case kEventType::MouseMove:
        if (m_dragging)
            Canvas::I->m_cam_pos = glm::vec3(m_pos_start + (me->m_pos - m_drag_start) * glm::vec2(1, -1) * 0.001f, Canvas::I->m_cam_pos.z);
        break;
    case kEventType::MouseCancel:
        m_dragging = false;
        node->mouse_release();
        break;
    default:
        break;
    }
}

////////////////////////////////////////////////////////////////////

void CanvasModeGrid::on_MouseEvent(MouseEvent* me, glm::vec2& loc)
{
    if (Canvas::I->m_touch_lock && me->m_source == kEventSource::Touch)
        return;
    switch (me->m_type)
    {
        case kEventType::MouseDownL:
        {
            node->mouse_capture();
            glm::vec3 ro, rd, hit_o, hit_d;
            glm::vec2 fb_pos;
            if (Canvas::I->point_trace(loc, ro, rd, hit_o, fb_pos, hit_d, m_plane_id))
            {
                m_action = std::make_unique<ActionModeGrid>();
                m_action->m_mode = this;
                m_action->m_highlight = m_highlight;
                m_action->m_selected_index = m_selected_index;
                m_action->m_lines = m_lines;
                
                int select = -1;
                for (int i = 0; i < m_lines.size(); i++)
                {
                    auto const& l = m_lines[i];
                    float d = lines_distance(ro, ro+rd*10.f, l.o-l.d*10.f, l.o+l.d*10.f);
                    if (d < 0.03f)
                        select = i;
                }
                if (select == -1)
                {
                    m_lines.push_back({ hit_o, hit_d });
                    m_selected_index = (int)m_lines.size() - 1;
                    m_highlight = false;
                    m_added = true;
                }
                else
                {
                    m_selected_index = select;
                    m_highlight = true;
                    m_added = false;
                }
                origin = hit_o;
                dir = hit_d;
                m_dragging = true;
            }
            break;
        }
        case kEventType::MouseUpL:
            node->mouse_release();
            m_dragging = false;
            ActionManager::add(m_action.release());
            //commit();
            break;
        case kEventType::MouseMove:
        {
            glm::vec3 ro, rd, hit_o, hit_d;
            glm::vec2 hit_fb;
            if (m_dragging && Canvas::I->point_trace_plane(loc, ro, rd, hit_o, hit_d, hit_fb, m_plane_id))
            {
                m_lines[m_selected_index] = { hit_o, hit_d };
                origin = hit_o;
                dir = hit_d;
                m_dragging = true;
            }
            break;
        }
        case kEventType::MouseCancel:
            if (m_dragging && m_selected_index == m_lines.size() - 1)
                m_lines.pop_back();
            m_dragging = false;
            node->mouse_release();
            break;
        default:
            break;
    }
}

void CanvasModeGrid::on_Draw(const glm::mat4& ortho, const glm::mat4& proj, const glm::mat4& camera)
{
    const glm::vec4 blue(0, 0, 1, 1);
    const glm::vec4 red(1, 0, 0, 1);
    for (int i = 0; i < m_lines.size(); i++)
    {
        auto const& l = m_lines[i];
        auto origin = l.o;
        auto dir = l.d;
        ShaderManager::use(kShader::Color);
        ShaderManager::u_mat4(kShaderUniform::MVP, proj * camera);
        ShaderManager::u_vec4(kShaderUniform::Col, m_highlight && i == m_selected_index ? blue : red);
        static glm::vec4 AB[2];
        AB[0] = {origin - dir * 10.f, 1};
        AB[1] = {origin + dir * 10.f, 1 };
        m_line.update_vertices(AB);
        m_line.draw_stroke();
    }
}

void CanvasModeGrid::init()
{
    m_line.create();
}

void CanvasModeGrid::commit()
{
    auto drawer = [this](const glm::mat4& camera, const glm::mat4& proj){
        ShaderManager::use(kShader::Color);
        ShaderManager::u_mat4(kShaderUniform::MVP, proj * camera);
        ShaderManager::u_vec4(kShaderUniform::Col, {1, 0, 0, 1});
        static glm::vec4 AB[2];
        AB[0] = {origin - dir * 10.f, 1};
        AB[1] = {origin + dir * 10.f, 1 };
        m_line.update_vertices(AB);
        m_line.draw_stroke();
    };
    Canvas::I->draw_objects(std::bind(drawer, std::placeholders::_1, std::placeholders::_2), Canvas::I->layer().m_frame_index, true);
}

void CanvasModeGrid::clear()
{
    auto a = new ActionModeGrid;
    a->m_mode = this;
    a->m_highlight = m_highlight;
    a->m_selected_index = m_selected_index;
    a->m_lines = m_lines;
    ActionManager::add(a);

    m_lines.clear();
}

void CanvasModeGrid::leave(kCanvasMode next)
{
    m_selected_index = -1;
}

void CanvasModeGrid::on_KeyEvent(KeyEvent* ke)
{
    if ((ke->m_key == kKey::KeyBackspace || ke->m_key == kKey::KeyDel)
        && ke->m_type == kEventType::KeyUp)
    {
        if (m_highlight)
        {
            auto a = new ActionModeGrid;
            a->m_mode = this;
            a->m_highlight = m_highlight;
            a->m_selected_index = m_selected_index;
            a->m_lines = m_lines;
            ActionManager::add(a);
            m_lines.erase(m_lines.begin() + m_selected_index);
            m_highlight = false;
        }
    }
}

Action* ActionModeGrid::get_redo()
{
    auto a = new ActionModeGrid;
    a->m_mode = m_mode;
    a->m_highlight = m_mode->m_highlight;
    a->m_selected_index = m_mode->m_selected_index;
    a->m_lines = m_mode->m_lines;
    return a;
}
void ActionModeGrid::undo()
{
    m_mode->m_highlight = m_highlight;
    m_mode->m_selected_index = m_selected_index;
    m_mode->m_lines = m_lines;
}

////////////////////////////////////////////////////////////////////

void CanvasModeMaskFree::init()
{
    m_shape.create();
}

void CanvasModeMaskFree::leave(kCanvasMode next)
{
//     Canvas::I->draw_objects(std::bind(&CanvasModeFill::on_Draw, this, glm::mat4(1), std::placeholders::_1, std::placeholders::_2));
    if (next != kCanvasMode::Draw && next != kCanvasMode::Erase && next != kCanvasMode::Line)
    {
        m_points.clear();
        Canvas::I->m_smask_active = false;
    }
}

void CanvasModeMaskFree::clear()
{
    m_points.clear();
    m_points2d.clear();
    m_shape.clear();
}

void CanvasModeMaskFree::on_MouseEvent(MouseEvent* me, glm::vec2& loc)
{
    static glm::vec2 oldpos;
    static glm::vec2 oldvec;
    static float acc = 0.f;
    if (Canvas::I->m_touch_lock && me->m_source == kEventSource::Touch)
        return;
    switch (me->m_type)
    {
    case kEventType::MouseDownL:
    {
        node->mouse_capture();
        m_dragging = true;
        m_points2d.clear();
        m_points.clear();
        oldpos = loc;
        oldvec = {1.f, 0.f};
        acc = 0;
        vertex_t vert;
        vert.pos = glm::vec4(loc, 0, 1);
        m_points2d.push_back(loc);
        m_points2d.push_back(loc);
        m_points.push_back(vert);
        m_points.push_back(vert);
        if (!(App::I->keys[(int)kKey::KeyShift] || App::I->keys[(int)kKey::KeyCtrl]))
            Canvas::I->m_smask.clear({0, 0, 0, 0});
        Canvas::I->m_smask_active = true;
        Canvas::I->m_smask_mode = 1;
        break;
    }
    case kEventType::MouseUpL:
        node->mouse_release();
        m_dragging = false;
        if (m_points2d.size() > 3)
        {
            if (!m_points.empty())
            {
                m_selection_cam = Canvas::I->get_camera();
                //m_points2d = poly_intersect(poly_remove_duplicate(m_points2d), Canvas::I->face_to_shape2D(0));
                auto drawer = [this](const glm::mat4& camera, const glm::mat4& proj) {
                    // blending state intentionally left unchanged here.
                    glDisable(pp::renderer::gl::depth_test_state());
                    ShaderManager::use(kShader::Color);
                    ShaderManager::u_mat4(kShaderUniform::MVP, proj * camera);
                    ShaderManager::u_vec4(kShaderUniform::Col,
                        App::I->keys[(int)kKey::KeyCtrl] ? glm::vec4(0, 0, 0, 1) : glm::vec4(1, 1, 1, 1));
                    m_shape.draw_fill();
                };
                // use m_shape to render the mask polygon
                auto v = triangulate(poly_remove_duplicate(m_points2d));
                Canvas::I->project2Dpoints(v);
                m_shape.update_vertices(v.data(), (int)v.size());
                Canvas::I->draw_objects_direct(std::bind(drawer, std::placeholders::_1, std::placeholders::_2), Canvas::I->m_smask, 0);
                
                // close the path and reset m_shape to contour rendering
                m_points.push_back(m_points.back());
                m_points.push_back(m_points.front());
                Canvas::I->project2Dpoints(m_points);
                m_shape.update_vertices(m_points.data(), (int)m_points.size());
            }
        }
        else
        {
            Canvas::I->m_smask_active = false;
        }
        break;
    case kEventType::MouseMove:
    {
        if (m_dragging)
        {
            auto v = loc - oldpos;
            float len = glm::length(v);
            if (len > 5)
            {
                m_points.back().pos = glm::vec4(loc, 0, 1);
                m_points2d.back() = loc;
                
                v = glm::normalize(v);
                float d = 1.f - glm::dot(v, oldvec);
                acc += d;
                oldpos = loc;
                oldvec = v;
                if (acc > 0.001) // angle change tolerance
                {
                    //LOG("d=%f acc=%f", d, acc);
                    acc = 0;
                    
                    m_points2d.push_back(loc);
                    vertex_t vert;
                    vert.pos = glm::vec4(loc, 0, 1);
                    m_points.push_back(vert);
                    m_points.push_back(vert);
                }
                m_shape.update_vertices(m_points.data(), (int)m_points.size());
            }
        }
        break;
    }
    case kEventType::MouseCancel:
        if (m_dragging)
        {
            m_points.pop_back();
            m_shape.update_vertices(m_points.data(), (int)m_points.size());
        }
        m_dragging = false;
        node->mouse_release();
        if (m_points.size() < 4)
        {
            m_points.clear();
            m_shape.update_vertices(m_points.data(), (int)m_points.size());
        }
        break;
    default:
        break;
    }
}

void CanvasModeMaskFree::on_Draw(const glm::mat4& ortho, const glm::mat4& proj, const glm::mat4& camera)
{
    bool depth = glIsEnabled(pp::renderer::gl::depth_test_state());
    glDisable(pp::renderer::gl::depth_test_state());
    if (m_points.size() > 3)
    {
        if (m_dragging)
        {
            ShaderManager::use(kShader::Color);
            ShaderManager::u_mat4(kShaderUniform::MVP, glm::scale(glm::vec3(1,-1,1)) * ortho);
            ShaderManager::u_vec4(kShaderUniform::Col, { 0, 0, 0, 1 });
            //m_dragging ? m_shape.draw_stroke() : m_shape.draw_fill();
            m_shape.draw_stroke();
        }
        //else
        //{
        //    ShaderManager::use(kShader::Color);
        //    ShaderManager::u_mat4(kShaderUniform::MVP, proj * camera);
        //    ShaderManager::u_vec4(kShaderUniform::Col, { 0, 0, 0, 1 });
        //    m_shape.draw_stroke();
        //}
    }
    if (depth) glEnable(pp::renderer::gl::depth_test_state());
}


////////////////////////////////////////////////////////////////////

void CanvasModeMaskLine::init()
{
    m_shape.create();
}

void CanvasModeMaskLine::leave(kCanvasMode next)
{
    if (next != kCanvasMode::Draw && next != kCanvasMode::Erase && next != kCanvasMode::Line)
    {
        m_points.clear();
        m_active_tool = false;
        Canvas::I->m_smask_active = false;
    }

    if (!m_active_tool)
        return;

    if (m_points2d.size() > 3)
    {
        std::vector<vertex_t> points;
        for (int i = 0; i < (int)m_points2d.size(); i++)
            points.emplace_back(m_points2d[i]);
        auto v = triangulate(poly_remove_duplicate(points));
        Canvas::I->project2Dpoints(v);
        LOG("%d points", (int)v.size());
        
        m_shape.update_vertices(v.data(), (int)v.size());
        
        if (!m_points.empty())
        {
            auto drawer = [this](const glm::mat4& camera, const glm::mat4& proj) {
                // blending state intentionally left unchanged here.
                ShaderManager::use(kShader::Color);
                ShaderManager::u_mat4(kShaderUniform::MVP, proj * camera);
                ShaderManager::u_vec4(kShaderUniform::Col, {1, 1, 1, 1});
                m_shape.draw_fill();
            };
            Canvas::I->draw_objects_direct(std::bind(drawer, std::placeholders::_1, std::placeholders::_2), Canvas::I->m_smask, 0);
            
            //m_points.clear();
            // close the path
            m_points.push_back(m_points.back());
            m_points.push_back(m_points.back());
            m_points.push_back(m_points.front());
            Canvas::I->project2Dpoints(m_points);
            // reset m_shape to contour rendering
            m_shape.update_vertices(m_points.data(), (int)m_points.size());
        }
    }
    else
    {
        Canvas::I->m_smask_active = false;
    }
    m_active_tool = false;
}

void CanvasModeMaskLine::enter(kCanvasMode prev)
{
    //m_points2d.clear();
    //m_points.clear();
    //Canvas::I->m_smask.clear({0, 0, 0, 0});
    //m_active_tool = true;
}

void CanvasModeMaskLine::on_MouseEvent(MouseEvent* me, glm::vec2& loc)
{
    if (Canvas::I->m_touch_lock && me->m_source == kEventSource::Touch)
        return;
    switch (me->m_type)
    {
    case kEventType::MouseDownL:
    {
        if (m_active_tool == false)
        {
            m_active_tool = true;
            m_points2d.clear();
            m_points.clear();
            Canvas::I->m_smask.clear({ 0, 0, 0, 0 });
            Canvas::I->m_smask_active = true;
            Canvas::I->m_smask_mode = 2;
        }
        node->mouse_capture();
        m_dragging = true;
        m_points2d.push_back(loc);
        vertex_t vert;
        vert.pos = glm::vec4(loc, 0, 1);
        m_points.push_back(vert);
        m_shape.update_vertices(m_points.data(), (int)m_points.size());
        break;
    }
    case kEventType::MouseUpL:
        node->mouse_release();
        m_dragging = false;
        if (m_points.size() > 1)
        {
            m_selection_cam = Canvas::I->get_camera();
            m_points.push_back(m_points.back());
            m_shape.update_vertices(m_points.data(), (int)m_points.size());
        }
        break;
    case kEventType::MouseMove:
    {
        if (m_dragging)
        {
            if (glm::distance(m_points2d.front(), loc) < 10)
                loc = m_points2d.front();
            m_points.back().pos = glm::vec4(loc, 0, 1);
            m_points2d.back() = loc;
            m_shape.update_vertices(m_points.data(), (int)m_points.size());
        }
        break;
    }
    case kEventType::MouseCancel:
        if (m_dragging)
        {
            m_points.pop_back();
            m_shape.update_vertices(m_points.data(), (int)m_points.size());
        }
        m_dragging = false;
        node->mouse_release();
        if (m_points.size() < 4)
        {
            m_points.clear();
            m_shape.update_vertices(m_points.data(), (int)m_points.size());
        }
        break;
    default:
        break;
    }
}

void CanvasModeMaskLine::on_Draw(const glm::mat4& ortho, const glm::mat4& proj, const glm::mat4& camera)
{
    if (m_points.size() > 1)
    {
        if (m_active_tool)
        {
            ShaderManager::use(kShader::Color);
            ShaderManager::u_mat4(kShaderUniform::MVP, glm::scale(glm::vec3(1,-1,1)) * ortho);
            ShaderManager::u_vec4(kShaderUniform::Col, { 0, 0, 0, 1 });
            //m_dragging ? m_shape.draw_stroke() : m_shape.draw_fill();
            m_shape.draw_stroke();
        }
        else
        {
            ShaderManager::use(kShader::Color);
            ShaderManager::u_mat4(kShaderUniform::MVP, proj * camera);
            ShaderManager::u_vec4(kShaderUniform::Col, { 0, 0, 0, 1 });
            m_shape.draw_stroke();
        }
    }
}

////////////////////////////////////////////////////////////////////

void CanvasModeFill::init()
{
    m_shape.create();
}

void CanvasModeFill::leave(kCanvasMode next)
{
    if (m_points.size() > 2)
    {
        auto drawer = [this](const glm::mat4& camera, const glm::mat4& proj) {
            ShaderManager::use(kShader::Color);
            ShaderManager::u_mat4(kShaderUniform::MVP, proj * camera);
            ShaderManager::u_vec4(kShaderUniform::Col, {1, 1, 1, 1});
            m_shape.draw_fill();
        };
        Canvas::I->draw_objects_direct(std::bind(drawer, std::placeholders::_1, std::placeholders::_2), Canvas::I->m_smask, 0);
        m_points.clear();
        Canvas::I->m_smask_active = true;
    }
    else
    {
        Canvas::I->m_smask_active = false;
    }
}

void CanvasModeFill::on_MouseEvent(MouseEvent* me, glm::vec2& loc)
{
    if (Canvas::I->m_touch_lock && me->m_source == kEventSource::Touch)
        return;
    switch (me->m_type)
    {
    case kEventType::MouseDownL:
    {
        node->mouse_capture();
        m_dragging = true;
        glm::vec3 ro, rd, hit_o, hit_d;
        glm::vec2 hit_fb;
        int plane_id;
        if (Canvas::I->point_trace_plane(loc, ro, rd, hit_o, hit_d, hit_fb, 0))
        {
            m_dirty_planes[plane_id]++;
            vertex_t v;
            v.pos = glm::vec4(hit_o, 1);
            v.uvs = glm::vec2(0);
            if (m_points.size() < 3)
            {
                m_points.push_back(v);
            }
            else
            {
                auto last = m_points.back();
                m_points.push_back(m_points[0]);
                m_points.push_back(last);
                m_points.push_back(v);
            }
            m_shape.update_vertices(m_points.data(), (int)m_points.size());
        }
        Canvas::I->m_smask.clear({0, 0, 0, 0});
        break;
    }
    case kEventType::MouseUpL:
        node->mouse_release();
        m_dragging = false;
        break;
    case kEventType::MouseMove:
    {
        glm::vec3 ro, rd, hit_o, hit_d;
        glm::vec2 fb_pos;
        int plane_id;
        if (m_dragging && Canvas::I->point_trace(loc, ro, rd, hit_o, fb_pos, hit_d, plane_id))
        {
            vertex_t v;
            v.pos = glm::vec4(hit_o, 1);
            v.uvs = glm::vec2(0);
            m_points.back() = v;
            m_shape.update_vertices(m_points.data(), (int)m_points.size());
        }
        break;
    }
    case kEventType::MouseCancel:
        if (m_dragging)
        {
            m_points.pop_back();
            m_shape.update_vertices(m_points.data(), (int)m_points.size());
        }
        m_dragging = false;
        node->mouse_release();
        if (m_points.size() < 4)
        {
            m_points.clear();
            m_shape.update_vertices(m_points.data(), (int)m_points.size());
        }
        break;
    default:
        break;
    }
}

void CanvasModeFill::on_Draw(const glm::mat4& ortho, const glm::mat4& proj, const glm::mat4& camera)
{
    if (!m_points.empty())
    {
        ShaderManager::use(kShader::Color);
        ShaderManager::u_mat4(kShaderUniform::MVP, proj * camera);
        ShaderManager::u_vec4(kShaderUniform::Col, { 0, 0, 0, .25 });
        m_dragging ? m_shape.draw_fill() : m_shape.draw_stroke();
    }
}

////////////////////////////////////////////////////////////////////

void CanvasModeTransform::init()
{
    m_sphere.create(1.f, glm::radians(-10.f), glm::radians(10.f), glm::radians(-10.f), glm::radians(10.f), 1.f);
    m_circle.create<16>(1.f);
    for (int i = 0; i < 6; i++)
    {
        m_shape[i].create();
        m_points_face[i].clear();
    }
    m_xform = glm::mat4(1);
    m_xform_local = glm::mat4(1);
}

void CanvasModeTransform::enter(kCanvasMode prev)
{
    m_commit_on_leave = false;
    for (int i = 0; i < 6; i++)
    {
        m_shape[i].clear();
        m_points_face[i].clear();
    }

    if (m_action == ActionType::Import)
    {
        float aspect = 1.f;
        if (m_source_image.data())
        {
            m_tex[0].create(m_source_image);
            aspect = (float)m_source_image.width / (float)m_source_image.height;
        }

        auto center = zw(Canvas::I->m_box) * 0.5f;
        glm::vec2 bb_sz = glm::vec2(aspect, 1.f) * 100.f * App::I->zoom;
        glm::vec2 bb_min = center - bb_sz * 0.5f;
        glm::vec2 bb_max = center + bb_sz * 0.5f;
        glm::vec2 midpoint = (bb_min + bb_max) * 0.5f;

        auto cam_up = glm::inverse(Canvas::I->m_mv) * glm::vec4(0, 1, 0, 1);
        auto center_mat = glm::lookAt({ 0, 0, 0 }, Canvas::I->point_trace(midpoint), xyz(cam_up));
        m_xform = glm::inverse(glm::lookAt({ 0, 0, 0 }, Canvas::I->point_trace(midpoint), xyz(cam_up)));
        m_xform_local = glm::mat4(1);
        
        corners.clear();
        corners.emplace_back(bb_min, 0);             // A
        corners.emplace_back(bb_max, 0);             // C
        corners.emplace_back(bb_max.x, bb_min.y, 0); // B
        corners.emplace_back(bb_min.x, bb_max.y, 0); // D
        corners.emplace_back(midpoint, 0);
        corners.emplace_back(midpoint + (bb_max - bb_min) * glm::vec2(0.75f, 0), 0);
        
        for (auto& c : corners)
            c = center_mat * glm::vec4(Canvas::I->point_trace(c), 1);

        m_points_face[0] = std::vector<vertex_t>({
            vertex_t(corners[0], { 0, 0 }),
            vertex_t(corners[2], { 1, 0 }),
            vertex_t(corners[1], { 1, 1 }),
            vertex_t(corners[3], { 0, 1 }),
        });
        auto shape3d = triangulate(m_points_face[0]);
        m_shape[0].update_vertices(shape3d.data(), (int)shape3d.size());

        m_commit_on_leave = true;

        return;
    }

    // avoid recursive loop, store the last different mode not using Transform
    static kCanvasMode last_prev = kCanvasMode::Draw;
    if (prev != kCanvasMode::Copy && prev != kCanvasMode::Cut && prev != kCanvasMode::Import)
        last_prev = prev;

    if (prev != kCanvasMode::MaskFree && prev != kCanvasMode::MaskLine)
    {
        Canvas::set_mode(last_prev);
        return;
    }

    auto m = static_cast<CanvasModeMaskBase*>(Canvas::I->modes[(int)prev][0]);

    if (m->m_points2d.size() < 3)
    {
        Canvas::set_mode(last_prev);
        return;
    }

    Canvas::I->m_smask_active = false;
    auto points = m->m_points2d;

    Canvas::I->push_camera();
    Canvas::I->set_camera(m->m_selection_cam);

    glm::vec2 bb_min(FLT_MAX);
    glm::vec2 bb_max(-FLT_MAX);
    for (auto p2d : points)
    {
        bb_min = glm::min(bb_min, p2d);
        bb_max = glm::max(bb_max, p2d);
    }
    glm::vec2 midpoint = (bb_min + bb_max) * 0.5f;
    auto cam_up = glm::inverse(Canvas::I->m_mv) * glm::vec4(0, 1, 0, 1);
    auto center_mat = glm::lookAt({ 0, 0, 0 }, Canvas::I->point_trace(midpoint), xyz(cam_up));
    m_xform = glm::inverse(glm::lookAt({ 0, 0, 0 }, Canvas::I->point_trace(midpoint), xyz(cam_up)));
    m_xform_local = glm::mat4(1);
    corners.clear();
    corners.emplace_back(bb_min, 0);
    corners.emplace_back(bb_max, 0);
    corners.emplace_back(bb_max.x, bb_min.y, 0);
    corners.emplace_back(bb_min.x, bb_max.y, 0);
    corners.emplace_back(midpoint, 0);
    corners.emplace_back(midpoint + (bb_max-bb_min) * glm::vec2(0.75f, 0), 0);
    for (auto& c : corners)
        c = center_mat * glm::vec4(Canvas::I->point_trace(c), 1);

    for (int plane = 0; plane < 6; plane++)
    {
        auto face = Canvas::I->face_to_shape2D(plane);
        auto shape2d = poly_intersect(points, face);
        if (shape2d.size() < 3 || face.empty())
        {
            m_shape[plane].clear();
            m_points_face[plane].clear();
            continue;
        }
        m_points_face[plane].reserve(shape2d.size());
        glm::vec2 bb_min(Canvas::I->m_size);
        glm::vec2 bb_max(0, 0);
        for (auto p2d : shape2d)
        {
            p2d.y = Canvas::I->m_box.w - p2d.y - 1;
            auto p2d_clip = ((p2d / zw(Canvas::I->m_box)) * 2.f - 1.f);
            auto p3d_plane = Canvas::I->m_plane_unproject[plane] * glm::vec4(p2d_clip, 0, 1);
            if (p3d_plane.w < 0)
                continue;
            auto p3d_norm = -p3d_plane / p3d_plane.z;
            auto p2d_plane = xy(p3d_norm);
            auto p2d_plane_raster = (p2d_plane * 0.5f + 0.5f) * Canvas::I->m_size;
            auto p3d_world = Canvas::I->m_plane_transform[plane] * glm::vec4(p2d_plane, -1, 1);
            bb_min = glm::min(bb_min, p2d_plane_raster);
            bb_max = glm::max(bb_max, p2d_plane_raster);

            //glm::vec3 pt_o, pt_d;
            //Canvas::I->point_unproject(p2d, pt_o, pt_d);

            vertex_t v;
            v.pos = glm::vec4(xyz(p3d_world), 1);
            v.uvs = p2d_plane_raster;
            m_points_face[plane].push_back(v);
        }

        if (m_points_face[plane].size() < 3)
        {
            m_shape[plane].clear();
            m_points_face[plane].clear();
            continue;
        }

        auto bb_sz = bb_max - bb_min;
        for (auto& v : m_points_face[plane])
        {
            v.uvs2 = v.uvs / Canvas::I->m_size;
            v.uvs = (v.uvs - bb_min) / bb_sz;
            v.pos = center_mat * v.pos;
        }

        auto shape3d = triangulate(m_points_face[plane]);

        App::I->render_task([&]
        {
            m_shape[plane].update_vertices(shape3d.data(), (int)shape3d.size());
            Canvas::I->m_layers[Canvas::I->m_current_layer_idx]->rtt(plane).bindFramebuffer();
            m_tex[plane].create(bb_sz.x, bb_sz.y);
            m_tex[plane].bind();
            glCopyTexSubImage2D(
                pp::renderer::gl::texture_2d_target(),
                0, 0, 0, bb_min.x, bb_min.y, bb_sz.x, bb_sz.y);
            m_tex[plane].unbind();
            Canvas::I->m_layers[Canvas::I->m_current_layer_idx]->rtt(plane).unbindFramebuffer();
        });

        m_commit_on_leave = true;
    }
    
    if (m_action == ActionType::Cut)
    {
        auto& layer = Canvas::I->m_layers[Canvas::I->m_current_layer_idx];

        glm::mat4 proj = glm::perspective(glm::radians(90.f), 1.f, .01f, 1000.f);

        auto action = new ActionStroke;
        action->was_saved = !Canvas::I->m_unsaved;

        for (int i = 0; i < 6; i++)
        {
            auto plane_camera = glm::lookAt(glm::vec3(0), Canvas::I->m_plane_origin[i], Canvas::I->m_plane_tangent[i]);
            auto mvp = proj * plane_camera * m_xform * m_xform_local;

            glm::vec2 bb_min(Canvas::I->m_size);
            glm::vec2 bb_max(0, 0);
            for (int j = 0; j < 6; j++)
            {
                for (auto p : m_points_face[j])
                {
                    auto p_clip = mvp * p.pos;
                    auto p_norm = p_clip / p_clip.w;
                    if (p_clip.w < 0 || glm::any(glm::greaterThan(glm::abs(xy(p_norm)), { 1, 1 })))
                        continue;
                    auto p_raster = (xy(p_norm) * 0.5f + 0.5f) * Canvas::I->m_size;
                    bb_min = glm::max({ 0, 0 }, glm::min(bb_min, p_raster));
                    bb_max = glm::min(Canvas::I->m_size, glm::max(bb_max, p_raster));
                }
            }
            glm::vec2 pad(2);
            bb_min = glm::max({ 0, 0 }, glm::floor(bb_min) - pad);
            bb_max = glm::min(Canvas::I->m_size, glm::ceil(bb_max) + pad);
            auto bb_sz = bb_max - bb_min;

            if (bb_sz.x <= 0.f || bb_sz.y <= 0.f)
                continue;

            action->m_image[i] = std::make_unique<uint8_t[]>(bb_sz.x * bb_sz.y * 4);
            action->m_box[i] = { bb_min, bb_max };
            action->m_old_box[i] = layer->box(i);
            action->m_old_dirty[i] = layer->face(i);

            layer->face(i) = true;
            layer->box(i) = {
                glm::min(xy(layer->box(i)), bb_min),
                glm::max(zw(layer->box(i)), bb_max),
            };

            App::I->render_task([&]
            {
                glViewport(0, 0, layer->w, layer->h);
                glDisable(pp::renderer::gl::depth_test_state());
                glDisable(pp::renderer::gl::blend_state());
                set_active_texture_unit(0);
                ShaderManager::use(kShader::Color);
                ShaderManager::u_mat4(kShaderUniform::MVP, mvp);
                ShaderManager::u_vec4(kShaderUniform::Col, { 0, 0, 0, 0 });
                layer->rtt(i).bindFramebuffer();
                // copy framebuffer to action data
                glReadPixels(
                    bb_min.x,
                    bb_min.y,
                    bb_sz.x,
                    bb_sz.y,
                    pp::renderer::gl::rgba_pixel_format(),
                    pp::renderer::gl::unsigned_byte_component_type(),
                    action->m_image[i].get());
                for (int j = 0; j < 6; j++)
                    m_shape[j].draw_fill();
                layer->rtt(i).unbindFramebuffer();
            });
        }

        action->m_layer_idx = Canvas::I->m_current_layer_idx;
        action->m_frame_idx = Canvas::I->layer().m_frame_index;
        action->m_canvas = Canvas::I;
        //action->m_stroke = std::move(m_current_stroke);
        ActionManager::add(action);

        m_source_image.destroy();
    }

    Canvas::I->pop_camera();
}

void CanvasModeTransform::leave(kCanvasMode next)
{
    if (!m_commit_on_leave)
        return;

    auto& layer = Canvas::I->m_layers[Canvas::I->m_current_layer_idx];

    glm::mat4 proj = glm::perspective(glm::radians(90.f), 1.f, .01f, 1000.f);

    auto action = new ActionStroke;
    action->was_saved = !Canvas::I->m_unsaved;

    for (int i = 0; i < 6; i++)
    {
        auto plane_camera = glm::lookAt(glm::vec3(0), Canvas::I->m_plane_origin[i], Canvas::I->m_plane_tangent[i]);
        auto mv = plane_camera * m_xform * m_xform_local;
        auto mvp = proj * mv;
        

        std::vector<glm::vec2> poly2d;
        static std::vector<glm::vec2> face_corners{ {1,1}, {-1,1}, {-1,-1}, {1,-1} };
        for (int j = 0; j < 6; j++)
        {
            std::vector<glm::vec3> poly_cam;
            poly_cam.reserve(m_points_face[j].size());
            for (auto p : m_points_face[j])
                poly_cam.push_back(mv * p.pos);
            
            auto poly_clipped = poly_clip_near(poly_cam, 0.01f);

            for (auto p : poly_clipped)
            {
                auto p_clip = proj * glm::vec4(p, 1);
                if (p_clip.w < 0)
                    continue;
                auto p_norm = p_clip / p_clip.w;
                poly2d.push_back(p_norm);
            }
        }

        auto clipped = poly_intersect(poly2d, face_corners);
        
        glm::vec2 bb_min(Canvas::I->m_size);
        glm::vec2 bb_max(0, 0);
        for (auto p_norm : clipped)
        {
            auto p_raster = (p_norm * 0.5f + 0.5f) * Canvas::I->m_size;
            bb_min = glm::max({ 0, 0 }, glm::min(bb_min, p_raster));
            bb_max = glm::min(Canvas::I->m_size, glm::max(bb_max, p_raster));
        }
        glm::vec2 pad(2);
        bb_min = glm::max({ 0, 0 }, glm::floor(bb_min) - pad);
        bb_max = glm::min(Canvas::I->m_size, glm::ceil(bb_max) + pad);
        auto bb_sz = bb_max - bb_min;

        if (clipped.empty() || bb_sz.x <= 0.f || bb_sz.y <= 0.f)
            continue;

        action->m_image[i] = std::make_unique<uint8_t[]>(bb_sz.x * bb_sz.y * 4);
        action->m_box[i] = { bb_min, bb_max };
        action->m_old_box[i] = layer->box(i);
        action->m_old_dirty[i] = layer->face(i);

        layer->face(i) = true;
        layer->box(i) = {
            glm::min(xy(layer->box(i)), bb_min),
            glm::max(zw(layer->box(i)), bb_max),
        };

        App::I->render_task([&]
        {
            layer->rtt(i).bindFramebuffer();

            glDisable(pp::renderer::gl::depth_test_state());
            glDisable(pp::renderer::gl::blend_state());
            set_active_texture_unit(0);
            glViewport(0, 0, layer->rtt(i).getWidth(), layer->rtt(i).getHeight());

            // save fb content for history
            glReadPixels(
                bb_min.x,
                bb_min.y,
                bb_sz.x,
                bb_sz.y,
                pp::renderer::gl::rgba_pixel_format(),
                pp::renderer::gl::unsigned_byte_component_type(),
                action->m_image[i].get());
            // copy fb content to texture for blending
            set_active_texture_unit(0);
            Canvas::I->m_tex2[i].bind();
            glCopyTexSubImage2D(
                pp::renderer::gl::texture_2d_target(),
                0, bb_min.x, bb_min.y, bb_min.x, bb_min.y, bb_sz.x, bb_sz.y);
            // slot for m_tex
            set_active_texture_unit(1);
            for (int j = 0; j < 6; j++)
            {
                ShaderManager::use(kShader::CompDraw);
                ShaderManager::u_mat4(kShaderUniform::MVP, mvp);
                ShaderManager::u_int(kShaderUniform::Tex, 0);
                ShaderManager::u_int(kShaderUniform::TexStroke, 1);
                ShaderManager::u_float(kShaderUniform::Alpha, 1);
                ShaderManager::u_int(kShaderUniform::UseFragcoord, true);
                ShaderManager::u_int(kShaderUniform::Lock, false);
                ShaderManager::u_int(kShaderUniform::Mask, false);
                ShaderManager::u_vec2(kShaderUniform::Resolution, Canvas::I->m_size);
                ShaderManager::u_int(kShaderUniform::BlendMode, 0);
                ShaderManager::u_int(kShaderUniform::UseDual, false);
                ShaderManager::u_int(kShaderUniform::UsePattern, false);
                Canvas::I->m_sampler_linear.bind(1);
                Canvas::I->m_sampler_linear.bind(0);
                m_tex[j].bind();
                m_shape[j].draw_fill();
                m_tex[j].unbind();
            }
            layer->rtt(i).unbindFramebuffer();
        });
    }

    action->m_layer_idx = Canvas::I->m_current_layer_idx;
    action->m_canvas = Canvas::I;
    action->m_frame_idx = Canvas::I->layer().m_frame_index;
    //action->m_stroke = std::move(m_current_stroke);
    ActionManager::add(action);
    layer->optimize();
    //auto m = static_cast<CanvasModeMaskFree*>(Canvas::I->modes[(int)kCanvasMode::MaskFree][0]);
    //m->clear();
}

void CanvasModeTransform::on_Draw(const glm::mat4& ortho, const glm::mat4& proj, const glm::mat4& camera)
{
    bool depth = glIsEnabled(pp::renderer::gl::depth_test_state());
    glDisable(pp::renderer::gl::depth_test_state());

    glEnable(pp::renderer::gl::blend_state());
    for (int i = 0; i < 6; i++)
    {
        ShaderManager::use(kShader::Color);
        ShaderManager::u_mat4(kShaderUniform::MVP, proj * camera * m_xform * m_xform_local);
        ShaderManager::u_vec4(kShaderUniform::Col, { 0, 1, 1, .1 });
        m_shape[i].draw_fill();

        ShaderManager::use(kShader::Texture);
        ShaderManager::u_int(kShaderUniform::Tex, 0);
        ShaderManager::u_mat4(kShaderUniform::MVP, proj * camera * m_xform * m_xform_local);
        set_active_texture_unit(0);
        m_tex[i].bind();
        Canvas::I->m_sampler_linear.bind(0);
        m_shape[i].draw_fill();
        m_tex[i].unbind();
    }

    ShaderManager::use(kShader::Color);
    auto m2d = Canvas::I->m_proj * Canvas::I->m_mv * m_xform * m_xform_local;
    for (int i = 0; i < corners.size(); i++)
    {
        auto c = m2d * glm::vec4(corners[i], 1);
        if (c.w < 0)
            continue;
        auto c3d = c / c.w;
        auto c2d = (xy(c3d) * 0.5f + 0.5f) * zw(Canvas::I->m_box);

        ShaderManager::u_mat4(kShaderUniform::MVP,
            ortho * glm::translate(glm::vec3(c2d, 0)) * glm::scale(glm::vec3(20.f) * App::I->zoom));

        // draw inside
        ShaderManager::u_vec4(kShaderUniform::Col, { 1, 1, 1, i == corner_hl ? 1.f : .1f });
        m_circle.draw_fill();

        // draw black border
        ShaderManager::u_vec4(kShaderUniform::Col, { 0, 0, 0, 1 });
        m_circle.draw_stroke();
    }

    if (depth) glEnable(pp::renderer::gl::depth_test_state());
}

void CanvasModeTransform::on_MouseEvent(MouseEvent* me, glm::vec2& loc)
{
    auto m2d = glm::scale(glm::vec3(1, -1, 1)) * Canvas::I->m_proj *
        Canvas::I->m_mv * m_xform * m_xform_local;

    switch (me->m_type)
    {
    case kEventType::MouseDownR:
    {
    }
    break;
    case kEventType::MouseUpL:
        m_dragging = false;
        corner_hl = -1;
        break;
    case kEventType::MouseDownL:
        corner_hl = -1;
        corners2d.resize(corners.size());
        for (int i = 0; i < corners.size(); i++)
        {
            auto c = m2d * glm::vec4(corners[i], 1);
            corners2d[i] = ((xy(c) / c.z) * 0.5f + 0.5f) * zw(Canvas::I->m_box);
            float d = glm::distance(corners2d[i], loc);
            if (d < 20.f * App::I->zoom)
                corner_hl = i;
        }
        if (corner_hl != -1)
        {
            m_dragging = true;
            m_drag_start = loc;
            m_drag_xform = m_xform;
            m_drag_xform_local = m_xform_local;
            m_drag_corner = corner_hl;
            m_drag_corners2d = corners2d;
            if (m_drag_corner < 4)
            {
                m_drag_diag = glm::distance(corners2d[4], corners2d[m_drag_corner]);
            }
        }
        break;
    case kEventType::MouseMove:
    {
        corner_hl = -1;
        corners2d.resize(corners.size());
        for (int i = 0; i < corners.size(); i++)
        {
            auto c = m2d * glm::vec4(corners[i], 1);
            corners2d[i] = ((xy(c) / c.z) * 0.5f + 0.5f) * zw(Canvas::I->m_box);
            float d = glm::distance(corners2d[i], loc);
            if (d < 20.f * App::I->zoom)
                corner_hl = i;
        }
        if (m_dragging)
        {
            auto cam_up = glm::inverse(Canvas::I->m_mv) * glm::vec4(0, 1, 0, 1);
            //auto diff = glm::radians(loc - m_drag_start) * 0.1f;
            //auto m = glm::eulerAngleXY(-diff.y, -diff.x);
            //m_xform = m * m_drag_xform;
            if (m_drag_corner > -1 && m_drag_corner < 4)
            {
                auto diag = glm::distance(corners2d[4], loc);
                auto scale = diag / m_drag_diag;
                m_xform_local = m_drag_xform_local * glm::scale(glm::vec3(scale, scale, 1));
            }
            if (m_drag_corner == 4)
            {
                m_xform = glm::inverse(glm::lookAt({ 0, 0, 0 }, Canvas::I->point_trace(loc), xyz(cam_up)));
            }
            if (m_drag_corner == 5)
            {
                auto a = glm::normalize(m_drag_corners2d[m_drag_corner] - m_drag_corners2d[4]);
                auto b = glm::normalize(loc - m_drag_corners2d[4]);
                auto angle = glm::orientedAngle(a, b);
                m_xform_local = m_drag_xform_local * glm::eulerAngleZ(-angle);
            }
        }
/*
        {
            auto p2d = loc;
            //p2d.y = Canvas::I->m_box.w - p2d.y - 1;
            auto p2d_clip = ((p2d / zw(Canvas::I->m_box)) * 2.f - 1.f) * glm::vec2(1, -1);
            auto p3d_plane = Canvas::I->m_plane_unproject[0] * glm::vec4(p2d_clip, 0, 1);
            auto p2d_plane = -p3d_plane / p3d_plane.z;
//             auto p3d_world = Canvas::I->m_plane_transform[0] * glm::vec4(p2d_plane, -1, 1);
            int x = 0;
            LOG("pt %f %f %f %f", p2d_plane.x, p2d_plane.y, p2d_plane.z, p2d_plane.w);
        }
*/

    }
    break;
    default:
        break;
    }
}

////////////////////////////////////////////////////////////////////

void CanvasModeFloodFill::on_MouseEvent(MouseEvent* me, glm::vec2& loc)
{
#if defined(__IOS__)
    m_draw_tip = (me->m_source == kEventSource::Mouse && me->m_type != kEventType::MouseUpL);
#else
    m_draw_tip = (me->m_source == kEventSource::Mouse || me->m_source == kEventSource::Stylus);
#endif
    if (Canvas::I->m_touch_lock && me->m_source == kEventSource::Touch)
        return;

    struct ActionFloodFill : public Action
    {
        std::shared_ptr<Layer> m_layer;
        std::shared_ptr<LayerFrame::Snapshot> m_snap;
        std::array<bool, 6> m_dirty_face = SIXPLETTE(false);
        std::array<glm::vec4, 6> m_dirty_box = SIXPLETTE(glm::vec4(0));
        glm::ivec2 m_pos;
        glm::vec4 m_color;
        float m_threshold;
        int m_layer_index;
        int m_plane;
        virtual void run() override { }
        virtual size_t memory() override { return m_snap->memsize(); }
        virtual Action* get_redo() override
        {
            auto a = new ActionFloodFill;
            a->m_direction = reverse_direction();
            a->m_layer = m_layer;
            a->m_snap = m_snap;
            a->m_pos = m_pos;
            a->m_color = m_color;
            a->m_layer_index = m_layer_index;
            a->m_threshold = m_threshold;
            a->m_plane = m_plane;
            a->m_dirty_box = m_dirty_box;
            a->m_dirty_face = m_dirty_face;
            return a;
        }
        virtual void undo() override
        {
            if (m_direction == Direction::Undo)
            {
                m_layer->restore(*m_snap);
            }
            else
            {
                Canvas::FloodData plane_data;
                std::unique_ptr<glm::vec4> color;
                Canvas::I->flood_fill(m_layer_index, m_plane, { m_pos },
                    plane_data, m_threshold, m_color, color);
                plane_data.apply();
            }
        }
    };


    switch (me->m_type)
    {
    case kEventType::MouseUpL:
        glm::vec3 ro, rd, hit, n;
        glm::vec2 pos;
        int plane;
        if (Canvas::I->point_trace(loc, ro, rd, hit, pos, n, plane))
        {
            Canvas::FloodData plane_data;
            std::unique_ptr<glm::vec4> color;
            Canvas::I->flood_fill(Canvas::I->m_current_layer_idx, plane, { (glm::ivec2)pos },
                plane_data, m_tool->get_threshold(), Canvas::I->m_current_brush->m_tip_color, color);
            
            auto a = new ActionFloodFill;
            a->m_direction = Action::Direction::Undo;
            a->m_layer = plane_data.layer;
            a->m_snap = std::make_shared<LayerFrame::Snapshot>(plane_data.layer->snapshot());
            a->m_pos = (glm::ivec2)pos;
            a->m_color = Canvas::I->m_current_brush->m_tip_color;
            a->m_layer_index = Canvas::I->m_current_layer_idx;
            a->m_threshold = m_tool->get_threshold();
            a->m_plane = plane;
            a->m_dirty_box = plane_data.layer->frame().m_dirty_box;
            a->m_dirty_face = plane_data.layer->frame().m_dirty_face;
            ActionManager::add(a);

            plane_data.apply();
        }
        break;
    default:
        break;
    }
}

void CanvasModeFloodFill::init()
{
    TextureManager::load(m_cursor_path.c_str());
}

void CanvasModeFloodFill::on_Draw(const glm::mat4& ortho, const glm::mat4& proj, const glm::mat4& camera)
{
    if (m_draw_tip)
    {
        ShaderManager::use(kShader::Texture);
        ShaderManager::u_mat4(kShaderUniform::MVP, 
            glm::scale(glm::vec3(1, -1, 1))
            * ortho
            * glm::translate(glm::vec3(Canvas::I->m_cur_pos, 0.f))
            * glm::scale(glm::vec3(32, 32, 1))
            * glm::translate(glm::vec3(0.5f, -0.5f, 0))
        );
        ShaderManager::u_int(kShaderUniform::Tex, 0);
    
        auto& t = TextureManager::get(m_cursor_id);
    
        Canvas::I->m_sampler_linear.bind(0);
        t.bind();
        Canvas::I->m_plane.draw_fill();
        t.unbind();
    }
}

void CanvasModeFloodFill::enter(kCanvasMode prev)
{
    auto tools = App::I->layout[App::I->main_id]->find("tools-container");
    m_tool = tools->add_child<NodeToolBucket>();
}

void CanvasModeFloodFill::leave(kCanvasMode next)
{
    m_tool->destroy();
    m_tool = nullptr;
}
