diff --git a/src/brush.cpp b/src/brush.cpp index 73b5998..f78e393 100644 --- a/src/brush.cpp +++ b/src/brush.cpp @@ -52,6 +52,62 @@ void Stroke::randomize_prng() } std::vector Stroke::compute_samples() +{ + std::vector samples; + auto dirs = m_interp_dir.compute(); + m_dirs_cache.insert(m_dirs_cache.end(), dirs.begin(), dirs.end()); + auto points = m_interp_main.compute(); + //if (!dirs.empty() && !points.empty()) + { + LOG("dirs %f - points %f", m_interp_dir.dist, m_interp_main.dist); + } + for (const auto& kp : points) + { + for (; m_dir_i < m_dirs_cache.size() && (kp.dist >= m_dirs_cache[m_dir_i].dist || !m_dir_valid); m_dir_i++) + { + m_dir_angle = -glm::orientedAngle(-m_dirs_cache[m_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 = -m_dirs_cache[m_dir_i].dir; + m_dir_ref_angle = -glm::orientedAngle(m_dir_ref, { 1, 0 }); + m_dir_angle = -glm::orientedAngle(-m_dirs_cache[m_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(); + + 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; + } + 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; +} + +std::vector Stroke::compute_samples_old() { if (m_keypoints.size() < 2) return {}; int nsamples = (int)glm::floor((m_keypoints.back().dist - m_dist) / m_step); @@ -75,11 +131,14 @@ std::vector Stroke::compute_samples() int next_kp = m_last_kp == m_dir_kp ? m_last_kp + 1 : m_dir_kp; glm::vec2 v = glm::normalize(m_keypoints[m_last_kp].pos - m_keypoints[next_kp].pos); m_dir_angle = -glm::orientedAngle(v, m_dir_ref); + LOG("DIR ANGLE %f PT (%f,%f)(%f,%f)", m_dir_angle, + m_keypoints[m_last_kp].pos.x, m_keypoints[m_last_kp].pos.y, + m_keypoints[next_kp].pos.x, m_keypoints[next_kp].pos.y); if (m_brush->m_tip_angle_smooth > 0 && (glm::abs(m_dir_angle) > glm::radians(30.f) || !m_dir_valid)) { if (glm::abs(m_dir_angle) > glm::radians(100.f)) { - //LOG("BIG ANGLE"); + LOG("BIG ANGLE"); m_direction.clear(); } @@ -90,6 +149,7 @@ std::vector Stroke::compute_samples() 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; + LOG("REF ANGLE %f", m_dir_ref_angle); } m_dir_kp = m_last_kp; m_dir_dist = 0; @@ -97,6 +157,7 @@ std::vector Stroke::compute_samples() { m_dir_init = m_dir_angle + m_dir_ref_angle; m_dir_valid = true; + LOG("DIR INVALID"); } } @@ -115,12 +176,13 @@ std::vector Stroke::compute_samples() if (m_brush->m_tip_angle_follow) { m_direction.add(m_dir_angle); - s.angle += m_direction.average() + m_dir_ref_angle; + s.angle += m_dir_angle /*m_direction.average()*/ + m_dir_ref_angle; } else if (m_brush->m_tip_angle_init) { s.angle += m_dir_init; } + LOG("angle %f", s.angle); m_prev_sample = s; samples.push_back(s); @@ -142,9 +204,11 @@ std::vector Stroke::compute_samples() } bool Stroke::has_sample() { - return m_keypoints.size() < 2 ? false : // no keypoints - (m_keypoints.back().dist > (m_dist + m_step)); // check if next kp is closer than spacing + //return m_keypoints.size() < 2 ? false : // no keypoints + // (m_keypoints.back().dist > (m_dist + m_step)); // check if next kp is closer than spacing + return m_interp_main.ready() && m_interp_dir.ready_first; } + void Stroke::reset(bool clear_keypoints /*= false*/) { m_dir_kp = 0; @@ -153,8 +217,12 @@ void Stroke::reset(bool clear_keypoints /*= false*/) m_dir_dist = 0; m_last_kp = 0; m_dist = 0.f; + m_dir_i = 0; + m_dirs_cache.clear(); 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) { @@ -189,9 +257,14 @@ void Stroke::add_point(glm::vec3 pos, float pressure) kp.pressure = pressure; kp.dist = dist; m_keypoints.push_back(kp); + + m_interp_main.add(kp); + m_interp_dir.add(kp); } void Stroke::start(const std::shared_ptr& brush) { + m_dir_i = 0; + m_dirs_cache.clear(); m_hold_points.clear(); m_curve = 0.f; m_direction.clear(); @@ -221,6 +294,9 @@ void Stroke::start(const std::shared_ptr& brush) m_direction.resize(std::max(1, m_brush->m_tip_angle_smooth * 200.f / m_step)); prng.seed(0); + + m_interp_main = SamplesInterpolator(m_step); + m_interp_dir = SamplesInterpolator(std::max(4.f, m_step)); } bool Brush::load_tip(const std::string& path, const std::string& thumb) @@ -579,3 +655,83 @@ void Brush::write(BinaryStreamWriter& w) const w << d; } + +////////////////////////////////////////////////////////////////////////// + +Stroke::SamplesInterpolator::SamplesInterpolator(float sp) noexcept +{ + spacing = sp; + dist = 0; + first = true; + ready_first = false; +} + +Stroke::SamplesInterpolator::SamplesInterpolator() noexcept +{ + spacing = 1.f; + dist = 0; + first = true; + ready_first = 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() > 1 && + glm::distance(last.pos, keypoints.back().pos) > spacing; +} + +void Stroke::SamplesInterpolator::reset(bool clear_keypoints) noexcept +{ + dist = 0; + if (clear_keypoints || keypoints.empty()) + { + ready_first = false; + first = true; + keypoints.clear(); + } + else + { + last = keypoints[0]; + } +} + +std::vector Stroke::SamplesInterpolator::compute() noexcept +{ + std::vector ret; + 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()) + last = ret.back(); + } + if (!ret.empty()) + keypoints.clear(); + return ret; +} diff --git a/src/brush.h b/src/brush.h index 9e29b8a..6f09324 100644 --- a/src/brush.h +++ b/src/brush.h @@ -146,15 +146,33 @@ public: glm::vec3 pos = { 0, 0, 0 }; float pressure = 0; float dist = 0; + glm::vec2 dir = { 0, 0 }; }; struct Camera { glm::mat4 rot; float fov = 0; }; + struct SamplesInterpolator + { + Keypoint last; + std::vector keypoints; + bool ready_first; + bool first; + float spacing; + float dist; + SamplesInterpolator() noexcept; + SamplesInterpolator(float sp) noexcept; + void add(const Keypoint& p) noexcept; + bool ready() const noexcept; + void reset(bool clear_keypoints) noexcept; + std::vector compute() noexcept; + }; int m_layer = 0; int m_dir_kp = 0; bool m_dir_valid = false; + std::vector m_dirs_cache; + int m_dir_i = 0; glm::vec2 m_dir_ref = { 1, 0 }; float m_dir_ref_angle = 0; float m_dir_dist = 0; @@ -183,7 +201,10 @@ public: void reset(bool clear_keypoints = false); bool has_sample(); std::vector compute_samples(); + std::vector compute_samples_old(); StrokeSample randomize_sample(const glm::vec3& pos, float pressure, float curve_angle); + SamplesInterpolator m_interp_main; + SamplesInterpolator m_interp_dir; void randomize_prng(); float rnd_nor() { return float((double)prng() / (double)prng.max()); }; // normalized [0, +1]