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 |