#include "pch.h"
#include "log.h"
#include "brush.h"
#include "asset.h"
#include "app.h"

StrokeSample Stroke::randomize_sample(const glm::vec3& pos, float pressure, float dir_angle)
{
    float size_dyn = m_brush->m_tip_size_pressure ? pressure : 1.f;
    float flow_dyn = m_brush->m_tip_flow_pressure ? pressure : 1.f;
    float opacity_dyn = m_brush->m_tip_opacity_pressure ? pressure : 1.f;
    float size = glm::min(m_brush->m_tip_size / glm::tan(glm::radians(m_camera.fov * 0.5f)), m_max_size);
    float randflipx = m_brush->m_tip_randflipx ? rnd_bneg() : 1.f;
    float randflipy = m_brush->m_tip_randflipy ? rnd_bneg() : 1.f;
    
    glm::vec2 scatter_axis = m_brush->m_jitter_scatter_bothaxis ? glm::vec2(1.f, 1.f) : glm::vec2(0.f, 1.f);
    auto scatter_scale = glm::vec3(scatter_axis * glm::orientate2(-dir_angle), 1.f);

    float aspect_jitter = m_brush->m_jitter_aspect_bothaxis ?
        rnd_neg() * 0.5f * m_brush->m_jitter_aspect :
        rnd_nor() * 0.5f * m_brush->m_jitter_aspect;
    float aspect = glm::clamp(m_brush->m_tip_aspect + aspect_jitter, 0.1f, 0.9f);
    glm::vec2 aspect_scale = {
        (aspect <= 0.5 ? aspect * 2.f : 1.f),
        (aspect > 0.5 ? 1.f - (aspect - .5f) * 2.f : 1.f)
    };

    StrokeSample s;
    s.origin = pos;
    s.scale = m_brush->m_tip_scale * randflipx * (m_brush->m_tip_flipx ? -1.f : 1.f) * aspect_scale;
    s.angle = (m_brush->m_tip_angle + rnd_neg() * m_brush->m_jitter_angle) * (float)(M_PI * 2.0);
    s.size = size * (1.f - rnd_nor() * m_brush->m_jitter_scale) * size_dyn;
    s.pos = pos + (scatter_scale * rnd_vec() * m_brush->m_jitter_scatter * s.size * 0.5f); // 0.5 because PS scatters by half size
    s.flow = m_brush->m_tip_flow * (1.f - rnd_nor() * m_brush->m_jitter_flow) * flow_dyn;
    s.opacity = m_brush->m_tip_opacity * (1.f - rnd_nor() * m_brush->m_jitter_opacity) * opacity_dyn;
    
    auto hsv = m_tip_color;
    float eachtip = m_brush->m_jitter_hsv_eachsample ? 1.f : 0.f;
    hsv.x = glm::fract(glm::mix(hsv.x, (pressure - 0.5f) * 2.0f, m_brush->m_tip_hue) + (rnd_nor() - 0.5f) * m_brush->m_jitter_hue * eachtip);
    hsv.y = glm::clamp(glm::mix(hsv.y, (1.f - pressure - 0.5f) * 2.0f, m_brush->m_tip_sat) + (rnd_nor() - 0.5f) * m_brush->m_jitter_sat * eachtip, 0.f, 1.f);
    hsv.z = glm::clamp(glm::mix(hsv.z, (pressure - 0.5f) * 2.0f, m_brush->m_tip_val) + (rnd_nor() - 0.5f) * m_brush->m_jitter_val * eachtip, 0.f, 1.f);
    //m_hsv_jitter.add(hsv);
    s.col = convert_hsv2rgb(hsv);

    return s;
}

void Stroke::randomize_prng()
{
    std::random_device rd;
    prng.seed(rd());
}

