diff --git a/CMakeLists.txt b/CMakeLists.txt index 115aaba..aeeb06d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,3 +39,6 @@ target_link_libraries(tctest eigen tc) add_executable(combotest src/combotest.cpp) target_link_libraries(combotest eigen tc) + +add_executable(geometrytest src/geometrytest.cpp) +target_link_libraries(geometrytest eigen tc) diff --git a/include/geometry.hpp b/include/geometry.hpp new file mode 100644 index 0000000..92c772c --- /dev/null +++ b/include/geometry.hpp @@ -0,0 +1,63 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include + +#include "combo.hpp" + +template +using Prims = Eigen::Matrix; + +template +using vec = Eigen::Matrix; +template +using mat = Eigen::Matrix; + +using vec1 = vec<1>; +using vec2 = vec<2>; +using vec3 = vec<3>; +using vec4 = vec<4>; +using vec5 = vec<5>; + +using mat1 = mat<1>; +using mat2 = mat<2>; +using mat3 = mat<3>; +using mat4 = mat<4>; +using mat5 = mat<5>; + +mat4 orthographic(float left, float right, float bottom, float top, float front, float back) { + mat4 res = mat4(); + res << + 2 / (right - left), 0, 0, -(right + left) / (right - left), + 0, 2 / (top - bottom), 0, -(top + bottom) / (top - bottom), + 0, 0, 2 / (front - back), -(front + back) / (front - back), + 0, 0, 0, 1; + return res; +} + +mat4 perspective(float fovy, float aspect, float zNear, float zFar) { + float tanHalfFovy(std::tan(fovy / 2)); + + mat4 res = mat4::Identity(); + res(0, 0) = 1 / (aspect * tanHalfFovy); + res(1, 1) = 1 / (tanHalfFovy); + res(2, 2) = -(zFar + zNear) / (zFar - zNear); + res(3, 2) = -1; + res(2, 3) = -(2 + zFar * zNear) / (zFar - zNear); + return res; +} + +mat4 translation(float x, float y, float z) { + mat4 res = mat4(); + res << + 1, 0, 0, x, + 0, 1, 0, y, + 0, 0, 1, z, + 0, 0, 0, 1; + return res; +} diff --git a/include/mirror.hpp b/include/mirror.hpp new file mode 100644 index 0000000..7f563f7 --- /dev/null +++ b/include/mirror.hpp @@ -0,0 +1,128 @@ +#pragma once + +#include + +#include +#include +#include + +#include + +template +float dot(int n, const V &a, const V &b) { + float sum = 0; + for (int i = 0; i < n; ++i) { + sum += a[i] * b[i]; + } + return sum; +} + +template +std::vector> mirror(const tc::Group &group) { + std::vector> mirrors; + + for (int p = 0; p < group.ngens; ++p) { + std::vector vp; + for (int m = 0; m < p; ++m) { + auto &vq = mirrors[m]; + vp.push_back((cos(M_PI / group.get(p, m)) - dot(m, vp, vq)) / vq[m]); + } + vp.push_back(std::sqrt(1 - dot(p, vp, vp))); + + for (const auto &v : mirrors) { + if (dot(p, vp, vp) > 0) { + for (auto &e : vp) { + e *= -1; + } + break; + } + } + + mirrors.push_back(vp); + } + + std::vector> res; + for (const auto &v : mirrors) { + vec rv = vec::Zero(); + + // ortho proj + for (int i = 0; i < std::min(v.size(), (size_t) N); ++i) { + rv[i] = v[i]; + } + + res.push_back(rv); + } + return res; +} + +template +vec stereo(const vec &v) { + vec r; + for (int i = 0; i < N; ++i) { + r[i] = v[i] / (1 - v[N]); + } + return r; +} + +template +vec ortho(const vec &v) { + vec r; + for (int i = 0; i < N; ++i) { + r[i] = v[i]; + } + return r; +} + +template +V project(const V &vec, const V &target) { + return vec.dot(target) / target.dot(target) * target; +} + +template +V reflect(const V &a, const V &axis) { + return a - 2.f * project(a, axis); +} + +template +V gram_schmidt_last(std::vector vecs) { + for (int i = 0; i < vecs.size(); ++i) { + for (int j = 0; j < i; ++j) { + vecs[i] -= project(vecs[i], vecs[j]); + } + } + + return vecs[vecs.size() - 1].normalized(); +} + +template +V barycentric(const std::vector &basis, const C &coords) { + V res = V::Zero(); + + int N = std::min((int) basis.size(), (int) coords.rows()); + for (int i = 0; i < N; ++i) { + res += basis[i] * coords[i]; + } + return res; +} + +template +std::vector plane_intersections(std::vector normals) { + std::vector results(normals.size()); + + for (int i = 0; i < normals.size(); ++i) { + std::rotate(normals.begin(), normals.begin() + 1, normals.end()); + results[i] = gram_schmidt_last(normals); + } + + return results; +} + +template +mat rot(int u, int v, float theta) { + mat res = mat::Identity(); + res(u, u) = std::cos(theta); + res(u, v) = std::sin(theta); + res(v, u) = -std::sin(theta); + res(v, v) = std::cos(theta); + return res; +} diff --git a/include/solver.hpp b/include/solver.hpp new file mode 100644 index 0000000..c1edd73 --- /dev/null +++ b/include/solver.hpp @@ -0,0 +1,222 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include + +#include "combo.hpp" + +/** + * Produce a list of all generators for the group context. The range [0..group.ngens). + */ +std::vector generators(const tc::Group &context) { + std::vector g_gens(context.ngens); + std::iota(g_gens.begin(), g_gens.end(), 0); + return g_gens; +} + +/** + * Determine which of g_gens are the correct names for sg_gens within the current context + */ +std::vector recontext_gens( + const tc::Group &context, + std::vector g_gens, + std::vector sg_gens) { + + std::sort(g_gens.begin(), g_gens.end()); + + int inv_gen_map[context.ngens]; + for (size_t i = 0; i < g_gens.size(); i++) { + inv_gen_map[g_gens[i]] = i; + } + + std::vector s_sg_gens; + s_sg_gens.reserve(sg_gens.size()); + for (const auto gen: sg_gens) { + s_sg_gens.push_back(inv_gen_map[gen]); + } + std::sort(s_sg_gens.begin(), s_sg_gens.end()); + + return s_sg_gens; +} + +/** + * Solve the cosets generated by sg_gens within the subgroup generated by g_gens of the group context + */ +tc::Cosets solve( + const tc::Group &context, + const std::vector &g_gens, + const std::vector &sg_gens +) { + const auto proper_sg_gens = recontext_gens(context, g_gens, sg_gens); + return context.subgroup(g_gens).solve(proper_sg_gens); +} + +/** + * Apply some context transformation to all primitives of this mesh. + */ +template +void apply(const tc::Cosets &table, int gen, Prims &mat) { + auto data = mat.data(); + for (int i = 0; i < mat.size(); ++i) { + data[i] = table.get(data[i], gen); + } +} + +/** + * Convert the indexes of this mesh to those of a different context, using g_gens to build the parent context and sg_gens to build this context. + */ +template +[[nodiscard]] +Prims recontext( + Prims prims, + const tc::Group &context, + const std::vector &g_gens, + const std::vector &sg_gens +) { + const auto proper_sg_gens = recontext_gens(context, g_gens, sg_gens); + const auto table = solve(context, g_gens, {}); + const auto path = solve(context, sg_gens, {}).path; + + auto map = path.template walk(0, proper_sg_gens, [table](int coset, int gen) { + return table.get(coset, gen); + }); + + Prims res(prims); + auto data = res.data(); + for (int i = 0; i < prims.size(); ++i) { + data[i] = map[data[i]]; + } + + return res; +} + +/** + * Union several meshes of the same dimension + */ +template +Prims merge(const std::vector> &meshes) { + size_t cols = 0; + for (const auto &mesh: meshes) { + cols += mesh.cols(); + } + + Prims res(N, cols); + + size_t offset = 0; + for (const Prims &mesh: meshes) { + res.middleCols(offset, mesh.cols()) = mesh; + offset += mesh.cols(); + } + + return res; +} + +template +[[nodiscard]] +std::vector> tile( + Prims prims, + const tc::Group &context, + const std::vector &g_gens, + const std::vector &sg_gens +) { + Prims base = recontext(prims, context, g_gens, sg_gens); + const auto proper_sg_gens = recontext_gens(context, g_gens, sg_gens); + + const auto table = solve(context, g_gens, {}); + const auto path = solve(context, g_gens, sg_gens).path; + + std::vector _gens = generators(context); + + std::vector> res = path.walk, int>( + base, _gens, + [&](Prims from, int gen) { + apply(table, gen, from); + return from; + } + ); + + return res; +} + +/** + * Produce a mesh of higher dimension by fanning a single point to all primitives in this mesh. + */ +template +[[nodiscard]] +Prims fan(Prims prims, int root) { + Prims res(N + 1, prims.cols()); + + res.topRows(1) = Prims<1>::Constant(1, prims.cols(), root); + res.bottomRows(N) = prims; + + return res; +} + +/** + * Produce a mesh of primitives that fill out the volume of the subgroup generated by generators g_gens within the group context + */ +template +Prims triangulate( + const tc::Group &context, + const std::vector &g_gens +) { + if (g_gens.size() + 1 != N) // todo make static assert + throw std::logic_error("g_gens size must be one less than N"); + + const auto &combos = combinations(g_gens, g_gens.size() - 1); + + std::vector> meshes; + + for (const auto &sg_gens: combos) { + auto base = triangulate(context, sg_gens); + auto parts = tile(base, context, g_gens, sg_gens); + parts.erase(parts.begin(), parts.begin() + 1); + auto raised = merge(parts); + auto fanned = fan(raised, 0); + meshes.push_back(fanned); + } + + return merge(meshes); +} + +/** + * Single-index primitives should not be further triangulated. + */ +template<> +Prims<1> triangulate<1>( + const tc::Group &context, + const std::vector &g_gens +) { + if (not g_gens.empty()) // todo make static assert + throw std::logic_error("g_gens must be empty for a trivial Mesh"); + + return Prims<1>::Zero(1, 1); +} + +template +auto hull(const tc::Group &group, T all_sg_gens, const std::vector> &exclude) { + std::vector> parts; + auto g_gens = generators(group); + for (const std::vector &sg_gens: all_sg_gens) { + bool excluded = false; + for (const auto &test: exclude) { + if (sg_gens == test) { + excluded = true; + break; + } + } + if (excluded) continue; + + const auto &base = triangulate(group, sg_gens); + const auto &tiles = tile(base, group, g_gens, sg_gens); + for (const auto &tile: tiles) { + parts.push_back(tile); + } + } + return parts; +} diff --git a/src/geometrytest.cpp b/src/geometrytest.cpp new file mode 100644 index 0000000..e718a1a --- /dev/null +++ b/src/geometrytest.cpp @@ -0,0 +1,44 @@ +#include +#include +#include +#include + +#include + +#include + +int main() { + std::vector symbol = {3, 4, 3, 2}; + vec5 root{0.80, 0.02, 0.02, 0.02, 0.02}; + + auto group = tc::schlafli(symbol); + auto gens = generators(group); + auto combos = combinations(gens, 3); + + const auto &inds = merge<4>(hull<4>(group, combos, {})); + + auto cosets = group.solve(); + auto mirrors = mirror<5>(group); + auto corners = plane_intersections(mirrors); + auto start = barycentric(corners, root); + + using Projective5f = Eigen::Transform; + Projective5f transform = Projective5f::Identity(); + + auto higher = cosets.path.walk(start, mirrors, reflect); + std::transform( + higher.begin(), higher.end(), higher.begin(), + [&](const vec5 &v) { + return (transform * v.homogeneous()).hnormalized(); + } + ); + + std::vector lower(higher.size()); + std::transform(higher.begin(), higher.end(), lower.begin(), stereo<4>); + + const auto &verts = lower; + + std::cout << inds.rows() << " " << inds.cols() << std::endl; + + return EXIT_SUCCESS; +}