change android icon, move code so .cpp and implement stroke preview using Stroke class for accurate preview.
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 25 KiB |
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">LayoutEngine</string>
|
||||
<string name="app_name">PanoPainter</string>
|
||||
</resources>
|
||||
|
||||
@@ -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<NodeButton>("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<NodeButton>("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();
|
||||
|
||||
215
engine/brush.cpp
@@ -3,3 +3,218 @@
|
||||
|
||||
|
||||
|
||||
void ui::BrushMesh::draw(const std::vector<StrokeSample>& samples, const glm::mat4& proj)
|
||||
{
|
||||
std::vector<instance_t> 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::StrokeSample> ui::Stroke::compute_samples()
|
||||
{
|
||||
int nsamples = (int)glm::floor((m_keypoints.back().dist - m_dist) / m_step);
|
||||
std::vector<StrokeSample> 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);
|
||||
}
|
||||
216
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<StrokeSample>& samples, const glm::mat4& proj)
|
||||
{
|
||||
std::vector<instance_t> 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<StrokeSample>& samples, const glm::mat4& proj);
|
||||
};
|
||||
|
||||
class Stroke
|
||||
@@ -211,62 +61,12 @@ public:
|
||||
std::vector<StrokeSample> 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<StrokeSample> compute_samples()
|
||||
{
|
||||
int nsamples = (int)glm::floor((m_keypoints.back().dist - m_dist) / m_step);
|
||||
std::vector<StrokeSample> 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<StrokeSample> compute_samples();
|
||||
StrokeSample randomize_sample(const glm::vec2& pos, float pressure);
|
||||
};
|
||||
|
||||
class Layer
|
||||
|
||||
@@ -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<Layer>();
|
||||
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;
|
||||
}
|
||||
134
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<Layer> m_layers;
|
||||
std::vector<Stroke> m_strokes;
|
||||
std::unique_ptr<Layer> 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<Layer>();
|
||||
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
|
||||
|
||||
@@ -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<glm::vec2> 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<glm::vec2> 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<float>(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<glm::vec2> 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();
|
||||
|
||||