std::vector<StrokeSample> Stroke::compute_samples()
{
    std::vector<StrokeSample> samples;
    auto dirs = m_interp_dir.compute();
    auto points = m_interp_main.compute();
    int dir_i = 0;
    for (const auto& kp : points)
    {
        for (; need_dir() && dir_i < dirs.size() && (kp.dist >= dirs[dir_i].dist || !m_dir_valid); dir_i++)
        {
            m_dir_angle = -glm::orientedAngle(-dirs[dir_i].dir, m_dir_ref);
            if (m_dir_angle > glm::radians(120.f) || !m_dir_valid)
            {
                auto old_dir = m_dir_ref;
                m_dir_ref = -dirs[dir_i].dir;
                m_dir_ref_angle = -glm::orientedAngle(m_dir_ref, { 1, 0 });
                m_dir_angle = -glm::orientedAngle(-dirs[dir_i].dir, m_dir_ref);
                auto angle_diff = -glm::orientedAngle(m_dir_ref, old_dir);
                for (int i = 0; i < m_direction.m_count; i++)
                    m_direction.m_vec[i] -= angle_diff;
            }
            if (glm::abs(m_direction.average() - m_dir_angle) > glm::radians(120.f))
                m_direction.clear();
            if (!m_dir_valid)
                m_dir_init = m_dir_angle + m_dir_ref_angle;
            m_dir_valid = true;
        }
        auto s = randomize_sample(kp.pos, kp.pressure, m_dir_angle);
        if (s.valid())
        {
            if (m_brush->m_tip_angle_follow)
            {
                m_direction.add(m_dir_angle);
                s.angle += m_direction.average() + m_dir_ref_angle;
            }
            else if (m_brush->m_tip_angle_init)
            {
                s.angle += m_dir_init;
            }
            m_prev_sample = s;
            samples.push_back(s);
        }
        else
        {
            static bool invalid_logged = false;
            if (!invalid_logged)
            {
                for (auto const& p : m_keypoints)
                    LOG("point dist %f pos %f %f %f", p.dist, p.pos.x, p.pos.y, p.pos.z);
                invalid_logged = true;
            }
            LOG("Invalid sample");
        }
    }
    return samples;
}

bool Stroke::has_sample()
{
    return m_interp_main.ready() && (m_interp_dir.ready_first || !need_dir());
}

bool Stroke::need_dir() const noexcept
{
    return m_brush->m_tip_angle_follow ||
        m_brush->m_tip_angle_init ||
        m_brush->m_jitter_angle > 0;
}

void Stroke::reset(bool clear_keypoints /*= false*/)
{
    m_dir_angle = 0;
    m_dir_valid = false;
    if (clear_keypoints)
        m_keypoints.clear();
    m_interp_main.reset(clear_keypoints);
    m_interp_dir.reset(clear_keypoints);
}
void Stroke::add_point(glm::vec3 pos, float pressure)
{
#ifdef __IOS__
    m_curve = glm::min(m_curve + 0.1f, 1.f);
    //pressure = pressure * glm::pow(m_curve, 2.f);
    if (m_filter_points && m_hold_points.size() < 5)
    {
        m_hold_points.push_back({pos, pressure});
        return;
    }
#endif // __IOS__
    //m_pressure_buff.add(pressure);
    //pressure = m_pressure_buff.average();
    
    if (m_brush->m_tip_size_pressure)
    {
        float aspect_width = glm::min(1.f, glm::clamp(m_brush->m_tip_aspect, .1f, .9f) * 2.f);
        float raw_size = glm::clamp(m_brush->m_tip_size / glm::tan(glm::radians(m_camera.fov * 0.5f)), 1.f, m_max_size);
        float size = aspect_width * glm::min(m_brush->m_tip_scale.x, m_brush->m_tip_scale.y) * raw_size;
        m_step = glm::max(0.5f, m_brush->m_tip_spacing * size * App::I->zoom * pressure);
    }

    if (m_keypoints.empty())
        m_prev_sample = randomize_sample(pos, pressure, 0);
    else if (m_keypoints.back().pos == pos)
        return; // skip same point, leading to black samples (NaN values)
    Keypoint kp;
    kp.pos = pos;
    kp.pressure = pressure;
    kp.dist = 0;
    m_keypoints.push_back(kp);

    m_interp_main.spacing = m_step;
    m_interp_main.add(kp);
    m_interp_dir.add(kp);
}
void Stroke::start(const std::shared_ptr<Brush>& brush)
{
    m_hold_points.clear();
    m_curve = 0.f;
    m_direction.clear();
    m_pressure_buff.clear();
    m_hsv_jitter.clear();
    m_dir_angle = 0;
    m_dir_valid = false;
    m_brush = brush;
    
    float aspect_width = glm::min(1.f, glm::clamp(m_brush->m_tip_aspect, .1f, .9f) * 2.f);
    float raw_size = glm::clamp(m_brush->m_tip_size / glm::tan(glm::radians(m_camera.fov * 0.5f)), 1.f, m_max_size);
    float size = aspect_width * glm::min(m_brush->m_tip_scale.x, m_brush->m_tip_scale.y) * raw_size;
    m_step = glm::max(0.5f, m_brush->m_tip_spacing * size * App::I->zoom);

    auto hsv = convert_rgb2hsv(m_brush->m_tip_color);
    if (!m_brush->m_jitter_hsv_eachsample)
    {
        hsv.x = glm::fract(hsv.x + (rnd_nor() - 0.5f) * m_brush->m_jitter_hue);
        hsv.y = glm::clamp(hsv.y + (rnd_nor() - 0.5f) * m_brush->m_jitter_sat, 0.f, 1.f);
        hsv.z = glm::clamp(hsv.z + (rnd_nor() - 0.5f) * m_brush->m_jitter_val, 0.f, 1.f);
    }
    m_tip_color = hsv;
    
    m_direction.resize(std::max<int>(1, m_brush->m_tip_angle_smooth * 200.f / m_step));
    prng.seed(0);

    m_interp_main = SamplesInterpolator(m_step, false);
    m_interp_dir = SamplesInterpolator(std::max(4.f, m_step), need_dir());
}

