1273 lines
34 KiB
C++
1273 lines
34 KiB
C++
/* common/polylib.h */
|
|
|
|
#pragma once
|
|
|
|
#include <common/mathlib.hh>
|
|
|
|
#include <common/log.hh>
|
|
#include <common/bspfile.hh>
|
|
#include <common/cmdlib.hh>
|
|
#include <common/aabb.hh>
|
|
#include <variant>
|
|
#include <array>
|
|
#include <vector>
|
|
|
|
#include <type_traits>
|
|
#include <stdexcept>
|
|
#include <optional>
|
|
|
|
#include <tbb/scalable_allocator.h>
|
|
|
|
namespace polylib
|
|
{
|
|
|
|
constexpr size_t MAX_POINTS_ON_WINDING = 96;
|
|
|
|
constexpr vec_t DEFAULT_BOGUS_RANGE = 65536.0;
|
|
|
|
using winding_edges_t = std::vector<qplane3d>;
|
|
|
|
inline bool PointInWindingEdges(const winding_edges_t &wi, const qvec3d &point)
|
|
{
|
|
/* edgeplane faces toward the center of the face */
|
|
for (auto &edgeplane : wi) {
|
|
if (edgeplane.distance_to(point) < 0) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Stack storage; uses stack allocation. Throws if it can't insert
|
|
// a new member.
|
|
template<size_t N>
|
|
struct winding_storage_stack_t
|
|
{
|
|
protected:
|
|
using array_type = std::array<qvec3d, N>;
|
|
array_type array;
|
|
|
|
public:
|
|
size_t count = 0;
|
|
|
|
// default constructor does nothing
|
|
inline winding_storage_stack_t() { }
|
|
|
|
// construct winding with initial size; may allocate
|
|
// memory, and sets size, but does not initialize any
|
|
// of them.
|
|
inline winding_storage_stack_t(const size_t &initial_size)
|
|
: count(initial_size)
|
|
{
|
|
if (initial_size > N) {
|
|
throw std::bad_alloc();
|
|
}
|
|
}
|
|
|
|
// construct winding from range.
|
|
// iterators must have operator+ and operator-.
|
|
template<typename Iter, std::enable_if_t<is_iterator_v<Iter>, int> = 0>
|
|
inline winding_storage_stack_t(Iter begin, Iter end)
|
|
: winding_storage_stack_t(end - begin)
|
|
{
|
|
std::copy_n(begin, count, array.begin());
|
|
}
|
|
|
|
// copy constructor; uses optimized method of copying
|
|
// data over.
|
|
inline winding_storage_stack_t(const winding_storage_stack_t ©)
|
|
: winding_storage_stack_t(copy.size())
|
|
{
|
|
std::copy_n(copy.array.begin(), copy.count, array.begin());
|
|
}
|
|
|
|
// move constructor
|
|
inline winding_storage_stack_t(winding_storage_stack_t &&move) noexcept
|
|
: count(move.count)
|
|
{
|
|
count = move.count;
|
|
|
|
std::copy_n(move.array.begin(), move.count, array.begin());
|
|
|
|
move.count = 0;
|
|
}
|
|
|
|
// assignment copy
|
|
inline winding_storage_stack_t &operator=(const winding_storage_stack_t ©)
|
|
{
|
|
count = copy.count;
|
|
|
|
// copy array range
|
|
std::copy_n(copy.array.begin(), copy.count, array.begin());
|
|
|
|
return *this;
|
|
}
|
|
|
|
// assignment move
|
|
inline winding_storage_stack_t &operator=(winding_storage_stack_t &&move) noexcept
|
|
{
|
|
count = move.count;
|
|
|
|
// blit over array data
|
|
std::copy_n(move.array.begin(), move.count, array.begin());
|
|
|
|
move.count = 0;
|
|
|
|
return *this;
|
|
}
|
|
|
|
inline size_t size() const { return count; }
|
|
|
|
inline qvec3d &at(const size_t &index)
|
|
{
|
|
#ifdef _DEBUG
|
|
if (index >= count)
|
|
throw std::invalid_argument("index");
|
|
#endif
|
|
|
|
return array[index];
|
|
}
|
|
|
|
inline const qvec3d &at(const size_t &index) const
|
|
{
|
|
#ifdef _DEBUG
|
|
if (index >= count)
|
|
throw std::invalid_argument("index");
|
|
#endif
|
|
|
|
return array[index];
|
|
}
|
|
|
|
// un-bounds-checked
|
|
inline qvec3d &operator[](const size_t &index) { return array[index]; }
|
|
|
|
// un-bounds-checked
|
|
inline const qvec3d &operator[](const size_t &index) const { return array[index]; }
|
|
|
|
using const_iterator = typename array_type::const_iterator;
|
|
|
|
inline const const_iterator begin() const { return array.begin(); }
|
|
|
|
inline const const_iterator end() const { return array.begin() + count; }
|
|
|
|
using iterator = typename array_type::iterator;
|
|
|
|
inline iterator begin() { return array.begin(); }
|
|
|
|
inline iterator end() { return array.begin() + count; }
|
|
|
|
inline qvec3d &emplace_back(const qvec3d &vec)
|
|
{
|
|
count++;
|
|
|
|
if (count > N) {
|
|
throw std::bad_alloc();
|
|
}
|
|
|
|
return (array[count - 1] = vec);
|
|
}
|
|
|
|
inline void resize(const size_t &new_size)
|
|
{
|
|
if (new_size > N) {
|
|
throw std::bad_alloc();
|
|
}
|
|
|
|
count = new_size;
|
|
}
|
|
|
|
inline void reserve(size_t size) { }
|
|
|
|
inline void clear() { count = 0; }
|
|
};
|
|
|
|
// Heap storage; uses a vector.
|
|
struct winding_storage_heap_t
|
|
{
|
|
protected:
|
|
std::vector<qvec3d, tbb::scalable_allocator<qvec3d>> values{};
|
|
|
|
public:
|
|
// default constructor does nothing
|
|
inline winding_storage_heap_t() { }
|
|
|
|
// construct winding with initial size; may allocate
|
|
// memory, and sets size, but does not initialize any
|
|
// of them.
|
|
inline winding_storage_heap_t(const size_t &initial_size)
|
|
: values(initial_size)
|
|
{
|
|
}
|
|
|
|
// construct winding from range.
|
|
// iterators must have operator+ and operator-.
|
|
template<typename Iter, std::enable_if_t<is_iterator_v<Iter>, int> = 0>
|
|
inline winding_storage_heap_t(Iter begin, Iter end)
|
|
: values(begin, end)
|
|
{
|
|
}
|
|
|
|
// copy constructor; uses optimized method of copying
|
|
// data over.
|
|
inline winding_storage_heap_t(const winding_storage_heap_t ©)
|
|
: values(copy.values)
|
|
{
|
|
}
|
|
|
|
// move constructor
|
|
inline winding_storage_heap_t(winding_storage_heap_t &&move) noexcept
|
|
: values(std::move(move.values))
|
|
{
|
|
}
|
|
|
|
// assignment copy
|
|
inline winding_storage_heap_t &operator=(const winding_storage_heap_t ©)
|
|
{
|
|
resize(copy.size());
|
|
|
|
// copy array range
|
|
std::copy_n(copy.values.data(), copy.size(), values.data());
|
|
|
|
return *this;
|
|
}
|
|
|
|
// assignment move
|
|
inline winding_storage_heap_t &operator=(winding_storage_heap_t &&move) noexcept
|
|
{
|
|
// take ownership of heap pointer
|
|
values = std::move(move.values);
|
|
|
|
return *this;
|
|
}
|
|
|
|
inline size_t size() const { return values.size(); }
|
|
|
|
inline qvec3d &at(const size_t &index) { return values[index]; }
|
|
|
|
inline const qvec3d &at(const size_t &index) const { return values[index]; }
|
|
|
|
// un-bounds-checked
|
|
inline qvec3d &operator[](const size_t &index) { return values[index]; }
|
|
|
|
// un-bounds-checked
|
|
inline const qvec3d &operator[](const size_t &index) const { return values[index]; }
|
|
|
|
inline const auto begin() const { return values.begin(); }
|
|
|
|
inline const auto end() const { return values.end(); }
|
|
|
|
inline auto begin() { return values.begin(); }
|
|
|
|
inline auto end() { return values.end(); }
|
|
|
|
inline qvec3d &emplace_back(const qvec3d &vec) { return values.emplace_back(vec); }
|
|
|
|
inline void resize(const size_t &new_size) { values.resize(new_size); }
|
|
|
|
inline void reserve(size_t size) { values.reserve(size); }
|
|
|
|
inline void clear() { values.clear(); }
|
|
};
|
|
|
|
// Hybrid storage; uses stack allocation for the first N
|
|
// points, and uses a dynamic vector for storage after that.
|
|
template<size_t N>
|
|
struct winding_storage_hybrid_t
|
|
{
|
|
protected:
|
|
using array_type = std::array<qvec3d, N>;
|
|
using vector_type = std::vector<qvec3d>;
|
|
using variant_type = std::variant<array_type, vector_type>;
|
|
array_type array;
|
|
vector_type vector;
|
|
|
|
public:
|
|
size_t count = 0;
|
|
|
|
template<bool is_const>
|
|
class iterator_base
|
|
{
|
|
friend struct winding_storage_hybrid_t;
|
|
|
|
using container_type =
|
|
typename std::conditional_t<is_const, const winding_storage_hybrid_t *, winding_storage_hybrid_t *>;
|
|
size_t index;
|
|
container_type w;
|
|
|
|
iterator_base(size_t index_in, container_type w_in)
|
|
: index(index_in),
|
|
w(w_in)
|
|
{
|
|
}
|
|
|
|
public:
|
|
using iterator_category = std::random_access_iterator_tag;
|
|
using value_type = typename std::conditional_t<is_const, const qvec3d, qvec3d>;
|
|
using difference_type = ptrdiff_t;
|
|
using pointer = value_type *;
|
|
using reference = value_type &;
|
|
|
|
iterator_base(const iterator_base &) = default;
|
|
iterator_base &operator=(const iterator_base &) noexcept = default;
|
|
|
|
[[nodiscard]] inline reference operator*() const noexcept { return (*w)[index]; }
|
|
|
|
constexpr iterator_base &operator++() noexcept
|
|
{
|
|
index++;
|
|
return *this;
|
|
}
|
|
|
|
constexpr iterator_base &operator++(int) noexcept
|
|
{
|
|
index++;
|
|
return *this;
|
|
}
|
|
|
|
constexpr iterator_base &operator--() noexcept
|
|
{
|
|
index--;
|
|
return *this;
|
|
}
|
|
|
|
constexpr iterator_base operator--(int) noexcept
|
|
{
|
|
index--;
|
|
return *this;
|
|
}
|
|
|
|
constexpr iterator_base &operator+=(const difference_type _Off) noexcept
|
|
{
|
|
index += _Off;
|
|
return *this;
|
|
}
|
|
|
|
[[nodiscard]] constexpr iterator_base operator+(const difference_type _Off) const noexcept
|
|
{
|
|
iterator_base _Tmp = *this;
|
|
_Tmp += _Off; // TRANSITION, LLVM-49342
|
|
return _Tmp;
|
|
}
|
|
|
|
constexpr iterator_base &operator-=(const difference_type _Off) noexcept
|
|
{
|
|
index -= _Off;
|
|
return *this;
|
|
}
|
|
|
|
[[nodiscard]] constexpr bool operator==(const iterator_base &_Off) const noexcept
|
|
{
|
|
return w == _Off.w && index == _Off.index;
|
|
}
|
|
|
|
[[nodiscard]] constexpr bool operator!=(const iterator_base &_Off) const noexcept { return !(*this == _Off); }
|
|
|
|
[[nodiscard]] constexpr difference_type operator-(const iterator_base &_Off) const noexcept
|
|
{
|
|
return index - _Off.index;
|
|
}
|
|
|
|
[[nodiscard]] constexpr iterator_base operator-(const difference_type _Off) const noexcept
|
|
{
|
|
iterator_base _Tmp = *this;
|
|
_Tmp -= _Off; // TRANSITION, LLVM-49342
|
|
return _Tmp;
|
|
}
|
|
|
|
[[nodiscard]] inline reference operator[](const difference_type _Off) const noexcept { return *(*this + _Off); }
|
|
};
|
|
|
|
// default constructor does nothing
|
|
inline winding_storage_hybrid_t() { }
|
|
|
|
// construct winding with initial size; may allocate
|
|
// memory, and sets size, but does not initialize any
|
|
// of them.
|
|
inline winding_storage_hybrid_t(const size_t &initial_size)
|
|
: count(initial_size)
|
|
{
|
|
if (count > N) {
|
|
vector.reserve(count);
|
|
vector.resize(count - N);
|
|
}
|
|
}
|
|
|
|
// construct winding from range.
|
|
// iterators must have operator+ and operator-.
|
|
template<typename Iter, std::enable_if_t<is_iterator_v<Iter>, int> = 0>
|
|
inline winding_storage_hybrid_t(Iter begin, Iter end)
|
|
: winding_storage_hybrid_t(end - begin)
|
|
{
|
|
// copy the array range
|
|
std::copy_n(begin, min(count, N), array.begin());
|
|
|
|
// copy the vector range, if required
|
|
if (count > N) {
|
|
std::copy(begin + N, end, vector.begin());
|
|
}
|
|
}
|
|
|
|
// copy constructor; uses optimized method of copying
|
|
// data over.
|
|
inline winding_storage_hybrid_t(const winding_storage_hybrid_t ©)
|
|
: winding_storage_hybrid_t(copy.size())
|
|
{
|
|
// copy array range
|
|
memcpy(&array.front(), ©.array.front(), min(count, N) * sizeof(qvec3d));
|
|
|
|
// copy vector range, if required
|
|
if (count > N) {
|
|
memcpy(&vector.front(), ©.vector.front(), (count - N) * sizeof(qvec3d));
|
|
}
|
|
}
|
|
|
|
// move constructor
|
|
inline winding_storage_hybrid_t(winding_storage_hybrid_t &&move) noexcept
|
|
: count(move.count)
|
|
{
|
|
count = move.count;
|
|
|
|
// blit over array data
|
|
memcpy(&array.front(), &move.array.front(), min(count, N) * sizeof(qvec3d));
|
|
|
|
// move vector data, if available
|
|
if (count > N) {
|
|
vector = std::move(move.vector);
|
|
}
|
|
|
|
move.count = 0;
|
|
}
|
|
|
|
// assignment copy
|
|
inline winding_storage_hybrid_t &operator=(const winding_storage_hybrid_t ©)
|
|
{
|
|
count = copy.count;
|
|
|
|
// copy array range
|
|
memcpy(&array.front(), ©.array.front(), min(count, N) * sizeof(qvec3d));
|
|
|
|
// copy vector range, if required
|
|
if (count > N) {
|
|
vector.reserve(count);
|
|
vector.resize(count - N);
|
|
memcpy(&vector.front(), ©.vector.front(), (count - N) * sizeof(qvec3d));
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
// assignment move
|
|
inline winding_storage_hybrid_t &operator=(winding_storage_hybrid_t &&move) noexcept
|
|
{
|
|
count = move.count;
|
|
|
|
// blit over array data
|
|
memcpy(&array.front(), &move.array.front(), min(count, N) * sizeof(qvec3d));
|
|
|
|
// move vector data, if available
|
|
if (count > N) {
|
|
vector = std::move(move.vector);
|
|
}
|
|
|
|
move.count = 0;
|
|
|
|
return *this;
|
|
}
|
|
|
|
inline size_t size() const { return count; }
|
|
|
|
inline size_t vector_size() const { return vector.size(); }
|
|
|
|
inline qvec3d &at(const size_t &index)
|
|
{
|
|
#ifdef _DEBUG
|
|
if (index >= count)
|
|
throw std::invalid_argument("index");
|
|
#endif
|
|
|
|
if (index >= N) {
|
|
return vector[index - N];
|
|
}
|
|
|
|
return array[index];
|
|
}
|
|
|
|
inline const qvec3d &at(const size_t &index) const
|
|
{
|
|
#ifdef _DEBUG
|
|
if (index >= count)
|
|
throw std::invalid_argument("index");
|
|
#endif
|
|
|
|
if (index >= N) {
|
|
return vector[index - N];
|
|
}
|
|
|
|
return array[index];
|
|
}
|
|
|
|
// un-bounds-checked
|
|
inline qvec3d &operator[](const size_t &index)
|
|
{
|
|
if (index >= N) {
|
|
return vector[index - N];
|
|
}
|
|
|
|
return array[index];
|
|
}
|
|
|
|
// un-bounds-checked
|
|
inline const qvec3d &operator[](const size_t &index) const
|
|
{
|
|
if (index >= N) {
|
|
return vector[index - N];
|
|
}
|
|
|
|
return array[index];
|
|
}
|
|
|
|
using const_iterator = iterator_base<true>;
|
|
|
|
inline const const_iterator begin() const { return const_iterator(0, this); }
|
|
|
|
inline const const_iterator end() const { return const_iterator(count, this); }
|
|
|
|
using iterator = iterator_base<false>;
|
|
|
|
inline iterator begin() { return iterator(0, this); }
|
|
|
|
inline iterator end() { return iterator(count, this); }
|
|
|
|
inline qvec3d &emplace_back(const qvec3d &vec)
|
|
{
|
|
count++;
|
|
|
|
if (count > N) {
|
|
return vector.emplace_back(vec);
|
|
}
|
|
|
|
return (array[count - 1] = vec);
|
|
}
|
|
|
|
inline void resize(const size_t &new_size)
|
|
{
|
|
// resize vector if necessary
|
|
if (new_size > N) {
|
|
vector.resize(new_size - N);
|
|
}
|
|
|
|
count = new_size;
|
|
}
|
|
|
|
inline void reserve(size_t size) { }
|
|
|
|
inline void clear() { count = 0; }
|
|
};
|
|
|
|
// Winding type, with storage template. Doesn't inherit the storage,
|
|
// since that might slow things down with virtual destructor.
|
|
template<typename TStorage>
|
|
struct winding_base_t
|
|
{
|
|
protected:
|
|
TStorage storage;
|
|
|
|
public:
|
|
// default constructor does nothing
|
|
inline winding_base_t() { }
|
|
|
|
// construct winding with initial size; may allocate
|
|
// memory, and sets size, but does not initialize any
|
|
// of them.
|
|
inline winding_base_t(const size_t &initial_size)
|
|
: storage(initial_size)
|
|
{
|
|
}
|
|
|
|
// construct winding from range.
|
|
// iterators must have operator+ and operator-.
|
|
template<typename Iter, std::enable_if_t<is_iterator_v<Iter>, int> = 0>
|
|
inline winding_base_t(Iter begin, Iter end)
|
|
: storage(begin, end)
|
|
{
|
|
}
|
|
|
|
// initializer list constructor
|
|
inline winding_base_t(std::initializer_list<qvec3d> l)
|
|
: storage(l.begin(), l.end())
|
|
{
|
|
}
|
|
|
|
// copy constructor; we require copying to be done with clone() to avoid performance bugs
|
|
inline winding_base_t(const winding_base_t ©) = delete;
|
|
|
|
// move constructor
|
|
inline winding_base_t(winding_base_t &&move) noexcept
|
|
: storage(std::move(move.storage))
|
|
{
|
|
}
|
|
|
|
// assignment copy
|
|
inline winding_base_t &operator=(const winding_base_t ©) = delete;
|
|
|
|
// assignment move
|
|
inline winding_base_t &operator=(winding_base_t &&move) noexcept
|
|
{
|
|
storage = std::move(move.storage);
|
|
return *this;
|
|
}
|
|
|
|
inline bool empty() const { return size() == 0; }
|
|
|
|
inline explicit operator bool() const { return !empty(); }
|
|
|
|
inline size_t size() const { return storage.size(); }
|
|
|
|
inline qvec3d &at(const size_t &index) { return storage.at(index); }
|
|
|
|
inline const qvec3d &at(const size_t &index) const { return storage.at(index); }
|
|
|
|
// un-bounds-checked
|
|
inline qvec3d &operator[](const size_t &index) { return storage[index]; }
|
|
|
|
// un-bounds-checked
|
|
inline const qvec3d &operator[](const size_t &index) const { return storage[index]; }
|
|
|
|
inline const auto begin() const { return storage.begin(); }
|
|
|
|
inline const auto end() const { return storage.end(); }
|
|
|
|
inline auto begin() { return storage.begin(); }
|
|
|
|
inline auto end() { return storage.end(); }
|
|
|
|
template<typename... Args>
|
|
inline qvec3d &emplace_back(Args &&...vec)
|
|
{
|
|
return storage.emplace_back(qvec3d(std::forward<Args>(vec)...));
|
|
}
|
|
|
|
inline void push_back(const qvec3d &vec) { storage.emplace_back(vec); }
|
|
|
|
inline void resize(const size_t &new_size) { storage.resize(new_size); }
|
|
|
|
inline void reserve(size_t size) { storage.reserve(size); }
|
|
|
|
inline void clear() { storage.clear(); }
|
|
|
|
template<typename TStor>
|
|
friend struct winding_base_t;
|
|
|
|
// explicit copying function
|
|
template<typename TStor = TStorage>
|
|
winding_base_t<TStor> clone() const
|
|
{
|
|
winding_base_t<TStor> result;
|
|
if constexpr (std::is_same_v<TStor, TStorage>) {
|
|
result.storage = storage;
|
|
} else {
|
|
result.storage = TStor{begin(), end()};
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// non-storage functions
|
|
|
|
vec_t area() const
|
|
{
|
|
vec_t total = 0;
|
|
|
|
for (size_t i = 2; i < size(); i++) {
|
|
qvec3d d1 = at(i - 1) - at(0);
|
|
qvec3d d2 = at(i) - at(0);
|
|
total += 0.5 * qv::length(qv::cross(d1, d2));
|
|
}
|
|
|
|
return total;
|
|
}
|
|
|
|
qvec3d center() const
|
|
{
|
|
qvec3d center{};
|
|
|
|
for (auto &point : *this)
|
|
center += point;
|
|
|
|
return center * (1.0 / size());
|
|
}
|
|
|
|
aabb3d bounds() const
|
|
{
|
|
aabb3d b;
|
|
|
|
for (auto &point : *this)
|
|
b += point;
|
|
|
|
return b;
|
|
}
|
|
|
|
/*
|
|
* ============
|
|
* RemoveColinearPoints
|
|
* ============
|
|
*/
|
|
void remove_colinear()
|
|
{
|
|
winding_base_t temp;
|
|
|
|
// CHECK: would this be more efficient to instead store a running
|
|
// list of indices that *are* collinear, so we can return sooner
|
|
// before copying points over?
|
|
for (size_t i = 0; i < size(); i++) {
|
|
size_t j = (i + 1) % size();
|
|
size_t k = (i + size() - 1) % size();
|
|
qvec3d v1 = qv::normalize(at(j) - at(i));
|
|
qvec3d v2 = qv::normalize(at(i) - at(k));
|
|
|
|
if (qv::dot(v1, v2) < 1.0 - DIST_EPSILON)
|
|
temp.push_back(at(i));
|
|
}
|
|
|
|
if (size() != temp.size()) {
|
|
*this = std::move(temp);
|
|
}
|
|
}
|
|
|
|
qplane3d plane() const
|
|
{
|
|
qvec3d v1 = at(0) - at(1);
|
|
qvec3d v2 = at(2) - at(1);
|
|
qvec3d normal = qv::normalize(qv::cross(v1, v2));
|
|
|
|
return {normal, qv::dot(at(0), normal)};
|
|
}
|
|
|
|
static winding_base_t from_plane(const qplane3d &plane, const vec_t &worldextent)
|
|
{
|
|
/* find the major axis */
|
|
vec_t max = -VECT_MAX;
|
|
int32_t x = -1;
|
|
for (size_t i = 0; i < 3; i++) {
|
|
vec_t v = fabs(plane.normal[i]);
|
|
|
|
if (v > max) {
|
|
x = i;
|
|
max = v;
|
|
}
|
|
}
|
|
|
|
if (x == -1 || max == -VECT_MAX) {
|
|
FError("no axis found");
|
|
}
|
|
|
|
qvec3d vup{};
|
|
|
|
switch (x) {
|
|
case 0:
|
|
case 1: vup[2] = 1; break;
|
|
case 2: vup[0] = 1; break;
|
|
}
|
|
|
|
vec_t v = qv::dot(vup, plane.normal);
|
|
vup += plane.normal * -v;
|
|
vup = qv::normalize(vup);
|
|
|
|
qvec3d org = plane.normal * plane.dist;
|
|
qvec3d vright = qv::cross(vup, plane.normal);
|
|
|
|
vup *= worldextent;
|
|
vright *= worldextent;
|
|
|
|
/* project a really big axis aligned box onto the plane */
|
|
winding_base_t w(4);
|
|
|
|
w[0] = org - vright + vup;
|
|
w[1] = org + vright + vup;
|
|
w[2] = org + vright - vup;
|
|
w[3] = org - vright - vup;
|
|
|
|
return w;
|
|
}
|
|
|
|
void check(const vec_t &bogus_range = DEFAULT_BOGUS_RANGE, const vec_t &on_epsilon = DEFAULT_ON_EPSILON) const
|
|
{
|
|
if (size() < 3)
|
|
FError("{} points", size());
|
|
|
|
vec_t a = area();
|
|
if (a < 1)
|
|
FError("{} area", a);
|
|
|
|
qplane3d face = plane();
|
|
|
|
for (size_t i = 0; i < size(); i++) {
|
|
const qvec3d &p1 = at(i);
|
|
size_t j = 0;
|
|
|
|
for (; j < 3; j++)
|
|
if (p1[j] > bogus_range || p1[j] < -bogus_range)
|
|
FError("BOGUS_RANGE: {}", p1[j]);
|
|
|
|
/* check the point is on the face plane */
|
|
vec_t d = face.distance_to(p1);
|
|
if (d < -on_epsilon || d > on_epsilon)
|
|
FError("point off plane");
|
|
|
|
/* check the edge isn't degenerate */
|
|
const qvec3d &p2 = at((i + 1) % size());
|
|
qvec3d dir = p2 - p1;
|
|
|
|
if (qv::length(dir) < on_epsilon)
|
|
FError("degenerate edge");
|
|
|
|
qvec3d edgenormal = qv::normalize(qv::cross(face.normal, dir));
|
|
vec_t edgedist = qv::dot(p1, edgenormal) + on_epsilon;
|
|
|
|
/* all other points must be on front side */
|
|
for (size_t j = 0; j < size(); j++) {
|
|
if (j == i)
|
|
continue;
|
|
d = qv::dot(at(j), edgenormal);
|
|
if (d > edgedist)
|
|
FError("non-convex");
|
|
}
|
|
}
|
|
}
|
|
|
|
std::vector<qvec3f> glm_winding_points() const { return {begin(), end()}; }
|
|
|
|
static inline winding_base_t from_winding_points(const std::vector<qvec3f> &points)
|
|
{
|
|
return {points.begin(), points.end()};
|
|
}
|
|
|
|
winding_edges_t winding_edges() const
|
|
{
|
|
qplane3d p = plane();
|
|
|
|
winding_edges_t result;
|
|
result.reserve(size());
|
|
|
|
for (size_t i = 0; i < size(); i++) {
|
|
const qvec3d &v0 = at(i);
|
|
const qvec3d &v1 = at((i + 1) % size());
|
|
|
|
qvec3d edgevec = qv::normalize(v1 - v0);
|
|
qvec3d normal = qv::cross(edgevec, p.normal);
|
|
|
|
result.emplace_back(normal, qv::dot(normal, v0));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// dists/sides can be null, or must have (size() + 1) reserved
|
|
inline std::array<size_t, SIDE_TOTAL> calc_sides(
|
|
const qplane3d &plane, vec_t *dists, planeside_t *sides, const vec_t &on_epsilon = DEFAULT_ON_EPSILON) const
|
|
{
|
|
std::array<size_t, SIDE_TOTAL> counts{};
|
|
|
|
/* determine sides for each point */
|
|
size_t i;
|
|
|
|
for (i = 0; i < size(); i++) {
|
|
vec_t dot = plane.distance_to(at(i));
|
|
|
|
if (dists) {
|
|
dists[i] = dot;
|
|
}
|
|
|
|
planeside_t side;
|
|
|
|
if (dot > on_epsilon)
|
|
side = SIDE_FRONT;
|
|
else if (dot < -on_epsilon)
|
|
side = SIDE_BACK;
|
|
else
|
|
side = SIDE_ON;
|
|
|
|
counts[side]++;
|
|
|
|
if (sides) {
|
|
sides[i] = side;
|
|
}
|
|
}
|
|
|
|
if (sides) {
|
|
sides[i] = sides[SIDE_FRONT];
|
|
}
|
|
if (dists) {
|
|
dists[i] = dists[SIDE_FRONT];
|
|
}
|
|
|
|
return counts;
|
|
}
|
|
|
|
vec_t max_dist_off_plane(const qplane3d &plane)
|
|
{
|
|
vec_t max_dist = 0.0;
|
|
for (size_t i = 0; i < size(); i++) {
|
|
vec_t dist = abs(plane.distance_to(at(i)));
|
|
if (dist > max_dist) {
|
|
max_dist = dist;
|
|
}
|
|
}
|
|
return max_dist;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
ClipWinding
|
|
|
|
Clips the winding to the plane, returning the new windings.
|
|
If keepon is true, an exactly on-plane winding will be saved, otherwise
|
|
it will be clipped away.
|
|
==================
|
|
*/
|
|
template<typename TStor = TStorage>
|
|
twosided<std::optional<winding_base_t<TStor>>> clip(
|
|
const qplane3d &plane, const vec_t &on_epsilon = DEFAULT_ON_EPSILON, const bool &keepon = false) const
|
|
{
|
|
vec_t *dists = (vec_t *)alloca(sizeof(vec_t) * (size() + 1));
|
|
planeside_t *sides = (planeside_t *)alloca(sizeof(planeside_t) * (size() + 1));
|
|
|
|
std::array<size_t, SIDE_TOTAL> counts = calc_sides(plane, dists, sides, on_epsilon);
|
|
|
|
if (keepon && !counts[SIDE_FRONT] && !counts[SIDE_BACK])
|
|
return {this->clone<TStor>(), std::nullopt};
|
|
|
|
if (!counts[SIDE_FRONT])
|
|
return {std::nullopt, this->clone<TStor>()};
|
|
else if (!counts[SIDE_BACK])
|
|
return {this->clone<TStor>(), std::nullopt};
|
|
|
|
twosided<winding_base_t<TStor>> results{};
|
|
|
|
for (auto &w : results) {
|
|
w.reserve(size() + 4);
|
|
}
|
|
|
|
for (size_t i = 0; i < size(); i++) {
|
|
const qvec3d &p1 = at(i);
|
|
|
|
if (sides[i] == SIDE_ON) {
|
|
results[SIDE_FRONT].push_back(p1);
|
|
results[SIDE_BACK].push_back(p1);
|
|
continue;
|
|
} else if (sides[i] == SIDE_FRONT) {
|
|
results[SIDE_FRONT].push_back(p1);
|
|
} else if (sides[i] == SIDE_BACK) {
|
|
results[SIDE_BACK].push_back(p1);
|
|
}
|
|
|
|
if (sides[i + 1] == SIDE_ON || sides[i + 1] == sides[i])
|
|
continue;
|
|
|
|
/* generate a split point */
|
|
const qvec3d &p2 = at((i + 1) % size());
|
|
|
|
vec_t dot = dists[i] / (dists[i] - dists[i + 1]);
|
|
qvec3d mid;
|
|
|
|
for (size_t j = 0; j < 3; j++) { /* avoid round off error when possible */
|
|
if (plane.normal[j] == 1)
|
|
mid[j] = plane.dist;
|
|
else if (plane.normal[j] == -1)
|
|
mid[j] = -plane.dist;
|
|
else
|
|
mid[j] = p1[j] + dot * (p2[j] - p1[j]);
|
|
}
|
|
|
|
results[SIDE_FRONT].push_back(mid);
|
|
results[SIDE_BACK].push_back(mid);
|
|
}
|
|
|
|
return {std::move(results[SIDE_FRONT]), std::move(results[SIDE_BACK])};
|
|
}
|
|
|
|
/*
|
|
==================
|
|
clip_front
|
|
|
|
The same as the above, except it will avoid allocating the front
|
|
if it doesn't need to be modified; destroys *this if it does end up
|
|
allocating a new winding.
|
|
|
|
Cheaper than clip(...)[SIDE_FRONT]
|
|
==================
|
|
*/
|
|
std::optional<winding_base_t> clip_front(
|
|
const qplane3d &plane, const vec_t &on_epsilon = DEFAULT_ON_EPSILON, const bool &keepon = false)
|
|
{
|
|
vec_t *dists = (vec_t *)alloca(sizeof(vec_t) * (size() + 1));
|
|
planeside_t *sides = (planeside_t *)alloca(sizeof(planeside_t) * (size() + 1));
|
|
|
|
std::array<size_t, SIDE_TOTAL> counts = calc_sides(plane, dists, sides, on_epsilon);
|
|
|
|
if (keepon && !counts[SIDE_FRONT] && !counts[SIDE_BACK])
|
|
return std::move(*this);
|
|
|
|
if (!counts[SIDE_FRONT])
|
|
return std::nullopt;
|
|
else if (!counts[SIDE_BACK])
|
|
return std::move(*this);
|
|
|
|
winding_base_t result;
|
|
result.reserve(size() + 4);
|
|
|
|
for (size_t i = 0; i < size(); i++) {
|
|
const qvec3d &p1 = at(i);
|
|
|
|
if (sides[i] == SIDE_ON) {
|
|
result.push_back(p1);
|
|
continue;
|
|
} else if (sides[i] == SIDE_FRONT) {
|
|
result.push_back(p1);
|
|
}
|
|
|
|
if (sides[i + 1] == SIDE_ON || sides[i + 1] == sides[i])
|
|
continue;
|
|
|
|
/* generate a split point */
|
|
const qvec3d &p2 = at((i + 1) % size());
|
|
|
|
vec_t dot = dists[i] / (dists[i] - dists[i + 1]);
|
|
qvec3d mid;
|
|
|
|
for (size_t j = 0; j < 3; j++) { /* avoid round off error when possible */
|
|
if (plane.normal[j] == 1)
|
|
mid[j] = plane.dist;
|
|
else if (plane.normal[j] == -1)
|
|
mid[j] = -plane.dist;
|
|
else
|
|
mid[j] = p1[j] + dot * (p2[j] - p1[j]);
|
|
}
|
|
|
|
result.push_back(mid);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
clip_back
|
|
|
|
The same as the above, except it will avoid allocating the front
|
|
if it doesn't need to be modified; destroys *this if it does end up
|
|
allocating a new winding.
|
|
|
|
Cheaper than clip(...)[SIDE_BACK]
|
|
==================
|
|
*/
|
|
std::optional<winding_base_t> clip_back(
|
|
const qplane3d &plane, const vec_t &on_epsilon = DEFAULT_ON_EPSILON, const bool &keepon = false)
|
|
{
|
|
vec_t *dists = (vec_t *)alloca(sizeof(vec_t) * (size() + 1));
|
|
planeside_t *sides = (planeside_t *)alloca(sizeof(planeside_t) * (size() + 1));
|
|
|
|
std::array<size_t, SIDE_TOTAL> counts = calc_sides(plane, dists, sides, on_epsilon);
|
|
|
|
if (keepon && !counts[SIDE_FRONT] && !counts[SIDE_BACK])
|
|
return std::nullopt;
|
|
|
|
if (!counts[SIDE_FRONT])
|
|
return std::move(*this);
|
|
else if (!counts[SIDE_BACK])
|
|
return std::nullopt;
|
|
|
|
winding_base_t result;
|
|
result.reserve(size() + 4);
|
|
|
|
for (size_t i = 0; i < size(); i++) {
|
|
const qvec3d &p1 = at(i);
|
|
|
|
if (sides[i] == SIDE_ON) {
|
|
result.push_back(p1);
|
|
continue;
|
|
} else if (sides[i] == SIDE_BACK) {
|
|
result.push_back(p1);
|
|
}
|
|
|
|
if (sides[i + 1] == SIDE_ON || sides[i + 1] == sides[i])
|
|
continue;
|
|
|
|
/* generate a split point */
|
|
const qvec3d &p2 = at((i + 1) % size());
|
|
|
|
vec_t dot = dists[i] / (dists[i] - dists[i + 1]);
|
|
qvec3d mid;
|
|
|
|
for (size_t j = 0; j < 3; j++) { /* avoid round off error when possible */
|
|
if (plane.normal[j] == 1)
|
|
mid[j] = plane.dist;
|
|
else if (plane.normal[j] == -1)
|
|
mid[j] = -plane.dist;
|
|
else
|
|
mid[j] = p1[j] + dot * (p2[j] - p1[j]);
|
|
}
|
|
|
|
result.push_back(mid);
|
|
}
|
|
|
|
return std::move(result);
|
|
}
|
|
|
|
// SaveFn is a callable of type `winding_base_t & -> void`
|
|
template<typename SaveFn>
|
|
void dice(vec_t subdiv, SaveFn &&save_fn)
|
|
{
|
|
if (!size())
|
|
return;
|
|
|
|
aabb3d b = bounds();
|
|
|
|
size_t i;
|
|
|
|
for (i = 0; i < 3; i++)
|
|
if (floor((b.mins()[i] + 1) / subdiv) < floor((b.maxs()[i] - 1) / subdiv))
|
|
break;
|
|
|
|
if (i == 3) {
|
|
// no splitting needed
|
|
save_fn(*this);
|
|
return;
|
|
}
|
|
|
|
//
|
|
// split the winding
|
|
//
|
|
qplane3d split{};
|
|
split.normal[i] = 1;
|
|
split.dist = subdiv * (1 + floor((b.mins()[i] + 1) / subdiv));
|
|
auto clipped = clip(split);
|
|
clear();
|
|
|
|
//
|
|
// create a new patch
|
|
//
|
|
for (auto &o : clipped)
|
|
if (o.has_value())
|
|
o->dice(subdiv, save_fn);
|
|
}
|
|
|
|
/**
|
|
* Returns a winding for the face. Colinear vertices are filtered out.
|
|
* Might return a degenerate polygon.
|
|
*/
|
|
static winding_base_t from_face(const mbsp_t *bsp, const mface_t *f)
|
|
{
|
|
winding_base_t w(f->numedges);
|
|
|
|
for (size_t i = 0; i < f->numedges; i++) {
|
|
int32_t se = bsp->dsurfedges[f->firstedge + i];
|
|
uint32_t v;
|
|
|
|
if (se < 0)
|
|
v = bsp->dedges[-se][1];
|
|
else
|
|
v = bsp->dedges[se][0];
|
|
|
|
w[i] = bsp->dvertexes[v];
|
|
}
|
|
|
|
// CHECK: can we do the above + colinear checks
|
|
// in a single pass?
|
|
w.remove_colinear();
|
|
|
|
return w;
|
|
}
|
|
|
|
winding_base_t flip() const
|
|
{
|
|
winding_base_t result(size());
|
|
|
|
std::reverse_copy(begin(), end(), result.begin());
|
|
|
|
return result;
|
|
}
|
|
|
|
winding_base_t translate(const qvec3d &offset) const
|
|
{
|
|
winding_base_t result = this->clone();
|
|
|
|
for (qvec3d &p : result) {
|
|
p += offset;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
bool directional_equal(const winding_base_t &w, const vec_t &equal_epsilon = POINT_EQUAL_EPSILON) const
|
|
{
|
|
if (this->size() != w.size()) {
|
|
return false;
|
|
}
|
|
|
|
const auto this_size = size();
|
|
|
|
// try different start offsets in `this`
|
|
for (int i = 0; i < this_size; ++i) {
|
|
bool all_equal = true;
|
|
|
|
// index in `w` to compare
|
|
for (int j = 0; j < this_size; ++j) {
|
|
const qvec3d &our_point = (*this)[(i + j) % this_size];
|
|
const qvec3d &their_point = w[j];
|
|
|
|
if (!qv::epsilonEqual(our_point, their_point, equal_epsilon)) {
|
|
all_equal = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (all_equal) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool undirectional_equal(const winding_base_t &w, const vec_t &equal_epsilon = POINT_EQUAL_EPSILON) const
|
|
{
|
|
return directional_equal(w, equal_epsilon) || directional_equal(w.flip(), equal_epsilon);
|
|
}
|
|
|
|
static std::array<winding_base_t, 6> aabb_windings(const aabb3d &bbox)
|
|
{
|
|
double worldextent = 0;
|
|
for (int i = 0; i < 3; ++i) {
|
|
worldextent = std::max(worldextent, std::abs(bbox.maxs()[i]));
|
|
worldextent = std::max(worldextent, std::abs(bbox.mins()[i]));
|
|
}
|
|
worldextent += 1;
|
|
|
|
std::array<winding_base_t, 6> result;
|
|
auto planes = aabb_planes(bbox);
|
|
|
|
for (int i = 0; i < 6; ++i) {
|
|
result[i] = winding_base_t::from_plane(planes[i], worldextent);
|
|
}
|
|
|
|
for (int i = 0; i < 6; ++i) {
|
|
for (int j = 0; j < 6; ++j) {
|
|
if (i == j)
|
|
continue;
|
|
|
|
result[i] = *result[i].clip_back(planes[j]);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
};
|
|
|
|
// the default amount of points to keep on stack
|
|
constexpr size_t STACK_POINTS_ON_WINDING = MAX_POINTS_ON_WINDING / 4;
|
|
|
|
using winding_t = winding_base_t<winding_storage_heap_t>;
|
|
|
|
}; // namespace polylib
|