diff --git a/engine/brush.h b/engine/brush.h index afefedc..ff7715a 100644 --- a/engine/brush.h +++ b/engine/brush.h @@ -23,20 +23,82 @@ public: class Stroke { public: + struct Sample + { + glm::vec2 pos; + float size; + float flow; + float angle; + }; struct Keypoint { glm::vec2 pos; float pressure; + float dist; }; int m_layer; float m_dist; + float m_step; ui::Brush m_brush; std::vector m_keypoints; + std::vector m_samples; + int m_last_kp; + std::minstd_rand prng; + void start(glm::vec2 pos, float pressure, const ui::Brush& brush) + { + m_last_kp = 0; + m_dist = 0.f; + m_step = glm::max(brush.m_tip_spacing * brush.m_tip_size * 30, 0.1f); + m_brush = brush; + add_point(pos, pressure); + } void 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; + } + bool has_sample() + { + return m_keypoints.empty() ? false : // no keypoints + (m_keypoints.back().dist > (m_dist + m_step)); // check if next kp is closer than spacing + } + std::vector compute_samples() + { + int nsamples = (int)glm::floor((m_keypoints.back().dist - m_dist) / m_step); + std::vector samples; + samples.reserve(nsamples); // preallocate the estimate number of samples + while (m_keypoints.back().dist > (m_dist + m_step)) + { + m_dist += m_step; + while (m_dist > m_keypoints[m_last_kp + 1].dist) + m_last_kp++; + const auto& A = m_keypoints[m_last_kp]; + const auto& B = m_keypoints[m_last_kp + 1]; // NOTE: this should be true when while is true + 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); + samples.push_back(s); + } + return std::move(samples); + } + Sample randomize_sample(const glm::vec2& pos, float pressure) + { + 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] + auto rnd_rad = [&] { return float((double)prng() / (double)prng.max() * M_PI * 2.0); }; // normalized [0, 2pi] + auto rnd_vec = [&] { float rad = rnd_rad(); return glm::vec2(cosf(rad), sinf(rad)); }; // normalized direction vector + + Sample s; + s.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 = 100.f * m_brush.m_tip_size * (1.f - rnd_nor() * m_brush.m_jitter_scale); + s.flow = m_brush.m_tip_flow * (1.f - rnd_nor() * m_brush.m_jitter_flow); + return s; } }; diff --git a/engine/canvas.h b/engine/canvas.h index 952eaa2..d699a7b 100644 --- a/engine/canvas.h +++ b/engine/canvas.h @@ -41,8 +41,7 @@ public: { prng.seed(0); m_strokes.emplace_back(); - m_strokes.back().m_brush = brush; - m_strokes.back().add_point(point, pressure); + m_strokes.back().start(point, pressure, brush); m_current_stroke = &m_strokes.back(); } void stroke_update(glm::vec2 point, float pressure) @@ -59,50 +58,26 @@ public: glViewport(0, 0, m_width, m_height); glEnable(GL_BLEND); - //auto mvp = glm::ortho(0.f, (float)m_width, (float)m_height, 0.f, -1.f, 1.f) * - // glm::translate(glm::vec3(point, 0)) * - // glm::scale(glm::vec3(30, 30, 1)); - //auto& tex = TextureManager::get(m_current_stroke->m_brush.m_tex_id); - //tex.bind(); - //m_sampler.bind(0); - //ui::ShaderManager::use(kShader::Stroke); - //ui::ShaderManager::u_int(kShaderUniform::Tex, 0); - //ui::ShaderManager::u_mat4(kShaderUniform::MVP, mvp); - //ui::ShaderManager::u_float(kShaderUniform::Alpha, 1.f); - //ui::ShaderManager::u_vec4(kShaderUniform::Col, { 0, 0, 0, 1 }); - //m_plane.draw_fill(); - //m_sampler.unbind(); - //tex.unbind(); - auto proj = glm::ortho(0.f, (float)m_width, (float)m_height, 0.f, -1.f, 1.f); auto m_brush = m_current_stroke->m_brush; - - 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] - auto rnd_rad = [&] { return float((double)prng() / (double)prng.max() * M_PI * 2.0); }; // normalized [0, 2pi] - auto rnd_vec = [&] { float rad = rnd_rad(); return glm::vec2(cosf(rad), sinf(rad)); }; // normalized direction vector - - float angle = (m_brush.m_tip_angle + rnd_nor() * m_brush.m_jitter_angle) * (float)(M_PI * 2.0); - glm::vec2 pos = point + (rnd_vec() * m_brush.m_jitter_spread * 100.f); - float size = 100.f * m_brush.m_tip_size * (1.f - rnd_nor() * m_brush.m_jitter_scale); - float flow = m_brush.m_tip_flow * (1.f - rnd_nor() * m_brush.m_jitter_flow); - - //alpha += glm::max(m_brush.m_tip_spacing * .2f, .01f); - auto mvp = proj * - //glm::translate(glm::vec3(i * 40 * m_tip_spacing, m_rtt.getHeight() / 2, 0)) * - glm::translate(glm::vec3(pos, 0)) * - glm::scale(glm::vec3(size, size, 1)) * - glm::eulerAngleZ(angle); - + auto samples = m_current_stroke->compute_samples(); auto& tex = TextureManager::get(m_brush.m_tex_id); tex.bind(); m_sampler.bind(0); ShaderManager::use("stroke"); ShaderManager::u_vec4(kShaderUniform::Col, m_brush.m_tip_color); ShaderManager::u_int(kShaderUniform::Tex, 0); - ShaderManager::u_mat4(kShaderUniform::MVP, mvp); - ShaderManager::u_float(kShaderUniform::Alpha, flow); - m_plane.draw_fill(); + for (const auto& s : samples) + { + auto mvp = proj * + glm::translate(glm::vec3(s.pos, 0)) * + glm::scale(glm::vec3(s.size, s.size, 1)) * + glm::eulerAngleZ(s.angle); + + ShaderManager::u_mat4(kShaderUniform::MVP, mvp); + ShaderManager::u_float(kShaderUniform::Alpha, s.flow); + m_plane.draw_fill(); + } m_sampler.unbind(); tex.unbind(); diff --git a/engine/layout.h b/engine/layout.h index 5295d2c..eba10b3 100644 --- a/engine/layout.h +++ b/engine/layout.h @@ -1875,7 +1875,7 @@ public: { if (new_size.x > m_canvas->m_width) { - m_canvas->create(new_size.x, new_size.y); + m_canvas->create((int)new_size.x, (int)new_size.y); m_canvas->clear(); } }