diff --git a/android/CMakeLists.txt b/android/CMakeLists.txt index 6bad1b6..5a65385 100644 --- a/android/CMakeLists.txt +++ b/android/CMakeLists.txt @@ -37,6 +37,8 @@ add_library( ../engine/shape.cpp ../engine/layout.cpp ../engine/app.cpp + ../engine/brush.cpp + ../engine/canvas.cpp ) target_include_directories(native-lib PRIVATE diff --git a/android/build.gradle b/android/build.gradle index f7915f8..e987447 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -28,7 +28,9 @@ android { // Sets a flag to enable format macro constants for the C++ compiler. //cppFlags "-D__STDC_FORMAT_MACROS" arguments '-DANDROID_PLATFORM=android-19', - '-DANDROID_TOOLCHAIN=clang', '-DANDROID_STL=gnustl_static' + '-DANDROID_TOOLCHAIN=clang', + '-DANDROID_STL=gnustl_static', + '-DCMAKE_BUILD_TYPE=Debug' } } ndk { diff --git a/android/src/main/cpp/main.cpp b/android/src/main/cpp/main.cpp index c28a950..42b3796 100755 --- a/android/src/main/cpp/main.cpp +++ b/android/src/main/cpp/main.cpp @@ -167,6 +167,10 @@ static void engine_draw_frame(struct engine* engine) { if (engine->display == NULL) return; + glClearColor(.1f, .1f, .1f, 1.f); + glViewport(0, 0, (GLsizei)engine->width, (GLsizei)engine->height); + glClear(GL_COLOR_BUFFER_BIT); + App::I.update(0); eglSwapBuffers(engine->display, engine->surface); diff --git a/android/src/main/res/drawable-hdpi/ic_launcher.png b/android/src/main/res/drawable-hdpi/ic_launcher.png index 96a442e..7917264 100644 Binary files a/android/src/main/res/drawable-hdpi/ic_launcher.png and b/android/src/main/res/drawable-hdpi/ic_launcher.png differ diff --git a/android/src/main/res/drawable-ldpi/ic_launcher.png b/android/src/main/res/drawable-ldpi/ic_launcher.png index 9923872..325d02c 100644 Binary files a/android/src/main/res/drawable-ldpi/ic_launcher.png and b/android/src/main/res/drawable-ldpi/ic_launcher.png differ diff --git a/android/src/main/res/drawable-mdpi/ic_launcher.png b/android/src/main/res/drawable-mdpi/ic_launcher.png index 359047d..af7a0c4 100644 Binary files a/android/src/main/res/drawable-mdpi/ic_launcher.png and b/android/src/main/res/drawable-mdpi/ic_launcher.png differ diff --git a/android/src/main/res/drawable-xhdpi/ic_launcher.png b/android/src/main/res/drawable-xhdpi/ic_launcher.png index 71c6d76..1afe1a5 100644 Binary files a/android/src/main/res/drawable-xhdpi/ic_launcher.png and b/android/src/main/res/drawable-xhdpi/ic_launcher.png differ diff --git a/android/src/main/res/values/strings.xml b/android/src/main/res/values/strings.xml index bafe393..7815956 100644 --- a/android/src/main/res/values/strings.xml +++ b/android/src/main/res/values/strings.xml @@ -1,4 +1,4 @@ - LayoutEngine + PanoPainter diff --git a/engine/app.cpp b/engine/app.cpp index 2fb52f3..56887f3 100644 --- a/engine/app.cpp +++ b/engine/app.cpp @@ -259,7 +259,8 @@ void App::initLayout() stroke->create(); stroke->loaded(); - canvas->m_brush = stroke->m_canvas->m_brush; + if (canvas) + canvas->m_brush = stroke->m_canvas->m_brush; brushes->on_brush_changed = [this](Node* target, int index) { auto tid = brushes->get_texture_id(index); @@ -273,13 +274,15 @@ void App::initLayout() color->on_color_changed = [this](Node* target, glm::vec4 color) { stroke->m_canvas->m_brush.m_tip_color = color; stroke->m_canvas->draw_stroke(); - canvas->m_brush = stroke->m_canvas->m_brush; + if (canvas) + canvas->m_brush = stroke->m_canvas->m_brush; if (on_color_change) on_color_change(color); }; stroke->on_stroke_change = [this](Node*target) { - canvas->m_brush = stroke->m_canvas->m_brush; + if (canvas) + canvas->m_brush = stroke->m_canvas->m_brush; if (on_stroke_change) on_stroke_change(); }; @@ -349,16 +352,20 @@ void App::initLayout() { button->on_click = [this,button](Node*) { //exit(0); - canvas->m_canvas->m_use_instanced = !canvas->m_canvas->m_use_instanced; - //button->color_normal = canvas->m_canvas->m_use_instanced ? glm::vec4(1, 0, 0, 1) : glm::vec4(0, 1, 0, 1); - button->m_text->set_text(canvas->m_canvas->m_use_instanced ? "INST" : "NORM"); + if (canvas) + { + canvas->m_canvas->m_use_instanced = !canvas->m_canvas->m_use_instanced; + //button->color_normal = canvas->m_canvas->m_use_instanced ? glm::vec4(1, 0, 0, 1) : glm::vec4(0, 1, 0, 1); + button->m_text->set_text(canvas->m_canvas->m_use_instanced ? "INST" : "NORM"); + } }; } if (auto* button = layout[main_id]->find("btn-close")) { button->on_click = [this](Node*) { //exit(0); - canvas->m_canvas->clear(); + if (canvas) + canvas->m_canvas->clear(); }; } if (auto* button = layout[main_id]->find("btn-popup")) @@ -491,9 +498,9 @@ void App::update(float dt) if (canvas && canvas->m_canvas) canvas->m_canvas->stroke_draw(); - glClearColor(.1f, .1f, .1f, 1.f); - glViewport(0, 0, (GLsizei)width, (GLsizei)height); - glClear(GL_COLOR_BUFFER_BIT); + //glClearColor(.1f, .1f, .1f, 1.f); + //glViewport(0, 0, (GLsizei)width, (GLsizei)height); + //glClear(GL_COLOR_BUFFER_BIT); #ifndef __ANDROID__ layout.reload(); diff --git a/engine/brush.cpp b/engine/brush.cpp index 15d3c66..50cd019 100644 --- a/engine/brush.cpp +++ b/engine/brush.cpp @@ -3,3 +3,218 @@ +void ui::BrushMesh::draw(const std::vector& samples, const glm::mat4& proj) +{ + std::vector attributes; + attributes.reserve(samples.size()); + 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); + attributes.emplace_back(instance_t{ mvp, s.flow }); + } +#ifdef USE_VBO + glBindBuffer(GL_ARRAY_BUFFER, buffers[2]); + glBufferData(GL_ARRAY_BUFFER, (int)(sizeof(instance_t) * attributes.size()), attributes.data(), GL_STATIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, 0); + + glBindVertexArray(vao); + glDrawElementsInstanced(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0, (int)samples.size()); + glBindVertexArray(0); +#else + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffers[1]); + glBindBuffer(GL_ARRAY_BUFFER, buffers[0]); + glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, sizeof(vertex_t), (GLvoid*)offsetof(vertex_t, pos)); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(vertex_t), (GLvoid*)offsetof(vertex_t, uvs)); + + // Likewise, we can do the same with the model matrix. Note that a + // matrix input to the vertex shader consumes N consecutive input + // locations, where N is the number of columns in the matrix. So... + // we have four vertex attributes to set up. + glBindBuffer(GL_ARRAY_BUFFER, buffers[2]); + glBufferData(GL_ARRAY_BUFFER, (int)(sizeof(instance_t) * attributes.size()), attributes.data(), GL_STATIC_DRAW); + // Loop over each column of the matrix... + for (int i = 0; i < 4; i++) + { + // Set up the vertex attribute + glVertexAttribPointer(loc_mvp + i, 4, GL_FLOAT, GL_FALSE, sizeof(instance_t), + (GLvoid*)(offsetof(instance_t, mvp) + sizeof(glm::vec4) * i)); + // Enable it + glEnableVertexAttribArray(loc_mvp + i); + // Make it instanced + glVertexAttribDivisor(loc_mvp + i, 1); + } + glEnableVertexAttribArray(loc_flow); + glVertexAttribPointer(loc_flow, 1, GL_FLOAT, GL_FALSE, sizeof(instance_t), + (GLvoid*)offsetof(instance_t, flow)); + glVertexAttribDivisor(loc_flow, 1); + + glDrawElementsInstanced(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0, (int)samples.size()); + //glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0); + + glDisableVertexAttribArray(0); + glDisableVertexAttribArray(1); + for (int i = 0; i < 4; i++) + glDisableVertexAttribArray(loc_mvp + i); + glDisableVertexAttribArray(loc_flow); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); +#endif // USE_VBO +} +bool ui::BrushMesh::create() +{ + static GLushort idx[6]{ 0, 1, 2, 0, 2, 3 }; + static vertex_t vertices[4]{ + { { -.5f, -.5f, 0, 1 }, { 0, 0 } }, // A B----C + { { -.5f, .5f, 0, 1 }, { 0, 1 } }, // B --\ | | + { { .5f, .5f, 0, 1 }, { 1, 1 } }, // C --/ | | + { { .5f, -.5f, 0, 1 }, { 1, 0 } }, // D A----D + }; + glGenBuffers(3, buffers); + if (!buffers) + return false; + + static instance_t inst{ glm::mat4(), .1f }; + glBindBuffer(GL_ARRAY_BUFFER, buffers[2]); + glBufferData(GL_ARRAY_BUFFER, sizeof(instance_t), &inst, GL_STATIC_DRAW); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffers[1]); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(idx), idx, GL_STATIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, buffers[0]); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + + // STROKE - INSTANCED + static const char* shader_stroke_inst_v = + SHADER_VERSION + "in vec4 pos;" + "in vec2 uvs;" + "in mat4 a_mvp;" + "in float a_flow;" + "out vec3 uv;" + "out float alpha;" + "void main(){" + " uv = vec3(uvs, pos.w);" + " alpha = a_flow;" + " gl_Position = a_mvp * vec4(pos.xyz, 1.0);" + "}"; + static const char* shader_stroke_inst_f = + SHADER_VERSION + "uniform sampler2D tex;" + "uniform vec4 col;" + "in float alpha;" + "in vec3 uv;" + "out vec4 frag;" + "void main(){" + " float a = (1.0 - texture(tex, uv.xy).r) * alpha;" + " frag = vec4(col.rgb, a);" + "}"; + + + if (!shader.create(shader_stroke_inst_v, shader_stroke_inst_f)) + LOG("Failed to create shader Texture"); + + loc_flow = shader.GetAttribLocation("a_flow"); + loc_mvp = shader.GetAttribLocation("a_mvp"); + +#if USE_VBO + glGenVertexArrays(1, &vao); + if (!vao) + return false; + glBindVertexArray(vao); + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffers[1]); + glBindBuffer(GL_ARRAY_BUFFER, buffers[0]); + glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, sizeof(vertex_t), (GLvoid*)offsetof(vertex_t, pos)); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(vertex_t), (GLvoid*)offsetof(vertex_t, uvs)); + glBindBuffer(GL_ARRAY_BUFFER, buffers[2]); + // Loop over each column of the matrix... + for (int i = 0; i < 4; i++) + { + // Set up the vertex attribute + glVertexAttribPointer(loc_mvp + i, 4, GL_FLOAT, GL_FALSE, sizeof(instance_t), + (GLvoid*)(offsetof(instance_t, mvp) + sizeof(glm::vec4) * i)); + // Enable it + glEnableVertexAttribArray(loc_mvp + i); + // Make it instanced + glVertexAttribDivisor(loc_mvp + i, 1); + } + glEnableVertexAttribArray(loc_flow); + glVertexAttribPointer(loc_flow, 1, GL_FLOAT, GL_FALSE, sizeof(instance_t), + (GLvoid*)offsetof(instance_t, flow)); + glVertexAttribDivisor(loc_flow, 1); + glBindVertexArray(0); +#endif + + return true; +} +ui::StrokeSample ui::Stroke::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 + + StrokeSample 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; +} +std::vector ui::Stroke::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); +} +bool ui::Stroke::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 +} +void ui::Stroke::reset(bool clear_keypoints /*= false*/) +{ + m_last_kp = 0; + m_dist = 0.f; + if (clear_keypoints) + m_keypoints.clear(); +} +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; +} +void ui::Stroke::start(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; + prng.seed(0); +} \ No newline at end of file diff --git a/engine/brush.h b/engine/brush.h index ed2afa1..7a7caf0 100644 --- a/engine/brush.h +++ b/engine/brush.h @@ -40,158 +40,8 @@ public: int loc_flow; int loc_mvp; - bool create() - { - static GLushort idx[6] { 0, 1, 2, 0, 2, 3 }; - static vertex_t vertices[4]{ - { { -.5f, -.5f, 0, 1 }, { 0, 0 } }, // A B----C - { { -.5f, .5f, 0, 1 }, { 0, 1 } }, // B --\ | | - { { .5f, .5f, 0, 1 }, { 1, 1 } }, // C --/ | | - { { .5f, -.5f, 0, 1 }, { 1, 0 } }, // D A----D - }; - glGenBuffers(3, buffers); - if (!buffers) - return false; - - static instance_t inst{ glm::mat4(), .1f }; - glBindBuffer(GL_ARRAY_BUFFER, buffers[2]); - glBufferData(GL_ARRAY_BUFFER, sizeof(instance_t), &inst, GL_STATIC_DRAW); - - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffers[1]); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(idx), idx, GL_STATIC_DRAW); - glBindBuffer(GL_ARRAY_BUFFER, buffers[0]); - glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - glBindBuffer(GL_ARRAY_BUFFER, 0); - - // STROKE - INSTANCED - static const char* shader_stroke_inst_v = - SHADER_VERSION - "in vec4 pos;" - "in vec2 uvs;" - "in mat4 a_mvp;" - "in float a_flow;" - "out vec3 uv;" - "out float alpha;" - "void main(){" - " uv = vec3(uvs, pos.w);" - " alpha = a_flow;" - " gl_Position = a_mvp * vec4(pos.xyz, 1.0);" - "}"; - static const char* shader_stroke_inst_f = - SHADER_VERSION - "uniform sampler2D tex;" - "uniform vec4 col;" - "in float alpha;" - "in vec3 uv;" - "out vec4 frag;" - "void main(){" - " float a = (1.0 - texture(tex, uv.xy).r) * alpha;" - " frag = vec4(col.rgb, a);" - "}"; - - - if (!shader.create(shader_stroke_inst_v, shader_stroke_inst_f)) - LOG("Failed to create shader Texture"); - - loc_flow = shader.GetAttribLocation("a_flow"); - loc_mvp = shader.GetAttribLocation("a_mvp"); - -#if USE_VBO - glGenVertexArrays(1, &vao); - if (!vao) - return false; - glBindVertexArray(vao); - glEnableVertexAttribArray(0); - glEnableVertexAttribArray(1); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffers[1]); - glBindBuffer(GL_ARRAY_BUFFER, buffers[0]); - glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, sizeof(vertex_t), (GLvoid*)offsetof(vertex_t, pos)); - glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(vertex_t), (GLvoid*)offsetof(vertex_t, uvs)); - glBindBuffer(GL_ARRAY_BUFFER, buffers[2]); - // Loop over each column of the matrix... - for (int i = 0; i < 4; i++) - { - // Set up the vertex attribute - glVertexAttribPointer(loc_mvp + i, 4, GL_FLOAT, GL_FALSE, sizeof(instance_t), - (GLvoid*)(offsetof(instance_t, mvp) + sizeof(glm::vec4) * i)); - // Enable it - glEnableVertexAttribArray(loc_mvp + i); - // Make it instanced - glVertexAttribDivisor(loc_mvp + i, 1); - } - glEnableVertexAttribArray(loc_flow); - glVertexAttribPointer(loc_flow, 1, GL_FLOAT, GL_FALSE, sizeof(instance_t), - (GLvoid*)offsetof(instance_t, flow)); - glVertexAttribDivisor(loc_flow, 1); - glBindVertexArray(0); -#endif - - return true; - } - void draw(const std::vector& samples, const glm::mat4& proj) - { - std::vector attributes; - attributes.reserve(samples.size()); - 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); - attributes.emplace_back(instance_t{ mvp, s.flow }); - } -#ifdef USE_VBO - glBindBuffer(GL_ARRAY_BUFFER, buffers[2]); - glBufferData(GL_ARRAY_BUFFER, (int)(sizeof(instance_t) * attributes.size()), attributes.data(), GL_STATIC_DRAW); - glBindBuffer(GL_ARRAY_BUFFER, 0); - - glBindVertexArray(vao); - glDrawElementsInstanced(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0, (int)samples.size()); - glBindVertexArray(0); -#else - glEnableVertexAttribArray(0); - glEnableVertexAttribArray(1); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffers[1]); - glBindBuffer(GL_ARRAY_BUFFER, buffers[0]); - glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, sizeof(vertex_t), (GLvoid*)offsetof(vertex_t, pos)); - glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(vertex_t), (GLvoid*)offsetof(vertex_t, uvs)); - - // Likewise, we can do the same with the model matrix. Note that a - // matrix input to the vertex shader consumes N consecutive input - // locations, where N is the number of columns in the matrix. So... - // we have four vertex attributes to set up. - glBindBuffer(GL_ARRAY_BUFFER, buffers[2]); - glBufferData(GL_ARRAY_BUFFER, (int)(sizeof(instance_t) * attributes.size()), attributes.data(), GL_STATIC_DRAW); - // Loop over each column of the matrix... - for (int i = 0; i < 4; i++) - { - // Set up the vertex attribute - glVertexAttribPointer(loc_mvp + i, 4, GL_FLOAT, GL_FALSE, sizeof(instance_t), - (GLvoid*)(offsetof(instance_t, mvp) + sizeof(glm::vec4) * i)); - // Enable it - glEnableVertexAttribArray(loc_mvp + i); - // Make it instanced - glVertexAttribDivisor(loc_mvp + i, 1); - } - glEnableVertexAttribArray(loc_flow); - glVertexAttribPointer(loc_flow, 1, GL_FLOAT, GL_FALSE, sizeof(instance_t), - (GLvoid*)offsetof(instance_t, flow)); - glVertexAttribDivisor(loc_flow, 1); - - glDrawElementsInstanced(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0, (int)samples.size()); - //glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0); - - glDisableVertexAttribArray(0); - glDisableVertexAttribArray(1); - for (int i = 0; i < 4; i++) - glDisableVertexAttribArray(loc_mvp + i); - glDisableVertexAttribArray(loc_flow); - - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); -#endif // USE_VBO - } + bool create(); + void draw(const std::vector& samples, const glm::mat4& proj); }; class Stroke @@ -211,62 +61,12 @@ public: 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); - } - StrokeSample 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 - - StrokeSample 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; - } + void start(const ui::Brush& brush); + void add_point(glm::vec2 pos, float pressure); + void reset(bool clear_keypoints = false); + bool has_sample(); + std::vector compute_samples(); + StrokeSample randomize_sample(const glm::vec2& pos, float pressure); }; class Layer diff --git a/engine/canvas.cpp b/engine/canvas.cpp index 8f50fbd..1862899 100644 --- a/engine/canvas.cpp +++ b/engine/canvas.cpp @@ -3,3 +3,119 @@ +void ui::Canvas::clear() +{ + m_fb.bindFramebuffer(); + + GLint vp[4]; + GLfloat cc[4]; + glGetIntegerv(GL_VIEWPORT, vp); + glGetFloatv(GL_COLOR_CLEAR_VALUE, cc); + + glClearColor(1, 1, 1, 1); + glClear(GL_COLOR_BUFFER_BIT); + glViewport(0, 0, m_width, m_height); + + // + + glViewport(vp[0], vp[1], vp[2], vp[3]); + glClearColor(cc[0], cc[1], cc[2], cc[3]); + + m_fb.unbindFramebuffer(); +} +void ui::Canvas::stroke_end() +{ + m_current_stroke = nullptr; +} +void ui::Canvas::stroke_draw() +{ + if (!(m_current_stroke && m_current_stroke->has_sample())) + return; + + m_fb.bindFramebuffer(); + + GLint vp[4]; + GLfloat cc[4]; + glGetIntegerv(GL_VIEWPORT, vp); + glGetFloatv(GL_COLOR_CLEAR_VALUE, cc); + + glViewport(0, 0, m_width, m_height); + glEnable(GL_BLEND); + + 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 samples = m_current_stroke->compute_samples(); + auto& tex = TextureManager::get(m_brush.m_tex_id); + tex.bind(); + m_sampler.bind(0); + + if (m_use_instanced) + { + m_mesh.shader.use(); + m_mesh.shader.u_vec4(kShaderUniform::Col, m_brush.m_tip_color); + m_mesh.shader.u_int(kShaderUniform::Tex, 0); + m_mesh.draw(samples, proj); + } + else + { + ShaderManager::use("stroke"); + ShaderManager::u_vec4(kShaderUniform::Col, m_brush.m_tip_color); + ShaderManager::u_int(kShaderUniform::Tex, 0); + 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(); + + glDisable(GL_BLEND); + + glViewport(vp[0], vp[1], vp[2], vp[3]); + glClearColor(cc[0], cc[1], cc[2], cc[3]); + + m_fb.unbindFramebuffer(); +} +void ui::Canvas::stroke_update(glm::vec2 point, float pressure) +{ + m_current_stroke->add_point(point, pressure); +} +void ui::Canvas::stroke_start(glm::vec2 point, float pressure, const ui::Brush& brush) +{ + m_strokes.emplace_back(); + m_strokes.back().start(brush); + m_strokes.back().add_point(point, pressure); + m_current_stroke = &m_strokes.back(); +} +void ui::Canvas::layer_add(std::string name) +{ + m_layers.emplace_back(); + m_layers.back().create(m_width, m_height, name); +} +void ui::Canvas::resize(int width, int height) +{ + m_width = width; + m_height = height; + m_tmp->create(width, height, "tmp"); + m_fb.create(width, height); +} +bool ui::Canvas::create(int width, int height) +{ + m_width = width; + m_height = height; + m_tmp = std::make_unique(); + m_tmp->create(width, height, "tmp"); + m_fb.create(width, height); + m_sampler.create(); + m_plane.create<1>(1, 1); + m_mesh.create(); + return true; +} \ No newline at end of file diff --git a/engine/canvas.h b/engine/canvas.h index 1279edf..6dd3f5b 100644 --- a/engine/canvas.h +++ b/engine/canvas.h @@ -9,136 +9,28 @@ NS_START class Canvas { + Stroke* m_current_stroke = nullptr; + Plane m_plane; + BrushMesh m_mesh; + int m_current_layer_idx = 0; public: int m_width; int m_height; + bool m_use_instanced = false; std::vector m_layers; std::vector m_strokes; std::unique_ptr m_tmp; - Stroke* m_current_stroke = nullptr; - int m_current_layer_idx = 0; RTT m_fb; Sampler m_sampler; - Plane m_plane; - BrushMesh m_mesh; - bool m_use_instanced = false; - std::minstd_rand prng; - bool create(int width, int height) - { - m_width = width; - m_height = height; - m_tmp = std::make_unique(); - m_tmp->create(width, height, "tmp"); - m_fb.create(width, height); - m_sampler.create(); - m_plane.create<1>(1, 1); - m_mesh.create(); - return true; - } - void resize(int width, int height) - { - m_width = width; - m_height = height; - m_tmp->create(width, height, "tmp"); - m_fb.create(width, height); - } - void layer_add(std::string name) - { - m_layers.emplace_back(); - m_layers.back().create(m_width, m_height, name); - } - void stroke_start(glm::vec2 point, float pressure, const ui::Brush& brush) - { - prng.seed(0); - m_strokes.emplace_back(); - m_strokes.back().start(point, pressure, brush); - m_current_stroke = &m_strokes.back(); - } - void stroke_update(glm::vec2 point, float pressure) - { - m_current_stroke->add_point(point, pressure); - } - void stroke_draw() - { - if (!(m_current_stroke && m_current_stroke->has_sample())) - return; - m_fb.bindFramebuffer(); - - GLint vp[4]; - GLfloat cc[4]; - glGetIntegerv(GL_VIEWPORT, vp); - glGetFloatv(GL_COLOR_CLEAR_VALUE, cc); - - glViewport(0, 0, m_width, m_height); - glEnable(GL_BLEND); - - 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 samples = m_current_stroke->compute_samples(); - auto& tex = TextureManager::get(m_brush.m_tex_id); - tex.bind(); - m_sampler.bind(0); - - if (m_use_instanced) - { - m_mesh.shader.use(); - m_mesh.shader.u_vec4(kShaderUniform::Col, m_brush.m_tip_color); - m_mesh.shader.u_int(kShaderUniform::Tex, 0); - m_mesh.draw(samples, proj); - } - else - { - ShaderManager::use("stroke"); - ShaderManager::u_vec4(kShaderUniform::Col, m_brush.m_tip_color); - ShaderManager::u_int(kShaderUniform::Tex, 0); - 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(); - - glDisable(GL_BLEND); - - glViewport(vp[0], vp[1], vp[2], vp[3]); - glClearColor(cc[0], cc[1], cc[2], cc[3]); - - m_fb.unbindFramebuffer(); - } - void stroke_end() - { - m_current_stroke = nullptr; - } - void clear() - { - m_fb.bindFramebuffer(); - - GLint vp[4]; - GLfloat cc[4]; - glGetIntegerv(GL_VIEWPORT, vp); - glGetFloatv(GL_COLOR_CLEAR_VALUE, cc); - - glClearColor(1, 1, 1, 1); - glClear(GL_COLOR_BUFFER_BIT); - glViewport(0, 0, m_width, m_height); - - // - - glViewport(vp[0], vp[1], vp[2], vp[3]); - glClearColor(cc[0], cc[1], cc[2], cc[3]); - - m_fb.unbindFramebuffer(); - } + bool create(int width, int height); + void resize(int width, int height); + void layer_add(std::string name); + void stroke_start(glm::vec2 point, float pressure, const ui::Brush& brush); + void stroke_update(glm::vec2 point, float pressure); + void stroke_draw(); + void stroke_end(); + void clear(); }; NS_END diff --git a/engine/layout.h b/engine/layout.h index 6ba3dee..1ca9e3c 100644 --- a/engine/layout.h +++ b/engine/layout.h @@ -1676,8 +1676,11 @@ class NodeStrokePreview : public NodeBorder { RTT m_rtt; Sampler m_sampler; + ui::BrushMesh m_mesh; public: ui::Brush m_brush; + ui::Stroke m_stroke; + std::vector m_bez_points; virtual Node* clone_instantiate() const override { return new NodeStrokePreview(); } virtual void clone_copy(Node* dest) const override { @@ -1694,6 +1697,7 @@ public: } void init_controls() { + m_mesh.create(); m_sampler.create(); TextureManager::load("data/Icons/Round-Hard.png"); m_brush.m_tex_id = const_hash("data/Icons/Round-Hard.png"); @@ -1710,46 +1714,47 @@ public: double w = (double)m_rtt.getWidth(); double h = (double)m_rtt.getHeight(); - std::vector kp = { {30, 30}, {30, h-30}, {w-30, 30}, { w-30, h-30 } }; glClearColor(1, 1, 1, 1); glClear(GL_COLOR_BUFFER_BIT); glViewport(0, 0, m_rtt.getWidth(), m_rtt.getHeight()); glEnable(GL_BLEND); glm::mat4 proj = glm::ortho(0, (float)m_rtt.getWidth(), 0, (float)m_rtt.getHeight(), -1, 1); - auto& t = TextureManager::get(m_brush.m_tex_id); - float alpha = 0; - std::minstd_rand prng; - ShaderManager::use("stroke"); - ShaderManager::u_vec4(kShaderUniform::Col, m_brush.m_tip_color); - ShaderManager::u_int(kShaderUniform::Tex, 0); - t.bind(); + m_stroke.reset(); + m_stroke.start(m_brush); + auto samples = m_stroke.compute_samples(); + auto& tex = TextureManager::get(m_brush.m_tex_id); + tex.bind(); m_sampler.bind(0); - while (alpha < 1.f) + + if (true) { - 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 = BezierCurve::Bezier2D(kp, alpha) + (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); - ShaderManager::u_mat4(kShaderUniform::MVP, mvp); - ShaderManager::u_float(kShaderUniform::Alpha, flow); - m_plane.draw_fill(); + m_mesh.shader.use(); + m_mesh.shader.u_vec4(kShaderUniform::Col, m_brush.m_tip_color); + m_mesh.shader.u_int(kShaderUniform::Tex, 0); + m_mesh.draw(samples, proj); } +// else +// { +// ShaderManager::use("stroke"); +// ShaderManager::u_vec4(kShaderUniform::Col, m_brush.m_tip_color); +// ShaderManager::u_int(kShaderUniform::Tex, 0); +// 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(); - t.unbind(); + tex.unbind(); glDisable(GL_BLEND); glViewport(vp[0], vp[1], vp[2], vp[3]); @@ -1771,6 +1776,14 @@ public: } virtual void handle_resize(glm::vec2 old_size, glm::vec2 new_size) override { + float pad = 30.f; + 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.start(m_brush); + for (int i = 0; i < 20; i++) + m_stroke.add_point(BezierCurve::Bezier2D(kp, i / 20.f), 1.f); + m_rtt.destroy(); m_rtt.create((int)new_size.x, (int)new_size.y); draw_stroke();