bool Brush::load_tip(const std::string& path, const std::string& thumb)
{
    if (m_tip_texture && m_brush_path == path)
        return true;
    m_tip_texture = std::make_shared<Texture2D>();
    if (!m_tip_texture->load(path))
    {
        m_tip_texture = nullptr;
        return false;
    }
    m_tip_texture->create_mipmaps();
    m_brush_path = path;
    m_brush_thumb_path = thumb;
    auto sz = m_tip_texture->size();
    m_tip_scale = sz.y > sz.x ? glm::vec2(sz.x / sz.y, 1.f) : glm::vec2(1.f, sz.y / sz.x);
    return true;
}

bool Brush::load_dual(const std::string& path, const std::string& thumb)
{
    if (m_dual_texture && m_dual_path == path)
        return true;
    m_dual_texture = std::make_shared<Texture2D>();
    if (!m_dual_texture->load(path))
    {
        m_dual_texture = nullptr;
        return false;
    }
    m_dual_texture->create_mipmaps();
    m_dual_path = path;
    m_dual_thumb_path = thumb;
    return true;
}

bool Brush::load_pattern(const std::string& path, const std::string& thumb)
{
    if (m_pattern_texture && m_pattern_path == path)
        return true;
    m_pattern_texture = std::make_shared<Texture2D>();
    if (!m_pattern_texture->load(path))
    {
        m_pattern_texture = nullptr;
        return false;
    }
    m_pattern_texture->create_mipmaps();
    m_pattern_path = path;
    m_pattern_thumb_path = thumb;
    return true;
}

bool Brush::load()
{
    if (!m_brush_path.empty() && !m_tip_texture)
    {
        m_tip_texture = std::make_shared<Texture2D>();
        auto loaded = m_tip_img ?
            m_tip_texture->create(*m_tip_img) :
            m_tip_texture->load(m_brush_path);
        if (!loaded)
        {
            LOG("failed to load %s", m_brush_path.c_str());
            m_tip_texture = nullptr;
            return false;
        }
        m_tip_texture->create_mipmaps();
    }
    if (!m_dual_path.empty() && !m_dual_texture)
    {
        m_dual_texture = std::make_shared<Texture2D>();
        auto loaded = m_dual_img ?
            m_dual_texture->create(*m_dual_img) :
            m_dual_texture->load(m_dual_path);
        if (!loaded)
        {
            LOG("failed to load %s", m_dual_path.c_str());
            m_tip_texture = nullptr;
            m_dual_texture = nullptr;
            return false;
        }
        m_dual_texture->create_mipmaps();
    }
    if (!m_pattern_path.empty() && !m_pattern_texture)
    {
        m_pattern_texture = std::make_shared<Texture2D>();
        auto loaded = m_pattern_img ?
            m_pattern_texture->create(*m_pattern_img) :
            m_pattern_texture->load(m_pattern_path);
        if (!loaded)
        {
            LOG("failed to load %s", m_pattern_path.c_str());
            m_tip_texture = nullptr;
            m_dual_texture = nullptr;
            m_pattern_texture = nullptr;
            return false;
        }
        m_pattern_texture->create_mipmaps();
    }
    return true;
}

