GCC Code Coverage Report


Directory: libimgdoc2/
File: libimgdoc2/src/db/sqlite/custom_functions.cpp
Date: 2025-02-03 12:41:04
Exec Total Coverage
Lines: 40 112 35.7%
Functions: 5 9 55.6%
Branches: 16 142 11.3%

Line Branch Exec Source
1 // SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH
2 //
3 // SPDX-License-Identifier: MIT
4
5 #include <limits>
6 #include "custom_functions.h"
7 #include <stdexcept>
8
9 #include "exceptions.h"
10
11 using namespace imgdoc2;
12
13 830 /*static*/const char* SqliteCustomFunctions::GetQueryFunctionName(Query query)
14 {
15
3/4
✓ Branch 0 taken 272 times.
✓ Branch 1 taken 286 times.
✓ Branch 2 taken 272 times.
✗ Branch 3 not taken.
830 switch (query)
16 {
17 272 case Query::RTree_LineSegment2D:
18 272 return "LineThroughPoints2d";
19 286 case Query::RTree_PlaneAabb3D:
20 286 return "PlaneNormalDistance3d";
21 272 case Query::Scalar_DoesIntersectWithLine:
22 272 return "IntersectsWithLine";
23 }
24
25 throw std::invalid_argument("Unknown enumeration");
26 }
27
28 272 /*static*/void SqliteCustomFunctions::SetupCustomQueries(sqlite3* database)
29 {
30 // TODO(JBL):
31 // * Maybe consider this https://www.sqlite.org/c3ref/auto_extension.html instead of registering this stuff here by hand.
32 // * It would also be nice to have a loadable extension with this functionality (https://www.sqlite.org/loadext.html).
33 272 auto return_code = sqlite3_rtree_query_callback(
34 database,
35 SqliteCustomFunctions::GetQueryFunctionName(SqliteCustomFunctions::Query::RTree_LineSegment2D),
36 SqliteCustomFunctions::LineThrough2Points2d_Query,
37 nullptr,
38 nullptr);
39
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 272 times.
272 if (return_code != SQLITE_OK)
40 {
41 throw database_exception("Error registering \"RTree_LineSegment2D\".", return_code);
42 }
43
44 272 return_code = sqlite3_rtree_query_callback(
45 database,
46 SqliteCustomFunctions::GetQueryFunctionName(SqliteCustomFunctions::Query::RTree_PlaneAabb3D),
47 SqliteCustomFunctions::Plane3d_Query,
48 nullptr,
49 nullptr);
50
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 272 times.
272 if (return_code != SQLITE_OK)
51 {
52 throw database_exception("Error registering \"RTree_PlaneAabb3D\".", return_code);
53 }
54
55 272 return_code = sqlite3_create_function_v2(
56 database,
57 SqliteCustomFunctions::GetQueryFunctionName(SqliteCustomFunctions::Query::Scalar_DoesIntersectWithLine),
58 kNumberOfArgumentsForScalarFunctionDoesIntersectWithLine,
59 SQLITE_UTF8 | SQLITE_DETERMINISTIC | SQLITE_DIRECTONLY,
60 nullptr,
61 SqliteCustomFunctions::ScalarFunctionDoesIntersectWithLine,
62 nullptr,
63 nullptr,
64 nullptr);
65
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 272 times.
272 if (return_code != SQLITE_OK)
66 {
67 throw database_exception("Error registering \"Scalar_DoesIntersectWithLine\".", return_code);
68 }
69 272 }
70
71 /*static*/int SqliteCustomFunctions::LineThrough2Points2d_Query(sqlite3_rtree_query_info* info)
72 {
73 auto* pLine = static_cast<LineThruTwoPointsD*>(info->pUser);
74 if (pLine == nullptr)
75 {
76 /* If pUser is still 0, then the parameter values have not been tested
77 ** for correctness or stored into a "LineThruTwoPoints" structure yet. Do this now. */
78
79 /* This geometry callback is for use with a 2-dimensional r-tree table.
80 ** Return an error if the table does not have exactly 2 dimensions. */
81 if (info->nCoord != 4)
82 {
83 return SQLITE_ERROR;
84 }
85
86 /* Test that the correct number of parameters (4) have been supplied,
87 */
88 if (info->nParam != 4)
89 {
90 return SQLITE_ERROR;
91 }
92
93 /*Allocate a structure to cache parameter data in.Return SQLITE_NOMEM
94 ** if the allocation fails.*/
95 pLine = static_cast<LineThruTwoPointsD*>(info->pUser = sqlite3_malloc(sizeof(LineThruTwoPointsD)));
96 if (pLine == nullptr)
97 {
98 return SQLITE_NOMEM;
99 }
100
101 info->xDelUser = Free_LineThruTwoPointsD;
102
103 // NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) pointer-arithmetic is fine here
104 pLine->a.x = info->aParam[0];
105 pLine->a.y = info->aParam[1];
106 pLine->b.x = info->aParam[2];
107 pLine->b.y = info->aParam[3];
108 // NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic)
109 }
110
111 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) pointer-arithmetic is fine here
112 const RectangleD rect(info->aCoord[0], info->aCoord[2], info->aCoord[1] - info->aCoord[0], info->aCoord[3] - info->aCoord[2]);
113
114 // check whether the start-/end-point is inside the rectangle
115 const bool firstPointInside = rect.IsPointInside(pLine->a);
116 const bool secondPointInside = rect.IsPointInside(pLine->b);
117 if (firstPointInside && secondPointInside)
118 {
119 // if both are inside, we report "fully within"
120 info->eWithin = FULLY_WITHIN;
121 }
122 else
123 {
124 if (firstPointInside || secondPointInside)
125 {
126 // if one of the start-/end-point is inside - then report "partly within"
127 info->eWithin = PARTLY_WITHIN;
128 }
129
130 // now we determine whether the line-segment "pLine" intersects with the diagonals
131 if (SqliteCustomFunctions::DoLinesIntersect(pLine->a, pLine->b, PointD(rect.x, rect.y), PointD(rect.x + rect.w, rect.y + rect.h)) ||
132 SqliteCustomFunctions::DoLinesIntersect(pLine->a, pLine->b, PointD(rect.x, rect.y + rect.h), PointD(rect.x + rect.w, rect.y)))
133 {
134 info->eWithin = PARTLY_WITHIN;
135 }
136
137 else
138 {
139 info->eWithin = NOT_WITHIN;
140 }
141 }
142
143 info->rScore = info->iLevel;
144 return SQLITE_OK;
145 }
146
147 8468 /*static*/int SqliteCustomFunctions::Plane3d_Query(sqlite3_rtree_query_info* info)
148 {
149 8468 auto* pPlane = static_cast<Plane_NormalAndDistD*>(info->pUser);
150
2/2
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 8454 times.
8468 if (pPlane == nullptr)
151 {
152 /* If pUser is still 0, then the parameter values have not been tested
153 ** for correctness or stored into a "LineThruTwoPoints" structure yet. Do this now. */
154
155 /* This geometry callback is for use with a 3-dimensional r-tree table.
156 ** Return an error if the table does not have exactly 3 dimensions. */
157
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
14 if (info->nCoord != kNumberOfParametersExpectedForPlane3DQuery)
158 {
159 return SQLITE_ERROR;
160 }
161
162 /* Test that the correct number of parameters (4) have been supplied */
163
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
14 if (info->nParam != 4)
164 {
165 return SQLITE_ERROR;
166 }
167
168 /*Allocate a structure to cache parameter data in.Return SQLITE_NOMEM
169 ** if the allocation fails.*/
170
1/2
✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
14 pPlane = static_cast<Plane_NormalAndDistD*>(info->pUser = sqlite3_malloc(sizeof(Plane_NormalAndDistD)));
171
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
14 if (pPlane == nullptr)
172 {
173 return SQLITE_NOMEM;
174 }
175
176 14 info->xDelUser = Free_PlaneNormalAndDistD;
177 // NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) pointer-arithmetic is fine here
178 14 pPlane->normal.x = info->aParam[0];
179 14 pPlane->normal.y = info->aParam[1];
180 14 pPlane->normal.z = info->aParam[2];
181 14 pPlane->distance = info->aParam[3];
182 // NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic)
183 }
184
185 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic,cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) pointer-arithmetic is fine here
186
1/2
✓ Branch 1 taken 8468 times.
✗ Branch 2 not taken.
8468 CuboidD aabb(info->aCoord[0], info->aCoord[2], info->aCoord[4], info->aCoord[1] - info->aCoord[0], info->aCoord[3] - info->aCoord[2], info->aCoord[5] - info->aCoord[4]);
187
1/2
✓ Branch 1 taken 8468 times.
✗ Branch 2 not taken.
8468 const bool doIntersect = SqliteCustomFunctions::DoAabbAndPlaneIntersect(aabb, *pPlane);
188
2/2
✓ Branch 0 taken 1794 times.
✓ Branch 1 taken 6674 times.
8468 if (doIntersect)
189 {
190 1794 info->eWithin = PARTLY_WITHIN;
191 }
192 else
193 {
194 6674 info->eWithin = NOT_WITHIN;
195 }
196
197 8468 info->rScore = info->iLevel;
198 8468 return SQLITE_OK;
199 }
200
201 /*static*/void SqliteCustomFunctions::Free_LineThruTwoPointsD(void* pointer)
202 {
203 sqlite3_free(pointer);
204 }
205
206 14 /*static*/void SqliteCustomFunctions::Free_PlaneNormalAndDistD(void* pointer)
207 {
208 14 sqlite3_free(pointer);
209 14 }
210
211 /*static*/bool SqliteCustomFunctions::DoLinesIntersect(const imgdoc2::PointD& point_a1, const imgdoc2::PointD& point_a2, const imgdoc2::PointD& point_b1, const imgdoc2::PointD& point_b2)
212 {
213 const PointD point_b(point_a2.x - point_a1.x, point_a2.y - point_a1.y);
214 const PointD point_d(point_b2.x - point_b1.x, point_b2.y - point_b1.y);
215
216 const double bDotDPerp = point_b.x * point_d.y - point_b.y * point_d.x;
217
218 // if b dot d == 0, it means the lines are parallel so have infinite intersection points
219 if (std::fabs(bDotDPerp) <= std::numeric_limits<double>::epsilon())
220 {
221 return false;
222 }
223
224 const PointD point_c(point_b1.x - point_a1.x, point_b1.y - point_a1.y);// = point_b1 - point_a1;
225 const double slope_t = (point_c.x * point_d.y - point_c.y * point_d.x) / bDotDPerp;
226 if (slope_t < 0 || slope_t > 1)
227 {
228 return false;
229 }
230
231 const double slope_u = (point_c.x * point_b.y - point_c.y * point_b.x) / bDotDPerp;
232 if (slope_u < 0 || slope_u > 1)
233 {
234 // NOLINTNEXTLINE(readability-simplify-boolean-expr)
235 return false;
236 }
237
238 return true;
239 }
240
241 8468 /*static*/bool SqliteCustomFunctions::DoAabbAndPlaneIntersect(const imgdoc2::CuboidD& aabb, const imgdoc2::Plane_NormalAndDistD& plane)
242 {
243 8468 return aabb.DoesIntersectWith(plane);
244 }
245
246 /*static*/void SqliteCustomFunctions::ScalarFunctionDoesIntersectWithLine(sqlite3_context* context, int argc, sqlite3_value** argv)
247 {
248 if (argc != kNumberOfArgumentsForScalarFunctionDoesIntersectWithLine)
249 {
250 return sqlite3_result_null(context);
251 }
252
253 // NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) pointer-arithmetic is fine here
254 const double rect_x = sqlite3_value_double(argv[0]);
255 const double rect_y = sqlite3_value_double(argv[1]);
256 const double rect_width = sqlite3_value_double(argv[2]);
257 const double rect_height = sqlite3_value_double(argv[3]);
258 const double p1x = sqlite3_value_double(argv[4]);
259 const double p1y = sqlite3_value_double(argv[5]);
260 const double p2x = sqlite3_value_double(argv[6]);
261 const double p2y = sqlite3_value_double(argv[7]);
262 // NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic)
263
264 const RectangleD rect(rect_x, rect_y, rect_width, rect_height);
265 const LineThruTwoPointsD twoPoints{ {p1x, p1y}, {p2x, p2y} };
266
267 bool doesIntersect = false;
268
269 // check whether the start-/end-point is inside the rectangle
270 if (rect.IsPointInside(twoPoints.a) || rect.IsPointInside(twoPoints.b))
271 {
272 // if at least one point is inside the rectangle, then we are done
273 doesIntersect = true;
274 }
275 else
276 {
277 // now we determine whether the line-segment "twoPoints" intersects with the diagonals
278 if (SqliteCustomFunctions::DoLinesIntersect(twoPoints.a, twoPoints.b, PointD(rect.x, rect.y), PointD(rect.x + rect.w, rect.y + rect.h)) ||
279 SqliteCustomFunctions::DoLinesIntersect(twoPoints.a, twoPoints.b, PointD(rect.x, rect.y + rect.h), PointD(rect.x + rect.w, rect.y)))
280 {
281 doesIntersect = true;
282 }
283 }
284
285 return sqlite3_result_int(context, doesIntersect ? 1 : 0);
286 }
287