TerrainGen coverage


terraingen/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
124 / 138
89.9%
Branches:
106 / 114
93.0%
Functions:
23 / 24
95.8%

python/src/util.cpp
Line Branch Exec Source
1 #include "bindings.h"
2 #include "common.h"
3
4 #include "terraingen/util/build_info.h"
5 #include "terraingen/util/cells.h"
6 #include "terraingen/util/log.h"
7 #include "terraingen/util/radial_shape.h"
8 #include "terraingen/util/special_functions.h"
9 #include "terraingen/util/surface.h"
10 #include "terraingen/util/version.h"
11
12 #include <memory>
13 #include <optional>
14 #include <string>
15 #include <tuple>
16
17 namespace terraingen::python {
18
19 namespace {
20
21 44 void BindVersion(nb::module_& m) {
22
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 m.attr("__version__") = TERRAINGEN_FULL_VERSION_STRING;
23
1/1
✓ Branch 1 taken 44 times.
44 m.def(
24 "version",
25 ✗ [] {
26
1/1
✓ Branch 1 taken 1 time.
1 const Version v = GetVersion();
27
1/1
✓ Branch 1 taken 1 time.
2 return nb::make_tuple(v.major, v.minor, v.patch);
28 },
29 "Library version as (major, minor, patch).");
30
1/1
✓ Branch 1 taken 44 times.
44 m.def("api_version", &GetApiVersion, "Library API version.");
31
1/1
✓ Branch 1 taken 44 times.
44 m.def(
32 "build_info",
33 45 [] {
34
1/1
✓ Branch 1 taken 1 time.
1 const BuildInfo info = GetBuildInfo();
35
1/1
✓ Branch 1 taken 1 time.
1 nb::dict d;
36
2/2
✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
1 d["git_sha"] = info.gitSha;
37
2/2
✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
1 d["git_dirty"] = info.gitDirty;
38
2/2
✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
1 d["build_date"] = info.buildDate;
39 2 return d;
40 1 },
41 "Build metadata of the compiled library: git_sha, git_dirty and build_date.");
42 44 }
43
44 /// @brief A log sink that calls `callback(level, category, message)` with the GIL held.
45 48 LogSink PythonLogSink(nb::callable callback) {
46
1/1
✓ Branch 2 taken 48 times.
48 auto function = std::make_shared<nb::callable>(std::move(callback));
47 96 return [function](const LogRecord& record) {
48 2 nb::gil_scoped_acquire gil;
49 try {
50
2/2
✓ Branch 2 taken 2 times.
✓ Branch 5 taken 2 times.
4 (*function)(LogLevelName(record.level), record.category, record.message);
51
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ } catch (nb::python_error& error) {
52 ✗ error.discard_as_unraisable("TerrainGen log sink");
53 }
54
1/1
✓ Branch 1 taken 48 times.
98 };
55 48 }
56
57 44 void BindLog(nb::module_& m) {
58
1/1
✓ Branch 1 taken 44 times.
44 m.def(
59 "set_log_level",
60 ✗ [](std::string_view name) {
61
1/1
✓ Branch 1 taken 8 times.
8 const std::optional<LogLevel> level = ParseLogLevel(name);
62
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 7 times.
8 if (!level) {
63 throw nb::value_error(
64
1/1
✓ Branch 2 taken 1 time.
1 "Log level must be trace, debug, info, warning, error or off");
65 }
66
1/1
✓ Branch 2 taken 7 times.
7 SetLogLevel(*level);
67 7 },
68
1/1
✓ Branch 1 taken 44 times.
44 "level"_a,
69 "Sets the lowest level TerrainGen logs: 'trace', 'debug', 'info', 'warning' (the "
70 "default), 'error' or 'off'.");
71
1/1
✓ Branch 1 taken 44 times.
44 m.def(
72
3/3
✓ Branch 2 taken 2 times.
✓ Branch 5 taken 2 times.
✓ Branch 8 taken 2 times.
4 "get_log_level", [] { return std::string(LogLevelName(GetLogLevel())); },
73 "The lowest level TerrainGen currently logs.");
74 44 m.def(
75 "set_log_sink",
76 ✗ [](std::optional<nb::callable> callback) {
77
6/8
✓ Branch 1 taken 48 times.
✓ Branch 2 taken 43 times.
✓ Branch 7 taken 48 times.
✓ Branch 11 taken 91 times.
✓ Branch 14 taken 48 times.
✓ Branch 15 taken 43 times.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
91 SetLogSink(callback ? PythonLogSink(std::move(*callback)) : nullptr);
78 91 },
79
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
88 "callback"_a.none(),
80 "Sends every log record to callback(level, category, message), or restores standard "
81 "error when callback is None. The package installs a sink that forwards to the logging "
82 "module on import. The callback runs with the GIL held, on whichever thread logged.");
83 44 }
84
85 44 void BindSurfacePoints(nb::class_<Surface>& surface) {
86 surface
87
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 .def("distance", &Surface::Distance, "a"_a, "b"_a,
88 "The distance in metres between two points, great-circle on a sphere.")
89
1/1
✓ Branch 1 taken 44 times.
44 .def("position", &Surface::Position, "point"_a,
90 "The 3D position (m) of a point; on a sphere, body-fixed with z north.")
91
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 .def("offset", &Surface::Offset, "centre"_a, "point"_a,
92 "`point` as (east, north) metres from `centre` (azimuthal equidistant on a sphere).")
93
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 .def("locate", &Surface::Locate, "centre"_a, "offset"_a,
94 "The point (east, north) metres from `centre`.")
95 44 .def(
96 "frame",
97 ✗ [](const Surface& s, const Vec2d& point) {
98
1/1
✓ Branch 1 taken 1 time.
1 const LocalFrame frame = s.Frame(point);
99 2 return std::tuple{frame.east, frame.north, frame.up};
100 },
101
1/1
✓ Branch 1 taken 44 times.
44 "point"_a, "The (east, north, up) unit vectors at a point.")
102
1/1
✓ Branch 1 taken 44 times.
44 .def("point_of", &Surface::PointOf, "direction"_a,
103 "The surface point of a direction from the sphere's centre.")
104
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 .def("expand", &Surface::Expand, "region"_a, "metres"_a,
105 "A box holding every point within `metres` of `region`.")
106
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 .def("contains", &Surface::Contains, "region"_a, "point"_a,
107 "Whether `region` holds `point`, across the antimeridian and at the poles on a "
108 "sphere.");
109 44 }
110
111 44 void BindRadialShape(nb::module_& m) {
112 ✗ nb::class_<RadialShape>(m, "RadialShape",
113 "A star-shaped body's radius along each direction from its centre, on "
114 "a radial ICQ grid of labels (i, j, face) interpolated bilinearly.")
115
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
88 .def(nb::init<std::size_t, std::vector<double>>(), "q"_a, "radii"_a,
116 "From 6 (q + 1)^2 label radii in metres, in ICQ file order (face, then j, then i).")
117
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 .def_static("sample", &RadialShape::Sample, "q"_a, "radius"_a,
118 "From `radius(direction)` in metres, evaluated at every label.")
119
4/4
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
✓ Branch 7 taken 44 times.
✓ Branch 10 taken 44 times.
44 .def_static("label_direction", &RadialShape::LabelDirection, "q"_a, "i"_a, "j"_a, "face"_a,
120 "The unit direction of label (i, j, face), face from 1 to 6.")
121
3/3
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
✓ Branch 7 taken 44 times.
44 .def("label_index", &RadialShape::LabelIndex, "i"_a, "j"_a, "face"_a)
122
1/1
✓ Branch 1 taken 44 times.
44 .def("at", &RadialShape::At, "direction"_a, "The radius in metres along `direction`.")
123 44 .def_prop_ro("q", &RadialShape::Q)
124 44 .def_prop_ro("radii", &RadialShape::Radii)
125 44 .def_prop_ro("min_radius", &RadialShape::MinRadius)
126 44 .def_prop_ro("max_radius", &RadialShape::MaxRadius)
127 88 .def_prop_ro("max_area_per_steradian", &RadialShape::MaxAreaPerSteradian)
128 46 .def("__eq__", [](const RadialShape& a, const RadialShape& b) { return a == b; });
129 44 }
130
131 44 void BindSurface(nb::module_& m) {
132 nb::class_<Surface> surface(m, "Surface",
133 "What a terrain lies on: a plane, with coordinates in metres, or "
134 "a sphere, with coordinates in degrees of longitude and latitude "
135 44 "and lengths in metres.");
136 ✗ surface.def(nb::init<>(), "A plane.")
137 44 .def_static("plane", &Surface::Plane)
138
1/1
✓ Branch 1 taken 44 times.
44 .def_static("sphere", &Surface::Sphere, "radius"_a, "A sphere of `radius` metres.")
139
3/3
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
✓ Branch 7 taken 44 times.
44 .def_static("ellipsoid", &Surface::Ellipsoid, "a"_a, "b"_a, "c"_a,
140 "A triaxial ellipsoid with semi-axes `a`, `b` and `c` metres along x, y and z; "
141 "equal radii give a sphere.")
142 44 .def_static(
143 "shape_model",
144 ✗ [](const RadialShape& shape) {
145
2/2
✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
1 return Surface::ShapeModel(std::make_shared<const RadialShape>(shape));
146 },
147
1/1
✓ Branch 1 taken 44 times.
44 "shape"_a, "A star-shaped body whose radius by direction is `shape`.")
148 44 .def_prop_ro(
149 "shape",
150 ✗ [](const Surface& s) -> std::optional<RadialShape> {
151
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
2 return s.Shape() ? std::optional<RadialShape>(*s.Shape()) : std::nullopt;
152 },
153 "A copy of the body's shape model; None for a plane, sphere or ellipsoid.")
154 44 .def_prop_ro("is_sphere", &Surface::IsSphere)
155 44 .def_prop_ro("min_radius", &Surface::MinRadius)
156 44 .def_prop_ro("max_radius", &Surface::MaxRadius)
157 44 .def_prop_ro("radii", &Surface::Radii, "The semi-axes (a, b, c) in metres.")
158
1/1
✓ Branch 1 taken 44 times.
44 .def("radius_at", &Surface::RadiusAt, "point"_a,
159 "The distance in metres from the centre to the surface at (longitude, latitude).")
160
1/1
✓ Branch 1 taken 44 times.
44 .def("area_per_steradian", &Surface::AreaPerSteradian, "point"_a,
161 "The surface area per steradian of direction from the centre at a point, in m^2.")
162 44 .def_prop_ro("is_body", &Surface::IsBody)
163 44 .def_prop_ro("radius", &Surface::Radius)
164 88 .def_prop_ro("unit_length", &Surface::UnitLength,
165 "Metres per coordinate unit: 1 on a plane, metres per degree along a "
166 "meridian on a sphere.")
167 47 .def("__eq__", [](const Surface& a, const Surface& b) { return a == b; });
168
1/1
✓ Branch 1 taken 44 times.
44 BindSurfacePoints(surface);
169 44 }
170
171 44 void BindCellIndex(nb::module_& m) {
172 ✗ nb::class_<CellIndex>(m, "CellIndex", "Cell (i, j) of a surface's cells.")
173 44 .def(
174 "__init__",
175 6 [](CellIndex* self, std::int32_t i, std::int32_t j) { new (self) CellIndex{i, j}; },
176
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 "i"_a, "j"_a)
177 44 .def_rw("i", &CellIndex::i)
178 44 .def_rw("j", &CellIndex::j)
179 49 .def("__eq__", [](const CellIndex& a, const CellIndex& b) { return a == b; })
180 44 .def("__repr__", [](const CellIndex& c) {
181
0/4
✗ Branch 3 not taken.
✗ Branch 6 not taken.
✗ Branch 9 not taken.
✗ Branch 12 not taken.
✗ return "CellIndex(" + std::to_string(c.i) + ", " + std::to_string(c.j) + ")";
182 });
183 44 }
184
185 44 void BindCells(nb::module_& m) {
186 44 BindCellIndex(m);
187 44 nb::class_<SurfaceCells>(m, "SurfaceCells",
188 "Cells that tile a surface, which crater and boulder generation draws "
189 "from one cell at a time.")
190
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 .def("cells_covering", &SurfaceCells::CellsCovering, "region"_a, "cell_size"_a,
191 "The cells of `cell_size` metres that may hold points of `region`.")
192
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 .def("cell_area", &SurfaceCells::CellArea, "cell"_a, "cell_size"_a,
193 "The cell's area in square metres.")
194
4/4
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
✓ Branch 7 taken 44 times.
✓ Branch 10 taken 44 times.
88 .def("sample_point", &SurfaceCells::SamplePoint, "u"_a, "v"_a, "cell"_a, "cell_size"_a,
195 "The point at (u, v) in [0, 1)^2 of the cell, uniform in area.");
196 88 nb::class_<PlanarCells, SurfaceCells>(m, "PlanarCells", "Square cells of a plane.")
197 44 .def(nb::init<>());
198 ✗ nb::class_<CubeSphereCells, SurfaceCells>(
199 m, "CubeSphereCells",
200 "Equiangular cube-sphere cells of a sphere of `radius` metres, of nearly equal area, or "
201 "of a body `Surface`; on an ellipsoid they are sized by its shortest semi-axis.")
202
1/1
✓ Branch 1 taken 44 times.
88 .def(nb::init<double>(), "radius"_a)
203
1/1
✓ Branch 1 taken 44 times.
88 .def(nb::init<const Surface&>(), "surface"_a)
204 44 .def(
205 "cell_sampling",
206 ✗ [](const CubeSphereCells& cells, const CellIndex& cell, double cellSize) {
207
1/1
✓ Branch 1 taken 3 times.
3 const SurfaceCells::Sampling sampling = cells.CellSampling(cell, cellSize);
208
1/1
✓ Branch 1 taken 3 times.
6 return nb::make_tuple(sampling.area, sampling.bound);
209 },
210
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 "cell"_a, "cell_size"_a,
211 "(area, bound): draw counts over `area` m^2 and keep a point with chance "
212 "weight(point) / bound; bound 0 keeps every point.")
213
1/1
✓ Branch 1 taken 44 times.
44 .def("weight", &CubeSphereCells::Weight, "point"_a, "The area per steradian at a point.")
214
3/3
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
✓ Branch 7 taken 44 times.
44 .def("cell_of", &CubeSphereCells::CellOf, "longitude"_a, "latitude"_a, "cell_size"_a,
215 "The cell holding a point.")
216
3/3
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
✓ Branch 7 taken 44 times.
132 .def("neighbours", &CubeSphereCells::Neighbours, "cell"_a, "cell_size"_a, "rings"_a = 1,
217 "The cells within `rings` of `cell`, across face edges too.")
218
1/1
✓ Branch 1 taken 44 times.
44 .def("cells_per_edge", &CubeSphereCells::CellsPerEdge, "cell_size"_a)
219 44 .def_prop_ro("radius", &CubeSphereCells::Radius)
220
3/3
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
✓ Branch 7 taken 44 times.
88 .def_static("direction", &CubeSphereCells::Direction, "face"_a, "alpha"_a, "beta"_a,
221 "The unit direction of equiangular coordinates (alpha, beta) on cube face "
222 "`face`.");
223 44 }
224
225 } // namespace
226
227 44 void BindUtil(nb::module_& m) {
228 44 BindVersion(m);
229 44 BindLog(m);
230 44 BindRadialShape(m);
231 44 BindSurface(m);
232 44 BindCells(m);
233
3/3
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
✓ Branch 7 taken 44 times.
44 m.def("exponential_integral", &ExponentialIntegral, "n"_a, "x"_a,
234 "The generalized exponential integral E_n(x), as Golombek and Rapp's model uses.");
235 44 }
236
237 } // namespace terraingen::python
238