bool Brush::preload()
{
    if (!m_brush_path.empty() && !m_tip_texture && !m_tip_img)
    {
        m_tip_img = std::make_shared<Image>();
        if (!m_tip_img->load(m_brush_path))
            return false;
    }
    if (!m_dual_path.empty() && !m_dual_texture && !m_dual_img)
    {
        m_dual_img = std::make_shared<Image>();
        if (!m_dual_img->load(m_dual_path))
            return false;
    }
    if (!m_pattern_path.empty() && !m_pattern_texture && !m_pattern_img)
    {
        m_pattern_img = std::make_shared<Image>();
        if (!m_pattern_img->load(m_pattern_path))
            return false;
    }
    return true;
}

void Brush::unload()
{
    m_tip_texture = nullptr;
    m_tip_img = nullptr;
    m_dual_texture = nullptr;
    m_dual_img = nullptr;
    m_pattern_texture = nullptr;
    m_pattern_img = nullptr;
}

bool Brush::valid()
{
    if (!m_brush_path.empty() && !Asset::exist(m_brush_path))
        return false;
    if (!m_dual_path.empty() && !Asset::exist(m_dual_path))
        return false;
    if (!m_pattern_path.empty() && !Asset::exist(m_pattern_path))
        return false;
    return true;
}

void Brush::relocate_paths(std::string base)
{
    if (!Asset::is_asset(m_brush_path))
        m_brush_path = replace_path(m_brush_path, base + "/brushes/");
    if (!Asset::is_asset(m_dual_path))
        m_dual_path = replace_path(m_dual_path, base + "/brushes/");
    if (!Asset::is_asset(m_pattern_path))
        m_pattern_path = replace_path(m_pattern_path, base + "/patterns/");
}

std::string Brush::replace_path(std::string path, std::string new_base)
{
    if (path.empty())
        return path;
    std::regex r(R"((.*)[\\/]([^\\/]+)\.(\w+)$)");
    std::smatch m;
    if (!std::regex_search(path, m, r))
        return path;
    std::string base = m[1].str();
    std::string name = m[2].str();
    std::string ext = m[3].str();
    return new_base + "/" + name + "." + ext;
}

