#pragma once #include #include #include #include #include #include #include #include "combo_iterator.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; } namespace { /** * 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); } } } //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; //} template class Mesh { public: const tc::Group *g; // todo this needs to be handled more consistently std::vector ctx; Prims prims; Mesh(const tc::Group &g_, std::vector ctx_, size_t cols); Mesh(const tc::Group &g_, std::vector ctx_); Mesh recontext(std::vector ctx_); std::vector> tile(const std::vector &ctx_); Mesh fan(unsigned root); [[nodiscard]] size_t size() const { return prims.size(); } [[nodiscard]] size_t rows() const { return prims.rows(); } [[nodiscard]] size_t cols() const { return prims.cols(); } [[nodiscard]] unsigned *data() { return prims.data(); } [[nodiscard]] const unsigned *data() const { return prims.data(); } }; template M merge(const std::vector &meshes) { if (meshes.empty()) throw std::logic_error("cannot merge an empty list of meshes"); auto g = meshes[0].g; auto ctx = meshes[0].ctx; size_t cols = 0; for (const auto &mesh : meshes) { cols += mesh.prims.cols(); } M res(*g, ctx, cols); size_t offset = 0; for (const auto &mesh : meshes) { res.prims.middleCols(offset, mesh.prims.cols()) = mesh.prims; offset += mesh.prims.cols(); } return res; } template Mesh Mesh::recontext(std::vector ctx_) { Mesh res = *this; res.ctx = ctx_; const auto proper_sg_gens = recontext_gens(*g, res.ctx, ctx); const auto table = solve(*g, res.ctx, {}); const auto path = solve(*g, ctx, {}).path; auto map = path.template walk(0, proper_sg_gens, [table](int coset, int gen) { return table.get(coset, gen); }); auto data = res.prims.data(); for (int i = 0; i < res.prims.size(); ++i) { data[i] = map[data[i]]; } return res; } template std::vector> Mesh::tile(const std::vector &ctx_) { auto base = recontext(ctx_); auto table = solve(*g, base.ctx, {}); auto path = solve(*g, base.ctx, ctx).path; std::vector> res = path.template walk, int>( base, generators(*g), [&](Mesh mesh, int gen) { apply(table, gen, mesh.prims); return mesh; } ); return res; } template Mesh Mesh::fan(unsigned root) { Mesh res(*g, ctx, prims.cols()); res.prims.topRows(1) = Prims<1>::Constant(1, prims.cols(), root); res.prims.bottomRows(N) = prims; return res; } template Mesh::Mesh(const tc::Group &g_, std::vector ctx_, size_t cols) : g(&g_), ctx(std::move(ctx_)) { prims.setZero(N, cols); } template Mesh::Mesh(const tc::Group &g_, std::vector ctx_) : g(&g_), ctx(std::move(ctx_)) { if (ctx.size() + 1 != N) // todo make static assert throw std::logic_error("ctx size must be one less than N"); const auto &combos = Combos(ctx, ctx.size() - 1); std::vector> meshes; for (const auto &sctx : combos) { Mesh base(*g, sctx); auto parts = base.tile(ctx); parts.erase(parts.begin(), parts.begin() + 1); if (parts.empty()) continue; auto raised = merge(parts); auto fanned = raised.fan(0); meshes.push_back(fanned); } prims = merge(meshes).prims; } template<> Mesh<1>::Mesh(const tc::Group &g_, std::vector ctx_) : g(&g_), ctx(std::move(ctx_)) { if (not ctx.empty()) throw std::logic_error("ctx must be empty for a trivial Mesh."); prims.setZero(1, 1); } template auto hull(const tc::Group &g, const std::vector> &exclude) { std::vector> parts; auto ctx = generators(g); auto sub_ctxs = Combos(ctx, N - 1); for (const auto &sub_ctx : sub_ctxs) { bool excluded = std::any_of( exclude.begin(), exclude.end(), [&](auto e) { return e == sub_ctx; } ); if (excluded) continue; auto sub_parts = Mesh(g, sub_ctx).tile(ctx); parts.insert(parts.end(), sub_parts.begin(), sub_parts.end()); } return parts; }