diff --git a/src/polymesh/detail/math.hh b/src/polymesh/detail/math.hh new file mode 100644 index 0000000000000000000000000000000000000000..d0e87fc80e8037fbf633aba724f01d79af7774ae --- /dev/null +++ b/src/polymesh/detail/math.hh @@ -0,0 +1,16 @@ +#pragma once + +namespace polymesh::detail +{ +template <class ScalarT> +struct pos3 +{ + ScalarT x; + ScalarT y; + ScalarT z; +}; + +using pos3f = pos3<float>; +using pos3d = pos3<double>; + +} diff --git a/src/polymesh/objects/ico_sphere.cc b/src/polymesh/objects/ico_sphere.cc new file mode 100644 index 0000000000000000000000000000000000000000..f58acee498293c0400bc1881939b8c2f81b4c3ac --- /dev/null +++ b/src/polymesh/objects/ico_sphere.cc @@ -0,0 +1,166 @@ +#include "ico_sphere.hh" + +#include <array> +#include <cmath> +#include <map> +#include <vector> + +namespace +{ +polymesh::detail::pos3f slerp(polymesh::detail::pos3f const& x, polymesh::detail::pos3f const& y, float a) +{ + float cos_alpha = x.x * y.x + x.y * y.y + x.z * y.z; + float alpha = std::acos(cos_alpha); + float sin_alpha = std::sin(alpha); + float t1 = std::sin((1.0f - a) * alpha) / sin_alpha; + float t2 = std::sin(a * alpha) / sin_alpha; + return {x.x * t1 + y.x * t2, x.y * t1 + y.y * t2, x.z * t1 + y.z * t2}; +} +} + +polymesh::unique_array<polymesh::detail::pos3f> polymesh::detail::add_ico_sphere_impl(polymesh::Mesh& m, int subdiv) +{ + auto const start_vertex_index = m.all_vertices().size(); + auto const total_positions = 12 + 30 * subdiv + 20 * subdiv * (subdiv - 1) / 2; + auto position = unique_array<pos3f>(total_positions); + + int next_vertex_index = 0; + auto const add_vertex = [&]() { + m.vertices().add(); + return next_vertex_index++; + }; + + // base icosahedron + position[add_vertex()] = {0.000000f, -1.000000f, 0.000000f}; + position[add_vertex()] = {0.723600f, -0.447215f, 0.525720f}; + position[add_vertex()] = {-0.276385f, -0.447215f, 0.850640f}; + position[add_vertex()] = {-0.894425f, -0.447215f, 0.000000f}; + position[add_vertex()] = {-0.276385f, -0.447215f, -0.850640f}; + position[add_vertex()] = {0.723600f, -0.447215f, -0.525720f}; + position[add_vertex()] = {0.276385f, 0.447215f, 0.850640f}; + position[add_vertex()] = {-0.723600f, 0.447215f, 0.525720f}; + position[add_vertex()] = {-0.723600f, 0.447215f, -0.525720f}; + position[add_vertex()] = {0.276385f, 0.447215f, -0.850640f}; + position[add_vertex()] = {0.894425f, 0.447215f, 0.000000f}; + position[add_vertex()] = {0.000000f, 1.000000f, 0.000000f}; + + auto const indices = std::array{1, 2, 3, 2, 1, 6, 1, 3, 4, 1, 4, 5, 1, 5, 6, 2, 6, 11, 3, 2, 7, 4, 3, 8, 5, 4, 9, 6, 5, 10, + 2, 11, 7, 3, 7, 8, 4, 8, 9, 5, 9, 10, 6, 10, 11, 7, 11, 12, 8, 7, 12, 9, 8, 12, 10, 9, 12, 11, 10, 12}; + + std::map<std::pair<int, int>, std::vector<int>> edge_verts; + + auto const segments = subdiv + 1; + + // edge vertices + for (auto i = 0; i < int(indices.size()); i += 3) + { + auto v0 = indices[i + 0] - 1; + auto v1 = indices[i + 1] - 1; + auto v2 = indices[i + 2] - 1; + + auto process = [&](int v0, int v1) { + auto& verts = edge_verts[{v0, v1}]; + for (auto i = 0; i <= segments; ++i) + { + if (i == 0) + verts.push_back(v1); + else if (i == segments) + verts.push_back(v0); + else + { + auto const v = add_vertex(); + position[v] = slerp(position[v1], position[v0], float(i) / float(segments)); + verts.push_back(v); + } + } + }; + + if (v0 < v1) + process(v0, v1); + if (v1 < v2) + process(v1, v2); + if (v2 < v0) + process(v2, v0); + } + + + std::vector<int> verts; + verts.resize((segments + 1) * (segments + 1)); + + // face vertices + for (auto i = 0; i < int(indices.size()); i += 3) + { + auto const v0 = indices[i + 0] - 1; + auto const v1 = indices[i + 1] - 1; + auto const v2 = indices[i + 2] - 1; + auto const p0 = position[v0]; + auto const p1 = position[v1]; + auto const p2 = position[v2]; + + auto const& ev01 = v0 < v1 ? edge_verts[{v0, v1}] : edge_verts[{v1, v0}]; + auto const& ev02 = v0 < v2 ? edge_verts[{v0, v2}] : edge_verts[{v2, v0}]; + auto const& ev12 = v1 < v2 ? edge_verts[{v1, v2}] : edge_verts[{v2, v1}]; + + for (auto ia = 0; ia <= segments; ++ia) + for (auto ib = 0; ib <= segments - ia; ++ib) + { + int v; + if (ia == 0) + { + if (v1 < v2) + v = ev12[ib]; + else + v = ev12[segments - ib]; + } + else if (ib == 0) + { + if (v0 < v2) + v = ev02[ia]; + else + v = ev02[segments - ia]; + } + else if (ia + ib == segments) + { + if (v0 < v1) + v = ev01[ia]; + else + v = ev01[ib]; + } + else + { + v = add_vertex(); + + auto const a = float(ia) / float(segments); + auto const b = float(ib) / float(segments); + auto const c = 1.0f - a - b; + POLYMESH_ASSERT(a >= 0 && b >= 0 && c >= 0); + + auto const p = slerp(p1, p0, (a / (a + b))); + position[v] = slerp(p, p2, (c / (a + b + c))); + } + + verts[ib * (segments + 1) + ia] = v; + } + + // faces + for (auto ia = 0; ia < segments; ++ia) + for (auto ib = 0; ib < segments - ia; ++ib) + { + auto v00 = vertex_index(verts[(ib + 0) * (segments + 1) + (ia + 0)] + start_vertex_index).of(m); + auto v10 = vertex_index(verts[(ib + 1) * (segments + 1) + (ia + 0)] + start_vertex_index).of(m); + auto v01 = vertex_index(verts[(ib + 0) * (segments + 1) + (ia + 1)] + start_vertex_index).of(m); + auto v11 = vertex_index(verts[(ib + 1) * (segments + 1) + (ia + 1)] + start_vertex_index).of(m); + + POLYMESH_ASSERT(v00.is_valid()); + POLYMESH_ASSERT(v10.is_valid()); + POLYMESH_ASSERT(v01.is_valid()); + + m.faces().add(v10, v00, v01); + + if (ia + ib + 2 <= segments) + m.faces().add(v11, v10, v01); + } + } + + return position; +} diff --git a/src/polymesh/objects/ico_sphere.hh b/src/polymesh/objects/ico_sphere.hh new file mode 100644 index 0000000000000000000000000000000000000000..cc368eb0bd54ebbfa21fdf33c8c5b2ec878d64fe --- /dev/null +++ b/src/polymesh/objects/ico_sphere.hh @@ -0,0 +1,30 @@ +#pragma once + +#include <polymesh/Mesh.hh> +#include <polymesh/detail/math.hh> +#include <polymesh/detail/unique_array.hh> + +namespace polymesh::detail +{ +unique_array<pos3f> add_ico_sphere_impl(Mesh& m, int subdiv); +} + +namespace polymesh::objects +{ +/// Adds a (subdivided) ico_sphere to the given mesh +/// sf is called with (v, x, y, z), with vertex handle v and coordinates (x,y,z) on the unit sphere +template <class SphereF> +vertex_handle add_ico_sphere(Mesh& m, SphereF&& sf, int subdiv = 0) +{ + POLYMESH_ASSERT(subdiv >= 0); + + auto const last_vertex_count = m.all_vertices().size(); + auto const positions = detail::add_ico_sphere_impl(m, subdiv); + auto const total_positions = 12 + 30 * subdiv + 20 * subdiv * (subdiv - 1) / 2; + for (auto i = 0; i < total_positions; ++i) + { + sf(vertex_index(last_vertex_count + i).of(m), positions[i].x, positions[i].y, positions[i].z); + } + return vertex_index(last_vertex_count).of(m); +} +}