bool Brush::read(BinaryStreamReader& r)
{
    Serializer::Descriptor d;
    r >> d;
    d.value<Serializer::CString>("m_name", m_name);
    d.value<Serializer::CString>("m_brush_path", m_brush_path);
    m_brush_path = str_replace(m_brush_path, "{data_path}", App::I->data_path);
    d.value<Serializer::CString>("m_brush_thumb_path", m_brush_thumb_path);
    m_brush_thumb_path = str_replace(m_brush_thumb_path, "{data_path}", App::I->data_path);
    d.value<Serializer::CString>("m_dual_path", m_dual_path);
    m_dual_path = str_replace(m_dual_path, "{data_path}", App::I->data_path);
    d.value<Serializer::CString>("m_dual_thumb_path", m_dual_thumb_path);
    m_dual_thumb_path = str_replace(m_dual_thumb_path, "{data_path}", App::I->data_path);
    d.value<Serializer::CString>("m_pattern_path", m_pattern_path);
    m_pattern_path = str_replace(m_pattern_path, "{data_path}", App::I->data_path);
    d.value<Serializer::CString>("m_pattern_thumb_path", m_pattern_thumb_path);
    m_pattern_thumb_path = str_replace(m_pattern_thumb_path, "{data_path}", App::I->data_path);

    d.value<Serializer::Vec4>("m_tip_color", m_tip_color);
    d.value<Serializer::Vec2>("m_tip_scale", m_tip_scale);
    d.value<Serializer::Vec2>("m_dual_scale", m_dual_scale);

    d.value<Serializer::Float>("m_tip_size", m_tip_size);
    d.value<Serializer::Float>("m_tip_spacing", m_tip_spacing);
    d.value<Serializer::Float>("m_tip_flow", m_tip_flow);
    d.value<Serializer::Float>("m_tip_opacity", m_tip_opacity);
    d.value<Serializer::Float>("m_tip_angle", m_tip_angle);
    d.value<Serializer::Float>("m_tip_angle_smooth", m_tip_angle_smooth);
    d.value<Serializer::Float>("m_tip_mix", m_tip_mix);
    d.value<Serializer::Float>("m_tip_wet", m_tip_wet);
    d.value<Serializer::Float>("m_tip_noise", m_tip_noise);
    d.value<Serializer::Float>("m_tip_hue", m_tip_hue);
    d.value<Serializer::Float>("m_tip_sat", m_tip_sat);
    d.value<Serializer::Float>("m_tip_val", m_tip_val);
    d.value<Serializer::Float>("m_jitter_scale", m_jitter_scale);
    d.value<Serializer::Float>("m_jitter_angle", m_jitter_angle);
    d.value<Serializer::Float>("m_jitter_scatter", m_jitter_scatter);
    d.value<Serializer::Float>("m_jitter_flow", m_jitter_flow);
    d.value<Serializer::Float>("m_jitter_opacity", m_jitter_opacity);
    d.value<Serializer::Float>("m_jitter_hue", m_jitter_hue);
    d.value<Serializer::Float>("m_jitter_sat", m_jitter_sat);
    d.value<Serializer::Float>("m_jitter_val", m_jitter_val);
    d.value<Serializer::Float>("m_jitter_aspect", m_jitter_aspect);
    d.value<Serializer::Float>("m_dual_size", m_dual_size);
    d.value<Serializer::Float>("m_dual_spacing", m_dual_spacing);
    d.value<Serializer::Float>("m_dual_scatter", m_dual_scatter);
    d.value<Serializer::Float>("m_tip_aspect", m_tip_aspect);
    d.value<Serializer::Float>("m_dual_flow", m_dual_flow);
    d.value<Serializer::Float>("m_dual_opacity", m_dual_opacity);
    d.value<Serializer::Float>("m_dual_rotate", m_dual_rotate);
    d.value<Serializer::Float>("m_dual_angle", m_dual_angle);
    d.value<Serializer::Float>("m_dual_aspect", m_dual_aspect);
    d.value<Serializer::Float>("m_pattern_scale", m_pattern_scale);
    d.value<Serializer::Float>("m_pattern_brightness", m_pattern_brightness);
    d.value<Serializer::Float>("m_pattern_contrast", m_pattern_contrast);
    d.value<Serializer::Float>("m_pattern_depth", m_pattern_depth);

    d.value<Serializer::Boolean>("m_tip_angle_init", m_tip_angle_init);
    d.value<Serializer::Boolean>("m_tip_angle_follow", m_tip_angle_follow);
    d.value<Serializer::Boolean>("m_tip_flow_pressure", m_tip_flow_pressure);
    d.value<Serializer::Boolean>("m_tip_opacity_pressure", m_tip_opacity_pressure);
    d.value<Serializer::Boolean>("m_tip_size_pressure", m_tip_size_pressure);
    d.value<Serializer::Boolean>("m_jitter_scatter_bothaxis", m_jitter_scatter_bothaxis);
    d.value<Serializer::Boolean>("m_jitter_hsv_eachsample", m_jitter_hsv_eachsample);
    d.value<Serializer::Boolean>("m_jitter_aspect_bothaxis", m_jitter_aspect_bothaxis);
    d.value<Serializer::Boolean>("m_tip_invert", m_tip_invert);
    d.value<Serializer::Boolean>("m_tip_flipx", m_tip_flipx);
    d.value<Serializer::Boolean>("m_tip_flipy", m_tip_flipy);
    d.value<Serializer::Boolean>("m_pattern_enabled", m_pattern_enabled);
    d.value<Serializer::Boolean>("m_dual_enabled", m_dual_enabled);
    d.value<Serializer::Boolean>("m_dual_randflip", m_dual_randflip);
    d.value<Serializer::Boolean>("m_dual_scatter_bothaxis", m_dual_scatter_bothaxis);
    d.value<Serializer::Boolean>("m_dual_invert", m_dual_invert);
    d.value<Serializer::Boolean>("m_dual_flipx", m_dual_flipx);
    d.value<Serializer::Boolean>("m_dual_flipy", m_dual_flipy);
    d.value<Serializer::Boolean>("m_tip_randflipx", m_tip_randflipx);
    d.value<Serializer::Boolean>("m_tip_randflipy", m_tip_randflipy);
    d.value<Serializer::Boolean>("m_pattern_eachsample", m_pattern_eachsample);
    d.value<Serializer::Boolean>("m_pattern_invert", m_pattern_invert);
    d.value<Serializer::Boolean>("m_pattern_flipx", m_pattern_flipx);
    d.value<Serializer::Boolean>("m_pattern_flipy", m_pattern_flipy);
    d.value<Serializer::Boolean>("m_pattern_rand_offset", m_pattern_rand_offset);

    d.value<Serializer::Integer>("m_blend_mode", m_blend_mode);
    d.value<Serializer::Integer>("m_dual_blend_mode", m_dual_blend_mode);
    d.value<Serializer::Integer>("m_dual_count", m_dual_count);
    d.value<Serializer::Integer>("m_pattern_blend_mode", m_pattern_blend_mode);

    return true;
}

