BLT-With-Graphics-Template/src/blt/gfx/renderer/batch_2d_renderer.cpp

533 lines
16 KiB
C++

/*
* <Short Description>
* Copyright (C) 2023 Brett Terpstra
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <blt/gfx/renderer/batch_2d_renderer.h>
#include <blt/gfx/renderer/shaders/2d_textured.vert>
#include <blt/gfx/renderer/shaders/2d_textured.frag>
#include <blt/gfx/renderer/shaders/2d_textured_circle.frag>
// https://stackoverflow.com/questions/60440682/drawing-a-line-in-modern-opengl
float square_vertices[20] = {
// positions // texture coords
0.5f, 0.5f, 0.0f, 1.0f, 1.0f, // top right
0.5f, -0.5f, 0.0f, 1.0f, 0.0f, // bottom right
-0.5f, -0.5f, 0.0f, 0.0f, 0.0f, // bottom left
-0.5f, 0.5f, 0.0f, 0.0f, 1.0f // top left
};
const unsigned int square_indices[6] = {
0, 1, 3, // first triangle
1, 2, 3 // second triangle
};
float line_vertices[20] = {
// positions // texture coords
0.5f, 0.5f, 0.0f, 1.0f, 1.0f, // top right
0.5f, -0.5f, 0.0f, 1.0f, 0.0f, // bottom right
-0.5f, -0.5f, 0.0f, 0.0f, 0.0f, // bottom left
-0.5f, 0.5f, 0.0f, 0.0f, 1.0f // top left
};
// 0, 1 (top right)
// 5, 6 (bottom right)
// 10, 11 (bottom left)
// 15, 16 (top left)
namespace blt::gfx
{
curve2d_mesh_data_t::draw_t curve2d_mesh_data_t::to_vertex_array() const
{
const auto vertices = calculate_vertices();
auto vao = std::make_unique<vertex_array_t>();
vertex_buffer_t vertex_buffer;
vertex_buffer.create();
vertex_buffer.allocate(static_cast<long>(vertices.size() * sizeof(line_vertex_t)), vertices.data());
const auto vb = vertex_array_t::make_vbo(vertex_buffer);
vao->bindVBO(vb, 0, 3, GL_FLOAT, sizeof(line_vertex_t), 0);
vao->bindVBO(vb, 1, 2, GL_FLOAT, sizeof(line_vertex_t), sizeof(vec3));
return {std::move(vao), static_cast<i32>(vertices.size())};
}
size_t curve2d_mesh_data_t::populate_vertex_array(vertex_array_t& va) const
{
const auto vertices = calculate_vertices();
va.getBuffer(0).update(static_cast<long>(vertices.size() * sizeof(line_vertex_t)), vertices.data());
return vertices.size();
}
std::vector<curve2d_mesh_data_t::line_vertex_t> curve2d_mesh_data_t::calculate_vertices() const
{
std::vector<line_vertex_t> vertices;
for (const auto [i, line] : enumerate(lines))
{
const float thickness = line.thickness;
const vec2 dir = (line.p1 - line.p2).normalize();
const vec2 right = {dir.y(), -dir.x()};
const vec2 left = {-dir.y(), dir.x()};
auto top_left = line.p2 + left * thickness;
auto top_right = line.p2 + right * thickness;
if (i == 0)
{
auto bottom_left = line.p1 + left * thickness;
auto bottom_right = line.p1 + right * thickness;
vertices.push_back({make_vec3(bottom_right), vec2{1, 0}});
vertices.push_back({make_vec3(bottom_left), vec2{0, 0}});
}
if (i == lines.size() - 1)
{
vertices.push_back({make_vec3(top_right), vec2{1, 1}});
vertices.push_back({make_vec3(top_left), vec2{0, 1}});
} else
{
auto& next = lines[i + 1];
vec2 next_dir = (next.p1 - next.p2).normalize();
vec2 next_right = {next_dir.y(), -next_dir.x()};
vec2 next_left = {-next_dir.y(), next_dir.x()};
auto next_bottom_left = next.p1 + next_left * next.thickness;
auto next_bottom_right = next.p1 + next_right * next.thickness;
auto avg_top_left = (next_bottom_left + top_left) / 2.0f;
auto avg_top_right = (next_bottom_right + top_right) / 2.0f;
vertices.push_back({make_vec3(avg_top_right), vec2{1, 1}});
vertices.push_back({make_vec3(avg_top_left), vec2{0, 1}});
}
}
return vertices;
}
f32 curve2d_mesh_data_t::length() const
{
f32 length = 0;
for (const auto& line : lines)
length += (line.p2 - line.p1).magnitude();
return length;
}
curve2d_t::curve2d_t(const vec2 p0, const vec2 p1, const vec2 p2): m_p0(p0), m_p1(p1), m_p2(p1), m_p3(p2)
{}
curve2d_t::curve2d_t(const vec2 p0, const vec2 p1, const vec2 p2, const vec2 p3): m_p0(p0), m_p1(p1), m_p2(p2), m_p3(p3)
{}
vec2 curve2d_t::get_point(const float t) const
{
const auto t_inv = 1.0f - t;
const auto t_inv_sq = t_inv * t_inv;
const auto t_inv_cub = t_inv_sq * t_inv;
const auto t_sq = t * t;
const auto t_cub = t_sq * t;
return t_inv_cub * m_p0 + 3 * t_inv_sq * t * m_p1 + 3 * t_inv * t_sq * m_p2 + t_cub * m_p3;
}
std::vector<line2d_t> curve2d_t::to_lines(const i32 segments, const float thickness) const
{
std::vector<line2d_t> lines;
float t = 0;
const float diff = 1.0f / static_cast<float>(segments);
for (i32 i = 0; i < segments; ++i)
{
auto begin = get_point(t);
t += diff;
auto end = get_point(t);
lines.emplace_back(begin, end, thickness);
}
return lines;
}
curve2d_mesh_data_t curve2d_t::to_mesh(const i32 segments, const float thickness) const
{
curve2d_mesh_data_t mesh_data;
mesh_data.lines = to_lines(segments, thickness);
return mesh_data;
}
f32 curve2d_t::length(const i32 segments) const
{
f32 length = 0;
float t = 0;
const float diff = 1.0f / static_cast<float>(segments);
for (i32 i = 0; i < segments; ++i)
{
auto begin = get_point(t);
t += diff;
auto end = get_point(t);
length += (end - begin).magnitude();
}
return length;
}
f32 curve2d_t::length_fast() const
{
const auto d1 = (m_p1 - m_p0).magnitude();
const auto d2 = (m_p2 - m_p1).magnitude();
const auto d3 = (m_p3 - m_p2).magnitude();
return d1 + d2 + d3;
}
void batch_renderer_2d::create()
{
{
vertex_buffer_t vertices_vbo;
element_buffer_t indices_vbo;
vertices_vbo.create();
indices_vbo.create();
vertices_vbo.allocate(sizeof(square_vertices), square_vertices);
indices_vbo.allocate(sizeof(square_indices), square_indices);
square_vao = std::make_unique<vertex_array_t>();
auto vb = vertex_array_t::make_vbo(vertices_vbo);
square_vao->bindVBO(vb, 0, 3, GL_FLOAT, 5 * sizeof(float), 0);
square_vao->bindVBO(vb, 1, 2, GL_FLOAT, 5 * sizeof(float), 3 * sizeof(float));
square_vao->bindElement(indices_vbo);
}
{
vertex_buffer_t vertices_vbo;
element_buffer_t indices_vbo;
vertices_vbo.create();
indices_vbo.create();
vertices_vbo.allocate(sizeof(line_vertices), line_vertices, GL_DYNAMIC_DRAW);
indices_vbo.allocate(sizeof(square_indices), square_indices, GL_DYNAMIC_DRAW);
line_vao = std::make_unique<vertex_array_t>();
const auto vb = vertex_array_t::make_vbo(vertices_vbo);
line_vao->bindVBO(vb, 0, 3, GL_FLOAT, 5 * sizeof(float), 0);
line_vao->bindVBO(vb, 1, 2, GL_FLOAT, 5 * sizeof(float), 3 * sizeof(float));
line_vao->bindElement(indices_vbo);
}
{
curve_vao = std::make_unique<vertex_array_t>();
vertex_buffer_t vertex_buffer;
vertex_buffer.create();
vertex_buffer.allocate(0, GL_DYNAMIC_DRAW);
const auto vb = vertex_array_t::make_vbo(vertex_buffer);
curve_vao->bindVBO(vb, 0, 3, GL_FLOAT, sizeof(curve2d_mesh_data_t::line_vertex_t), 0);
curve_vao->bindVBO(vb, 1, 2, GL_FLOAT, sizeof(curve2d_mesh_data_t::line_vertex_t), sizeof(vec3));
}
square_shader = shader_t::make_unique(shader_2d_textured_vert, shader_2d_textured_frag);
square_shader->bindAttribute(0, "vertex");
square_shader->bindAttribute(1, "uv_in");
point_shader = shader_t::make_unique(shader_2d_textured_vert, shader_2d_textured_cirlce_frag);
point_shader->bindAttribute(0, "vertex");
point_shader->bindAttribute(1, "uv_in");
engine->create();
}
void batch_renderer_2d::drawRectangle(const rectangle2d_t& rectangle, const std::string_view texture, const f32 z_index)
{
update_z_index(z_index);
insert_obj(draw.complex_rectangles, render_info_t::make_info(texture), {-z_index, rectangle});
}
void batch_renderer_2d::drawRectangle(const rectangle2d_t& rectangle, const vec4& color, const f32 z_index)
{
update_z_index(z_index);
insert_obj(draw.complex_rectangles, render_info_t::make_info(color), {-z_index, rectangle});
}
void batch_renderer_2d::drawRectangle(const rectangle2d_t& rectangle, const render_info_t& draw_info, const f32 z_index)
{
update_z_index(z_index);
insert_obj(draw.complex_rectangles, draw_info, {-z_index, rectangle});
}
void batch_renderer_2d::drawLine(const line2d_t& line, const std::string_view texture, const f32 z_index)
{
update_z_index(z_index);
insert_obj(draw.complex_lines, render_info_t::make_info(texture), {-z_index, line});
}
void batch_renderer_2d::drawLine(const line2d_t& line, const vec4& color, const f32 z_index)
{
update_z_index(z_index);
insert_obj(draw.complex_lines, render_info_t::make_info(color), {-z_index, line});
}
void batch_renderer_2d::drawLine(const line2d_t& line, const render_info_t& draw_info, const f32 z_index)
{
update_z_index(z_index);
insert_obj(draw.complex_lines, draw_info, {-z_index, line});
}
void batch_renderer_2d::drawPoint(const point2d_t& point, const std::string_view texture, const f32 z_index)
{
update_z_index(z_index);
insert_obj(draw.complex_points, render_info_t::make_info(texture), {-z_index, point});
}
void batch_renderer_2d::drawPoint(const point2d_t& point, const vec4& color, f32 z_index)
{
update_z_index(z_index);
insert_obj(draw.complex_points, render_info_t::make_info(color), {-z_index, point});
}
void batch_renderer_2d::drawPoint(const point2d_t& point, const render_info_t& draw_info, f32 z_index)
{
update_z_index(z_index);
insert_obj(draw.complex_points, draw_info, {-z_index, point});
}
void batch_renderer_2d::drawCurve(const curve2d_mesh_data_t& curve, std::string_view texture, f32 z_index)
{
update_z_index(z_index);
insert_obj(draw.complex_curves, render_info_t::make_info(texture), {-z_index, curve});
}
void batch_renderer_2d::drawCurve(const curve2d_mesh_data_t& curve, const vec4& color, f32 z_index)
{
update_z_index(z_index);
insert_obj(draw.complex_curves, render_info_t::make_info(color), {-z_index, curve});
}
void batch_renderer_2d::drawCurve(const curve2d_mesh_data_t& curve, const render_info_t& draw_info, f32 z_index)
{
update_z_index(z_index);
insert_obj(draw.complex_curves, draw_info, {-z_index, curve});
}
void batch_renderer_2d::cleanup()
{
engine->cleanup();
engine = nullptr;
square_vao = nullptr;
line_vao = nullptr;
curve_vao = nullptr;
square_shader = nullptr;
point_shader = nullptr;
}
void batch_renderer_2d::render(i32, i32, const bool transparency, const bool postprocessing)
{
//draw_buffer.bind();
//draw_buffer.updateBuffersStorage(width, height);
//glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
if (transparency)
{
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
glEnable(GL_DEPTH_TEST);
glActiveTexture(GL_TEXTURE0);
if (engine && postprocessing)
engine->bind();
draw_objects();
glDisable(GL_DEPTH_TEST);
if (transparency)
glDisable(GL_BLEND);
if (engine && postprocessing)
engine->render();
render_reset();
}
void batch_renderer_2d::draw_objects()
{
pre_reset();
const f32 denominator = 1.0f / (draw.z_max - draw.z_min);
glClear(GL_STENCIL_BUFFER_BIT);
draw_points(denominator);
draw_rectangles(denominator);
draw_lines(denominator);
draw_curves(denominator);
post_reset();
}
void batch_renderer_2d::render_reset()
{
draw.z_min = std::numeric_limits<f32>::max();
draw.z_max = std::numeric_limits<f32>::min();
}
void batch_renderer_2d::pre_reset()
{
draw.draw_count = 0;
}
void batch_renderer_2d::post_reset()
{}
void batch_renderer_2d::draw_points(const f32 denominator)
{
point_shader->bind();
square_vao->bind();
for (auto& [texture, objects] : draw.complex_points)
{
// resource manager handles the check for empty string
if (auto val = resources.get(texture))
val.value()->bind();
for (auto& [render_info, object] : objects)
{
auto& [z_index, point] = object;
mat4x4 model;
model.translate(point.pos.x(), point.pos.y(), 0.0f);
model.scale(point.scale, point.scale, 1.0);
point_shader->setVec4("color", render_info.color);
point_shader->setVec4("use_texture", render_info.blend);
//point_shader->setVec4("outline_color", render_info.outline_color);
point_shader->setFloat("z_index", (z_index - draw.z_min) * denominator);
point_shader->setMatrix("model", model);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
draw.draw_count++;
}
objects.clear();
}
}
void batch_renderer_2d::draw_lines(const f32 denominator)
{
mat4x4 empty_model;
square_shader->setMatrix("model", empty_model);
line_vao->bind();
auto& buf = line_vao->getBuffer(0);
buf.bind();
for (auto& [texture, objects] : draw.complex_lines)
{
// resource manager handles the check for empty string
if (auto val = resources.get(texture))
val.value()->bind();
for (auto& [render_info, object] : objects)
{
auto& [z_index, line] = object;
float thickness = line.thickness;
square_shader->setVec4("color", render_info.color);
square_shader->setVec4("use_texture", render_info.blend);
//square_shader->setVec4("outline_color", render_info.outline_color);
square_shader->setFloat("z_index", (z_index - draw.z_min) * denominator);
// 0, 1 (top right)
// 5, 6 (bottom right)
// 10, 11 (bottom left)
// 15, 16 (top left)
vec2 dir = (line.p1 - line.p2).normalize();
vec2 right = {dir.y(), -dir.x()};
vec2 left = {-dir.y(), dir.x()};
auto bottom_left = line.p1 + left * thickness;
auto bottom_right = line.p1 + right * thickness;
auto top_left = line.p2 + left * thickness;
auto top_right = line.p2 + right * thickness;
line_vertices[0] = top_right.x();
line_vertices[1] = top_right.y();
line_vertices[5] = bottom_right.x();
line_vertices[6] = bottom_right.y();
line_vertices[10] = bottom_left.x();
line_vertices[11] = bottom_left.y();
line_vertices[15] = top_left.x();
line_vertices[16] = top_left.y();
buf.update(sizeof(line_vertices), line_vertices);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
draw.draw_count++;
}
objects.clear();
}
}
void batch_renderer_2d::draw_curves(const f32 denominator)
{
curve_vao->bind();
mat4x4 empty_model;
square_shader->setMatrix("model", empty_model);
for (auto& [texture, objects] : draw.complex_curves)
{
if (auto val = resources.get(texture))
val.value()->bind();
for (auto& [render_info, object] : objects)
{
auto& [z_index, curve] = object;
square_shader->setVec4("color", render_info.color);
square_shader->setVec4("use_texture", render_info.blend);
square_shader->setFloat("z_index", (z_index - draw.z_min) * denominator);
const auto count = curve.populate_vertex_array(*curve_vao);
glDrawArrays(GL_TRIANGLE_STRIP, 0, static_cast<i32>(count));
draw.draw_count++;
}
objects.clear();
}
}
void batch_renderer_2d::draw_rectangles(const f32 denominator)
{
square_shader->bind();
square_vao->bind();
for (auto& [texture, objects] : draw.complex_rectangles)
{
// resource manager handles the check for empty string
if (auto val = resources.get(texture))
val.value()->bind();
for (auto& [render_info, object] : objects)
{
auto& [z_index, rect] = object;
mat4x4 model;
model.translate(rect.pos);
model.scale(rect.size);
square_shader->setVec4("color", render_info.color);
square_shader->setVec4("use_texture", render_info.blend);
if (rect.rotation != 0)
model.rotateZ(toRadians(rect.rotation));
//square_shader->setVec4("outline_color", render_info.outline_color);
square_shader->setFloat("z_index", (z_index - draw.z_min) * denominator);
square_shader->setMatrix("model", model);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
draw.draw_count++;
}
objects.clear();
}
}
}