Phase 8b-Lite extension: output_uv_map named parameter for integrated layout
Closes the UX gap identified in the Phase-8b-Lite design discussion:
the four classical-DCE CGAL entries now optionally run their `*_layout()`
step internally if the caller supplies `CGAL::parameters::output_uv_map(pmap)`.
Before this PR, the workflow was:
auto res = CGAL::discrete_conformal_map_euclidean(mesh);
// ... user has to re-set up maps, re-pin vertex, then ...
auto layout = euclidean_layout(mesh, res.x, maps);
// ... and copy coordinates into a property map manually.
After this PR:
auto uv = mesh.add_property_map<Vertex_index, K::Point_2>("uv", ...).first;
CGAL::discrete_conformal_map_euclidean(
mesh, CGAL::parameters::output_uv_map(uv));
// ... uv now populated for every vertex.
Coverage
────────
* `discrete_conformal_map_euclidean` — `Point_2` per vertex.
* `discrete_conformal_map_spherical` — `Point_3` per vertex (on S²).
* `discrete_conformal_map_hyper_ideal` — `Point_2` per vertex (Poincaré disk).
CP-Euclidean and Inversive-Distance entries do not yet support
`output_uv_map` — face-based packing has no per-vertex UV concept, and
inversive-distance needs a dedicated layout routine that uses Luo's
edge-length formula (planned follow-up).
New named parameters (`code/include/CGAL/Conformal_map/internal/parameters.h`)
─────────────────────────────────────────────────────────────────────────────
* `output_uv_map(pmap)` — write coordinates into pmap after layout.
* `normalise_layout(bool)` — apply post-layout canonical normalisation
(PCA centroid for Euclidean, north-pole alignment for Spherical,
Möbius centring for Hyper-ideal).
Both follow the existing Phase-8a-MVP named-parameter convention.
Chained syntax (`.output_uv_map(...).normalise_layout(true)`) is not
yet supported — pass them one at a time.
Tests (5 new in test_cgal_phase8b_lite.cpp)
───────────────────────────────────────────
* `OutputUvMap_Euclidean_PopulatesPmap` — UVs are finite + non-trivial.
* `OutputUvMap_Spherical_PopulatesXyz` — every output on unit S².
* `OutputUvMap_HyperIdeal_PointsInPoincareDisk` — |p|² ≤ 1 if converged.
* `OutputUvMap_Absent_DoesNotRunLayout` — no parameter ⇒ no layout.
* `OutputUvMap_NormaliseLayout_TakesEffect` — both raw + norm calls
return finite UVs (named-parameter chaining limitation documented).
All five pass. Full CGAL suite: 232/232, 0 skipped (was 227).
doc/api/tests.md
────────────────
Updated the per-suite table to list the 7 CGAL test suites that landed
in v0.9.0 (8a MVP + 9a + 9b + 8b-Lite) but had not yet been added to the
canonical table. Total: 227 → 232. This brings the doc back in sync
with `ctest` output and unblocks future `scripts/check-test-counts.sh`
runs.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -59,6 +59,7 @@ auto result = CGAL::discrete_conformal_map_euclidean(
|
||||
|
||||
// Existing implementation headers (Layer 1 — unchanged).
|
||||
#include "../euclidean_functional.hpp"
|
||||
#include "../layout.hpp"
|
||||
#include "../spherical_functional.hpp"
|
||||
#include "../hyper_ideal_functional.hpp"
|
||||
#include "../gauss_bonnet.hpp"
|
||||
@@ -268,6 +269,33 @@ auto discrete_conformal_map_euclidean(
|
||||
result.gradient_norm = nr.grad_inf_norm;
|
||||
result.converged = nr.converged;
|
||||
|
||||
// ── 8. Optional layout step (Phase 8b-Lite extension) ──────────────────
|
||||
//
|
||||
// If the caller supplied `output_uv_map(pmap)`, run the priority-BFS
|
||||
// trilateration on the converged x and write per-vertex `Point_2`
|
||||
// coordinates into `pmap`. Optional `normalise_layout(true)` applies
|
||||
// the canonical PCA centroid + major-axis normalisation.
|
||||
auto uv_param = parameters::get_parameter(
|
||||
np, Conformal_map::internal_np::output_uv_map);
|
||||
constexpr bool has_uv = !std::is_same_v<
|
||||
decltype(uv_param), internal_np::Param_not_found>;
|
||||
if constexpr (has_uv) {
|
||||
if (nr.converged) {
|
||||
auto layout = ::conformallab::euclidean_layout(mesh, nr.x, maps);
|
||||
|
||||
const bool do_norm = parameters::choose_parameter(
|
||||
parameters::get_parameter(np, Conformal_map::internal_np::normalise_layout),
|
||||
false);
|
||||
if (do_norm) ::conformallab::normalise_euclidean(layout);
|
||||
|
||||
for (auto v : mesh.vertices()) {
|
||||
const auto& uv = layout.uv[v.idx()];
|
||||
put(uv_param, v,
|
||||
typename Traits::Kernel::Point_2(uv.x(), uv.y()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -378,6 +406,27 @@ auto discrete_conformal_map_spherical(
|
||||
result.iterations = nr.iterations;
|
||||
result.gradient_norm = nr.grad_inf_norm;
|
||||
result.converged = nr.converged;
|
||||
|
||||
// Optional 3-D layout step (point on S²)
|
||||
auto uv_param = parameters::get_parameter(
|
||||
np, Conformal_map::internal_np::output_uv_map);
|
||||
constexpr bool has_uv = !std::is_same_v<
|
||||
decltype(uv_param), internal_np::Param_not_found>;
|
||||
if constexpr (has_uv) {
|
||||
if (nr.converged) {
|
||||
auto layout = ::conformallab::spherical_layout(mesh, nr.x, maps);
|
||||
const bool do_norm = parameters::choose_parameter(
|
||||
parameters::get_parameter(np, Conformal_map::internal_np::normalise_layout),
|
||||
false);
|
||||
if (do_norm) ::conformallab::normalise_spherical(layout);
|
||||
for (auto v : mesh.vertices()) {
|
||||
const auto& p = layout.pos[v.idx()];
|
||||
put(uv_param, v,
|
||||
typename Traits::Kernel::Point_3(p.x(), p.y(), p.z()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -482,6 +531,28 @@ auto discrete_conformal_map_hyper_ideal(
|
||||
result.iterations = nr.iterations;
|
||||
result.gradient_norm = nr.grad_inf_norm;
|
||||
result.converged = nr.converged;
|
||||
|
||||
// Optional Poincaré-disk layout (2-D in the unit disk).
|
||||
auto uv_param = parameters::get_parameter(
|
||||
np, Conformal_map::internal_np::output_uv_map);
|
||||
constexpr bool has_uv = !std::is_same_v<
|
||||
decltype(uv_param), internal_np::Param_not_found>;
|
||||
if constexpr (has_uv) {
|
||||
if (nr.converged) {
|
||||
auto layout = ::conformallab::hyper_ideal_layout(mesh, nr.x, maps);
|
||||
const bool do_norm = parameters::choose_parameter(
|
||||
parameters::get_parameter(np, Conformal_map::internal_np::normalise_layout),
|
||||
false);
|
||||
if (do_norm) ::conformallab::normalise_hyperbolic(layout);
|
||||
for (auto v : mesh.vertices()) {
|
||||
const auto& uv = layout.uv[v.idx()];
|
||||
put(uv_param, v,
|
||||
typename Traits::Kernel::Point_2(uv.x(), uv.y()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(void)n; // already-counted by maps; silence unused-var warnings if any
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user