void Brush::write(BinaryStreamWriter& w) const
{
    Serializer::Descriptor d;
    d.class_id = "brush";
    d.name = L"Brush class";
    d.props["m_name"] = std::make_shared<Serializer::CString>(m_name);
    d.props["m_brush_path"] = std::make_shared<Serializer::CString>(
        str_replace(m_brush_path, App::I->data_path, "{data_path}"));
    d.props["m_brush_thumb_path"] = std::make_shared<Serializer::CString>(
        str_replace(m_brush_thumb_path, App::I->data_path, "{data_path}"));
    d.props["m_dual_path"] = std::make_shared<Serializer::CString>(
        str_replace(m_dual_path, App::I->data_path, "{data_path}"));
    d.props["m_dual_thumb_path"] = std::make_shared<Serializer::CString>(
        str_replace(m_dual_thumb_path, App::I->data_path, "{data_path}"));
    d.props["m_pattern_path"] = std::make_shared<Serializer::CString>(
        str_replace(m_pattern_path, App::I->data_path, "{data_path}"));
    d.props["m_pattern_thumb_path"] = std::make_shared<Serializer::CString>(
        str_replace(m_pattern_thumb_path, App::I->data_path, "{data_path}"));

    d.props["m_tip_color"] = std::make_shared<Serializer::Vec4>(m_tip_color);
    d.props["m_tip_scale"] = std::make_shared<Serializer::Vec2>(m_tip_scale);
    d.props["m_dual_scale"] = std::make_shared<Serializer::Vec2>(m_dual_scale);

    d.props["m_tip_size"] = std::make_shared<Serializer::Float>(m_tip_size);
    d.props["m_tip_spacing"] = std::make_shared<Serializer::Float>(m_tip_spacing);
    d.props["m_tip_flow"] = std::make_shared<Serializer::Float>(m_tip_flow);
    d.props["m_tip_opacity"] = std::make_shared<Serializer::Float>(m_tip_opacity);
    d.props["m_tip_angle"] = std::make_shared<Serializer::Float>(m_tip_angle);
    d.props["m_tip_angle_smooth"] = std::make_shared<Serializer::Float>(m_tip_angle_smooth);
    d.props["m_tip_mix"] = std::make_shared<Serializer::Float>(m_tip_mix);
    d.props["m_tip_wet"] = std::make_shared<Serializer::Float>(m_tip_wet);
    d.props["m_tip_noise"] = std::make_shared<Serializer::Float>(m_tip_noise);
    d.props["m_tip_hue"] = std::make_shared<Serializer::Float>(m_tip_hue);
    d.props["m_tip_sat"] = std::make_shared<Serializer::Float>(m_tip_sat);
    d.props["m_tip_val"] = std::make_shared<Serializer::Float>(m_tip_val);
    d.props["m_jitter_scale"] = std::make_shared<Serializer::Float>(m_jitter_scale);
    d.props["m_jitter_angle"] = std::make_shared<Serializer::Float>(m_jitter_angle);
    d.props["m_jitter_scatter"] = std::make_shared<Serializer::Float>(m_jitter_scatter);
    d.props["m_jitter_flow"] = std::make_shared<Serializer::Float>(m_jitter_flow);
    d.props["m_jitter_opacity"] = std::make_shared<Serializer::Float>(m_jitter_opacity);
    d.props["m_jitter_hue"] = std::make_shared<Serializer::Float>(m_jitter_hue);
    d.props["m_jitter_sat"] = std::make_shared<Serializer::Float>(m_jitter_sat);
    d.props["m_jitter_val"] = std::make_shared<Serializer::Float>(m_jitter_val);
    d.props["m_jitter_aspect"] = std::make_shared<Serializer::Float>(m_jitter_aspect);
    d.props["m_dual_size"] = std::make_shared<Serializer::Float>(m_dual_size);
    d.props["m_dual_spacing"] = std::make_shared<Serializer::Float>(m_dual_spacing);
    d.props["m_dual_scatter"] = std::make_shared<Serializer::Float>(m_dual_scatter);
    d.props["m_tip_aspect"] = std::make_shared<Serializer::Float>(m_tip_aspect);
    d.props["m_dual_flow"] = std::make_shared<Serializer::Float>(m_dual_flow);
    d.props["m_dual_opacity"] = std::make_shared<Serializer::Float>(m_dual_opacity);
    d.props["m_dual_rotate"] = std::make_shared<Serializer::Float>(m_dual_rotate);
    d.props["m_dual_angle"] = std::make_shared<Serializer::Float>(m_dual_angle);
    d.props["m_dual_aspect"] = std::make_shared<Serializer::Float>(m_dual_aspect);
    d.props["m_pattern_scale"] = std::make_shared<Serializer::Float>(m_pattern_scale);
    d.props["m_pattern_brightness"] = std::make_shared<Serializer::Float>(m_pattern_brightness);
    d.props["m_pattern_contrast"] = std::make_shared<Serializer::Float>(m_pattern_contrast);
    d.props["m_pattern_depth"] = std::make_shared<Serializer::Float>(m_pattern_depth);
    
    d.props["m_tip_angle_init"] = std::make_shared<Serializer::Boolean>(m_tip_angle_init);
    d.props["m_tip_angle_follow"] = std::make_shared<Serializer::Boolean>(m_tip_angle_follow);
    d.props["m_tip_flow_pressure"] = std::make_shared<Serializer::Boolean>(m_tip_flow_pressure);
    d.props["m_tip_opacity_pressure"] = std::make_shared<Serializer::Boolean>(m_tip_opacity_pressure);
    d.props["m_tip_size_pressure"] = std::make_shared<Serializer::Boolean>(m_tip_size_pressure);
    d.props["m_jitter_scatter_bothaxis"] = std::make_shared<Serializer::Boolean>(m_jitter_scatter_bothaxis);
    d.props["m_jitter_hsv_eachsample"] = std::make_shared<Serializer::Boolean>(m_jitter_hsv_eachsample);
    d.props["m_jitter_aspect_bothaxis"] = std::make_shared<Serializer::Boolean>(m_jitter_aspect_bothaxis);
    d.props["m_tip_invert"] = std::make_shared<Serializer::Boolean>(m_tip_invert);
    d.props["m_tip_flipx"] = std::make_shared<Serializer::Boolean>(m_tip_flipx);
    d.props["m_tip_flipy"] = std::make_shared<Serializer::Boolean>(m_tip_flipy);
    d.props["m_pattern_enabled"] = std::make_shared<Serializer::Boolean>(m_pattern_enabled);
    d.props["m_dual_enabled"] = std::make_shared<Serializer::Boolean>(m_dual_enabled);
    d.props["m_dual_randflip"] = std::make_shared<Serializer::Boolean>(m_dual_randflip);
    d.props["m_dual_scatter_bothaxis"] = std::make_shared<Serializer::Boolean>(m_dual_scatter_bothaxis);
    d.props["m_dual_invert"] = std::make_shared<Serializer::Boolean>(m_dual_invert);
    d.props["m_dual_flipx"] = std::make_shared<Serializer::Boolean>(m_dual_flipx);
    d.props["m_dual_flipy"] = std::make_shared<Serializer::Boolean>(m_dual_flipy);
    d.props["m_tip_randflipx"] = std::make_shared<Serializer::Boolean>(m_tip_randflipx);
    d.props["m_tip_randflipy"] = std::make_shared<Serializer::Boolean>(m_tip_randflipy);
    d.props["m_pattern_eachsample"] = std::make_shared<Serializer::Boolean>(m_pattern_eachsample);
    d.props["m_pattern_invert"] = std::make_shared<Serializer::Boolean>(m_pattern_invert);
    d.props["m_pattern_flipx"] = std::make_shared<Serializer::Boolean>(m_pattern_flipx);
    d.props["m_pattern_flipy"] = std::make_shared<Serializer::Boolean>(m_pattern_flipy);
    d.props["m_pattern_rand_offset"] = std::make_shared<Serializer::Boolean>(m_pattern_rand_offset);

    d.props["m_blend_mode"] = std::make_shared<Serializer::Integer>(m_blend_mode);
    d.props["m_dual_blend_mode"] = std::make_shared<Serializer::Integer>(m_dual_blend_mode);
    d.props["m_dual_count"] = std::make_shared<Serializer::Integer>(m_dual_count);
    d.props["m_pattern_blend_mode"] = std::make_shared<Serializer::Integer>(m_pattern_blend_mode);

    w << d;
}

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

