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/shape.cpp
|
||||||
../engine/layout.cpp
|
../engine/layout.cpp
|
||||||
../engine/app.cpp
|
../engine/app.cpp
|
||||||
|
../engine/brush.cpp
|
||||||
|
../engine/canvas.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(native-lib PRIVATE
|
target_include_directories(native-lib PRIVATE
|
||||||
|
|||||||
@@ -28,7 +28,9 @@ android {
|
|||||||
// Sets a flag to enable format macro constants for the C++ compiler.
|
// Sets a flag to enable format macro constants for the C++ compiler.
|
||||||
//cppFlags "-D__STDC_FORMAT_MACROS"
|
//cppFlags "-D__STDC_FORMAT_MACROS"
|
||||||
arguments '-DANDROID_PLATFORM=android-19',
|
arguments '-DANDROID_PLATFORM=android-19',
|
||||||
'-DANDROID_TOOLCHAIN=clang', '-DANDROID_STL=gnustl_static'
|
'-DANDROID_TOOLCHAIN=clang',
|
||||||
|
'-DANDROID_STL=gnustl_static',
|
||||||
|
'-DCMAKE_BUILD_TYPE=Debug'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ndk {
|
ndk {
|
||||||
|
|||||||
@@ -167,6 +167,10 @@ static void engine_draw_frame(struct engine* engine) {
|
|||||||
if (engine->display == NULL)
|
if (engine->display == NULL)
|
||||||
return;
|
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);
|
App::I.update(0);
|
||||||
|
|
||||||
eglSwapBuffers(engine->display, engine->surface);
|
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"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">LayoutEngine</string>
|
<string name="app_name">PanoPainter</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -259,6 +259,7 @@ void App::initLayout()
|
|||||||
stroke->create();
|
stroke->create();
|
||||||
stroke->loaded();
|
stroke->loaded();
|
||||||
|
|
||||||
|
if (canvas)
|
||||||
canvas->m_brush = stroke->m_canvas->m_brush;
|
canvas->m_brush = stroke->m_canvas->m_brush;
|
||||||
|
|
||||||
brushes->on_brush_changed = [this](Node* target, int index) {
|
brushes->on_brush_changed = [this](Node* target, int index) {
|
||||||
@@ -273,12 +274,14 @@ void App::initLayout()
|
|||||||
color->on_color_changed = [this](Node* target, glm::vec4 color) {
|
color->on_color_changed = [this](Node* target, glm::vec4 color) {
|
||||||
stroke->m_canvas->m_brush.m_tip_color = color;
|
stroke->m_canvas->m_brush.m_tip_color = color;
|
||||||
stroke->m_canvas->draw_stroke();
|
stroke->m_canvas->draw_stroke();
|
||||||
|
if (canvas)
|
||||||
canvas->m_brush = stroke->m_canvas->m_brush;
|
canvas->m_brush = stroke->m_canvas->m_brush;
|
||||||
if (on_color_change)
|
if (on_color_change)
|
||||||
on_color_change(color);
|
on_color_change(color);
|
||||||
};
|
};
|
||||||
|
|
||||||
stroke->on_stroke_change = [this](Node*target) {
|
stroke->on_stroke_change = [this](Node*target) {
|
||||||
|
if (canvas)
|
||||||
canvas->m_brush = stroke->m_canvas->m_brush;
|
canvas->m_brush = stroke->m_canvas->m_brush;
|
||||||
if (on_stroke_change)
|
if (on_stroke_change)
|
||||||
on_stroke_change();
|
on_stroke_change();
|
||||||
@@ -349,15 +352,19 @@ void App::initLayout()
|
|||||||
{
|
{
|
||||||
button->on_click = [this,button](Node*) {
|
button->on_click = [this,button](Node*) {
|
||||||
//exit(0);
|
//exit(0);
|
||||||
|
if (canvas)
|
||||||
|
{
|
||||||
canvas->m_canvas->m_use_instanced = !canvas->m_canvas->m_use_instanced;
|
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->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");
|
button->m_text->set_text(canvas->m_canvas->m_use_instanced ? "INST" : "NORM");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (auto* button = layout[main_id]->find<NodeButton>("btn-close"))
|
if (auto* button = layout[main_id]->find<NodeButton>("btn-close"))
|
||||||
{
|
{
|
||||||
button->on_click = [this](Node*) {
|
button->on_click = [this](Node*) {
|
||||||
//exit(0);
|
//exit(0);
|
||||||
|
if (canvas)
|
||||||
canvas->m_canvas->clear();
|
canvas->m_canvas->clear();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -491,9 +498,9 @@ void App::update(float dt)
|
|||||||
if (canvas && canvas->m_canvas)
|
if (canvas && canvas->m_canvas)
|
||||||
canvas->m_canvas->stroke_draw();
|
canvas->m_canvas->stroke_draw();
|
||||||
|
|
||||||
glClearColor(.1f, .1f, .1f, 1.f);
|
//glClearColor(.1f, .1f, .1f, 1.f);
|
||||||
glViewport(0, 0, (GLsizei)width, (GLsizei)height);
|
//glViewport(0, 0, (GLsizei)width, (GLsizei)height);
|
||||||
glClear(GL_COLOR_BUFFER_BIT);
|
//glClear(GL_COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
#ifndef __ANDROID__
|
#ifndef __ANDROID__
|
||||||
layout.reload();
|
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_flow;
|
||||||
int loc_mvp;
|
int loc_mvp;
|
||||||
|
|
||||||
bool create()
|
bool create();
|
||||||
{
|
void draw(const std::vector<StrokeSample>& samples, const glm::mat4& proj);
|
||||||
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
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class Stroke
|
class Stroke
|
||||||
@@ -211,62 +61,12 @@ public:
|
|||||||
std::vector<StrokeSample> m_samples;
|
std::vector<StrokeSample> m_samples;
|
||||||
int m_last_kp;
|
int m_last_kp;
|
||||||
std::minstd_rand prng;
|
std::minstd_rand prng;
|
||||||
void start(glm::vec2 pos, float pressure, const ui::Brush& brush)
|
void start(const ui::Brush& brush);
|
||||||
{
|
void add_point(glm::vec2 pos, float pressure);
|
||||||
m_last_kp = 0;
|
void reset(bool clear_keypoints = false);
|
||||||
m_dist = 0.f;
|
bool has_sample();
|
||||||
m_step = glm::max(brush.m_tip_spacing * brush.m_tip_size * 30, 0.1f);
|
std::vector<StrokeSample> compute_samples();
|
||||||
m_brush = brush;
|
StrokeSample randomize_sample(const glm::vec2& pos, float pressure);
|
||||||
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;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class Layer
|
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
|
class Canvas
|
||||||
{
|
{
|
||||||
|
Stroke* m_current_stroke = nullptr;
|
||||||
|
Plane m_plane;
|
||||||
|
BrushMesh m_mesh;
|
||||||
|
int m_current_layer_idx = 0;
|
||||||
public:
|
public:
|
||||||
int m_width;
|
int m_width;
|
||||||
int m_height;
|
int m_height;
|
||||||
|
bool m_use_instanced = false;
|
||||||
std::vector<Layer> m_layers;
|
std::vector<Layer> m_layers;
|
||||||
std::vector<Stroke> m_strokes;
|
std::vector<Stroke> m_strokes;
|
||||||
std::unique_ptr<Layer> m_tmp;
|
std::unique_ptr<Layer> m_tmp;
|
||||||
Stroke* m_current_stroke = nullptr;
|
|
||||||
int m_current_layer_idx = 0;
|
|
||||||
RTT m_fb;
|
RTT m_fb;
|
||||||
Sampler m_sampler;
|
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();
|
bool create(int width, int height);
|
||||||
|
void resize(int width, int height);
|
||||||
GLint vp[4];
|
void layer_add(std::string name);
|
||||||
GLfloat cc[4];
|
void stroke_start(glm::vec2 point, float pressure, const ui::Brush& brush);
|
||||||
glGetIntegerv(GL_VIEWPORT, vp);
|
void stroke_update(glm::vec2 point, float pressure);
|
||||||
glGetFloatv(GL_COLOR_CLEAR_VALUE, cc);
|
void stroke_draw();
|
||||||
|
void stroke_end();
|
||||||
glViewport(0, 0, m_width, m_height);
|
void clear();
|
||||||
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();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
NS_END
|
NS_END
|
||||||
|
|||||||
@@ -1676,8 +1676,11 @@ class NodeStrokePreview : public NodeBorder
|
|||||||
{
|
{
|
||||||
RTT m_rtt;
|
RTT m_rtt;
|
||||||
Sampler m_sampler;
|
Sampler m_sampler;
|
||||||
|
ui::BrushMesh m_mesh;
|
||||||
public:
|
public:
|
||||||
ui::Brush m_brush;
|
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 Node* clone_instantiate() const override { return new NodeStrokePreview(); }
|
||||||
virtual void clone_copy(Node* dest) const override
|
virtual void clone_copy(Node* dest) const override
|
||||||
{
|
{
|
||||||
@@ -1694,6 +1697,7 @@ public:
|
|||||||
}
|
}
|
||||||
void init_controls()
|
void init_controls()
|
||||||
{
|
{
|
||||||
|
m_mesh.create();
|
||||||
m_sampler.create();
|
m_sampler.create();
|
||||||
TextureManager::load("data/Icons/Round-Hard.png");
|
TextureManager::load("data/Icons/Round-Hard.png");
|
||||||
m_brush.m_tex_id = const_hash("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 w = (double)m_rtt.getWidth();
|
||||||
double h = (double)m_rtt.getHeight();
|
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);
|
glClearColor(1, 1, 1, 1);
|
||||||
glClear(GL_COLOR_BUFFER_BIT);
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
glViewport(0, 0, m_rtt.getWidth(), m_rtt.getHeight());
|
glViewport(0, 0, m_rtt.getWidth(), m_rtt.getHeight());
|
||||||
glEnable(GL_BLEND);
|
glEnable(GL_BLEND);
|
||||||
glm::mat4 proj = glm::ortho<float>(0, (float)m_rtt.getWidth(), 0, (float)m_rtt.getHeight(), -1, 1);
|
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;
|
m_stroke.reset();
|
||||||
ShaderManager::use("stroke");
|
m_stroke.start(m_brush);
|
||||||
ShaderManager::u_vec4(kShaderUniform::Col, m_brush.m_tip_color);
|
auto samples = m_stroke.compute_samples();
|
||||||
ShaderManager::u_int(kShaderUniform::Tex, 0);
|
auto& tex = TextureManager::get(m_brush.m_tex_id);
|
||||||
t.bind();
|
tex.bind();
|
||||||
m_sampler.bind(0);
|
m_sampler.bind(0);
|
||||||
while (alpha < 1.f)
|
|
||||||
|
if (true)
|
||||||
{
|
{
|
||||||
auto rnd_nor = [&] { return float((double)prng() / (double)prng.max()); }; // normalized [0, +1]
|
m_mesh.shader.use();
|
||||||
auto rnd_neg = [&] { return float((double)prng() / (double)prng.max() * 2.0 - 1.0); }; // normalized [-1, +1]
|
m_mesh.shader.u_vec4(kShaderUniform::Col, m_brush.m_tip_color);
|
||||||
auto rnd_rad = [&] { return float((double)prng() / (double)prng.max() * M_PI * 2.0); }; // normalized [0, 2pi]
|
m_mesh.shader.u_int(kShaderUniform::Tex, 0);
|
||||||
auto rnd_vec = [&] { float rad = rnd_rad(); return glm::vec2(cosf(rad), sinf(rad)); }; // normalized direction vector
|
m_mesh.draw(samples, proj);
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
// 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();
|
m_sampler.unbind();
|
||||||
t.unbind();
|
tex.unbind();
|
||||||
glDisable(GL_BLEND);
|
glDisable(GL_BLEND);
|
||||||
|
|
||||||
glViewport(vp[0], vp[1], vp[2], vp[3]);
|
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
|
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.destroy();
|
||||||
m_rtt.create((int)new_size.x, (int)new_size.y);
|
m_rtt.create((int)new_size.x, (int)new_size.y);
|
||||||
draw_stroke();
|
draw_stroke();
|
||||||
|
|||||||