diff --git a/data/layout.xml b/data/layout.xml index 9139d82..5fed29c 100644 --- a/data/layout.xml +++ b/data/layout.xml @@ -123,7 +123,12 @@ - + + + + + + diff --git a/engine/brush.cpp b/engine/brush.cpp index b685626..12f1c0a 100644 --- a/engine/brush.cpp +++ b/engine/brush.cpp @@ -154,7 +154,7 @@ bool ui::BrushMesh::create() return true; } -ui::StrokeSample ui::Stroke::randomize_sample(const glm::vec2& pos, float pressure) +ui::StrokeSample ui::Stroke::randomize_sample(const glm::vec2& pos, float pressure, float curve_angle) { auto rnd_nor = [&] { return float((double)prng() / (double)prng.max()); }; // normalized [0, +1] //auto rnd_neg = [&] { return float((double)prng() / (double)prng.max() * 2.0 - 1.0); }; // normalized [-1, +1] @@ -162,7 +162,8 @@ ui::StrokeSample ui::Stroke::randomize_sample(const glm::vec2& pos, float pressu auto rnd_vec = [&] { float rad = rnd_rad(); return glm::vec2(cosf(rad), sinf(rad)); }; // normalized direction vector StrokeSample s; - s.angle = (m_brush.m_tip_angle + rnd_nor() * m_brush.m_jitter_angle) * (float)(M_PI * 2.0); + s.origin = pos; + s.angle = -curve_angle + (m_brush.m_tip_angle + rnd_nor() * m_brush.m_jitter_angle) * (float)(M_PI * 2.0); s.pos = pos + (rnd_vec() * m_brush.m_jitter_spread * 100.f); s.size = 800.f * glm::pow(m_brush.m_tip_size, 3.f) * (1.f - rnd_nor() * m_brush.m_jitter_scale); s.flow = glm::pow(m_brush.m_tip_flow, 2.f) * (1.f - rnd_nor() * m_brush.m_jitter_flow) * pressure; @@ -176,6 +177,7 @@ std::vector ui::Stroke::compute_samples() samples.reserve(nsamples); // preallocate the estimate number of samples while (m_keypoints.back().dist > (m_dist + m_step)) { + bool is_first = m_last_kp == 0; m_dist += m_step; while (m_dist > m_keypoints[m_last_kp + 1].dist) m_last_kp++; @@ -184,7 +186,21 @@ std::vector ui::Stroke::compute_samples() float t = (m_dist - A.dist) / (B.dist - A.dist); // NOTE: must be A != B auto pos = glm::lerp(A.pos, B.pos, t); float pressure = glm::lerp(A.pressure, B.pressure, t); - auto s = randomize_sample(pos, pressure); + + auto s = randomize_sample(pos, pressure, 0); + if (m_brush.m_tip_angle_follow) + { + auto& pre = m_prev_sample; + glm::vec2 v = glm::normalize(s.origin - pre.origin); + float curve_angle = -glm::orientedAngle(v, glm::vec2(1, 0)); + + // NOTE: average angles need correction for 0-360 discontinuity + //m_curve_angles.add(curve_angle); + //float avg = m_curve_angles.average(); + + s.angle += curve_angle; + } + m_prev_sample = s; samples.push_back(s); } return std::move(samples); @@ -205,16 +221,20 @@ void ui::Stroke::add_point(glm::vec2 pos, float pressure) { float dist = m_keypoints.empty() ? 0.f : m_keypoints.back().dist + glm::distance(m_keypoints.back().pos, pos); - m_keypoints.emplace_back(); - m_keypoints.back().pos = pos; - m_keypoints.back().pressure = pressure; - m_keypoints.back().dist = dist; + if (m_keypoints.empty()) + m_prev_sample.origin = pos; + Keypoint kp; + kp.pos = pos; + kp.pressure = pressure; + kp.dist = dist; + m_keypoints.push_back(kp); } void ui::Stroke::start(const ui::Brush& brush) { + m_curve_angles.clear(); m_last_kp = 0; m_dist = 0.f; m_step = glm::max(brush.m_tip_spacing * brush.m_tip_size * 100, 1.f); m_brush = brush; prng.seed(0); -} \ No newline at end of file +} diff --git a/engine/brush.h b/engine/brush.h index 1df4d01..f183bdc 100644 --- a/engine/brush.h +++ b/engine/brush.h @@ -16,6 +16,7 @@ public: float m_tip_flow; float m_tip_opacity; float m_tip_angle; + bool m_tip_angle_follow = false; float m_jitter_scale; float m_jitter_angle; float m_jitter_spread; @@ -25,6 +26,7 @@ public: struct StrokeSample { glm::vec2 pos; + glm::vec2 origin; float size; float flow; float angle; @@ -64,6 +66,8 @@ public: float m_step; Camera m_camera; ui::Brush m_brush; + cbuffer m_curve_angles; + StrokeSample m_prev_sample; std::vector m_keypoints; std::vector m_samples; int m_last_kp; @@ -73,7 +77,7 @@ public: void reset(bool clear_keypoints = false); bool has_sample(); std::vector compute_samples(); - StrokeSample randomize_sample(const glm::vec2& pos, float pressure); + StrokeSample randomize_sample(const glm::vec2& pos, float pressure, float curve_angle); }; NS_END diff --git a/engine/node_panel_stroke.cpp b/engine/node_panel_stroke.cpp index 2942909..d85d5bb 100644 --- a/engine/node_panel_stroke.cpp +++ b/engine/node_panel_stroke.cpp @@ -32,15 +32,18 @@ void NodePanelStroke::init_controls() init_slider(m_jitter_angle, "jitter-angle", &ui::Brush::m_jitter_angle); init_slider(m_jitter_spread, "jitter-spread", &ui::Brush::m_jitter_spread); init_slider(m_jitter_flow, "jitter-flow", &ui::Brush::m_jitter_flow); + + init_checkbox(m_tip_angle_follow, "tip-angle-follow", &ui::Brush::m_tip_angle_follow); + //m_canvas->draw_stroke(); } -void NodePanelStroke::init_slider(NodeSliderH*& slider, const char* id, float ui::Brush::* prop) +void NodePanelStroke::init_slider(NodeSliderH*& target, const char* id, float ui::Brush::* prop) { - slider = find(id); - slider->on_value_changed = std::bind(&NodePanelStroke::handle_slide, + target = find(id); + target->on_value_changed = std::bind(&NodePanelStroke::handle_slide, this, prop, std::placeholders::_1, std::placeholders::_2); - m_canvas->m_brush.*prop = slider->m_value.x; + m_canvas->m_brush.*prop = target->m_value.x; } void NodePanelStroke::handle_slide(float ui::Brush::* prop, Node* target, float value) @@ -50,3 +53,19 @@ void NodePanelStroke::handle_slide(float ui::Brush::* prop, Node* target, float if (on_stroke_change) on_stroke_change(this); } + +void NodePanelStroke::init_checkbox(NodeCheckBox*& target, const char* id, bool ui::Brush::* prop) +{ + target = find(id); + target->on_value_changed = std::bind(&NodePanelStroke::handle_checkbox, + this, prop, std::placeholders::_1, std::placeholders::_2); + m_canvas->m_brush.*prop = target->checked; +} + +void NodePanelStroke::handle_checkbox(bool ui::Brush::* prop, Node *target, bool value) +{ + m_canvas->m_brush.*prop = value; + m_canvas->draw_stroke(); + if (on_stroke_change) + on_stroke_change(this); +} diff --git a/engine/node_panel_stroke.h b/engine/node_panel_stroke.h index 5a759c7..ca88a58 100644 --- a/engine/node_panel_stroke.h +++ b/engine/node_panel_stroke.h @@ -3,6 +3,7 @@ #include "node_stroke_preview.h" #include "node_slider.h" #include "brush.h" +#include "node_checkbox.h" class NodePanelStroke : public Node { @@ -17,11 +18,16 @@ public: NodeSliderH* m_jitter_angle; NodeSliderH* m_jitter_spread; NodeSliderH* m_jitter_flow; + NodeCheckBox* m_tip_angle_follow; std::function on_stroke_change; virtual Node* clone_instantiate() const override; virtual void clone_finalize(Node* dest) const override; virtual void init() override; void init_controls(); + void init_slider(NodeSliderH*& slider, const char* id, float ui::Brush::* prop); void handle_slide(float ui::Brush::* prop, Node* target, float value); + + void init_checkbox(NodeCheckBox*& slider, const char* id, bool ui::Brush::* prop); + void handle_checkbox(bool ui::Brush::* prop, Node* target, bool value); }; diff --git a/engine/node_stroke_preview.cpp b/engine/node_stroke_preview.cpp index fa5f58e..ca0d1b3 100644 --- a/engine/node_stroke_preview.cpp +++ b/engine/node_stroke_preview.cpp @@ -69,6 +69,8 @@ void NodeStrokePreview::draw_stroke() b.m_tip_size *= .7f; // reduce the size in the preview m_stroke.reset(); m_stroke.start(b); + if (!m_stroke.m_keypoints.empty()) + m_stroke.m_prev_sample.origin = m_stroke.m_keypoints[0].pos; auto samples = m_stroke.compute_samples(); auto& tex = TextureManager::get(m_brush.m_tex_id); tex.bind(); @@ -127,6 +129,7 @@ void NodeStrokePreview::handle_resize(glm::vec2 old_size, glm::vec2 new_size) float w = new_size.x; float h = new_size.y; std::vector kp = { { pad, pad },{ pad, h - pad },{ w - pad, pad },{ w - pad, h - pad } }; + m_stroke.reset(); m_stroke.start(m_brush); for (int i = 0; i < 20; i++) m_stroke.add_point(BezierCurve::Bezier2D(kp, i / 20.f), 1.f); diff --git a/engine/pch.h b/engine/pch.h index 0c9f9e1..6aa8331 100644 --- a/engine/pch.h +++ b/engine/pch.h @@ -100,6 +100,7 @@ #include #include #include +#include #include #include diff --git a/engine/util.h b/engine/util.h index 58150ca..b94b4bc 100644 --- a/engine/util.h +++ b/engine/util.h @@ -30,6 +30,11 @@ template struct cbuffer m_index = 0; m_count = 0; } + void clear() + { + m_index = 0; + m_count = 0; + } T& head() { return m_index == 0 ? m_vec[m_count - 1] : m_vec[m_index - 1];