Stroke::SamplesInterpolator::SamplesInterpolator(float sp, bool compute_dir) noexcept
{
    spacing = sp;
    dist = 0;
    first = true;
    ready_first = false;
    calc_dir = compute_dir;
    initial_kp_done = false;
}

Stroke::SamplesInterpolator::SamplesInterpolator() noexcept
{
    spacing = 1.f;
    dist = 0;
    first = true;
    ready_first = false;
    calc_dir = false;
    initial_kp_done = false;
}

void Stroke::SamplesInterpolator::add(const Keypoint& p) noexcept
{
    keypoints.push_back(p);
    if (first)
    {
        last = p;
        first = false;
    }
    if (!ready_first)
        ready_first = ready();
}

bool Stroke::SamplesInterpolator::ready() const noexcept
{
    return keypoints.size() > 0 &&
        (glm::distance(last.pos, keypoints.back().pos) > spacing || !calc_dir);
}

void Stroke::SamplesInterpolator::reset(bool clear_keypoints) noexcept
{
    dist = 0;
    if (clear_keypoints || keypoints.empty())
    {
        initial_kp_done = false;
        ready_first = false;
        first = true;
        keypoints.clear();
    }
    else
    {
        last = keypoints[0];
    }
}

std::vector<Stroke::Keypoint> Stroke::SamplesInterpolator::compute() noexcept
{
    std::vector<Stroke::Keypoint> ret;
    if (!calc_dir && last.dist == 0 && !initial_kp_done)
    {
        initial_kp_done = true;
        ret.push_back(last);
    }
    for (const auto& kp : keypoints)
    {
        float d = glm::distance(last.pos, kp.pos);
        if (d < spacing)
            continue;
        int n = glm::floor(d / spacing);
        float t_step = spacing / d;
        for (int i = 0; i < n; i++)
        {
            float t = (float)(i + 1) * t_step;
            dist += spacing;
            Keypoint p;
            p.pos = glm::lerp(last.pos, kp.pos, t);
            p.dir = glm::normalize(last.pos - p.pos);
            p.pressure = glm::lerp(last.pressure, kp.pressure, t);
            p.dist = dist;
            ret.push_back(p);
        }
        if (!ret.empty())
        {
            if (last.dist == 0 && calc_dir && !initial_kp_done)
            {
                last.dir = ret.front().dir;
                ret.insert(ret.begin(), last);
                initial_kp_done = true;
            }
            last = ret.back();
        }
    }
    if (!ret.empty())
        keypoints.clear();
    return ret;
}
