|
|
- /*
- * Copyright (C) 2023 Christopher J. Howard
- *
- * This file is part of Antkeeper source code.
- *
- * Antkeeper source code 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.
- *
- * Antkeeper source code 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 Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
- */
-
- #include <engine/geom/brep/brep-operations.hpp>
- #include <engine/math/vector.hpp>
- #include <engine/render/vertex-attribute-location.hpp>
- #include <engine/debug/log.hpp>
- #include <algorithm>
- #include <cmath>
-
- namespace geom {
-
- void generate_face_normals(brep_mesh& mesh)
- {
- const auto& vertex_positions = mesh.vertices().attributes().at<math::fvec3>("position");
- auto& face_normals = static_cast<brep_attribute<math::fvec3>&>(*mesh.faces().attributes().try_emplace<math::fvec3>("normal").first);
-
- for (brep_face* face: mesh.faces())
- {
- auto loop = face->loops().begin();
- const auto& a = vertex_positions[loop->vertex()->index()];
- const auto& b = vertex_positions[(++loop)->vertex()->index()];
- const auto& c = vertex_positions[(++loop)->vertex()->index()];
-
- face_normals[face->index()] = math::normalize(math::cross(b - a, c - a));
- }
- }
-
- void generate_vertex_normals(brep_mesh& mesh)
- {
- // Generate face normals if they don't exist
- if (!mesh.faces().attributes().contains("normal"))
- {
- generate_face_normals(mesh);
- }
-
- const auto& vertex_positions = mesh.vertices().attributes().at<math::fvec3>("position");
- const auto& face_normals = mesh.faces().attributes().at<math::fvec3>("normal");
- auto& vertex_normals = static_cast<brep_attribute<math::fvec3>&>(*mesh.vertices().attributes().try_emplace<math::fvec3>("normal").first);
-
- for (brep_vertex* vertex: mesh.vertices())
- {
- // Zero vertex normal
- auto& vertex_normal = vertex_normals[vertex->index()];
- vertex_normal = {};
-
- // Skip vertices with no edges
- if (vertex->edges().empty())
- {
- continue;
- }
-
- // Get vertex position
- const auto& vertex_position = vertex_positions[vertex->index()];
-
- // For each edge bounded by this vertex
- for (brep_edge* edge: vertex->edges())
- {
- // Skip edges with no associated face
- if (edge->loops().empty())
- {
- continue;
- }
-
- // Calculate direction vector of current edge
- const auto direction0 = math::normalize
- (
- vertex_positions[edge->vertices()[edge->vertices().front() == vertex]->index()] -
- vertex_position
- );
-
- // For each edge loop
- for (brep_loop* loop: edge->loops())
- {
- // Skip loops not originating at vertex
- if (loop->vertex() != vertex)
- {
- continue;
- }
-
- // Calculate direction vector of previous edge
- const auto direction1 = math::normalize
- (
- vertex_positions[loop->previous()->vertex()->index()] -
- vertex_position
- );
-
- // Find angle between two edges
- const auto cos_edge_angle = math::dot(direction0, direction1);
- const auto edge_angle = std::acos(cos_edge_angle);
-
- // Weigh face normal by edge angle and add to vertex normal
- vertex_normal += face_normals[loop->face()->index()] * edge_angle;
- }
- }
-
- // Normalize vertex normal
- const auto sqr_length = math::sqr_length(vertex_normal);
- if (sqr_length)
- {
- vertex_normal /= std::sqrt(sqr_length);
- }
- }
- }
-
- void generate_loop_barycentric(brep_mesh& mesh)
- {
- const auto& vertex_positions = mesh.vertices().attributes().at<math::fvec3>("position");
- auto& loop_barycentric = static_cast<brep_attribute<math::fvec3>&>(*mesh.loops().attributes().try_emplace<math::fvec3>("barycentric").first);
-
- for (brep_face* face: mesh.faces())
- {
- auto loop = face->loops().begin();
- loop_barycentric[loop->index()] = {1.0f, 0.0f, 0.0f};
- loop_barycentric[(++loop)->index()] = {0.0f, 1.0f, 0.0f};
- loop_barycentric[(++loop)->index()] = {0.0f, 0.0f, 1.0f};
- }
- }
-
- std::unique_ptr<render::model> generate_model(const brep_mesh& mesh, std::shared_ptr<render::material> material)
- {
- // Get vertex positions
- const geom::brep_attribute<math::fvec3>* vertex_positions = nullptr;
- if (auto attribute_it = mesh.vertices().attributes().find("position"); attribute_it != mesh.vertices().attributes().end())
- {
- vertex_positions = &static_cast<const geom::brep_attribute<math::fvec3>&>(*attribute_it);
- }
-
- // Get vertex normals
- const geom::brep_attribute<math::fvec3>* vertex_normals = nullptr;
- if (auto attribute_it = mesh.vertices().attributes().find("normal"); attribute_it != mesh.vertices().attributes().end())
- {
- vertex_normals = &static_cast<const geom::brep_attribute<math::fvec3>&>(*attribute_it);
- }
-
- // Allocate model
- auto model = std::make_unique<render::model>();
-
- // Init model bounds
- auto& bounds = model->get_bounds();
- bounds = {math::fvec3::infinity(), -math::fvec3::infinity()};
-
- // Construct model VAO
- std::size_t vertex_stride = 0;
- std::vector<gl::vertex_input_attribute> vertex_attributes;
- gl::vertex_input_attribute position_attribute{};
- if (vertex_positions)
- {
- position_attribute.location = render::vertex_attribute_location::position;
- position_attribute.binding = 0;
- position_attribute.format = gl::format::r32g32b32_sfloat;
- position_attribute.offset = 0;
- vertex_attributes.emplace_back(position_attribute);
-
- vertex_stride += 3 * sizeof(float);
- }
- gl::vertex_input_attribute normal_attribute{};
- if (vertex_normals)
- {
- normal_attribute.location = render::vertex_attribute_location::normal;
- normal_attribute.binding = 0;
- normal_attribute.format = gl::format::r32g32b32_sfloat;
- normal_attribute.offset = static_cast<std::uint32_t>(vertex_stride);
- vertex_attributes.emplace_back(normal_attribute);
-
- vertex_stride += 3 * sizeof(float);
- }
- auto& vao = model->get_vertex_array();
- vao = std::make_unique<gl::vertex_array>(vertex_attributes);
-
- // Interleave vertex data
- std::vector<std::byte> vertex_data(mesh.faces().size() * 3 * vertex_stride);
- if (vertex_positions)
- {
- std::byte* v = vertex_data.data() + position_attribute.offset;
- for (auto face: mesh.faces())
- {
- for (auto loop: face->loops())
- {
- const auto& position = (*vertex_positions)[loop->vertex()->index()];
- std::memcpy(v, position.data(), sizeof(float) * 3);
- v += vertex_stride;
-
- // Extend model bounds
- bounds.extend(position);
- }
- }
- }
- if (vertex_normals)
- {
- std::byte* v = vertex_data.data() + normal_attribute.offset;
- for (auto face: mesh.faces())
- {
- for (auto loop: face->loops())
- {
- const auto& normal = (*vertex_normals)[loop->vertex()->index()];
- std::memcpy(v, normal.data(), sizeof(float) * 3);
- v += vertex_stride;
- }
- }
- }
-
- // Construct model VBO
- auto& vbo = model->get_vertex_buffer();
- vbo = std::make_unique<gl::vertex_buffer>(gl::buffer_usage::static_draw, vertex_data);
- model->set_vertex_offset(0);
- model->set_vertex_stride(vertex_stride);
-
- // Create material group
- model->get_groups().resize(1);
- render::model_group& model_group = model->get_groups().front();
-
- model_group.id = {};
- model_group.material = material;
- model_group.primitive_topology = gl::primitive_topology::triangle_list;
- model_group.first_vertex = 0;
- model_group.vertex_count = static_cast<std::uint32_t>(mesh.faces().size() * 3);
-
- return model;
- }
-
- } // namespace geom
|