#pragma once #include #include #include #include #include #include #include namespace tc { std::vector combinations(const Symbol &symbol, size_t srank) { size_t rank = symbol.size(); std::vector mask(rank, false); std::fill_n(mask.begin(), srank, true); std::vector combos; combos.reserve(choose(rank, srank)); Symbol row(srank); do { for (int j = 0, k = 0; j < rank; ++j) { if (mask[j]) { row(k++) = symbol(j); } } combos.emplace_back(row); } while (std::prev_permutation(mask.begin(), mask.end())); return combos; } // todo remove ArrayXui fan(const ArrayXui &prims, int root) { ArrayXui res(prims.rows() + 1, prims.cols()); res.topRows(prims.rows()) << prims; res.bottomRows(1).fill(root); return res; } // todo visitor void apply(const tc::Cosets &table, unsigned int gen, ArrayXui &prims) { for (Eigen::Index i = 0; i < prims.size(); ++i) { prims(i) = table.get(prims(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. */ void recontext( ArrayXui &prims, const tc::Group &context, const Symbol &g_gens, const Symbol &sg_gens ) { const auto proper_sg_gens = recontext_gens(context.rank(), g_gens, sg_gens); const auto table = solve(context, g_gens, Symbol(0)); const auto path = solve(context, sg_gens, Symbol(0)).path(); auto map = path.walk(0U, proper_sg_gens, [&table](auto coset, auto gen) { return table.get(coset, gen); }); // todo visitor for (Eigen::Index i = 0; i < prims.size(); ++i) { prims(i) = map[prims(i)]; } } // todo remove ArrayXui merge(const std::vector &meshes) { Eigen::Index cols = 0; for (const auto &mesh: meshes) { cols += mesh.cols(); } ArrayXui res(meshes[0].rows(), cols); Eigen::Index offset = 0; for (const auto &mesh: meshes) { res.middleCols(offset, mesh.cols()) << mesh; offset += mesh.cols(); } return res; } std::vector each_tile( ArrayXui base, const tc::Group &context, const Symbol &g_gens, const Symbol &sg_gens ) { recontext(base, context, g_gens, sg_gens); const auto table = solve(context, g_gens, Symbol(0)); const auto path = solve(context, g_gens, sg_gens).path(); auto _gens = context.gens; auto res = path.walk(base, _gens, [&table](auto from, auto &gen) { apply(table, gen, from); return from; }); return res; } /** * Produce a mesh of primitives that fill out the volume of the subgroup generated by generators g_gens within the group context */ ArrayXui triangulate( const tc::Group &context, const Symbol &g_gens ) { if (g_gens.size() == 0) { return ArrayXui::Zero(1, 1); } const auto &combos = combinations(g_gens, g_gens.size() - 1); std::vector meshes; // todo inline logic // erase, merge, and fan can be inlined // for 1..#tiles // cols += cols // parts.append(cols) // result(rows, cols) // for 1..#parts // result.middlecols << part // result.bottomrow.fill(0) // todo subgroup/coset metadata // would be good to also output which coset of which subgroup each primitive is a part of. // this could be used in shaders etc to make rendering things easier for (const auto &sg_gens: combos) { auto base = triangulate(context, sg_gens); auto tiles = each_tile(base, context, g_gens, sg_gens); tiles.erase(tiles.begin(), tiles.begin() + 1); auto raised = merge(tiles); auto fanned = fan(raised, 0); meshes.push_back(fanned); } const ArrayXui &result = merge(meshes); return result; } template auto hull(const tc::Group &group, T all_sg_gens) { std::vector parts; auto g_gens = group.gens; // todo inline logic // should be able to inline in a similar way as is possible in triangulate for (const Symbol &sg_gens: all_sg_gens) { const auto &base = triangulate(group, sg_gens); const auto &tiles = each_tile(base, group, g_gens, sg_gens); for (const auto &tile: tiles) { parts.push_back(tile); } } return parts; } }