@ -0,0 +1,395 @@ | |||||
/* | |||||
* 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/>. | |||||
*/ | |||||
#ifndef ANTKEEPER_GEOM_BREP_ATTRIBUTE_MAP_HPP | |||||
#define ANTKEEPER_GEOM_BREP_ATTRIBUTE_MAP_HPP | |||||
#include <engine/geom/brep/brep-attribute.hpp> | |||||
#include <engine/utility/hash/fnv1a.hpp> | |||||
#include <iterator> | |||||
#include <memory> | |||||
#include <stdexcept> | |||||
#include <type_traits> | |||||
#include <unordered_map> | |||||
#include <utility> | |||||
namespace geom { | |||||
/** | |||||
* Maps names to B-rep attributes. | |||||
*/ | |||||
class brep_attribute_map | |||||
{ | |||||
public: | |||||
template <class Iter, bool Const> | |||||
class iterator_template | |||||
{ | |||||
public: | |||||
using iterator_type = Iter; | |||||
using iterator_category = std::bidirectional_iterator_tag; | |||||
using iterator_concept = std::bidirectional_iterator_tag; | |||||
using difference_type = std::iter_difference_t<Iter>; | |||||
using value_type = brep_attribute_base; | |||||
using pointer = std::conditional<Const, const value_type*, value_type*>::type; | |||||
using reference = std::conditional<Const, const value_type&, value_type&>::type; | |||||
[[nodiscard]] inline constexpr reference operator*() const noexcept | |||||
{ | |||||
return *m_it->second; | |||||
} | |||||
[[nodiscard]] inline constexpr pointer operator->() const noexcept | |||||
{ | |||||
return &(*m_it->second); | |||||
} | |||||
[[nodiscard]] inline constexpr reference operator[](difference_type i) const noexcept | |||||
{ | |||||
return *(m_it[i]); | |||||
} | |||||
inline iterator_template& operator++() noexcept | |||||
{ | |||||
++m_it; | |||||
return *this; | |||||
} | |||||
[[nodiscard]] inline iterator_template operator++(int) noexcept | |||||
{ | |||||
iterator_template tmp = *this; | |||||
++(*this); | |||||
return tmp; | |||||
} | |||||
inline iterator_template& operator--() noexcept | |||||
{ | |||||
--m_it; | |||||
return *this; | |||||
} | |||||
[[nodiscard]] inline iterator_template operator--(int) noexcept | |||||
{ | |||||
iterator_template tmp = *this; | |||||
--(*this); | |||||
return tmp; | |||||
} | |||||
inline iterator_template& operator+=(difference_type n) noexcept | |||||
{ | |||||
m_it += n; | |||||
return *this; | |||||
} | |||||
inline iterator_template& operator-=(difference_type n) noexcept | |||||
{ | |||||
m_it -= n; | |||||
return *this; | |||||
} | |||||
[[nodiscard]] inline bool operator==(const iterator_template& other) const noexcept | |||||
{ | |||||
return m_it == other.m_it; | |||||
}; | |||||
[[nodiscard]] inline std::weak_ordering operator<=>(const iterator_template& other) const noexcept | |||||
{ | |||||
return m_it <=> other.m_it; | |||||
} | |||||
[[nodiscard]] inline difference_type operator-(const iterator_template& rhs) const noexcept | |||||
{ | |||||
return m_it - rhs.m_it; | |||||
} | |||||
[[nodiscard]] inline iterator_template operator+(difference_type n) const noexcept | |||||
{ | |||||
return iterator_template{m_it + n}; | |||||
} | |||||
[[nodiscard]] inline iterator_template operator-(difference_type n) const noexcept | |||||
{ | |||||
return iterator_template{m_it - n}; | |||||
} | |||||
[[nodiscard]] friend iterator_template operator+(difference_type lhs, const iterator_template& rhs) noexcept | |||||
{ | |||||
return iterator_template{lhs + rhs.m_it}; | |||||
} | |||||
[[nodiscard]] friend iterator_template operator-(difference_type lhs, const iterator_template& rhs) noexcept | |||||
{ | |||||
return iterator_template{lhs - rhs.m_it}; | |||||
} | |||||
private: | |||||
friend class brep_attribute_map; | |||||
iterator_type m_it; | |||||
}; | |||||
using iterator = iterator_template<std::unordered_map<hash::fnv1a32_t, std::unique_ptr<brep_attribute_base>>::iterator, false>; | |||||
using const_iterator = iterator_template<std::unordered_map<hash::fnv1a32_t, std::unique_ptr<brep_attribute_base>>::const_iterator, true>; | |||||
/// @name Iterators | |||||
/// @{ | |||||
/// Returns an iterator to the first attribute. | |||||
/// @{ | |||||
[[nodiscard]] inline const_iterator begin() const noexcept | |||||
{ | |||||
const_iterator it; | |||||
it.m_it = m_attributes.begin(); | |||||
return it; | |||||
} | |||||
[[nodiscard]] inline iterator begin() noexcept | |||||
{ | |||||
iterator it; | |||||
it.m_it = m_attributes.begin(); | |||||
return it; | |||||
} | |||||
[[nodiscard]] inline const_iterator cbegin() const noexcept | |||||
{ | |||||
return begin(); | |||||
} | |||||
/// @} | |||||
/// Returns an iterator to the attribute following the last attribute. | |||||
/// @{ | |||||
[[nodiscard]] inline const_iterator end() const noexcept | |||||
{ | |||||
const_iterator it; | |||||
it.m_it = m_attributes.end(); | |||||
return it; | |||||
} | |||||
[[nodiscard]] inline iterator end() noexcept | |||||
{ | |||||
iterator it; | |||||
it.m_it = m_attributes.end(); | |||||
return it; | |||||
} | |||||
[[nodiscard]] inline const_iterator cend() const noexcept | |||||
{ | |||||
return end(); | |||||
} | |||||
/// @} | |||||
/// @} | |||||
/// @name Capacity | |||||
/// @{ | |||||
/// Returns `true` if the container is empty, `false` otherwise. | |||||
[[nodiscard]] inline bool empty() const noexcept | |||||
{ | |||||
return m_attributes.empty(); | |||||
} | |||||
/// Returns the number of attributes in the container. | |||||
[[nodiscard]] inline std::size_t size() const noexcept | |||||
{ | |||||
return m_attributes.size(); | |||||
} | |||||
/// @} | |||||
/// @name Modifiers | |||||
/// @{ | |||||
/** | |||||
* Removes all attributes from the container. | |||||
*/ | |||||
inline void clear() noexcept | |||||
{ | |||||
m_attributes.clear(); | |||||
} | |||||
/** | |||||
* Constructs a new attribute. If an attribute with the given name exists, it will be replaced. | |||||
* | |||||
* @tparam T Attribute data type. | |||||
* | |||||
* @param name Name of the new attribute. | |||||
* | |||||
* @return Iterator to the new attribute. | |||||
*/ | |||||
template <class T> | |||||
iterator emplace(hash::fnv1a32_t name) | |||||
{ | |||||
if (auto i = m_attributes.find(name); i != m_attributes.end()) | |||||
{ | |||||
i->second.reset(); | |||||
i->second = std::make_unique<brep_attribute<T>>(name, m_element_count); | |||||
iterator it; | |||||
it.m_it = i; | |||||
return it; | |||||
} | |||||
iterator it; | |||||
it.m_it = m_attributes.emplace(name, std::make_unique<brep_attribute<T>>(name, m_element_count)).first; | |||||
return it; | |||||
} | |||||
/** | |||||
* Removes an attribute from the container. | |||||
* | |||||
* @param pos Iterator to the attribute to remove. | |||||
* | |||||
* @return Iterator following the erased attribute. | |||||
*/ | |||||
inline iterator erase(iterator pos) | |||||
{ | |||||
iterator it; | |||||
it.m_it = m_attributes.erase(pos.m_it); | |||||
return it; | |||||
} | |||||
/** | |||||
* Removes an attribute from the container. | |||||
* | |||||
* @param name Name of the attribute to remove. | |||||
* | |||||
* @return Number of attributes removed (0 or 1). | |||||
*/ | |||||
inline std::size_t erase(hash::fnv1a32_t name) | |||||
{ | |||||
return m_attributes.erase(name); | |||||
} | |||||
/** | |||||
* Constructs a new attribute if an attribute with the given name does not exist. | |||||
* | |||||
* @tparam T Attribute data type. | |||||
* | |||||
* @param name Name of the new attribute. | |||||
* | |||||
* @return Pair consisting of an iterator to the new or pre-existing attribute, and a Boolean value that's `true` if the new attribute was constructed, or `false` if an attribute with the given name pre-existed. | |||||
*/ | |||||
template <class T> | |||||
std::pair<iterator, bool> try_emplace(hash::fnv1a32_t name) | |||||
{ | |||||
if (auto i = m_attributes.find(name); i != m_attributes.end()) | |||||
{ | |||||
iterator it; | |||||
it.m_it = i; | |||||
return {it, false}; | |||||
} | |||||
auto pair = m_attributes.emplace(name, std::make_unique<brep_attribute<T>>(name, m_element_count)); | |||||
iterator it; | |||||
it.m_it = pair.first; | |||||
return {it, pair.second}; | |||||
} | |||||
/// @} | |||||
/// @name Lookup | |||||
/// @{ | |||||
/** | |||||
* Returns a reference to the attribute with the given name. If no such attribute exists, an exception of type std::out_of_range is thrown. | |||||
* | |||||
* @tparam T Attribute data type. | |||||
* | |||||
* @return Reference to the attribute with the given name. | |||||
* | |||||
* @except std::out_of_range B-rep attribute not found. | |||||
*/ | |||||
/// @{ | |||||
template <class T> | |||||
[[nodiscard]] const brep_attribute<T>& at(hash::fnv1a32_t name) const | |||||
{ | |||||
auto it = find(name); | |||||
if (it == end()) | |||||
{ | |||||
throw std::out_of_range("B-rep attribute not found"); | |||||
} | |||||
return static_cast<const brep_attribute<T>&>(*it); | |||||
} | |||||
template <class T> | |||||
[[nodiscard]] brep_attribute<T>& at(hash::fnv1a32_t name) | |||||
{ | |||||
auto it = find(name); | |||||
if (it == end()) | |||||
{ | |||||
throw std::out_of_range("B-rep attribute not found"); | |||||
} | |||||
return static_cast<brep_attribute<T>&>(*it); | |||||
} | |||||
/// @} | |||||
/** | |||||
* Finds an attribute with the given name. | |||||
* | |||||
* @param name Name of an attribute. | |||||
* | |||||
* @return Iterator to the attribute with the given name. If no such attribute is found, an end iterator is returned. | |||||
*/ | |||||
/// @{ | |||||
[[nodiscard]] inline const_iterator find(hash::fnv1a32_t name) const | |||||
{ | |||||
const_iterator it; | |||||
it.m_it = m_attributes.find(name); | |||||
return it; | |||||
} | |||||
[[nodiscard]] inline iterator find(hash::fnv1a32_t name) | |||||
{ | |||||
iterator it; | |||||
it.m_it = m_attributes.find(name); | |||||
return it; | |||||
} | |||||
/// @} | |||||
/** | |||||
* Checks if there is an attribute with a given name in the container. | |||||
* | |||||
* @param name Attribute name. | |||||
* | |||||
* @return `true` if an attribute with the given name was found, `false` otherwise. | |||||
*/ | |||||
[[nodiscard]] inline bool contains(hash::fnv1a32_t name) const | |||||
{ | |||||
return m_attributes.contains(name); | |||||
} | |||||
/// @} | |||||
private: | |||||
template <class T> | |||||
friend class brep_element_container; | |||||
friend class brep_mesh; | |||||
brep_attribute_map& operator=(const brep_attribute_map& other) | |||||
{ | |||||
m_element_count = other.m_element_count; | |||||
m_attributes.clear(); | |||||
for (const auto& [key, value]: other.m_attributes) | |||||
{ | |||||
m_attributes.emplace(key, value->clone()); | |||||
} | |||||
return *this; | |||||
} | |||||
std::size_t m_element_count{}; | |||||
std::unordered_map<hash::fnv1a32_t, std::unique_ptr<brep_attribute_base>> m_attributes; | |||||
}; | |||||
} // namespace geom | |||||
#endif // ANTKEEPER_GEOM_BREP_ATTRIBUTE_MAP_HPP |
@ -0,0 +1,266 @@ | |||||
/* | |||||
* 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/>. | |||||
*/ | |||||
#ifndef ANTKEEPER_GEOM_BREP_ATTRIBUTE_HPP | |||||
#define ANTKEEPER_GEOM_BREP_ATTRIBUTE_HPP | |||||
#include <engine/utility/hash/fnv1a.hpp> | |||||
#include <memory> | |||||
#include <vector> | |||||
namespace geom { | |||||
/** | |||||
* Abstract base class for B-rep element attributes. | |||||
*/ | |||||
class brep_attribute_base | |||||
{ | |||||
public: | |||||
/// Returns the name of the attribute. | |||||
[[nodiscard]] inline constexpr hash::fnv1a32_t name() const noexcept | |||||
{ | |||||
return m_name; | |||||
} | |||||
protected: | |||||
inline explicit constexpr brep_attribute_base(hash::fnv1a32_t name) noexcept: | |||||
m_name(name) | |||||
{} | |||||
private: | |||||
friend class brep_attribute_map; | |||||
template <class T> | |||||
friend class brep_element_container; | |||||
/** | |||||
* Erases the attribute value of an element with the given index. | |||||
* | |||||
* @param i Index of an element. | |||||
*/ | |||||
virtual void erase(std::size_t i) = 0; | |||||
/** | |||||
* Appends a new attribute value to the end of the container. | |||||
*/ | |||||
virtual void emplace_back() = 0; | |||||
/// Returns a copy of this attribute. | |||||
[[nodiscard]] virtual std::unique_ptr<brep_attribute_base> clone() const = 0; | |||||
hash::fnv1a32_t m_name; | |||||
}; | |||||
/** | |||||
* Per-element B-rep data. | |||||
* | |||||
* @tparam T Data type. | |||||
*/ | |||||
template <class T> | |||||
class brep_attribute: public brep_attribute_base | |||||
{ | |||||
public: | |||||
using value_type = T; | |||||
using reference = value_type&; | |||||
using const_reference = const value_type&; | |||||
using pointer = value_type*; | |||||
using const_pointer = const value_type*; | |||||
using iterator = std::vector<value_type>::iterator; | |||||
using const_iterator = std::vector<value_type>::const_iterator; | |||||
using reverse_iterator = std::vector<value_type>::reverse_iterator; | |||||
using const_reverse_iterator = std::vector<value_type>::const_reverse_iterator; | |||||
/** | |||||
* Constructs an attribute. | |||||
* | |||||
* @param name Name of the attribute. | |||||
* @param element_count Number of elements. | |||||
*/ | |||||
brep_attribute(hash::fnv1a32_t name, std::size_t element_count): | |||||
brep_attribute_base(name), | |||||
m_values(element_count) | |||||
{} | |||||
/// @name Attribute access | |||||
/// @{ | |||||
/** | |||||
* Returns a reference to the attribute value of an element. | |||||
* | |||||
* @param i Index of an element. | |||||
* | |||||
* @return Reference to the attribute value of the element at index @p i. | |||||
*/ | |||||
/// @{ | |||||
[[nodiscard]] inline constexpr const_reference operator[](std::size_t i) const | |||||
{ | |||||
return m_values[i]; | |||||
} | |||||
[[nodiscard]] inline constexpr reference operator[](std::size_t i) | |||||
{ | |||||
return m_values[i]; | |||||
} | |||||
/// @} | |||||
/// Returns a reference to the attribute value of the first element. | |||||
/// @{ | |||||
[[nodiscard]] inline constexpr const_reference front() const | |||||
{ | |||||
return m_values.front(); | |||||
} | |||||
[[nodiscard]] inline constexpr reference front() | |||||
{ | |||||
return m_values.front(); | |||||
} | |||||
/// @} | |||||
/// Returns a reference to the attribute value of the last element. | |||||
/// @{ | |||||
[[nodiscard]] inline constexpr const_reference back() const | |||||
{ | |||||
return m_values.back(); | |||||
} | |||||
[[nodiscard]] inline constexpr reference back() | |||||
{ | |||||
return m_values.back(); | |||||
} | |||||
/// @} | |||||
/// Returns a pointer to the underlying array serving as attribute value storage. | |||||
/// @{ | |||||
[[nodiscard]] inline constexpr const value_type* data() const noexcept | |||||
{ | |||||
return m_values.data(); | |||||
} | |||||
[[nodiscard]] inline constexpr value_type* data() noexcept | |||||
{ | |||||
return m_values.data(); | |||||
} | |||||
/// @} | |||||
/// @} | |||||
/// @name Iterators | |||||
/// @{ | |||||
/// Returns an iterator to the attribute value of the first element. | |||||
/// @{ | |||||
[[nodiscard]] inline constexpr const_iterator begin() const noexcept | |||||
{ | |||||
return m_values.begin(); | |||||
} | |||||
[[nodiscard]] inline constexpr iterator begin() noexcept | |||||
{ | |||||
return m_values.begin(); | |||||
} | |||||
[[nodiscard]] inline constexpr const_iterator cbegin() const noexcept | |||||
{ | |||||
return m_values.begin(); | |||||
} | |||||
/// @} | |||||
/// Returns an iterator to the attribute value of the element following the last element. | |||||
/// @{ | |||||
[[nodiscard]] inline constexpr const_iterator end() const noexcept | |||||
{ | |||||
return m_values.end(); | |||||
} | |||||
[[nodiscard]] inline constexpr iterator end() noexcept | |||||
{ | |||||
return m_values.end(); | |||||
} | |||||
[[nodiscard]] inline constexpr const_iterator cend() const noexcept | |||||
{ | |||||
return m_values.end(); | |||||
} | |||||
/// @} | |||||
/// Returns a reverse iterator to the attribute value of the first element of the reversed container. | |||||
/// @{ | |||||
[[nodiscard]] inline constexpr const_reverse_iterator rbegin() const noexcept | |||||
{ | |||||
return m_values.rbegin(); | |||||
} | |||||
[[nodiscard]] inline constexpr reverse_iterator rbegin() noexcept | |||||
{ | |||||
return m_values.rbegin(); | |||||
} | |||||
[[nodiscard]] inline constexpr const_reverse_iterator crbegin() const noexcept | |||||
{ | |||||
return m_values.rbegin(); | |||||
} | |||||
/// @} | |||||
/// Returns a reverse iterator to the attribute value of the element following the last element of the reversed container. | |||||
/// @{ | |||||
[[nodiscard]] inline constexpr const_reverse_iterator rend() const noexcept | |||||
{ | |||||
return m_values.rend(); | |||||
} | |||||
[[nodiscard]] inline constexpr reverse_iterator rend() noexcept | |||||
{ | |||||
return m_values.rend(); | |||||
} | |||||
[[nodiscard]] inline constexpr const_reverse_iterator crend() const noexcept | |||||
{ | |||||
return m_values.rend(); | |||||
} | |||||
/// @} | |||||
/// @} | |||||
/// @name Capacity | |||||
/// @{ | |||||
/// Returns `true` if the container is empty, `false` otherwise. | |||||
[[nodiscard]] inline constexpr bool empty() const noexcept | |||||
{ | |||||
return m_values.empty(); | |||||
} | |||||
/// Returns the number of attribute values in the container. | |||||
[[nodiscard]] inline constexpr std::size_t size() const noexcept | |||||
{ | |||||
return m_values.size(); | |||||
} | |||||
/// @} | |||||
private: | |||||
void erase(std::size_t i) override | |||||
{ | |||||
m_values[i] = std::move(m_values.back()); | |||||
m_values.pop_back(); | |||||
} | |||||
void emplace_back() override | |||||
{ | |||||
m_values.emplace_back(); | |||||
} | |||||
[[nodiscard]] std::unique_ptr<brep_attribute_base> clone() const override | |||||
{ | |||||
auto copy = std::make_unique<brep_attribute<T>>(name(), 0); | |||||
copy->m_values = m_values; | |||||
return std::move(copy); | |||||
} | |||||
std::vector<value_type> m_values; | |||||
}; | |||||
} // namespace geom | |||||
#endif // ANTKEEPER_GEOM_BREP_ATTRIBUTE_HPP |
@ -0,0 +1,132 @@ | |||||
/* | |||||
* 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-edge.hpp> | |||||
#include <engine/geom/brep/brep-mesh.hpp> | |||||
#include <algorithm> | |||||
namespace geom { | |||||
void brep_edge_loop_list::push_back(brep_loop* loop) | |||||
{ | |||||
if (empty()) | |||||
{ | |||||
// List empty, initialize | |||||
m_head = loop; | |||||
loop->m_edge_next = loop; | |||||
loop->m_edge_previous = loop; | |||||
} | |||||
else | |||||
{ | |||||
// Append loop | |||||
loop->m_edge_next = m_head; | |||||
loop->m_edge_previous = m_head->m_edge_previous; | |||||
m_head->m_edge_previous->m_edge_next = loop; | |||||
m_head->m_edge_previous = loop; | |||||
} | |||||
++m_size; | |||||
} | |||||
void brep_edge_loop_list::remove(brep_loop* loop) | |||||
{ | |||||
// Directly link next and previous loops | |||||
loop->m_edge_next->m_edge_previous = loop->m_edge_previous; | |||||
loop->m_edge_previous->m_edge_next = loop->m_edge_next; | |||||
// If loop was the list head, update head | |||||
if (m_head == loop) | |||||
{ | |||||
m_head = loop->m_edge_next; | |||||
} | |||||
--m_size; | |||||
} | |||||
brep_edge* brep_edge_container::emplace_back(brep_vertex* a, brep_vertex* b) | |||||
{ | |||||
if (a == b) | |||||
{ | |||||
return nullptr; | |||||
} | |||||
brep_edge* ab = brep_element_container<brep_edge>::emplace_back(); | |||||
ab->m_index = size() - 1; | |||||
ab->m_vertices[0] = a; | |||||
ab->m_vertices[1] = b; | |||||
// Append edge AB to the edge lists of vertices A and B | |||||
a->m_edges.push_back(ab); | |||||
b->m_edges.push_back(ab); | |||||
return ab; | |||||
}; | |||||
void brep_edge_container::erase(brep_edge* edge) | |||||
{ | |||||
// Kill all loops and faces bounded by this edge | |||||
while (!edge->loops().empty()) | |||||
{ | |||||
m_mesh->faces().erase(edge->loops().back()->face()); | |||||
} | |||||
// Remove this edge from its vertices' lists of edges | |||||
edge->vertices().front()->m_edges.remove(edge); | |||||
edge->vertices().back()->m_edges.remove(edge); | |||||
// Erase edge | |||||
brep_element_container<brep_edge>::erase(edge); | |||||
} | |||||
void brep_edge_container::clear() noexcept | |||||
{ | |||||
while (!empty()) | |||||
{ | |||||
erase(back()); | |||||
} | |||||
} | |||||
brep_edge* brep_edge_container::find(brep_vertex* a, brep_vertex* b) const | |||||
{ | |||||
if (!a->edges().empty() && !b->edges().empty() && a != b) | |||||
{ | |||||
brep_edge* ea = a->edges().front(); | |||||
brep_edge* eb = b->edges().front(); | |||||
const std::size_t n = std::min<std::size_t>(a->edges().size(), b->edges().size()); | |||||
for (std::size_t i = 0; i < n; ++i) | |||||
{ | |||||
if (ea->vertices()[1] == b || ea->vertices()[0] == b) | |||||
{ | |||||
return ea; | |||||
} | |||||
if (eb->vertices()[1] == a || eb->vertices()[0] == a) | |||||
{ | |||||
return eb; | |||||
} | |||||
ea = ea->m_vertex_next[ea->vertices()[1] == a]; | |||||
eb = eb->m_vertex_next[eb->vertices()[1] == b]; | |||||
} | |||||
} | |||||
return nullptr; | |||||
} | |||||
} // namespace geom |
@ -0,0 +1,330 @@ | |||||
/* | |||||
* 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/>. | |||||
*/ | |||||
#ifndef ANTKEEPER_GEOM_BREP_EDGE_HPP | |||||
#define ANTKEEPER_GEOM_BREP_EDGE_HPP | |||||
#include <engine/geom/brep/brep-loop.hpp> | |||||
#include <engine/geom/brep/brep-element-container.hpp> | |||||
#include <array> | |||||
#include <cstddef> | |||||
#include <iterator> | |||||
namespace geom { | |||||
class brep_vertex; | |||||
class brep_loop; | |||||
template <class T> | |||||
class brep_element_container; | |||||
/** | |||||
* List of B-rep loops that share a common edge. | |||||
*/ | |||||
class brep_edge_loop_list | |||||
{ | |||||
public: | |||||
friend class brep_mesh; | |||||
friend class brep_face_container; | |||||
struct const_iterator | |||||
{ | |||||
public: | |||||
friend class brep_edge_loop_list; | |||||
using iterator_category = std::bidirectional_iterator_tag; | |||||
using iterator_concept = std::bidirectional_iterator_tag; | |||||
using difference_type = std::ptrdiff_t; | |||||
using value_type = brep_loop*; | |||||
using pointer = const value_type*; | |||||
using reference = const value_type&; | |||||
[[nodiscard]] inline constexpr value_type operator*() const noexcept | |||||
{ | |||||
return m_loop; | |||||
} | |||||
[[nodiscard]] inline constexpr value_type operator->() const noexcept | |||||
{ | |||||
return m_loop; | |||||
} | |||||
inline const_iterator& operator++() noexcept | |||||
{ | |||||
m_loop = m_loop->m_edge_next; | |||||
++m_position; | |||||
return *this; | |||||
} | |||||
[[nodiscard]] inline const_iterator operator++(int) noexcept | |||||
{ | |||||
const_iterator tmp = *this; | |||||
++(*this); | |||||
return tmp; | |||||
} | |||||
inline const_iterator& operator--() noexcept | |||||
{ | |||||
m_loop = m_loop->m_edge_previous; | |||||
--m_position; | |||||
return *this; | |||||
} | |||||
[[nodiscard]] inline const_iterator operator--(int) noexcept | |||||
{ | |||||
const_iterator tmp = *this; | |||||
--(*this); | |||||
return tmp; | |||||
} | |||||
[[nodiscard]] inline bool operator==(const const_iterator& other) const noexcept | |||||
{ | |||||
return m_position == other.m_position; | |||||
}; | |||||
[[nodiscard]] inline std::weak_ordering operator<=>(const const_iterator& other) const noexcept | |||||
{ | |||||
return m_position <=> other.m_position; | |||||
} | |||||
[[nodiscard]] inline difference_type operator-(const const_iterator& rhs) const noexcept | |||||
{ | |||||
return m_position - rhs.m_position; | |||||
} | |||||
private: | |||||
brep_loop* m_loop; | |||||
std::ptrdiff_t m_position; | |||||
}; | |||||
using const_reverse_iterator = std::reverse_iterator<const_iterator>; | |||||
/// @name Element access | |||||
/// @{ | |||||
/// Returns the first loop. | |||||
[[nodiscard]] inline brep_loop* front() const noexcept | |||||
{ | |||||
return m_head; | |||||
} | |||||
/// Returns the last loop. | |||||
[[nodiscard]] inline brep_loop* back() const noexcept | |||||
{ | |||||
return m_head->m_edge_previous; | |||||
} | |||||
/// @} | |||||
/// @name Iterators | |||||
/// @{ | |||||
/// Returns an iterator to the first loop. | |||||
/// @{ | |||||
[[nodiscard]] inline constexpr const_iterator begin() const noexcept | |||||
{ | |||||
const_iterator it; | |||||
it.m_loop = m_head; | |||||
it.m_position = 0; | |||||
return it; | |||||
} | |||||
[[nodiscard]] inline constexpr const_iterator cbegin() const noexcept | |||||
{ | |||||
return begin(); | |||||
} | |||||
/// @} | |||||
/// Returns an iterator to the loop following the last loop. | |||||
/// @{ | |||||
[[nodiscard]] inline constexpr const_iterator end() const noexcept | |||||
{ | |||||
const_iterator it; | |||||
it.m_loop = m_head; | |||||
it.m_position = static_cast<std::ptrdiff_t>(m_size); | |||||
return it; | |||||
} | |||||
[[nodiscard]] inline constexpr const_iterator cend() const noexcept | |||||
{ | |||||
return end(); | |||||
} | |||||
/// @} | |||||
/// Returns a reverse iterator to the first loop of the reversed list. | |||||
/// @{ | |||||
[[nodiscard]] inline constexpr const_reverse_iterator rbegin() const noexcept | |||||
{ | |||||
return std::make_reverse_iterator(end()); | |||||
} | |||||
[[nodiscard]] inline constexpr const_reverse_iterator crbegin() const noexcept | |||||
{ | |||||
return rbegin(); | |||||
} | |||||
/// @} | |||||
/// Returns a reverse iterator to the loop following the last loop of the reversed list. | |||||
/// @{ | |||||
[[nodiscard]] inline constexpr const_reverse_iterator rend() const noexcept | |||||
{ | |||||
return std::make_reverse_iterator(begin()); | |||||
} | |||||
[[nodiscard]] inline constexpr const_reverse_iterator crend() const noexcept | |||||
{ | |||||
return rend(); | |||||
} | |||||
/// @} | |||||
/// @} | |||||
/// @name Capacity | |||||
/// @{ | |||||
/// Returns `true` if the list is empty, `false` otherwise. | |||||
[[nodiscard]] inline constexpr bool empty() const noexcept | |||||
{ | |||||
return !m_size; | |||||
} | |||||
/// Returns the number of loops in the list. | |||||
[[nodiscard]] inline constexpr std::size_t size() const noexcept | |||||
{ | |||||
return m_size; | |||||
} | |||||
/// @} | |||||
/// @name Modifiers | |||||
/// @{ | |||||
/** | |||||
* Appends a loop to the end of the list. | |||||
* | |||||
* @param loop Pointer to the loop to append. | |||||
*/ | |||||
void push_back(brep_loop* loop); | |||||
/** | |||||
* Removes an loop from the list. | |||||
* | |||||
* @param loop Pointer to the loop to remove. | |||||
*/ | |||||
void remove(brep_loop* loop); | |||||
/// @} | |||||
private: | |||||
brep_loop* m_head{}; | |||||
std::size_t m_size{}; | |||||
}; | |||||
/** | |||||
* Curve segment bounded by two vertices. | |||||
*/ | |||||
class brep_edge | |||||
{ | |||||
public: | |||||
friend class brep_mesh; | |||||
friend class brep_vertex_edge_list; | |||||
friend class brep_element_container<brep_edge>; | |||||
friend class brep_edge_container; | |||||
friend class brep_face_container; | |||||
/** | |||||
* Returns the index of this edge in the mesh edge array. | |||||
* | |||||
* @warning This index may change if any edges are removed from the mesh. | |||||
*/ | |||||
[[nodiscard]] inline constexpr std::size_t index() const noexcept | |||||
{ | |||||
return m_index; | |||||
} | |||||
/// Returns the pair of vertices that bound this edge. | |||||
[[nodiscard]] inline constexpr const std::array<brep_vertex*, 2>& vertices() const noexcept | |||||
{ | |||||
return m_vertices; | |||||
} | |||||
/// Returns the list of loops that share this edge. | |||||
[[nodiscard]] inline constexpr const brep_edge_loop_list& loops() const noexcept | |||||
{ | |||||
return m_loops; | |||||
} | |||||
private: | |||||
std::size_t m_index; | |||||
std::array<brep_vertex*, 2> m_vertices; | |||||
std::array<brep_edge*, 2> m_vertex_next; | |||||
std::array<brep_edge*, 2> m_vertex_previous; | |||||
brep_edge_loop_list m_loops; | |||||
}; | |||||
/** | |||||
* B-rep edge container. | |||||
*/ | |||||
class brep_edge_container: public brep_element_container<brep_edge> | |||||
{ | |||||
public: | |||||
/** | |||||
* Appends a new edge to the end of the container. | |||||
* | |||||
* @param a First bounding vertex of the edge. | |||||
* @param b Second bounding vertex of the edge. | |||||
* | |||||
* @return Pointer to the new edge. | |||||
*/ | |||||
brep_edge* emplace_back(brep_vertex* a, brep_vertex* b); | |||||
/** | |||||
* Erases an edge and all dependent loops and faces. | |||||
* | |||||
* @param edge Pointer to the edge to erase. | |||||
* | |||||
* @warning Invalidates iterators and indices of edges, loops, and faces. | |||||
*/ | |||||
void erase(brep_edge* edge) override; | |||||
/** | |||||
* Erases all edges and their dependent loops and faces. | |||||
*/ | |||||
void clear() noexcept; | |||||
/** | |||||
* Finds an edge bounded by vertices @p a and @p b (in any order). | |||||
* | |||||
* @param a First (or second) bounding vertex of the edge. | |||||
* @param b Second (or first) bounding vertex of the edge. | |||||
* | |||||
* @return Pointer to an edge bounded by vertices @p a and @p b, or `nullptr` if no such edge was found. | |||||
*/ | |||||
[[nodiscard]] brep_edge* find(brep_vertex* a, brep_vertex* b) const; | |||||
private: | |||||
friend class brep_mesh; | |||||
/** | |||||
* Constructs a B-rep edge container. | |||||
* | |||||
* @param mesh Pointer to the parent mesh. | |||||
*/ | |||||
inline explicit brep_edge_container(brep_mesh* mesh) noexcept: | |||||
brep_element_container<brep_edge>(mesh) | |||||
{} | |||||
}; | |||||
} // namespace geom | |||||
#endif // ANTKEEPER_GEOM_BREP_EDGE_HPP |
@ -0,0 +1,329 @@ | |||||
/* | |||||
* 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/>. | |||||
*/ | |||||
#ifndef ANTKEEPER_GEOM_BREP_ELEMENT_CONTAINER_HPP | |||||
#define ANTKEEPER_GEOM_BREP_ELEMENT_CONTAINER_HPP | |||||
#include <engine/geom/brep/brep-attribute-map.hpp> | |||||
#include <engine/utility/hash/fnv1a.hpp> | |||||
#include <cstddef> | |||||
#include <iterator> | |||||
#include <memory> | |||||
#include <vector> | |||||
namespace geom { | |||||
/** | |||||
* Container for B-rep elements. | |||||
* | |||||
* @tparam T Element type. | |||||
*/ | |||||
template <class T> | |||||
class brep_element_container | |||||
{ | |||||
public: | |||||
friend class brep_mesh; | |||||
using element_type = T; | |||||
struct const_iterator | |||||
{ | |||||
public: | |||||
using iterator_category = std::random_access_iterator_tag; | |||||
using iterator_concept = std::random_access_iterator_tag; | |||||
using difference_type = std::ptrdiff_t; | |||||
using value_type = element_type*; | |||||
using pointer = const value_type*; | |||||
using reference = const value_type&; | |||||
[[nodiscard]] inline constexpr value_type operator*() const noexcept | |||||
{ | |||||
return m_it->get(); | |||||
} | |||||
[[nodiscard]] inline constexpr value_type operator->() const noexcept | |||||
{ | |||||
return m_it->get(); | |||||
} | |||||
[[nodiscard]] inline constexpr value_type operator[](difference_type i) const noexcept | |||||
{ | |||||
return m_it[i]->get(); | |||||
} | |||||
inline const_iterator& operator++() noexcept | |||||
{ | |||||
++m_it; | |||||
return *this; | |||||
} | |||||
[[nodiscard]] inline const_iterator operator++(int) noexcept | |||||
{ | |||||
const_iterator tmp = *this; | |||||
++(*this); | |||||
return tmp; | |||||
} | |||||
inline const_iterator& operator--() noexcept | |||||
{ | |||||
--m_it; | |||||
return *this; | |||||
} | |||||
[[nodiscard]] inline const_iterator operator--(int) noexcept | |||||
{ | |||||
const_iterator tmp = *this; | |||||
--(*this); | |||||
return tmp; | |||||
} | |||||
inline const_iterator& operator+=(difference_type n) noexcept | |||||
{ | |||||
m_it += n; | |||||
return *this; | |||||
} | |||||
inline const_iterator& operator-=(difference_type n) noexcept | |||||
{ | |||||
m_it -= n; | |||||
return *this; | |||||
} | |||||
[[nodiscard]] inline bool operator==(const const_iterator& other) const noexcept | |||||
{ | |||||
return m_it == other.m_it; | |||||
}; | |||||
[[nodiscard]] inline std::weak_ordering operator<=>(const const_iterator& other) const noexcept | |||||
{ | |||||
return m_it <=> other.m_it; | |||||
} | |||||
[[nodiscard]] inline difference_type operator-(const const_iterator& rhs) const noexcept | |||||
{ | |||||
return m_it - rhs.m_it; | |||||
} | |||||
[[nodiscard]] inline const_iterator operator+(difference_type n) const noexcept | |||||
{ | |||||
return const_iterator{m_it + n}; | |||||
} | |||||
[[nodiscard]] inline const_iterator operator-(difference_type n) const noexcept | |||||
{ | |||||
return const_iterator{m_it - n}; | |||||
} | |||||
[[nodiscard]] friend const_iterator operator+(difference_type lhs, const const_iterator& rhs) noexcept | |||||
{ | |||||
return const_iterator{lhs + rhs.m_it}; | |||||
} | |||||
[[nodiscard]] friend const_iterator operator-(difference_type lhs, const const_iterator& rhs) noexcept | |||||
{ | |||||
return const_iterator{lhs - rhs.m_it}; | |||||
} | |||||
private: | |||||
friend class brep_element_container; | |||||
std::vector<std::unique_ptr<element_type>>::const_iterator m_it; | |||||
}; | |||||
using const_reverse_iterator = std::reverse_iterator<const_iterator>; | |||||
/// @name Element access | |||||
/// @{ | |||||
/** | |||||
* Returns a pointer to the element at the specified index. | |||||
* | |||||
* @param i Index of an element. | |||||
* | |||||
* @return Pointer to the element at index @p i. | |||||
*/ | |||||
[[nodiscard]] inline constexpr element_type* operator[](std::size_t i) const | |||||
{ | |||||
return m_elements[i].get(); | |||||
} | |||||
/// Returns the first element. | |||||
[[nodiscard]] inline constexpr element_type* front() const noexcept | |||||
{ | |||||
return m_elements.front().get(); | |||||
} | |||||
/// Returns the last element. | |||||
[[nodiscard]] inline constexpr element_type* back() const noexcept | |||||
{ | |||||
return m_elements.back().get(); | |||||
} | |||||
/// @} | |||||
/// @name Iterators | |||||
/// @{ | |||||
/// Returns an iterator to the first element. | |||||
/// @{ | |||||
[[nodiscard]] inline constexpr const_iterator begin() const noexcept | |||||
{ | |||||
const_iterator it; | |||||
it.m_it = m_elements.begin(); | |||||
return it; | |||||
} | |||||
[[nodiscard]] inline constexpr const_iterator cbegin() const noexcept | |||||
{ | |||||
return begin(); | |||||
} | |||||
/// @} | |||||
/// Returns an iterator to the element following the last element. | |||||
/// @{ | |||||
[[nodiscard]] inline constexpr const_iterator end() const noexcept | |||||
{ | |||||
const_iterator it; | |||||
it.m_it = m_elements.end(); | |||||
return it; | |||||
} | |||||
[[nodiscard]] inline constexpr const_iterator cend() const noexcept | |||||
{ | |||||
return end(); | |||||
} | |||||
/// @} | |||||
/// Returns a reverse iterator to the first element of the reversed container. | |||||
/// @{ | |||||
[[nodiscard]] inline constexpr const_reverse_iterator rbegin() const noexcept | |||||
{ | |||||
return std::make_reverse_iterator(end()); | |||||
} | |||||
[[nodiscard]] inline constexpr const_reverse_iterator crbegin() const noexcept | |||||
{ | |||||
return rbegin(); | |||||
} | |||||
/// @} | |||||
/// Returns a reverse iterator to the element following the last element of the reversed container. | |||||
/// @{ | |||||
[[nodiscard]] inline constexpr const_reverse_iterator rend() const noexcept | |||||
{ | |||||
return std::make_reverse_iterator(begin()); | |||||
} | |||||
[[nodiscard]] inline constexpr const_reverse_iterator crend() const noexcept | |||||
{ | |||||
return rend(); | |||||
} | |||||
/// @} | |||||
/// @} | |||||
/// @name Capacity | |||||
/// @{ | |||||
/// Returns `true` if the container is empty, `false` otherwise. | |||||
[[nodiscard]] inline constexpr bool empty() const noexcept | |||||
{ | |||||
return m_elements.empty(); | |||||
} | |||||
/// Returns the number of elements in the container. | |||||
[[nodiscard]] inline constexpr std::size_t size() const noexcept | |||||
{ | |||||
return m_elements.size(); | |||||
} | |||||
/// @} | |||||
/// @name Attributes | |||||
/// @{ | |||||
/// Returns the element attribute map. | |||||
/// @{ | |||||
[[nodiscard]] const brep_attribute_map& attributes() const noexcept | |||||
{ | |||||
return m_attribute_map; | |||||
} | |||||
[[nodiscard]] brep_attribute_map& attributes() noexcept | |||||
{ | |||||
return m_attribute_map; | |||||
} | |||||
/// @} | |||||
/// @} | |||||
protected: | |||||
friend class brep_mesh; | |||||
/** | |||||
* Constructs a B-rep element container. | |||||
* | |||||
* @param mesh Pointer to the parent mesh. | |||||
*/ | |||||
inline explicit brep_element_container(brep_mesh* mesh) noexcept: | |||||
m_mesh(mesh) | |||||
{} | |||||
/// @name Modifiers | |||||
/// @{ | |||||
/** | |||||
* Erases an element from the container. | |||||
* | |||||
* @param element Pointer to the element to erase. | |||||
*/ | |||||
virtual void erase(element_type* element) | |||||
{ | |||||
const auto index = element->m_index; | |||||
for (auto& [key, values]: m_attribute_map.m_attributes) | |||||
{ | |||||
values->erase(index); | |||||
} | |||||
--m_attribute_map.m_element_count; | |||||
m_elements.back()->m_index = index; | |||||
m_elements[index] = std::move(m_elements.back()); | |||||
m_elements.pop_back(); | |||||
} | |||||
/** | |||||
* Appends a new element to the end of the container. | |||||
* | |||||
* @return Pointer to the new element. | |||||
*/ | |||||
virtual element_type* emplace_back() | |||||
{ | |||||
for (auto& [key, values]: m_attribute_map.m_attributes) | |||||
{ | |||||
values->emplace_back(); | |||||
} | |||||
++m_attribute_map.m_element_count; | |||||
return m_elements.emplace_back(std::make_unique<element_type>()).get(); | |||||
} | |||||
/// @} | |||||
brep_mesh* m_mesh; | |||||
private: | |||||
std::vector<std::unique_ptr<element_type>> m_elements; | |||||
brep_attribute_map m_attribute_map; | |||||
}; | |||||
} // namespace geom | |||||
#endif // ANTKEEPER_GEOM_BREP_ELEMENT_CONTAINER_HPP |
@ -0,0 +1,158 @@ | |||||
/* | |||||
* 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-face.hpp> | |||||
#include <engine/geom/brep/brep-mesh.hpp> | |||||
#include <algorithm> | |||||
#include <vector> | |||||
namespace geom { | |||||
void brep_face_loop_list::push_back(brep_loop* loop) | |||||
{ | |||||
if (empty()) | |||||
{ | |||||
// List empty, initialize | |||||
m_head = loop; | |||||
loop->m_face_next = loop; | |||||
loop->m_face_previous = loop; | |||||
} | |||||
else | |||||
{ | |||||
// Append loop | |||||
loop->m_face_next = m_head; | |||||
loop->m_face_previous = m_head->m_face_previous; | |||||
m_head->m_face_previous->m_face_next = loop; | |||||
m_head->m_face_previous = loop; | |||||
} | |||||
++m_size; | |||||
} | |||||
void brep_face_loop_list::insert(brep_loop* next, brep_loop* loop) | |||||
{ | |||||
loop->m_face_next = next; | |||||
loop->m_face_previous = next->m_face_previous; | |||||
next->m_face_previous->m_face_next = loop; | |||||
next->m_face_previous = loop; | |||||
++m_size; | |||||
} | |||||
void brep_face_loop_list::remove(brep_loop* loop) | |||||
{ | |||||
// Directly link next and previous loops | |||||
loop->m_face_next->m_face_previous = loop->m_face_previous; | |||||
loop->m_face_previous->m_face_next = loop->m_face_next; | |||||
// If loop was the list head, update head | |||||
if (m_head == loop) | |||||
{ | |||||
m_head = loop->m_face_next; | |||||
} | |||||
--m_size; | |||||
} | |||||
brep_face* brep_face_container::emplace_back(const std::span<brep_vertex*> vertices) | |||||
{ | |||||
if (vertices.size() < 3) | |||||
{ | |||||
return nullptr; | |||||
} | |||||
// Find or make edges | |||||
std::vector<brep_edge*> edges(vertices.size()); | |||||
std::size_t i = vertices.size() - 1; | |||||
for (std::size_t j = 0; j < vertices.size(); ++j) | |||||
{ | |||||
edges[i] = m_mesh->edges().find(vertices[i], vertices[j]); | |||||
if (!edges[i]) | |||||
{ | |||||
edges[i] = m_mesh->edges().emplace_back(vertices[i], vertices[j]); | |||||
} | |||||
i = j; | |||||
} | |||||
// Allocate face | |||||
brep_face* face = brep_element_container<brep_face>::emplace_back(); | |||||
face->m_index = size() - 1; | |||||
// Make face loops | |||||
for (std::size_t i = 0; i < vertices.size(); ++i) | |||||
{ | |||||
brep_loop* loop = m_mesh->loops().emplace_back(); | |||||
loop->m_vertex = vertices[i]; | |||||
loop->m_edge = edges[i]; | |||||
loop->m_face = face; | |||||
// Append loop to its edge's list of loops | |||||
loop->m_edge->m_loops.push_back(loop); | |||||
// Append loop to its face's list of loops | |||||
face->m_loops.push_back(loop); | |||||
} | |||||
return face; | |||||
}; | |||||
void brep_face_container::erase(brep_face* face) | |||||
{ | |||||
brep_loop* loop = face->loops().front(); | |||||
do | |||||
{ | |||||
// Remove loop from its edge's list of loops | |||||
loop->m_edge->m_loops.remove(loop); | |||||
brep_loop* next_loop = loop->m_face_next; | |||||
// Erase loop | |||||
m_mesh->loops().erase(loop); | |||||
loop = next_loop; | |||||
} | |||||
while (loop != face->loops().front()); | |||||
// Erase face | |||||
brep_element_container<brep_face>::erase(face); | |||||
} | |||||
void brep_face_container::clear() noexcept | |||||
{ | |||||
while (!empty()) | |||||
{ | |||||
erase(back()); | |||||
} | |||||
} | |||||
void brep_face_container::reverse(brep_face* face) | |||||
{ | |||||
for (brep_loop* loop: face->loops()) | |||||
{ | |||||
// Swap order of loop vertices | |||||
loop->m_vertex = loop->edge()->vertices()[loop->edge()->vertices()[0] == loop->vertex()]; | |||||
// Swap pointers to next and previous face loops | |||||
std::swap(loop->m_face_next, loop->m_face_previous); | |||||
} | |||||
} | |||||
} // namespace geom |
@ -0,0 +1,322 @@ | |||||
/* | |||||
* 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/>. | |||||
*/ | |||||
#ifndef ANTKEEPER_GEOM_BREP_FACE_HPP | |||||
#define ANTKEEPER_GEOM_BREP_FACE_HPP | |||||
#include <engine/geom/brep/brep-loop.hpp> | |||||
#include <engine/geom/brep/brep-element-container.hpp> | |||||
#include <cstddef> | |||||
#include <iterator> | |||||
#include <span> | |||||
namespace geom { | |||||
class brep_vertex; | |||||
class brep_loop; | |||||
template <class T> | |||||
class brep_element_container; | |||||
/** | |||||
* List of B-rep loops that bound a common face. | |||||
*/ | |||||
class brep_face_loop_list | |||||
{ | |||||
public: | |||||
friend class brep_mesh; | |||||
struct const_iterator | |||||
{ | |||||
public: | |||||
friend class brep_face_loop_list; | |||||
using iterator_category = std::bidirectional_iterator_tag; | |||||
using iterator_concept = std::bidirectional_iterator_tag; | |||||
using difference_type = std::ptrdiff_t; | |||||
using value_type = brep_loop*; | |||||
using pointer = const value_type*; | |||||
using reference = const value_type&; | |||||
[[nodiscard]] inline constexpr value_type operator*() const noexcept | |||||
{ | |||||
return m_loop; | |||||
} | |||||
[[nodiscard]] inline constexpr value_type operator->() const noexcept | |||||
{ | |||||
return m_loop; | |||||
} | |||||
inline const_iterator& operator++() noexcept | |||||
{ | |||||
m_loop = m_loop->m_face_next; | |||||
++m_position; | |||||
return *this; | |||||
} | |||||
[[nodiscard]] inline const_iterator operator++(int) noexcept | |||||
{ | |||||
const_iterator tmp = *this; | |||||
++(*this); | |||||
return tmp; | |||||
} | |||||
inline const_iterator& operator--() noexcept | |||||
{ | |||||
m_loop = m_loop->m_face_previous; | |||||
--m_position; | |||||
return *this; | |||||
} | |||||
[[nodiscard]] inline const_iterator operator--(int) noexcept | |||||
{ | |||||
const_iterator tmp = *this; | |||||
--(*this); | |||||
return tmp; | |||||
} | |||||
[[nodiscard]] inline bool operator==(const const_iterator& other) const noexcept | |||||
{ | |||||
return m_position == other.m_position; | |||||
}; | |||||
[[nodiscard]] inline std::weak_ordering operator<=>(const const_iterator& other) const noexcept | |||||
{ | |||||
return m_position <=> other.m_position; | |||||
} | |||||
[[nodiscard]] inline difference_type operator-(const const_iterator& rhs) const noexcept | |||||
{ | |||||
return m_position - rhs.m_position; | |||||
} | |||||
private: | |||||
brep_loop* m_loop; | |||||
std::ptrdiff_t m_position; | |||||
}; | |||||
using const_reverse_iterator = std::reverse_iterator<const_iterator>; | |||||
/// @name Element access | |||||
/// @{ | |||||
/// Returns the first loop. | |||||
[[nodiscard]] inline brep_loop* front() const noexcept | |||||
{ | |||||
return m_head; | |||||
} | |||||
/// Returns the last loop. | |||||
[[nodiscard]] inline brep_loop* back() const noexcept | |||||
{ | |||||
return m_head->m_face_previous; | |||||
} | |||||
/// @} | |||||
/// @name Iterators | |||||
/// @{ | |||||
/// Returns an iterator to the first loop. | |||||
/// @{ | |||||
[[nodiscard]] inline constexpr const_iterator begin() const noexcept | |||||
{ | |||||
const_iterator it; | |||||
it.m_loop = m_head; | |||||
it.m_position = 0; | |||||
return it; | |||||
} | |||||
[[nodiscard]] inline constexpr const_iterator cbegin() const noexcept | |||||
{ | |||||
return begin(); | |||||
} | |||||
/// @} | |||||
/// Returns an iterator to the loop following the last loop. | |||||
/// @{ | |||||
[[nodiscard]] inline constexpr const_iterator end() const noexcept | |||||
{ | |||||
const_iterator it; | |||||
it.m_loop = m_head; | |||||
it.m_position = static_cast<std::ptrdiff_t>(m_size); | |||||
return it; | |||||
} | |||||
[[nodiscard]] inline constexpr const_iterator cend() const noexcept | |||||
{ | |||||
return end(); | |||||
} | |||||
/// @} | |||||
/// Returns a reverse iterator to the first loop of the reversed list. | |||||
/// @{ | |||||
[[nodiscard]] inline constexpr const_reverse_iterator rbegin() const noexcept | |||||
{ | |||||
return std::make_reverse_iterator(end()); | |||||
} | |||||
[[nodiscard]] inline constexpr const_reverse_iterator crbegin() const noexcept | |||||
{ | |||||
return rbegin(); | |||||
} | |||||
/// @} | |||||
/// Returns a reverse iterator to the loop following the last loop of the reversed list. | |||||
/// @{ | |||||
[[nodiscard]] inline constexpr const_reverse_iterator rend() const noexcept | |||||
{ | |||||
return std::make_reverse_iterator(begin()); | |||||
} | |||||
[[nodiscard]] inline constexpr const_reverse_iterator crend() const noexcept | |||||
{ | |||||
return rend(); | |||||
} | |||||
/// @} | |||||
/// @} | |||||
/// @name Capacity | |||||
/// @{ | |||||
/// Returns `true` if the list is empty, `false` otherwise. | |||||
[[nodiscard]] inline constexpr bool empty() const noexcept | |||||
{ | |||||
return !m_size; | |||||
} | |||||
/// Returns the number of loops in the list. | |||||
[[nodiscard]] inline constexpr std::size_t size() const noexcept | |||||
{ | |||||
return m_size; | |||||
} | |||||
/// @} | |||||
/// @name Modifiers | |||||
/// @{ | |||||
/** | |||||
* Appends a loop to the end of the list. | |||||
* | |||||
* @param loop Pointer to the loop to append. | |||||
*/ | |||||
void push_back(brep_loop* loop); | |||||
/** | |||||
* Inserts a loop before a given loop. | |||||
* | |||||
* @param next Loop before which this loop should be inserted. | |||||
* @param loop Pointer to the loop to insert. | |||||
*/ | |||||
void insert(brep_loop* next, brep_loop* loop); | |||||
/** | |||||
* Removes an loop from the list. | |||||
* | |||||
* @param loop Pointer to the loop to remove. | |||||
*/ | |||||
void remove(brep_loop* loop); | |||||
/// @} | |||||
private: | |||||
brep_loop* m_head{}; | |||||
std::size_t m_size{}; | |||||
}; | |||||
/** | |||||
* Portion of a shell bounded by loops. | |||||
*/ | |||||
class brep_face | |||||
{ | |||||
public: | |||||
friend class brep_mesh; | |||||
friend class brep_element_container<brep_face>; | |||||
friend class brep_face_container; | |||||
/** | |||||
* Returns the index of this face in the mesh face array. | |||||
* | |||||
* @warning This index may change if any faces are removed from the mesh. | |||||
*/ | |||||
[[nodiscard]] inline constexpr std::size_t index() const noexcept | |||||
{ | |||||
return m_index; | |||||
} | |||||
/// Returns the list of loops associated with this face. | |||||
[[nodiscard]] inline constexpr const brep_face_loop_list& loops() const noexcept | |||||
{ | |||||
return m_loops; | |||||
} | |||||
private: | |||||
std::size_t m_index; | |||||
brep_face_loop_list m_loops; | |||||
}; | |||||
/** | |||||
* B-rep face container. | |||||
*/ | |||||
class brep_face_container: public brep_element_container<brep_face> | |||||
{ | |||||
public: | |||||
/** | |||||
* Appends a new face to the end of the container. | |||||
* | |||||
* @param vertices Ordered vertices of the face. | |||||
* | |||||
* @return Pointer to the new face. | |||||
*/ | |||||
brep_face* emplace_back(const std::span<brep_vertex*> vertices); | |||||
/** | |||||
* Erases a face and all of its loops. | |||||
* | |||||
* @param face Pointer to the face to erase. | |||||
* | |||||
* @warning Invalidates iterators and indices of loops and faces. | |||||
*/ | |||||
void erase(brep_face* face) override; | |||||
/** | |||||
* Erases all faces and their loops. | |||||
*/ | |||||
void clear() noexcept; | |||||
/** | |||||
* Reverses the direction of a face's bounding loops. | |||||
* | |||||
* @param face Face bounded by the loops to reverse. | |||||
*/ | |||||
void reverse(brep_face* face); | |||||
private: | |||||
friend class brep_mesh; | |||||
/** | |||||
* Constructs a B-rep face container. | |||||
* | |||||
* @param mesh Pointer to the parent mesh. | |||||
*/ | |||||
inline explicit brep_face_container(brep_mesh* mesh) noexcept: | |||||
brep_element_container<brep_face>(mesh) | |||||
{} | |||||
}; | |||||
} // namespace geom | |||||
#endif // ANTKEEPER_GEOM_BREP_FACE_HPP |
@ -0,0 +1,32 @@ | |||||
/* | |||||
* 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-loop.hpp> | |||||
namespace geom { | |||||
brep_loop* brep_loop_container::emplace_back() | |||||
{ | |||||
brep_loop* loop = brep_element_container<brep_loop>::emplace_back(); | |||||
loop->m_index = size() - 1; | |||||
return loop; | |||||
}; | |||||
} // namespace geom |
@ -0,0 +1,114 @@ | |||||
/* | |||||
* 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/>. | |||||
*/ | |||||
#ifndef ANTKEEPER_GEOM_BREP_LOOP_HPP | |||||
#define ANTKEEPER_GEOM_BREP_LOOP_HPP | |||||
#include <engine/geom/brep/brep-element-container.hpp> | |||||
#include <cstddef> | |||||
namespace geom { | |||||
class brep_vertex; | |||||
class brep_edge; | |||||
class brep_face; | |||||
template <class T> | |||||
class brep_element_container; | |||||
/** | |||||
* Connected boundary of a single face. | |||||
*/ | |||||
class brep_loop | |||||
{ | |||||
public: | |||||
friend class brep_mesh; | |||||
friend class brep_edge_loop_list; | |||||
friend class brep_face_loop_list; | |||||
friend class brep_element_container<brep_loop>; | |||||
friend class brep_loop_container; | |||||
friend class brep_face_container; | |||||
/** | |||||
* Returns the index of this loop in the mesh loop array. | |||||
* | |||||
* @warning This index may change if any loops are removed from the mesh. | |||||
*/ | |||||
[[nodiscard]] inline constexpr std::size_t index() const noexcept | |||||
{ | |||||
return m_index; | |||||
} | |||||
/// Returns a pointer to the loop vertex. | |||||
[[nodiscard]] inline constexpr brep_vertex* vertex() const noexcept | |||||
{ | |||||
return m_vertex; | |||||
} | |||||
/// Returns a pointer to the loop edge. | |||||
[[nodiscard]] inline constexpr brep_edge* edge() const noexcept | |||||
{ | |||||
return m_edge; | |||||
} | |||||
/// Returns a pointer to the loop face. | |||||
[[nodiscard]] inline constexpr brep_face* face() const noexcept | |||||
{ | |||||
return m_face; | |||||
} | |||||
private: | |||||
std::size_t m_index; | |||||
brep_vertex* m_vertex; | |||||
brep_edge* m_edge; | |||||
brep_face* m_face; | |||||
brep_loop* m_edge_next; | |||||
brep_loop* m_edge_previous; | |||||
brep_loop* m_face_next; | |||||
brep_loop* m_face_previous; | |||||
}; | |||||
/** | |||||
* B-rep loop container. | |||||
*/ | |||||
class brep_loop_container: public brep_element_container<brep_loop> | |||||
{ | |||||
private: | |||||
friend class brep_mesh; | |||||
friend class brep_face_container; | |||||
/** | |||||
* Constructs a B-rep face container. | |||||
* | |||||
* @param mesh Pointer to the parent mesh. | |||||
*/ | |||||
inline explicit brep_loop_container(brep_mesh* mesh) noexcept: | |||||
brep_element_container<brep_loop>(mesh) | |||||
{} | |||||
/** | |||||
* Appends a new loop to the end of the container. | |||||
* | |||||
* @return Pointer to the new loop. | |||||
*/ | |||||
brep_loop* emplace_back() override; | |||||
}; | |||||
} // namespace geom | |||||
#endif // ANTKEEPER_GEOM_BREP_LOOP_HPP |
@ -0,0 +1,182 @@ | |||||
/* | |||||
* 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-mesh.hpp> | |||||
#include <engine/math/vector.hpp> | |||||
#include <engine/resources/resource-loader.hpp> | |||||
#include <engine/resources/deserialize-error.hpp> | |||||
#include <engine/resources/deserializer.hpp> | |||||
#include <array> | |||||
namespace geom { | |||||
brep_mesh::brep_mesh() noexcept: | |||||
m_vertices(this), | |||||
m_edges(this), | |||||
m_loops(this), | |||||
m_faces(this) | |||||
{} | |||||
brep_mesh::brep_mesh(const brep_mesh& other): | |||||
brep_mesh() | |||||
{ | |||||
*this = other; | |||||
} | |||||
brep_mesh& brep_mesh::operator=(const brep_mesh& other) | |||||
{ | |||||
// Allocate containers | |||||
m_vertices.m_elements.resize(other.m_vertices.m_elements.size()); | |||||
m_edges.m_elements.resize(other.m_edges.m_elements.size()); | |||||
m_loops.m_elements.resize(other.m_loops.m_elements.size()); | |||||
m_faces.m_elements.resize(other.m_faces.m_elements.size()); | |||||
// Copy-construct elements | |||||
for (std::size_t i = 0; i < m_vertices.m_elements.size(); ++i) | |||||
{ | |||||
m_vertices.m_elements[i] = std::make_unique<brep_vertex>(*other.m_vertices.m_elements[i]); | |||||
} | |||||
for (std::size_t i = 0; i < m_edges.m_elements.size(); ++i) | |||||
{ | |||||
m_edges.m_elements[i] = std::make_unique<brep_edge>(*other.m_edges.m_elements[i]); | |||||
} | |||||
for (std::size_t i = 0; i < m_loops.m_elements.size(); ++i) | |||||
{ | |||||
m_loops.m_elements[i] = std::make_unique<brep_loop>(*other.m_loops.m_elements[i]); | |||||
} | |||||
for (std::size_t i = 0; i < m_faces.m_elements.size(); ++i) | |||||
{ | |||||
m_faces.m_elements[i] = std::make_unique<brep_face>(*other.m_faces.m_elements[i]); | |||||
} | |||||
// Copy per-element attributes | |||||
m_vertices.m_attribute_map = other.m_vertices.m_attribute_map; | |||||
m_edges.m_attribute_map = other.m_edges.m_attribute_map; | |||||
m_loops.m_attribute_map = other.m_loops.m_attribute_map; | |||||
m_faces.m_attribute_map = other.m_faces.m_attribute_map; | |||||
// Reassign element pointers | |||||
for (const auto& vertex: m_vertices.m_elements) | |||||
{ | |||||
if (!vertex->edges().empty()) | |||||
{ | |||||
vertex->m_edges.m_head = m_edges.m_elements[vertex->m_edges.m_head->m_index].get(); | |||||
} | |||||
} | |||||
for (const auto& edge: m_edges.m_elements) | |||||
{ | |||||
edge->m_vertices[0] = m_vertices.m_elements[edge->m_vertices[0]->m_index].get(); | |||||
edge->m_vertices[1] = m_vertices.m_elements[edge->m_vertices[1]->m_index].get(); | |||||
edge->m_vertex_next[0] = m_edges.m_elements[edge->m_vertex_next[0]->m_index].get(); | |||||
edge->m_vertex_next[1] = m_edges.m_elements[edge->m_vertex_next[1]->m_index].get(); | |||||
edge->m_vertex_previous[0] = m_edges.m_elements[edge->m_vertex_previous[0]->m_index].get(); | |||||
edge->m_vertex_previous[1] = m_edges.m_elements[edge->m_vertex_previous[1]->m_index].get(); | |||||
if (!edge->loops().empty()) | |||||
{ | |||||
edge->m_loops.m_head = m_loops.m_elements[edge->m_loops.m_head->m_index].get(); | |||||
} | |||||
} | |||||
for (const auto& loop: m_loops.m_elements) | |||||
{ | |||||
loop->m_vertex = m_vertices.m_elements[loop->m_vertex->m_index].get(); | |||||
loop->m_edge = m_edges.m_elements[loop->m_edge->m_index].get(); | |||||
loop->m_face = m_faces.m_elements[loop->m_face->m_index].get(); | |||||
loop->m_edge_next = m_loops.m_elements[loop->m_edge_next->m_index].get(); | |||||
loop->m_edge_previous = m_loops.m_elements[loop->m_edge_previous->m_index].get(); | |||||
loop->m_face_next = m_loops.m_elements[loop->m_face_next->m_index].get(); | |||||
loop->m_face_previous = m_loops.m_elements[loop->m_face_previous->m_index].get(); | |||||
} | |||||
for (const auto& face: m_faces.m_elements) | |||||
{ | |||||
face->m_loops.m_head = m_loops.m_elements[face->m_loops.m_head->m_index].get(); | |||||
} | |||||
return *this; | |||||
} | |||||
void brep_mesh::clear() noexcept | |||||
{ | |||||
m_vertices.clear(); | |||||
} | |||||
} // namespace geom | |||||
/** | |||||
* Deserializes a mesh. | |||||
* | |||||
* @param[out] mesh Mesh to deserialize. | |||||
* @param[in,out] ctx Deserialize context. | |||||
* | |||||
* @throw deserialize_error Read error. | |||||
*/ | |||||
template <> | |||||
void deserializer<geom::brep_mesh>::deserialize(geom::brep_mesh& mesh, deserialize_context& ctx) | |||||
{ | |||||
// Read vertex, edge, and face counts | |||||
std::uint32_t vertex_count = 0; | |||||
std::uint32_t edge_count = 0; | |||||
std::uint32_t face_count = 0; | |||||
ctx.read32<std::endian::little>(reinterpret_cast<std::byte*>(&vertex_count), 1); | |||||
ctx.read32<std::endian::little>(reinterpret_cast<std::byte*>(&edge_count), 1); | |||||
ctx.read32<std::endian::little>(reinterpret_cast<std::byte*>(&face_count), 1); | |||||
// Make vertices | |||||
for (std::size_t i = 0; i < vertex_count; ++i) | |||||
{ | |||||
mesh.vertices().emplace_back(); | |||||
} | |||||
// Read edge vertex indices into buffer | |||||
std::vector<std::array<std::uint32_t, 2>> edges(edge_count); | |||||
ctx.read32<std::endian::little>(reinterpret_cast<std::byte*>(edges.data()), edge_count * 2); | |||||
// Make edges | |||||
for (const auto& e: edges) | |||||
{ | |||||
mesh.edges().emplace_back(mesh.vertices()[e[0]], mesh.vertices()[e[1]]); | |||||
} | |||||
edges.clear(); | |||||
// Read face vertex indices into buffer | |||||
std::vector<std::array<std::uint32_t, 3>> faces(face_count); | |||||
ctx.read32<std::endian::little>(reinterpret_cast<std::byte*>(faces.data()), face_count * 3); | |||||
// Make faces | |||||
for (const auto& f: faces) | |||||
{ | |||||
geom::brep_vertex* vertices[3] = {mesh.vertices()[f[0]], mesh.vertices()[f[1]], mesh.vertices()[f[2]]}; | |||||
mesh.faces().emplace_back(vertices); | |||||
} | |||||
faces.clear(); | |||||
// Make vertex attributes | |||||
auto& vertex_positions = static_cast<geom::brep_attribute<math::fvec3>&>(*mesh.vertices().attributes().emplace<math::fvec3>("position")); | |||||
// Read vertex attributes | |||||
ctx.read32<std::endian::little>(reinterpret_cast<std::byte*>(vertex_positions.data()), vertex_count * 3); | |||||
} | |||||
template <> | |||||
std::unique_ptr<geom::brep_mesh> resource_loader<geom::brep_mesh>::load(::resource_manager& resource_manager, deserialize_context& ctx) | |||||
{ | |||||
auto resource = std::make_unique<geom::brep_mesh>(); | |||||
deserializer<geom::brep_mesh>().deserialize(*resource, ctx); | |||||
return resource; | |||||
} |
@ -0,0 +1,126 @@ | |||||
/* | |||||
* 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/>. | |||||
*/ | |||||
#ifndef ANTKEEPER_GEOM_BREP_MESH_HPP | |||||
#define ANTKEEPER_GEOM_BREP_MESH_HPP | |||||
#include <engine/geom/brep/brep-vertex.hpp> | |||||
#include <engine/geom/brep/brep-edge.hpp> | |||||
#include <engine/geom/brep/brep-loop.hpp> | |||||
#include <engine/geom/brep/brep-face.hpp> | |||||
namespace geom { | |||||
/** | |||||
* Boundary representation (B-rep) of a mesh. | |||||
*/ | |||||
class brep_mesh | |||||
{ | |||||
public: | |||||
/** | |||||
* Constructs an empty mesh. | |||||
*/ | |||||
brep_mesh() noexcept; | |||||
/** | |||||
* Constructs a copy of another mesh. | |||||
* | |||||
* @param other Mesh to copy. | |||||
*/ | |||||
brep_mesh(const brep_mesh& other); | |||||
/** | |||||
* Copies another mesh into this mesh. | |||||
* | |||||
* @param other Mesh to copy. | |||||
* | |||||
* @return Reference to this mesh. | |||||
*/ | |||||
brep_mesh& operator=(const brep_mesh& other); | |||||
/// @name Modifiers | |||||
/// @{ | |||||
/// Erases all vertices, edges, loops, and faces. | |||||
void clear() noexcept; | |||||
/// @} | |||||
/// @name Element access | |||||
/// @{ | |||||
/// Returns the mesh vertices. | |||||
/// @{ | |||||
[[nodiscard]] inline const brep_vertex_container& vertices() const noexcept | |||||
{ | |||||
return m_vertices; | |||||
} | |||||
[[nodiscard]] inline brep_vertex_container& vertices() noexcept | |||||
{ | |||||
return m_vertices; | |||||
} | |||||
///@} | |||||
/// Returns the mesh edges. | |||||
/// @{ | |||||
[[nodiscard]] inline const brep_edge_container& edges() const noexcept | |||||
{ | |||||
return m_edges; | |||||
} | |||||
[[nodiscard]] inline brep_edge_container& edges() noexcept | |||||
{ | |||||
return m_edges; | |||||
} | |||||
/// @} | |||||
/// Returns the mesh loops. | |||||
/// @{ | |||||
[[nodiscard]] inline const brep_loop_container& loops() const noexcept | |||||
{ | |||||
return m_loops; | |||||
} | |||||
[[nodiscard]] inline brep_loop_container& loops() noexcept | |||||
{ | |||||
return m_loops; | |||||
} | |||||
/// @} | |||||
/// Returns the mesh faces. | |||||
/// @{ | |||||
[[nodiscard]] inline const brep_face_container& faces() const noexcept | |||||
{ | |||||
return m_faces; | |||||
} | |||||
[[nodiscard]] inline brep_face_container& faces() noexcept | |||||
{ | |||||
return m_faces; | |||||
} | |||||
/// @} | |||||
/// @} | |||||
private: | |||||
brep_vertex_container m_vertices; | |||||
brep_edge_container m_edges; | |||||
brep_loop_container m_loops; | |||||
brep_face_container m_faces; | |||||
}; | |||||
} // namespace geom | |||||
#endif // ANTKEEPER_GEOM_BREP_MESH_HPP |
@ -0,0 +1,106 @@ | |||||
/* | |||||
* 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 <algorithm> | |||||
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) | |||||
{ | |||||
const auto& vertex_positions = mesh.vertices().attributes().at<math::fvec3>("position"); | |||||
auto& vertex_normals = static_cast<brep_attribute<math::fvec3>&>(*mesh.vertices().attributes().try_emplace<math::fvec3>("normal").first); | |||||
// Init vertex normals to zero | |||||
std::fill(vertex_normals.begin(), vertex_normals.end(), math::fvec3::zero()); | |||||
if (auto face_normals_it = mesh.faces().attributes().find("normals"); face_normals_it != mesh.faces().attributes().end()) | |||||
{ | |||||
const auto& face_normals = static_cast<const brep_attribute<math::fvec3>&>(*face_normals_it); | |||||
for (brep_face* face: mesh.faces()) | |||||
{ | |||||
// Get face normal from face normal attribute | |||||
const auto& face_normal = face_normals[face->index()]; | |||||
// Accumulate vertex normals | |||||
for (brep_loop* loop: face->loops()) | |||||
{ | |||||
vertex_normals[loop->vertex()->index()] += face_normal; | |||||
} | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
for (brep_face* face: mesh.faces()) | |||||
{ | |||||
// Calculate face normal | |||||
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()]; | |||||
const auto face_normal = math::normalize(math::cross(b - a, c - a)); | |||||
// Accumulate vertex normals | |||||
for (brep_loop* loop: face->loops()) | |||||
{ | |||||
vertex_normals[loop->vertex()->index()] += face_normal; | |||||
} | |||||
} | |||||
} | |||||
// Normalize vertex normals | |||||
for (auto& vertex_normal: vertex_normals) | |||||
{ | |||||
vertex_normal = math::normalize(vertex_normal); | |||||
} | |||||
} | |||||
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}; | |||||
} | |||||
} | |||||
} // namespace geom |
@ -0,0 +1,54 @@ | |||||
/* | |||||
* 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/>. | |||||
*/ | |||||
#ifndef ANTKEEPER_GEOM_BREP_OPERATIONS_HPP | |||||
#define ANTKEEPER_GEOM_BREP_OPERATIONS_HPP | |||||
#include <engine/geom/brep/brep-mesh.hpp> | |||||
namespace geom { | |||||
/** | |||||
* Generates the math::fvec3 face attribute "normal" for a B-rep mesh. | |||||
* | |||||
* @param mesh Mesh for which to generate normals. | |||||
* | |||||
* @warning Requires the math::fvec3 vertex attribute "position". | |||||
*/ | |||||
void generate_face_normals(brep_mesh& mesh); | |||||
/** | |||||
* Generates the math::fvec3 vertex attribute "normal" for a B-rep mesh. | |||||
* | |||||
* @param mesh Mesh for which to generate normals. | |||||
* | |||||
* @warning Requires the math::fvec3 vertex attribute "position". | |||||
*/ | |||||
void generate_vertex_normals(brep_mesh& mesh); | |||||
/** | |||||
* Generates the math::fvec3 loop attribute "barycentric" for a B-rep mesh. | |||||
* | |||||
* @param mesh Mesh for which to generate barycentric coordinates. | |||||
*/ | |||||
void generate_loop_barycentric(brep_mesh& mesh); | |||||
} // namespace geom | |||||
#endif // ANTKEEPER_GEOM_BREP_OPERATIONS_HPP |
@ -0,0 +1,102 @@ | |||||
/* | |||||
* 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-vertex.hpp> | |||||
#include <engine/geom/brep/brep-mesh.hpp> | |||||
namespace geom { | |||||
void brep_vertex_edge_list::push_back(brep_edge* edge) | |||||
{ | |||||
// Determine index of vertex in new edge vertex array | |||||
const std::size_t i = (edge->m_vertices[1] == m_vertex); | |||||
if (empty()) | |||||
{ | |||||
// List empty, initialize | |||||
m_head = edge; | |||||
edge->m_vertex_next[i] = edge; | |||||
edge->m_vertex_previous[i] = edge; | |||||
} | |||||
else | |||||
{ | |||||
// Determine index of vertex in head edge vertex array | |||||
const std::size_t j = (m_head->m_vertices[1] == m_vertex); | |||||
// Append edge | |||||
edge->m_vertex_next[i] = m_head; | |||||
edge->m_vertex_previous[i] = m_head->m_vertex_previous[j]; | |||||
edge->m_vertex_previous[i]->m_vertex_next[edge->m_vertex_previous[i]->m_vertices[1] == m_vertex] = edge; | |||||
m_head->m_vertex_previous[j] = edge; | |||||
} | |||||
++m_size; | |||||
} | |||||
void brep_vertex_edge_list::remove(brep_edge* edge) | |||||
{ | |||||
// Determine index of vertex in edge vertex array | |||||
const std::size_t i = (edge->m_vertices[1] == m_vertex); | |||||
// Get pointers to the next and previous edges | |||||
brep_edge* next = edge->m_vertex_next[i]; | |||||
brep_edge* previous = edge->m_vertex_previous[i]; | |||||
// Directly link next and previous edges | |||||
next->m_vertex_previous[next->m_vertices[1] == m_vertex] = previous; | |||||
previous->m_vertex_next[previous->m_vertices[1] == m_vertex] = next; | |||||
// If edge was the list head, update head | |||||
if (m_head == edge) | |||||
{ | |||||
m_head = next; | |||||
} | |||||
--m_size; | |||||
} | |||||
brep_vertex* brep_vertex_container::emplace_back() | |||||
{ | |||||
brep_vertex* vertex = brep_element_container<brep_vertex>::emplace_back(); | |||||
vertex->m_index = size() - 1; | |||||
vertex->m_edges.m_vertex = vertex; | |||||
return vertex; | |||||
}; | |||||
void brep_vertex_container::erase(brep_vertex* vertex) | |||||
{ | |||||
// Erase all edges bounded by this vertex | |||||
while (!vertex->edges().empty()) | |||||
{ | |||||
m_mesh->edges().erase(vertex->edges().back()); | |||||
} | |||||
// Erase vertex | |||||
brep_element_container<brep_vertex>::erase(vertex); | |||||
} | |||||
void brep_vertex_container::clear() noexcept | |||||
{ | |||||
while (!empty()) | |||||
{ | |||||
erase(back()); | |||||
} | |||||
} | |||||
} // namespace geom |
@ -0,0 +1,312 @@ | |||||
/* | |||||
* 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/>. | |||||
*/ | |||||
#ifndef ANTKEEPER_GEOM_BREP_VERTEX_HPP | |||||
#define ANTKEEPER_GEOM_BREP_VERTEX_HPP | |||||
#include <engine/geom/brep/brep-edge.hpp> | |||||
#include <engine/geom/brep/brep-element-container.hpp> | |||||
#include <cstddef> | |||||
#include <iterator> | |||||
namespace geom { | |||||
class brep_mesh; | |||||
class brep_edge; | |||||
class brep_vertex; | |||||
template <class T> | |||||
class brep_element_container; | |||||
/** | |||||
* List of B-rep edges bounded by a common vertex. | |||||
*/ | |||||
class brep_vertex_edge_list | |||||
{ | |||||
public: | |||||
friend class brep_mesh; | |||||
friend class brep_vertex_container; | |||||
friend class brep_edge_container; | |||||
struct const_iterator | |||||
{ | |||||
public: | |||||
friend class brep_vertex_edge_list; | |||||
using iterator_category = std::bidirectional_iterator_tag; | |||||
using iterator_concept = std::bidirectional_iterator_tag; | |||||
using difference_type = std::ptrdiff_t; | |||||
using value_type = brep_edge*; | |||||
using pointer = const value_type*; | |||||
using reference = const value_type&; | |||||
[[nodiscard]] inline constexpr value_type operator*() const noexcept | |||||
{ | |||||
return m_edge; | |||||
} | |||||
[[nodiscard]] inline constexpr value_type operator->() const noexcept | |||||
{ | |||||
return m_edge; | |||||
} | |||||
inline const_iterator& operator++() noexcept | |||||
{ | |||||
m_edge = m_edge->m_vertex_next[m_edge->vertices()[1] == m_vertex]; | |||||
++m_position; | |||||
return *this; | |||||
} | |||||
[[nodiscard]] inline const_iterator operator++(int) noexcept | |||||
{ | |||||
const_iterator tmp = *this; | |||||
++(*this); | |||||
return tmp; | |||||
} | |||||
inline const_iterator& operator--() noexcept | |||||
{ | |||||
m_edge = m_edge->m_vertex_previous[m_edge->vertices()[1] == m_vertex]; | |||||
--m_position; | |||||
return *this; | |||||
} | |||||
[[nodiscard]] inline const_iterator operator--(int) noexcept | |||||
{ | |||||
const_iterator tmp = *this; | |||||
--(*this); | |||||
return tmp; | |||||
} | |||||
[[nodiscard]] inline bool operator==(const const_iterator& other) const noexcept | |||||
{ | |||||
return m_position == other.m_position; | |||||
}; | |||||
[[nodiscard]] inline std::weak_ordering operator<=>(const const_iterator& other) const noexcept | |||||
{ | |||||
return m_position <=> other.m_position; | |||||
} | |||||
[[nodiscard]] inline difference_type operator-(const const_iterator& rhs) const noexcept | |||||
{ | |||||
return m_position - rhs.m_position; | |||||
} | |||||
private: | |||||
brep_vertex* m_vertex; | |||||
brep_edge* m_edge; | |||||
std::ptrdiff_t m_position; | |||||
}; | |||||
using const_reverse_iterator = std::reverse_iterator<const_iterator>; | |||||
/// @name Element access | |||||
/// @{ | |||||
/// Returns the first edge. | |||||
[[nodiscard]] inline brep_edge* front() const noexcept | |||||
{ | |||||
return m_head; | |||||
} | |||||
/// Returns the last edge. | |||||
[[nodiscard]] inline brep_edge* back() const noexcept | |||||
{ | |||||
return m_head->m_vertex_previous[m_head->vertices()[1] == m_vertex]; | |||||
} | |||||
/// @} | |||||
/// @name Iterators | |||||
/// @{ | |||||
/// Returns an iterator to the first edge. | |||||
/// @{ | |||||
[[nodiscard]] inline constexpr const_iterator begin() const noexcept | |||||
{ | |||||
const_iterator it; | |||||
it.m_vertex = m_vertex; | |||||
it.m_edge = m_head; | |||||
it.m_position = 0; | |||||
return it; | |||||
} | |||||
[[nodiscard]] inline constexpr const_iterator cbegin() const noexcept | |||||
{ | |||||
return begin(); | |||||
} | |||||
/// @} | |||||
/// Returns an iterator to the edge following the last edge. | |||||
/// @{ | |||||
[[nodiscard]] inline constexpr const_iterator end() const noexcept | |||||
{ | |||||
const_iterator it; | |||||
it.m_vertex = m_vertex; | |||||
it.m_edge = m_head; | |||||
it.m_position = static_cast<std::ptrdiff_t>(m_size); | |||||
return it; | |||||
} | |||||
[[nodiscard]] inline constexpr const_iterator cend() const noexcept | |||||
{ | |||||
return end(); | |||||
} | |||||
/// @} | |||||
/// Returns a reverse iterator to the first edge of the reversed list. | |||||
/// @{ | |||||
[[nodiscard]] inline constexpr const_reverse_iterator rbegin() const noexcept | |||||
{ | |||||
return std::make_reverse_iterator(end()); | |||||
} | |||||
[[nodiscard]] inline constexpr const_reverse_iterator crbegin() const noexcept | |||||
{ | |||||
return rbegin(); | |||||
} | |||||
/// @} | |||||
/// Returns a reverse iterator to the edge following the last edge of the reversed list. | |||||
/// @{ | |||||
[[nodiscard]] inline constexpr const_reverse_iterator rend() const noexcept | |||||
{ | |||||
return std::make_reverse_iterator(begin()); | |||||
} | |||||
[[nodiscard]] inline constexpr const_reverse_iterator crend() const noexcept | |||||
{ | |||||
return rend(); | |||||
} | |||||
/// @} | |||||
/// @} | |||||
/// @name Capacity | |||||
/// @{ | |||||
/// Returns `true` if the list is empty, `false` otherwise. | |||||
[[nodiscard]] inline constexpr bool empty() const noexcept | |||||
{ | |||||
return !m_size; | |||||
} | |||||
/// Returns the number of edges in the list. | |||||
[[nodiscard]] inline constexpr std::size_t size() const noexcept | |||||
{ | |||||
return m_size; | |||||
} | |||||
/// @} | |||||
/// @name Modifiers | |||||
/// @{ | |||||
/** | |||||
* Appends an edge to the end of the list. | |||||
* | |||||
* @param edge Pointer to the edge to append. | |||||
*/ | |||||
void push_back(brep_edge* edge); | |||||
/** | |||||
* Removes an edge from the list. | |||||
* | |||||
* @param edge Pointer to the edge to remove. | |||||
*/ | |||||
void remove(brep_edge* edge); | |||||
/// @} | |||||
private: | |||||
brep_vertex* m_vertex{}; | |||||
brep_edge* m_head{}; | |||||
std::size_t m_size{}; | |||||
}; | |||||
/** | |||||
* A point in space. | |||||
*/ | |||||
class brep_vertex | |||||
{ | |||||
public: | |||||
friend class brep_mesh; | |||||
friend class brep_element_container<brep_vertex>; | |||||
friend class brep_vertex_container; | |||||
friend class brep_edge_container; | |||||
/** | |||||
* Returns the index of this vertex in the mesh vertex array. | |||||
* | |||||
* @warning This index may change if any vertices are removed from the mesh. | |||||
*/ | |||||
[[nodiscard]] inline constexpr std::size_t index() const noexcept | |||||
{ | |||||
return m_index; | |||||
} | |||||
/// Returns the list of edges bounded by this vertex. | |||||
[[nodiscard]] inline constexpr const brep_vertex_edge_list& edges() const noexcept | |||||
{ | |||||
return m_edges; | |||||
} | |||||
private: | |||||
std::size_t m_index; | |||||
brep_vertex_edge_list m_edges; | |||||
}; | |||||
/** | |||||
* B-rep vertex container. | |||||
*/ | |||||
class brep_vertex_container: public brep_element_container<brep_vertex> | |||||
{ | |||||
public: | |||||
/** | |||||
* Appends a new vertex to the end of the container. | |||||
* | |||||
* @return Pointer to the new vertex. | |||||
*/ | |||||
brep_vertex* emplace_back() override; | |||||
/** | |||||
* Erases a vertex and all dependent edges, loops, and faces. | |||||
* | |||||
* @param vertex Pointer to the vertex to erase. | |||||
* | |||||
* @warning Invalidates iterators and indices of vertices, edges, loops, and faces. | |||||
*/ | |||||
void erase(brep_vertex* vertex) override; | |||||
/** | |||||
* Erases all vertices and their dependent edges, loops, and faces. | |||||
*/ | |||||
void clear() noexcept; | |||||
private: | |||||
friend class brep_mesh; | |||||
/** | |||||
* Constructs a B-rep vertex container. | |||||
* | |||||
* @param mesh Pointer to the parent mesh. | |||||
*/ | |||||
inline explicit brep_vertex_container(brep_mesh* mesh) noexcept: | |||||
brep_element_container<brep_vertex>(mesh) | |||||
{} | |||||
}; | |||||
} // namespace geom | |||||
#endif // ANTKEEPER_GEOM_BREP_VERTEX_HPP |
@ -0,0 +1,51 @@ | |||||
/* | |||||
* 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/>. | |||||
*/ | |||||
#ifndef ANTKEEPER_GEOM_BVH_NODE_HPP | |||||
#define ANTKEEPER_GEOM_BVH_NODE_HPP | |||||
#include <engine/geom/primitives/box.hpp> | |||||
#include <cstdint> | |||||
namespace geom { | |||||
/** | |||||
* Single node in a bounding volume hierarchy. | |||||
*/ | |||||
struct bvh_node | |||||
{ | |||||
/// Returns `true` if the node is a leaf node, `false` otherwise. | |||||
[[nodiscard]] inline constexpr bool is_leaf() const noexcept | |||||
{ | |||||
return size; | |||||
} | |||||
/// Node bounds. | |||||
geom::box<float> bounds; | |||||
/// Number of primitives in the node. | |||||
std::uint32_t size; | |||||
/// Offset to the first child node (non-leaf) or primitive (leaf). | |||||
std::uint32_t offset; | |||||
}; | |||||
} // namespace geom | |||||
#endif // ANTKEEPER_GEOM_BVH_NODE_HPP |
@ -0,0 +1,199 @@ | |||||
/* | |||||
* 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/bvh/bvh.hpp> | |||||
#include <engine/geom/intersection.hpp> | |||||
#include <engine/geom/brep/brep-mesh.hpp> | |||||
#include <engine/debug/log.hpp> | |||||
#include <algorithm> | |||||
#include <numeric> | |||||
namespace geom { | |||||
bvh::bvh(std::span<const bvh_primitive> primitives) | |||||
{ | |||||
build(primitives); | |||||
} | |||||
bvh::bvh(const brep_mesh& mesh) | |||||
{ | |||||
build(mesh); | |||||
} | |||||
void bvh::build(std::span<const bvh_primitive> primitives) | |||||
{ | |||||
if (primitives.empty()) | |||||
{ | |||||
m_primitive_indices.clear(); | |||||
m_nodes.clear(); | |||||
m_node_count = 0; | |||||
} | |||||
else | |||||
{ | |||||
// Allocate and fill primitive index array | |||||
m_primitive_indices.resize(primitives.size()); | |||||
std::iota(m_primitive_indices.begin(), m_primitive_indices.end(), 0); | |||||
// Allocate nodes | |||||
m_nodes.resize(primitives.size() << 1); | |||||
// Init root node | |||||
m_node_count = 1; | |||||
auto& root = m_nodes.front(); | |||||
root.size = static_cast<std::uint32_t>(primitives.size()); | |||||
root.offset = 0; | |||||
update_bounds(root, primitives); | |||||
// Recursively build BVH | |||||
subdivide(root, primitives); | |||||
} | |||||
} | |||||
void bvh::build(const brep_mesh& mesh) | |||||
{ | |||||
// Get mesh vertex positions attribute | |||||
const auto& vertex_positions = mesh.vertices().attributes().at<math::fvec3>("position"); | |||||
// Allocate BVH primitives for mesh faces | |||||
std::vector<bvh_primitive> primitives(mesh.faces().size()); | |||||
// Calculate bounding boxes | |||||
for (brep_face* face: mesh.faces()) | |||||
{ | |||||
auto& primitive = primitives[face->index()]; | |||||
primitive.centroid = {}; | |||||
primitive.bounds = {math::fvec3::infinity(), -math::fvec3::infinity()}; | |||||
for (brep_loop* loop: face->loops()) | |||||
{ | |||||
const auto& vertex_position = vertex_positions[loop->vertex()->index()]; | |||||
primitive.centroid += vertex_position; | |||||
primitive.bounds.extend(vertex_position); | |||||
} | |||||
primitive.centroid /= static_cast<float>(face->loops().size()); | |||||
} | |||||
// Build BVH from the bounding boxes of the mesh faces | |||||
build(primitives); | |||||
} | |||||
void bvh::update_bounds(bvh_node& node, const std::span<const bvh_primitive>& primitives) | |||||
{ | |||||
node.bounds = {math::fvec3::infinity(), -math::fvec3::infinity()}; | |||||
for (std::uint32_t i = 0; i < node.size; ++i) | |||||
{ | |||||
const auto& primitive = primitives[m_primitive_indices[node.offset + i]]; | |||||
node.bounds.extend(primitive.bounds); | |||||
}; | |||||
} | |||||
void bvh::subdivide(bvh_node& node, const std::span<const bvh_primitive>& primitives) | |||||
{ | |||||
if (node.size <= 2) | |||||
{ | |||||
return; | |||||
} | |||||
// Determine index of split axis | |||||
const auto extents = node.bounds.size(); | |||||
std::uint8_t split_axis = 0; | |||||
if (extents.y() > extents.x()) | |||||
{ | |||||
split_axis = 1; | |||||
} | |||||
if (extents.z() > extents[split_axis]) | |||||
{ | |||||
split_axis = 2; | |||||
} | |||||
// Determine split coordinate | |||||
const float split_coord = node.bounds.min[split_axis] + extents[split_axis] * 0.5f; | |||||
std::uint32_t i = node.offset; | |||||
std::uint32_t j = (node.size) ? i + node.size - 1 : i; | |||||
while (i <= j) | |||||
{ | |||||
const auto& primitive = primitives[m_primitive_indices[i]]; | |||||
if (primitive.centroid[split_axis] < split_coord) | |||||
// if (primitive.bounds.center()[split_axis] < split_coord) | |||||
{ | |||||
++i; | |||||
} | |||||
else | |||||
{ | |||||
std::swap(m_primitive_indices[i], m_primitive_indices[j]); | |||||
if (!j) | |||||
{ | |||||
break; | |||||
} | |||||
--j; | |||||
} | |||||
} | |||||
const std::uint32_t left_size = i - node.offset; | |||||
if (!left_size || left_size == node.size) | |||||
{ | |||||
return; | |||||
} | |||||
const std::uint32_t left_index = m_node_count++; | |||||
auto& left_child = m_nodes[left_index]; | |||||
left_child.offset = node.offset; | |||||
left_child.size = left_size; | |||||
update_bounds(left_child, primitives); | |||||
const std::uint32_t right_index = m_node_count++; | |||||
auto& right_child = m_nodes[right_index]; | |||||
right_child.offset = i; | |||||
right_child.size = node.size - left_size; | |||||
update_bounds(right_child, primitives); | |||||
node.offset = left_index; | |||||
node.size = 0; | |||||
subdivide(left_child, primitives); | |||||
subdivide(right_child, primitives); | |||||
} | |||||
void bvh::visit(const bvh_node& node, const geom::ray<float, 3>& ray, const visitor_type& f) const | |||||
{ | |||||
if (!geom::intersection(ray, node.bounds)) | |||||
{ | |||||
return; | |||||
} | |||||
if (node.is_leaf()) | |||||
{ | |||||
// Visit leaf node primitives | |||||
for (std::uint32_t i = 0; i < node.size; ++i) | |||||
{ | |||||
f(m_primitive_indices[node.offset + i]); | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
// Recursively visit node children | |||||
visit(m_nodes[node.offset], ray, f); | |||||
visit(m_nodes[node.offset + 1], ray, f); | |||||
} | |||||
} | |||||
} // namespace geom |
@ -0,0 +1,113 @@ | |||||
/* | |||||
* 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/>. | |||||
*/ | |||||
#ifndef ANTKEEPER_GEOM_BVH_HPP | |||||
#define ANTKEEPER_GEOM_BVH_HPP | |||||
#include <engine/geom/primitives/ray.hpp> | |||||
#include <engine/geom/bvh/bvh-primitive.hpp> | |||||
#include <engine/geom/bvh/bvh-node.hpp> | |||||
#include <cstdint> | |||||
#include <functional> | |||||
#include <span> | |||||
#include <vector> | |||||
namespace geom { | |||||
class brep_mesh; | |||||
/** | |||||
* Bounding volume hierarchy (BVH). | |||||
*/ | |||||
class bvh | |||||
{ | |||||
public: | |||||
using visitor_type = std::function<void(std::uint32_t)>; | |||||
/** | |||||
* Constructs a BVH from a set of primitives. | |||||
* | |||||
* @param primitives Axis-aligned bounding boxes. | |||||
*/ | |||||
explicit bvh(std::span<const bvh_primitive> primitives); | |||||
/** | |||||
* Constructs a BVH from a B-rep mesh. | |||||
* | |||||
* @param mesh B-rep mesh from which to build the BVH. | |||||
*/ | |||||
explicit bvh(const brep_mesh& mesh); | |||||
/// Constructs an empty BVH. | |||||
constexpr bvh() noexcept = default; | |||||
/** | |||||
* Constructs a BVH from a set of primitives. | |||||
* | |||||
* @param primitives BVH primitives. | |||||
*/ | |||||
void build(std::span<const bvh_primitive> primitives); | |||||
/** | |||||
* Constructs a BVH from a B-rep mesh. | |||||
* | |||||
* @param mesh B-rep mesh from which to build the BVH. | |||||
*/ | |||||
void build(const brep_mesh& mesh); | |||||
/** | |||||
* Visits the primitive indices of all BVH nodes that intersect a ray. | |||||
* | |||||
* @param ray Query ray. | |||||
* @param f Unary visitor function which operates on a BVH primitive index. | |||||
*/ | |||||
inline void visit(const geom::ray<float, 3>& ray, const visitor_type& f) const | |||||
{ | |||||
if (m_node_count) | |||||
{ | |||||
visit(m_nodes.front(), ray, f); | |||||
} | |||||
} | |||||
/// Returns the BVH nodes. | |||||
[[nodiscard]] inline constexpr const std::vector<bvh_node>& nodes() const noexcept | |||||
{ | |||||
return m_nodes; | |||||
} | |||||
private: | |||||
void update_bounds(bvh_node& node, const std::span<const bvh_primitive>& primitives); | |||||
/** | |||||
* Builds the BVH through recursive subdivision. | |||||
* | |||||
* @param node Current node. | |||||
* @param primitives BVH primitives. | |||||
*/ | |||||
void subdivide(bvh_node& node, const std::span<const bvh_primitive>& primitives); | |||||
void visit(const bvh_node& node, const geom::ray<float, 3>& ray, const visitor_type& f) const; | |||||
std::vector<std::uint32_t> m_primitive_indices; | |||||
std::vector<bvh_node> m_nodes; | |||||
std::uint32_t m_node_count{}; | |||||
}; | |||||
} // namespace geom | |||||
#endif // ANTKEEPER_GEOM_BVH_HPP |
@ -1,179 +0,0 @@ | |||||
/* | |||||
* 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/mesh-accelerator.hpp> | |||||
#include <engine/geom/mesh-functions.hpp> | |||||
#include <engine/geom/morton.hpp> | |||||
#include <bitset> | |||||
namespace geom { | |||||
mesh_accelerator::mesh_accelerator() | |||||
{} | |||||
void mesh_accelerator::build(const mesh& mesh) | |||||
{ | |||||
// Clear octree and face map | |||||
octree.clear(); | |||||
face_map.clear(); | |||||
// Calculate mesh dimensions | |||||
box<float> bounds = calculate_bounds(mesh); | |||||
float3 mesh_dimensions = bounds.max - bounds.min; | |||||
center_offset = mesh_dimensions * 0.5f - (bounds.min + bounds.max) * 0.5f; | |||||
// Calculate node dimensions at each octree depth | |||||
for (auto i = 0; i <= octree_type::max_depth; ++i) | |||||
{ | |||||
node_dimensions[i] = mesh_dimensions * static_cast<float>((1.0f / std::pow(2, i))); | |||||
} | |||||
// Add faces to octree | |||||
for (mesh::face* face: mesh.get_faces()) | |||||
{ | |||||
// Calculate face bounds | |||||
float3 min_point = reinterpret_cast<const float3&>(face->edge->vertex->position); | |||||
float3 max_point = min_point; | |||||
mesh::edge* edge = face->edge; | |||||
do | |||||
{ | |||||
const auto& position = edge->vertex->position; | |||||
for (int i = 0; i < 3; ++i) | |||||
{ | |||||
min_point[i] = std::min<float>(min_point[i], position[i]); | |||||
max_point[i] = std::max<float>(max_point[i], position[i]); | |||||
} | |||||
edge = edge->next; | |||||
} | |||||
while (edge != face->edge); | |||||
// 1. Find max depth node of aabb min | |||||
// 2. Find max depth node of aabb max | |||||
// 3. Find common ancestor of the two nodes--that's the containing node. | |||||
typename octree_type::node_type min_node = find_node(min_point); | |||||
typename octree_type::node_type max_node = find_node(max_point); | |||||
typename octree_type::node_type containing_node = octree_type::common_ancestor(min_node, max_node); | |||||
// Insert containing node into octree | |||||
octree.insert(containing_node); | |||||
// Add face to face map | |||||
face_map[containing_node].push_back(face); | |||||
} | |||||
} | |||||
std::optional<mesh_accelerator::ray_query_result> mesh_accelerator::query_nearest(const ray<float, 3>& ray) const | |||||
{ | |||||
ray_query_result result; | |||||
result.t = std::numeric_limits<float>::infinity(); | |||||
result.face = nullptr; | |||||
query_nearest_recursive(result.t, result.face, octree.root, ray); | |||||
if (result.face) | |||||
return std::optional{result}; | |||||
return std::nullopt; | |||||
} | |||||
void mesh_accelerator::query_nearest_recursive(float& nearest_t, geom::mesh::face*& nearest_face, typename octree_type::node_type node, const ray<float, 3>& ray) const | |||||
{ | |||||
// Get node bounds | |||||
const box<float> node_bounds = get_node_bounds(node); | |||||
// If ray passed through this node | |||||
if (intersection(ray, node_bounds)) | |||||
{ | |||||
// Test all triangles in the node | |||||
if (auto it = face_map.find(node); it != face_map.end()) | |||||
{ | |||||
const std::list<mesh::face*>& faces = it->second; | |||||
for (mesh::face* face: faces) | |||||
{ | |||||
// Get triangle coordinates | |||||
const float3& a = reinterpret_cast<const float3&>(face->edge->vertex->position); | |||||
const float3& b = reinterpret_cast<const float3&>(face->edge->next->vertex->position); | |||||
const float3& c = reinterpret_cast<const float3&>(face->edge->previous->vertex->position); | |||||
// Test for intersection with triangle | |||||
auto triangle_intersection = intersection(ray, a, b, c); | |||||
if (triangle_intersection) | |||||
{ | |||||
const float t = std::get<0>(*triangle_intersection); | |||||
if (t < nearest_t) | |||||
{ | |||||
nearest_t = t; | |||||
nearest_face = face; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
// Test all child nodes | |||||
if (!octree.is_leaf(node)) | |||||
{ | |||||
for (int i = 0; i < 8; ++i) | |||||
{ | |||||
query_nearest_recursive(nearest_t, nearest_face, octree.child(node, i), ray); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
box<float> mesh_accelerator::get_node_bounds(typename octree_type::node_type node) const | |||||
{ | |||||
// Decode Morton location of node | |||||
std::uint32_t x, y, z; | |||||
morton::decode(octree_type::location(node), x, y, z); | |||||
float3 node_location = float3{static_cast<float>(x), static_cast<float>(y), static_cast<float>(z)}; | |||||
// Get node dimensions at node depth | |||||
const float3& dimensions = node_dimensions[octree_type::depth(node)]; | |||||
// Calculate AABB | |||||
float3 min_point = (node_location * dimensions) - center_offset; | |||||
return box<float>{min_point, min_point + dimensions}; | |||||
} | |||||
typename mesh_accelerator::octree_type::node_type mesh_accelerator::find_node(const float3& point) const | |||||
{ | |||||
// Transform point to octree space | |||||
float3 transformed_point = (point + center_offset); | |||||
// Account for floating-point tolerance | |||||
const float epsilon = 0.00001f; | |||||
transformed_point.x() = std::max<float>(0.0f, std::min<float>(node_dimensions[0].x() - epsilon, transformed_point.x())); | |||||
transformed_point.y() = std::max<float>(0.0f, std::min<float>(node_dimensions[0].y() - epsilon, transformed_point.y())); | |||||
transformed_point.z() = std::max<float>(0.0f, std::min<float>(node_dimensions[0].z() - epsilon, transformed_point.z())); | |||||
// Transform point to max-depth node space | |||||
transformed_point = transformed_point / node_dimensions[octree_type::max_depth]; | |||||
// Encode transformed point as a Morton location code | |||||
std::uint32_t location = morton::encode( | |||||
static_cast<std::uint32_t>(transformed_point.x()), | |||||
static_cast<std::uint32_t>(transformed_point.y()), | |||||
static_cast<std::uint32_t>(transformed_point.z())); | |||||
// Return max depth node at the determined location | |||||
return octree_type::node(octree_type::max_depth, location); | |||||
} | |||||
} // namespace geom |
@ -1,80 +0,0 @@ | |||||
/* | |||||
* 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/>. | |||||
*/ | |||||
#ifndef ANTKEEPER_GEOM_MESH_ACCELERATOR_HPP | |||||
#define ANTKEEPER_GEOM_MESH_ACCELERATOR_HPP | |||||
#include <engine/geom/mesh.hpp> | |||||
#include <engine/geom/octree.hpp> | |||||
#include <engine/geom/primitives/box.hpp> | |||||
#include <engine/geom/intersection.hpp> | |||||
#include <engine/utility/fundamental-types.hpp> | |||||
#include <list> | |||||
#include <optional> | |||||
#include <unordered_map> | |||||
namespace geom { | |||||
/** | |||||
* Acceleration structure for querying mesh geometry. | |||||
*/ | |||||
class mesh_accelerator | |||||
{ | |||||
public: | |||||
struct ray_query_result | |||||
{ | |||||
float t; | |||||
geom::mesh::face* face; | |||||
}; | |||||
mesh_accelerator(); | |||||
/** | |||||
* Builds the acceleration structure. | |||||
*/ | |||||
void build(const mesh& mesh); | |||||
/** | |||||
* Finds the first intersection between a ray and a triangle in the mesh. | |||||
* | |||||
* @param ray Ray to test for intersection. | |||||
* @return Ray query result on intersection, and `std::nullopt` if no intersection occurred. | |||||
*/ | |||||
std::optional<ray_query_result> query_nearest(const ray<float, 3>& ray) const; | |||||
private: | |||||
typedef unordered_octree32 octree_type; | |||||
box<float> get_node_bounds(typename octree_type::node_type node) const; | |||||
void query_nearest_recursive(float& nearest_t, geom::mesh::face*& nearest_face, typename octree_type::node_type node, const ray<float, 3>& ray) const; | |||||
/// Returns the max-depth node in which the point is located | |||||
typename octree_type::node_type find_node(const float3& point) const; | |||||
octree_type octree; | |||||
float3 node_dimensions[octree_type::max_depth + 1]; | |||||
float3 center_offset; | |||||
std::unordered_map<typename octree_type::node_type, std::list<mesh::face*>> face_map; | |||||
}; | |||||
} // namespace geom | |||||
#endif // ANTKEEPER_GEOM_MESH_ACCELERATOR_HPP | |||||
@ -1,222 +0,0 @@ | |||||
/* | |||||
* 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/mesh-functions.hpp> | |||||
#include <engine/utility/fundamental-types.hpp> | |||||
#include <unordered_map> | |||||
#include <vector> | |||||
namespace geom { | |||||
struct edge_hasher | |||||
{ | |||||
std::size_t operator()(const std::array<std::size_t, 2>& v) const noexcept | |||||
{ | |||||
std::size_t hash = std::hash<std::size_t>()(v[0]); | |||||
return hash ^ (std::hash<std::size_t>()(v[1]) + 0x9e3779b9 + (hash << 6) + (hash >> 2)); | |||||
} | |||||
}; | |||||
void create_triangle_mesh(mesh& mesh, const std::vector<float3>& vertices, const std::vector<std::array<std::uint_fast32_t, 3>>& triangles) | |||||
{ | |||||
for (const auto& vertex: vertices) | |||||
mesh.add_vertex(vertex); | |||||
std::unordered_map<std::array<std::size_t, 2>, geom::mesh::edge*, edge_hasher> edge_map; | |||||
const std::vector<mesh::vertex*>& mesh_vertices = mesh.get_vertices(); | |||||
std::vector<geom::mesh::edge*> loop(3); | |||||
for (const auto& triangle: triangles) | |||||
{ | |||||
geom::mesh::vertex* triangle_vertices[3] = | |||||
{ | |||||
mesh_vertices[triangle[0]], | |||||
mesh_vertices[triangle[1]], | |||||
mesh_vertices[triangle[2]] | |||||
}; | |||||
for (int j = 0; j < 3; ++j) | |||||
{ | |||||
geom::mesh::vertex* start = triangle_vertices[j]; | |||||
geom::mesh::vertex* end = triangle_vertices[(j + 1) % 3]; | |||||
if (auto it = edge_map.find({start->index, end->index}); it != edge_map.end()) | |||||
{ | |||||
loop[j] = it->second; | |||||
} | |||||
else | |||||
{ | |||||
loop[j] = mesh.add_edge(start, end); | |||||
edge_map[{start->index, end->index}] = loop[j]; | |||||
edge_map[{end->index, start->index}] = loop[j]->symmetric; | |||||
} | |||||
} | |||||
mesh.add_face(loop); | |||||
} | |||||
} | |||||
void calculate_face_normals(float3* normals, const mesh& mesh) | |||||
{ | |||||
const std::vector<mesh::face*>& faces = mesh.get_faces(); | |||||
for (std::size_t i = 0; i < faces.size(); ++i) | |||||
{ | |||||
const mesh::face& face = *(faces[i]); | |||||
const float3& a = face.edge->vertex->position; | |||||
const float3& b = face.edge->next->vertex->position; | |||||
const float3& c = face.edge->previous->vertex->position; | |||||
normals[i] = math::normalize(math::cross(b - a, c - a)); | |||||
} | |||||
} | |||||
float3 calculate_face_normal(const mesh::face& face) | |||||
{ | |||||
const float3& a = face.edge->vertex->position; | |||||
const float3& b = face.edge->next->vertex->position; | |||||
const float3& c = face.edge->previous->vertex->position; | |||||
return math::normalize(math::cross(b - a, c - a)); | |||||
} | |||||
void calculate_vertex_tangents(float4* tangents, const float2* texcoords, const float3* normals, const mesh& mesh) | |||||
{ | |||||
const std::vector<mesh::face*>& faces = mesh.get_faces(); | |||||
const std::vector<mesh::vertex*>& vertices = mesh.get_vertices(); | |||||
// Allocate tangent and bitangent buffers | |||||
std::vector<float3> tangent_buffer(vertices.size(), float3{0.0f, 0.0f, 0.0f}); | |||||
std::vector<float3> bitangent_buffer(vertices.size(), float3{0.0f, 0.0f, 0.0f}); | |||||
// Accumulate tangents and bitangents | |||||
for (std::size_t i = 0; i < faces.size(); ++i) | |||||
{ | |||||
const mesh::face& face = *(faces[i]); | |||||
std::size_t ia = face.edge->vertex->index; | |||||
std::size_t ib = face.edge->next->vertex->index; | |||||
std::size_t ic = face.edge->previous->vertex->index; | |||||
const float3& a = vertices[ia]->position; | |||||
const float3& b = vertices[ib]->position; | |||||
const float3& c = vertices[ic]->position; | |||||
const float2& uva = texcoords[ia]; | |||||
const float2& uvb = texcoords[ib]; | |||||
const float2& uvc = texcoords[ic]; | |||||
float3 ba = b - a; | |||||
float3 ca = c - a; | |||||
float2 uvba = uvb - uva; | |||||
float2 uvca = uvc - uva; | |||||
float f = 1.0f / (uvba.x() * uvca.y() - uvca.x() * uvba.y()); | |||||
float3 tangent = (ba * uvca.y() - ca * uvba.y()) * f; | |||||
float3 bitangent = (ba * -uvca.x() + ca * uvba.x()) * f; | |||||
tangent_buffer[ia] += tangent; | |||||
tangent_buffer[ib] += tangent; | |||||
tangent_buffer[ic] += tangent; | |||||
bitangent_buffer[ia] += bitangent; | |||||
bitangent_buffer[ib] += bitangent; | |||||
bitangent_buffer[ic] += bitangent; | |||||
} | |||||
// Orthogonalize tangents | |||||
for (std::size_t i = 0; i < vertices.size(); ++i) | |||||
{ | |||||
const float3& n = normals[i]; | |||||
const float3& t = tangent_buffer[i]; | |||||
const float3& b = bitangent_buffer[i]; | |||||
// Gram-Schmidt orthogonalize tangent | |||||
float3 tangent = math::normalize(t - n * math::dot(n, t)); | |||||
// Calculate bitangent sign | |||||
float bitangent_sign = (math::dot(math::cross(n, t), b) < 0.0f) ? -1.0f : 1.0f; | |||||
tangents[i] = {tangent.x(), tangent.y(), tangent.z(), bitangent_sign}; | |||||
} | |||||
} | |||||
box<float> calculate_bounds(const mesh& mesh) | |||||
{ | |||||
box<float> bounds; | |||||
for (int i = 0; i < 3; ++i) | |||||
{ | |||||
bounds.min[i] = std::numeric_limits<float>::infinity(); | |||||
bounds.max[i] = -std::numeric_limits<float>::infinity(); | |||||
} | |||||
for (const mesh::vertex* vertex: mesh.get_vertices()) | |||||
{ | |||||
const auto& position = vertex->position; | |||||
bounds.extend(position); | |||||
} | |||||
return bounds; | |||||
} | |||||
mesh::vertex* poke_face(mesh& mesh, std::size_t index) | |||||
{ | |||||
mesh::face* face = mesh.get_faces()[index]; | |||||
// Collect face edges and sum edge vertex positions | |||||
mesh::loop loop = {face->edge}; | |||||
float3 sum_positions = face->edge->vertex->position; | |||||
for (mesh::edge* edge = face->edge->next; edge != face->edge; edge = edge->next) | |||||
{ | |||||
loop.push_back(edge); | |||||
sum_positions += edge->vertex->position; | |||||
} | |||||
if (loop.size() <= 2) | |||||
return nullptr; | |||||
// Remove face | |||||
mesh.remove_face(face); | |||||
// Add vertex in center | |||||
mesh::vertex* center = mesh.add_vertex(sum_positions / static_cast<float>(loop.size())); | |||||
// Create first triangle | |||||
geom::mesh::edge* ab = loop[0]; | |||||
geom::mesh::edge* bc = mesh.add_edge(ab->next->vertex, center); | |||||
geom::mesh::edge* ca = mesh.add_edge(center, ab->vertex); | |||||
mesh.add_face({ab, bc, ca}); | |||||
// Save first triangle CA edge | |||||
geom::mesh::edge* first_triangle_ca = ca; | |||||
// Create remaining triangles | |||||
for (std::size_t i = 1; i < loop.size(); ++i) | |||||
{ | |||||
ab = loop[i]; | |||||
ca = bc->symmetric; | |||||
if (i == loop.size() - 1) | |||||
bc = first_triangle_ca->symmetric; | |||||
else | |||||
bc = mesh.add_edge(ab->next->vertex, center); | |||||
mesh.add_face({ab, bc, ca}); | |||||
} | |||||
return center; | |||||
} | |||||
} // namespace geom |
@ -1,76 +0,0 @@ | |||||
/* | |||||
* 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/>. | |||||
*/ | |||||
#ifndef ANTKEEPER_GEOM_MESH_FUNCTIONS_HPP | |||||
#define ANTKEEPER_GEOM_MESH_FUNCTIONS_HPP | |||||
#include <engine/geom/primitives/box.hpp> | |||||
#include <engine/geom/mesh.hpp> | |||||
#include <engine/utility/fundamental-types.hpp> | |||||
#include <array> | |||||
#include <vector> | |||||
namespace geom { | |||||
/** | |||||
* Creates a triangle mesh from a list of vertices and indices. | |||||
* | |||||
* @param[out] mesh Mesh to which vertices, edges, and faces will be added. | |||||
* @param vertices Vertex positions | |||||
* @param triangles Triangle indices | |||||
* @return Newly created triangle mesh. | |||||
*/ | |||||
void create_triangle_mesh(mesh& mesh, const std::vector<float3>& vertices, const std::vector<std::array<std::uint_fast32_t, 3>>& triangles); | |||||
/** | |||||
* Calculates normals for each face. | |||||
* | |||||
* @param[out] Array in which faceted normals will be stored. | |||||
*/ | |||||
void calculate_face_normals(float3* normals, const mesh& mesh); | |||||
float3 calculate_face_normal(const mesh::face& face); | |||||
/** | |||||
* Calculates smooth tangents per-vertex. | |||||
* | |||||
* @param[out] tangents Array in which vertex tangents will be stored. A bitangent sign is stored in each tangent vector's fourth component. | |||||
* @param[in] texcoords Array containing vertex texture coordinates. | |||||
* @param[in] normals Array containing vertex normals. | |||||
*/ | |||||
void calculate_vertex_tangents(float4* tangents, const float2* texcoords, const float3* normals, const mesh& mesh); | |||||
/** | |||||
* Calculates the AABB bounds of a mesh. | |||||
*/ | |||||
box<float> calculate_bounds(const mesh& mesh); | |||||
/** | |||||
* Triangulates a face by adding a new vertex in the center, then creating triangles between the edges of the original face and the new vertex. | |||||
* | |||||
* @param mesh Mesh containing the face to poke. | |||||
* @param index Index of the face to poke. | |||||
* @return Pointer to the newly-created vertex in the center of the face, or `nullptr` if the face could not be poked. | |||||
*/ | |||||
mesh::vertex* poke_face(mesh& mesh, std::size_t index); | |||||
} // namespace geom | |||||
#endif // ANTKEEPER_GEOM_MESH_FUNCTIONS_HPP | |||||
@ -1,436 +0,0 @@ | |||||
/* | |||||
* 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/mesh.hpp> | |||||
#include <stdexcept> | |||||
namespace geom { | |||||
mesh::mesh(const mesh& other) | |||||
{ | |||||
*this = other; | |||||
} | |||||
mesh::~mesh() | |||||
{ | |||||
clear(); | |||||
} | |||||
mesh& mesh::operator=(const mesh& other) | |||||
{ | |||||
// Clear the mesh | |||||
clear(); | |||||
// Resize vertices, edges, and faces | |||||
vertices.resize(other.vertices.size()); | |||||
edges.resize(other.edges.size()); | |||||
faces.resize(other.faces.size()); | |||||
// Allocate vertices | |||||
for (std::size_t i = 0; i < vertices.size(); ++i) | |||||
vertices[i] = new vertex(); | |||||
// Allocate edges | |||||
for (std::size_t i = 0; i < edges.size(); ++i) | |||||
{ | |||||
edges[i] = new edge(); | |||||
edges[i]->symmetric = new edge(); | |||||
edges[i]->symmetric->symmetric = edges[i]; | |||||
} | |||||
// Allocate faces | |||||
for (std::size_t i = 0; i < faces.size(); ++i) | |||||
faces[i] = new face(); | |||||
// Copy vertices | |||||
for (std::size_t i = 0; i < vertices.size(); ++i) | |||||
{ | |||||
vertex* va = vertices[i]; | |||||
const vertex* vb = other.vertices[i]; | |||||
va->index = vb->index; | |||||
va->position = vb->position; | |||||
va->edge = nullptr; | |||||
if (vb->edge) | |||||
{ | |||||
va->edge = edges[vb->edge->index]; | |||||
if (vb->edge != other.edges[vb->edge->index]) | |||||
va->edge = va->edge->symmetric; | |||||
} | |||||
} | |||||
// Copy edges | |||||
for (std::size_t i = 0; i < edges.size(); ++i) | |||||
{ | |||||
edge* ea = edges[i]; | |||||
const edge* eb = other.edges[i]; | |||||
for (std::size_t j = 0; j < 2; ++j) | |||||
{ | |||||
ea->index = eb->index; | |||||
ea->vertex = vertices[eb->vertex->index]; | |||||
ea->face = nullptr; | |||||
if (eb->face) | |||||
ea->face = faces[eb->face->index]; | |||||
ea->previous = edges[eb->previous->index]; | |||||
if (eb->previous != other.edges[eb->previous->index]) | |||||
ea->previous = ea->previous->symmetric; | |||||
ea->next = edges[eb->next->index]; | |||||
if (eb->next != other.edges[eb->next->index]) | |||||
ea->next = ea->next->symmetric; | |||||
ea = ea->symmetric; | |||||
eb = eb->symmetric; | |||||
} | |||||
} | |||||
// Copy faces | |||||
for (std::size_t i = 0; i < faces.size(); ++i) | |||||
{ | |||||
face* fa = faces[i]; | |||||
const face* fb = other.faces[i]; | |||||
fa->index = fb->index; | |||||
fa->edge = edges[fb->edge->index]; | |||||
if (fb->edge != other.edges[fb->edge->index]) | |||||
fa->edge = fa->edge->symmetric; | |||||
} | |||||
return *this; | |||||
} | |||||
void mesh::clear() noexcept | |||||
{ | |||||
// Deallocate vertices | |||||
for (mesh::vertex* vertex: vertices) | |||||
delete vertex; | |||||
// Deallocate edges | |||||
for (mesh::edge* edge: edges) | |||||
{ | |||||
delete edge->symmetric; | |||||
delete edge; | |||||
} | |||||
// Deallocate faces | |||||
for (mesh::face* face: faces) | |||||
delete face; | |||||
vertices.clear(); | |||||
edges.clear(); | |||||
faces.clear(); | |||||
} | |||||
mesh::vertex* mesh::add_vertex(const float3& position) | |||||
{ | |||||
mesh::vertex* vertex = new mesh::vertex | |||||
{ | |||||
vertices.size(), | |||||
nullptr, | |||||
position | |||||
}; | |||||
vertices.push_back(vertex); | |||||
return vertex; | |||||
} | |||||
mesh::edge* mesh::add_edge(mesh::vertex* a, mesh::vertex* b) | |||||
{ | |||||
mesh::edge* ab = new mesh::edge(); | |||||
mesh::edge* ba = new mesh::edge(); | |||||
ab->index = edges.size(); | |||||
ab->vertex = a; | |||||
ab->face = nullptr; | |||||
ab->previous = ba; | |||||
ab->next = ba; | |||||
ab->symmetric = ba; | |||||
ba->index = edges.size(); | |||||
ba->vertex = b; | |||||
ba->face = nullptr; | |||||
ba->previous = ab; | |||||
ba->next = ab; | |||||
ba->symmetric = ab; | |||||
if (!a->edge) | |||||
{ | |||||
a->edge = ab; | |||||
} | |||||
else | |||||
{ | |||||
mesh::edge* a_in = find_free_incident(a); | |||||
mesh::edge* a_out = a_in->next; | |||||
a_in->next = ab; | |||||
ab->previous = a_in; | |||||
ba->next = a_out; | |||||
a_out->previous = ba; | |||||
} | |||||
if (!b->edge) | |||||
{ | |||||
b->edge = ba; | |||||
} | |||||
else | |||||
{ | |||||
mesh::edge* b_in = find_free_incident(b); | |||||
mesh::edge* b_out = b_in->next; | |||||
b_in->next = ba; | |||||
ba->previous = b_in; | |||||
ab->next = b_out; | |||||
b_out->previous = ab; | |||||
} | |||||
// Add edge | |||||
edges.push_back(ab); | |||||
return ab; | |||||
} | |||||
mesh::face* mesh::add_face(const loop& loop) | |||||
{ | |||||
if (loop.empty()) | |||||
{ | |||||
throw std::runtime_error("Empty edge loop"); | |||||
} | |||||
// Validate edge loop | |||||
for (std::size_t i = 0; i < loop.size(); ++i) | |||||
{ | |||||
mesh::edge* current = loop[i]; | |||||
mesh::edge* next = loop[(i + 1) % loop.size()]; | |||||
if (current->symmetric->vertex != next->vertex) | |||||
{ | |||||
// Disconnected edge loop | |||||
throw std::runtime_error("Disconnected edge loop"); | |||||
} | |||||
if (current->face) | |||||
{ | |||||
// This edge already has a face | |||||
throw std::runtime_error("Non-manifold mesh 1"); | |||||
} | |||||
} | |||||
// Make edges adjacent | |||||
for (std::size_t i = 0; i < loop.size(); ++i) | |||||
{ | |||||
if (!make_adjacent(loop[i], loop[(i + 1) % loop.size()])) | |||||
{ | |||||
throw std::runtime_error("Non-manifold mesh 2"); | |||||
} | |||||
} | |||||
// Create face | |||||
mesh::face* face = new mesh::face(); | |||||
face->edge = loop[0]; | |||||
face->index = faces.size(); | |||||
// Add face | |||||
faces.push_back(face); | |||||
// Connect edges to the face | |||||
for (mesh::edge* edge: loop) | |||||
{ | |||||
edge->face = face; | |||||
} | |||||
return face; | |||||
} | |||||
void mesh::remove_face(mesh::face* face) | |||||
{ | |||||
// Nullify pointers to this face | |||||
mesh::edge* edge = face->edge; | |||||
do | |||||
{ | |||||
edge->face = nullptr; | |||||
edge = edge->next; | |||||
} | |||||
while (edge != face->edge); | |||||
// Adjust indices of faces after this face | |||||
for (std::size_t i = face->index + 1; i < faces.size(); ++i) | |||||
{ | |||||
--faces[i]->index; | |||||
} | |||||
// Remove face from the faces vector | |||||
faces.erase(faces.begin() + face->index); | |||||
// Deallocate face | |||||
delete face; | |||||
} | |||||
void mesh::remove_edge(mesh::edge* edge) | |||||
{ | |||||
mesh::edge* ab = edge; | |||||
mesh::edge* ba = edge->symmetric; | |||||
mesh::vertex* a = ab->vertex; | |||||
mesh::edge* a_in = ab->previous; | |||||
mesh::edge* a_out = ba->next; | |||||
mesh::vertex* b = ba->vertex; | |||||
mesh::edge* b_in = ba->previous; | |||||
mesh::edge* b_out = ab->next; | |||||
// Remove dependent faces | |||||
if (ab->face) | |||||
remove_face(ab->face); | |||||
if (ba->face) | |||||
remove_face(ba->face); | |||||
// Re-link edges | |||||
if (a->edge == ab) | |||||
a->edge = (a_out == ab) ? nullptr : a_out; | |||||
if (b->edge == ba) | |||||
b->edge = (b_out == ba) ? nullptr : b_out; | |||||
a_in->next = a_out; | |||||
a_out->previous = a_in; | |||||
b_in->next = b_out; | |||||
b_out->previous = b_in; | |||||
// Adjust indices of edges after this edge | |||||
for (std::size_t i = edge->index + 1; i < edges.size(); ++i) | |||||
{ | |||||
--edges[i]->index; | |||||
--edges[i]->symmetric->index; | |||||
} | |||||
// Remove edge from the edges vector | |||||
edges.erase(edges.begin() + edge->index); | |||||
// Deallocate edge | |||||
delete edge->symmetric; | |||||
delete edge; | |||||
} | |||||
void mesh::remove_vertex(mesh::vertex* vertex) | |||||
{ | |||||
// Remove connected edges | |||||
if (vertex->edge) | |||||
{ | |||||
mesh::edge* current = nullptr; | |||||
mesh::edge* next = vertex->edge; | |||||
do | |||||
{ | |||||
current = next; | |||||
next = next->symmetric->next; | |||||
if (next == current) | |||||
{ | |||||
next = next->symmetric->next; | |||||
} | |||||
remove_edge(current); | |||||
} | |||||
while (current != next); | |||||
} | |||||
// Adjust indices of vertices after this vertex | |||||
for (std::size_t i = vertex->index + 1; i < vertices.size(); ++i) | |||||
{ | |||||
--vertices[i]->index; | |||||
} | |||||
// Remove vertex from the vertices vector | |||||
vertices.erase(vertices.begin() + vertex->index); | |||||
// Deallocate vertex | |||||
delete vertex; | |||||
} | |||||
mesh::edge* mesh::find_free_incident(mesh::vertex* vertex) const | |||||
{ | |||||
mesh::edge* begin = vertex->edge->symmetric; | |||||
mesh::edge* current = begin; | |||||
do | |||||
{ | |||||
if (!current->face) | |||||
{ | |||||
return current; | |||||
} | |||||
current = current->next->symmetric; | |||||
} | |||||
while (current != begin); | |||||
return nullptr; | |||||
} | |||||
mesh::edge* mesh::find_free_incident(mesh::edge* start_edge, mesh::edge* end_edge) const | |||||
{ | |||||
if (start_edge == end_edge) | |||||
{ | |||||
return nullptr; | |||||
} | |||||
mesh::edge* current = start_edge; | |||||
do | |||||
{ | |||||
if (!current->face) | |||||
{ | |||||
return current; | |||||
} | |||||
current = current->next->symmetric; | |||||
} | |||||
while (current != end_edge); | |||||
return nullptr; | |||||
} | |||||
bool mesh::make_adjacent(mesh::edge* in, mesh::edge* out) | |||||
{ | |||||
if (in->next == out) | |||||
{ | |||||
return true; | |||||
} | |||||
mesh::edge* b = in->next; | |||||
mesh::edge* d = out->previous; | |||||
mesh::edge* g = find_free_incident(out->symmetric, in); | |||||
if (!g) | |||||
{ | |||||
return false; | |||||
} | |||||
mesh::edge* h = g->next; | |||||
in->next = out; | |||||
out->previous = in; | |||||
g->next = b; | |||||
b->previous = g; | |||||
d->next = h; | |||||
h->previous = d; | |||||
return true; | |||||
} | |||||
} // namespace geom |
@ -1,216 +0,0 @@ | |||||
/* | |||||
* 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/>. | |||||
*/ | |||||
#ifndef ANTKEEPER_GEOM_MESH_HPP | |||||
#define ANTKEEPER_GEOM_MESH_HPP | |||||
#include <engine/utility/fundamental-types.hpp> | |||||
#include <vector> | |||||
namespace geom { | |||||
/** | |||||
* Half-edge mesh. | |||||
* | |||||
* @see http://kaba.hilvi.org/homepage/blog/halfedge/halfedge.htm | |||||
*/ | |||||
class mesh | |||||
{ | |||||
public: | |||||
struct vertex; | |||||
struct edge; | |||||
struct face; | |||||
/** | |||||
* Half-edge mesh vertex, containing its index, a pointer to its parent edge, and its position vector. | |||||
*/ | |||||
struct vertex | |||||
{ | |||||
/// Index of this vertex. | |||||
std::size_t index; | |||||
/// Pointer to the edge to which this vertex belongs. | |||||
mesh::edge* edge; | |||||
/// Vertex position. | |||||
float3 position; | |||||
}; | |||||
/** | |||||
* Half-edge mesh edge, containing its index and pointers to its starting vertex, parent face, and related edges. | |||||
*/ | |||||
struct edge | |||||
{ | |||||
/// Index of this edge. | |||||
std::size_t index; | |||||
/// Pointer to the vertex at which the edge starts. | |||||
mesh::vertex* vertex; | |||||
/// Pointer to the face on the left of this edge. | |||||
mesh::face* face; | |||||
/// Pointer to the previous edge in the parent face. | |||||
mesh::edge* previous; | |||||
/// Pointer to the next edge in the parent face. | |||||
mesh::edge* next; | |||||
/// Pointer to the symmetric edge. | |||||
mesh::edge* symmetric; | |||||
}; | |||||
/** | |||||
* Half-edge mesh face, containing its index and a pointer to its first edge. | |||||
*/ | |||||
struct face | |||||
{ | |||||
/// Index of this face. | |||||
std::size_t index; | |||||
/// Pointer to the first edge in this face. | |||||
mesh::edge* edge; | |||||
}; | |||||
/** | |||||
* List of edges which form a face. | |||||
*/ | |||||
typedef std::vector<edge*> loop; | |||||
/** | |||||
* Constructs a mesh. | |||||
*/ | |||||
constexpr mesh() noexcept = default; | |||||
/** | |||||
* Copy-constructs a mesh. | |||||
*/ | |||||
mesh(const mesh& other); | |||||
/** | |||||
* Destructs a mesh. | |||||
*/ | |||||
~mesh() noexcept; | |||||
/** | |||||
* Copies another mesh into this mesh. | |||||
* | |||||
* @param other Mesh to copy. | |||||
* | |||||
* @return Reference to this mesh. | |||||
*/ | |||||
mesh& operator=(const mesh& other); | |||||
/** | |||||
* Removes all vertices, edges, and faces from the mesh. | |||||
*/ | |||||
void clear() noexcept; | |||||
/** | |||||
* Adds a vertex to the mesh. | |||||
* | |||||
* @param position Position of the vertex. | |||||
* | |||||
* @return Pointer to the added vertex. | |||||
* | |||||
* @warning The added vertex will initially have a null edge. | |||||
*/ | |||||
mesh::vertex* add_vertex(const float3& position); | |||||
/** | |||||
* Adds two half edges to the mesh. | |||||
* | |||||
* @param a Vertex from which the edge originates. | |||||
* @param b Vertex at which the edge ends. | |||||
* | |||||
* @return Pointer to the half edge `ab`. The symmetric half edge `ba` can be accessed through `ab->symmetric`. | |||||
*/ | |||||
mesh::edge* add_edge(mesh::vertex* a, mesh::vertex* b); | |||||
/** | |||||
* Adds a face to the mesh. | |||||
* | |||||
* @param loop List of edges which form the face. | |||||
* @return Pointer to the added face. | |||||
* | |||||
* @exception std::runtime_error Empty edge loop. | |||||
* @exception std::runtime_error Disconnected edge loop. | |||||
* @exception std::runtime_error Non-manifold mesh 1. | |||||
* @exception std::runtime_error Non-manifold mesh 2. | |||||
*/ | |||||
mesh::face* add_face(const loop& loop); | |||||
/** | |||||
* Removes a face from the mesh. | |||||
* | |||||
* @param face Face to be removed. This face will be deallocated after removal. | |||||
*/ | |||||
void remove_face(mesh::face* face); | |||||
/** | |||||
* Removes an edge and all dependent faces from the mesh. | |||||
* | |||||
* @param edge Edge to be removed. This edge will be deallocated after removal. | |||||
*/ | |||||
void remove_edge(mesh::edge* edge); | |||||
/** | |||||
* Removes a vertex, all dependent edges, and all dependent faces from the mesh. | |||||
* | |||||
* @param vertex Vertex to be removed. This vertex will be deallocated after removal. | |||||
*/ | |||||
void remove_vertex(mesh::vertex* vertex); | |||||
/// Returns the mesh vertices. | |||||
const std::vector<mesh::vertex*>& get_vertices() const; | |||||
/// Returns the mesh edges. | |||||
const std::vector<mesh::edge*>& get_edges() const; | |||||
/// Returns the mesh faces. | |||||
const std::vector<mesh::face*>& get_faces() const; | |||||
private: | |||||
mesh::edge* find_free_incident(mesh::vertex* vertex) const; | |||||
mesh::edge* find_free_incident(mesh::edge* start_edge, mesh::edge* end_edge) const; | |||||
bool make_adjacent(mesh::edge* in_edge, mesh::edge* out_edge); | |||||
std::vector<mesh::vertex*> vertices; | |||||
std::vector<mesh::edge*> edges; | |||||
std::vector<mesh::face*> faces; | |||||
}; | |||||
inline const std::vector<mesh::vertex*>& mesh::get_vertices() const | |||||
{ | |||||
return vertices; | |||||
} | |||||
inline const std::vector<mesh::edge*>& mesh::get_edges() const | |||||
{ | |||||
return edges; | |||||
} | |||||
inline const std::vector<mesh::face*>& mesh::get_faces() const | |||||
{ | |||||
return faces; | |||||
} | |||||
} // namespace geom | |||||
#endif // ANTKEEPER_GEOM_MESH_HPP |
@ -1,102 +0,0 @@ | |||||
/* | |||||
* 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/meshes/grid.hpp> | |||||
#include <array> | |||||
#include <cmath> | |||||
#include <map> | |||||
namespace geom { | |||||
namespace meshes { | |||||
std::unique_ptr<geom::mesh> grid_xy(float length, std::size_t subdivisions_x, std::size_t subdivisions_y) | |||||
{ | |||||
// Allocate new mesh | |||||
std::unique_ptr<geom::mesh> mesh = std::make_unique<geom::mesh>(); | |||||
// Determine vertex count and placement | |||||
std::size_t columns = subdivisions_x + 1; | |||||
std::size_t rows = subdivisions_y + 1; | |||||
float column_length = length / static_cast<float>(columns); | |||||
float row_length = length / static_cast<float>(rows); | |||||
// Generate mesh vertices | |||||
float3 position; | |||||
position.z() = 0.0f; | |||||
position.y() = -length * 0.5f; | |||||
for (std::size_t i = 0; i <= rows; ++i) | |||||
{ | |||||
position.x() = -length * 0.5f; | |||||
for (std::size_t j = 0; j <= columns; ++j) | |||||
{ | |||||
mesh->add_vertex(position); | |||||
position.x() += column_length; | |||||
} | |||||
position.y() += row_length; | |||||
} | |||||
// Function to eliminate duplicate edges | |||||
std::map<std::array<std::size_t, 2>, geom::mesh::edge*> edge_map; | |||||
auto add_or_find_edge = [&](geom::mesh::vertex* start, geom::mesh::vertex* end) -> geom::mesh::edge* | |||||
{ | |||||
geom::mesh::edge* edge; | |||||
if (auto it = edge_map.find({start->index, end->index}); it != edge_map.end()) | |||||
{ | |||||
edge = it->second; | |||||
} | |||||
else | |||||
{ | |||||
edge = mesh->add_edge(start, end); | |||||
edge_map[{start->index, end->index}] = edge; | |||||
edge_map[{end->index, start->index}] = edge->symmetric; | |||||
} | |||||
return edge; | |||||
}; | |||||
// Connect vertices with edges and faces | |||||
const std::vector<geom::mesh::vertex*>& vertices = mesh->get_vertices(); | |||||
for (std::size_t i = 0; i < rows; ++i) | |||||
{ | |||||
for (std::size_t j = 0; j < columns; ++j) | |||||
{ | |||||
geom::mesh::vertex* a = vertices[i * (columns + 1) + j]; | |||||
geom::mesh::vertex* b = vertices[(i + 1) * (columns + 1) + j]; | |||||
geom::mesh::vertex* c = vertices[i * (columns + 1) + j + 1]; | |||||
geom::mesh::vertex* d = vertices[(i + 1) * (columns + 1) + j + 1]; | |||||
// a---c | |||||
// | | | |||||
// b---d | |||||
geom::mesh::edge* ab = add_or_find_edge(a, b); | |||||
geom::mesh::edge* bd = add_or_find_edge(b, d); | |||||
geom::mesh::edge* dc = add_or_find_edge(d, c); | |||||
geom::mesh::edge* ca = add_or_find_edge(c, a); | |||||
mesh->add_face({ab, bd, dc, ca}); | |||||
} | |||||
} | |||||
return mesh; | |||||
} | |||||
} // namespace meshes | |||||
} // namespace geom |
@ -1,217 +0,0 @@ | |||||
/* | |||||
* 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/>. | |||||
*/ | |||||
#ifndef ANTKEEPER_MATH_GLSL_HPP | |||||
#define ANTKEEPER_MATH_GLSL_HPP | |||||
#include <engine/math/vector.hpp> | |||||
#include <engine/math/matrix.hpp> | |||||
#include <engine/math/quaternion.hpp> | |||||
namespace math { | |||||
namespace glsl { | |||||
/** | |||||
* Linear algebra data types with GLSL naming conventions. | |||||
*/ | |||||
namespace types | |||||
{ | |||||
/// @name Vector types | |||||
/// @{ | |||||
/** | |||||
* *n*-dimensional vector of booleans. | |||||
* | |||||
* @tparam N Number of elements | |||||
*/ | |||||
/// @{ | |||||
template <std::size_t N> | |||||
using bvec = math::vector<bool, N>; | |||||
using bvec2 = bvec<2>; | |||||
using bvec3 = bvec<3>; | |||||
using bvec4 = bvec<4>; | |||||
/// @} | |||||
/** | |||||
* *n*-dimensional vector of signed integers. | |||||
* | |||||
* @tparam N Number of elements | |||||
*/ | |||||
/// @{ | |||||
template <std::size_t N> | |||||
using ivec = math::vector<int, N>; | |||||
using ivec2 = ivec<2>; | |||||
using ivec3 = ivec<3>; | |||||
using ivec4 = ivec<4>; | |||||
/// @} | |||||
/** | |||||
* *n*-dimensional vector of unsigned integers. | |||||
* | |||||
* @tparam N Number of elements | |||||
*/ | |||||
/// @{ | |||||
template <std::size_t N> | |||||
using uvec = math::vector<unsigned int, N>; | |||||
using uvec2 = uvec<2>; | |||||
using uvec3 = uvec<3>; | |||||
using uvec4 = uvec<4>; | |||||
/// @} | |||||
/** | |||||
* *n*-dimensional vector of floating-point numbers. | |||||
* | |||||
* @tparam N Number of elements | |||||
*/ | |||||
/// @{ | |||||
template <std::size_t N> | |||||
using fvec = math::vector<float, N>; | |||||
using fvec2 = fvec<2>; | |||||
using fvec3 = fvec<3>; | |||||
using fvec4 = fvec<4>; | |||||
using vec2 = fvec2; | |||||
using vec3 = fvec3; | |||||
using vec4 = fvec4; | |||||
/// @} | |||||
/** | |||||
* *n*-dimensional vector of double-precision floating-point numbers. | |||||
* | |||||
* @tparam N Number of elements | |||||
*/ | |||||
/// @{ | |||||
template <std::size_t N> | |||||
using dvec = math::vector<double, N>; | |||||
using dvec2 = dvec<2>; | |||||
using dvec3 = dvec<3>; | |||||
using dvec4 = dvec<4>; | |||||
/// @} | |||||
/// @} | |||||
/// @name Matrix types | |||||
/// @{ | |||||
/** | |||||
* *n* by *m* matrix of floating-point numbers. | |||||
* | |||||
* @tparam N Number of columns. | |||||
* @tparam M Number of rows. | |||||
*/ | |||||
/// @{ | |||||
template <std::size_t N, std::size_t M> | |||||
using fmat = math::matrix<float, N, M>; | |||||
using fmat2x2 = fmat<2, 2>; | |||||
using fmat2x3 = fmat<2, 3>; | |||||
using fmat2x4 = fmat<2, 4>; | |||||
using fmat3x2 = fmat<3, 2>; | |||||
using fmat3x3 = fmat<3, 3>; | |||||
using fmat3x4 = fmat<3, 4>; | |||||
using fmat4x2 = fmat<4, 2>; | |||||
using fmat4x3 = fmat<4, 3>; | |||||
using fmat4x4 = fmat<4, 4>; | |||||
using mat2x2 = fmat2x2; | |||||
using mat2x3 = fmat2x3; | |||||
using mat2x4 = fmat2x4; | |||||
using mat3x2 = fmat3x2; | |||||
using mat3x3 = fmat3x3; | |||||
using mat3x4 = fmat3x4; | |||||
using mat4x2 = fmat4x2; | |||||
using mat4x3 = fmat4x3; | |||||
using mat4x4 = fmat4x4; | |||||
/// @} | |||||
/** | |||||
* *n* by *n* square matrix of floating-point numbers. | |||||
* | |||||
* @tparam N Number of columns and rows. | |||||
*/ | |||||
/// @{ | |||||
using fmat2 = fmat2x2; | |||||
using fmat3 = fmat3x3; | |||||
using fmat4 = fmat4x4; | |||||
using mat2 = fmat2; | |||||
using mat3 = fmat3; | |||||
using mat4 = fmat4; | |||||
/// @} | |||||
/** | |||||
* *n* by *m* matrix of double-precision floating-point numbers. | |||||
* | |||||
* @tparam N Number of columns. | |||||
* @tparam M Number of rows. | |||||
*/ | |||||
/// @{ | |||||
template <std::size_t N, std::size_t M> | |||||
using dmat = math::matrix<double, N, M>; | |||||
using dmat2x2 = dmat<2, 2>; | |||||
using dmat2x3 = dmat<2, 3>; | |||||
using dmat2x4 = dmat<2, 4>; | |||||
using dmat3x2 = dmat<3, 2>; | |||||
using dmat3x3 = dmat<3, 3>; | |||||
using dmat3x4 = dmat<3, 4>; | |||||
using dmat4x2 = dmat<4, 2>; | |||||
using dmat4x3 = dmat<4, 3>; | |||||
using dmat4x4 = dmat<4, 4>; | |||||
/// @} | |||||
/** | |||||
* *n* by *n* square matrix of double-precision floating-point numbers. | |||||
* | |||||
* @tparam N Number of columns and rows. | |||||
*/ | |||||
/// @{ | |||||
using dmat2 = dmat2x2; | |||||
using dmat3 = dmat3x3; | |||||
using dmat4 = dmat4x4; | |||||
/// @} | |||||
/// @} | |||||
/// @name Quaternion types | |||||
/// @{ | |||||
/** | |||||
* Quaternion with floating-point scalars. | |||||
* | |||||
* @tparam T Scalar type. | |||||
*/ | |||||
/// @{ | |||||
using fquat = math::quaternion<float>; | |||||
using quat = fquat; | |||||
/// @} | |||||
/** | |||||
* Quaternion with double-precision floating-point scalars. | |||||
* | |||||
* @tparam T Scalar type. | |||||
*/ | |||||
using dquat = math::quaternion<double>; | |||||
/// @} | |||||
} // namespace types | |||||
// Bring GLSL types into glsl namespace | |||||
using namespace types; | |||||
} // namespace glsl | |||||
} // namespace math | |||||
#endif // ANTKEEPER_MATH_GLSL_HPP |
@ -0,0 +1,36 @@ | |||||
/* | |||||
* 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/>. | |||||
*/ | |||||
#ifndef ANTKEEPER_MATH_TYPES_HPP | |||||
#define ANTKEEPER_MATH_TYPES_HPP | |||||
namespace math { | |||||
/** | |||||
* Linear algebra data types. | |||||
*/ | |||||
namespace types {} | |||||
} // namespace math | |||||
#include <engine/math/matrix.hpp> | |||||
#include <engine/math/quaternion.hpp> | |||||
#include <engine/math/vector.hpp> | |||||
#endif // ANTKEEPER_MATH_TYPES_HPP |