/src/postgis/liblwgeom/lwnurbscurve.c
Line | Count | Source |
1 | | /********************************************************************** |
2 | | * |
3 | | * PostGIS - Spatial Types for PostgreSQL |
4 | | * http://postgis.net |
5 | | * |
6 | | * PostGIS is free software: you can redistribute it and/or modify |
7 | | * it under the terms of the GNU General Public License as published by |
8 | | * the Free Software Foundation, either version 2 of the License, or |
9 | | * (at your option) any later version. |
10 | | * |
11 | | * PostGIS is distributed in the hope that it will be useful, |
12 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 | | * GNU General Public License for more details. |
15 | | * |
16 | | * You should have received a copy of the GNU General Public License |
17 | | * along with PostGIS. If not, see <http://www.gnu.org/licenses/>. |
18 | | * |
19 | | ********************************************************************** |
20 | | * |
21 | | * Copyright (C) 2025 PostGIS contributors |
22 | | * Basic NURBS curve support for PostGIS |
23 | | * |
24 | | **********************************************************************/ |
25 | | |
26 | | #include <stdio.h> |
27 | | #include <stdlib.h> |
28 | | #include <string.h> |
29 | | #include <math.h> |
30 | | #include "liblwgeom_internal.h" |
31 | | #include "lwgeom_log.h" |
32 | | |
33 | | /** |
34 | | * Construct a new NURBS curve. |
35 | | * |
36 | | * Creates a NURBS (Non-Uniform Rational B-Spline) curve object from the |
37 | | * provided control points, optional weights, and optional knot vector. |
38 | | * |
39 | | * Validation and ownership: |
40 | | * - Returns NULL if degree is outside [1,10]. |
41 | | * - If `weights` is non-NULL, `nweights` must equal the number of control points. |
42 | | * - If `knots` is non-NULL, `nknots` must equal npoints + degree + 1. |
43 | | * - Ownership of `points` is transferred to the returned curve (caller must not free it). |
44 | | * - `weights` and `knots`, if provided, are deep-copied into the new curve. |
45 | | * |
46 | | * Behavioral notes: |
47 | | * - If `weights` is NULL, the curve is treated as non-rational (implicit weights = 1.0). |
48 | | * - If `knots` is NULL, a uniform clamped knot vector will be generated on demand. |
49 | | * |
50 | | * @param srid Spatial reference identifier for the curve. |
51 | | * @param bbox Bounding box for the curve (can be NULL). |
52 | | * @param degree Polynomial degree of the curve (1..10). |
53 | | * @param points Control points array; ownership is transferred to the returned curve. |
54 | | * @param weights Optional weights array; copied when non-NULL. |
55 | | * @param knots Optional knot vector; copied when non-NULL. |
56 | | * @param nweights Number of entries in `weights` (must match point count if `weights` provided). |
57 | | * @param nknots Number of entries in `knots` (must equal npoints + degree + 1 if `knots` provided). |
58 | | * @return Pointer to the newly allocated LWNURBSCURVE, or NULL on invalid parameters. |
59 | | */ |
60 | | LWNURBSCURVE * |
61 | | lwnurbscurve_construct(int32_t srid, GBOX *bbox, uint32_t degree, POINTARRAY *points, |
62 | | const double *weights, const double *knots, uint32_t nweights, uint32_t nknots) |
63 | 0 | { |
64 | 0 | LWNURBSCURVE *result; |
65 | | |
66 | | /* Validate degree: must be between 1 and 10 */ |
67 | 0 | if (degree < 1 || degree > 10) |
68 | 0 | { |
69 | 0 | lwerror("NURBS: degree %u outside valid range [1,10]", degree); |
70 | 0 | return NULL; |
71 | 0 | } |
72 | | |
73 | | /* Basic invariants */ |
74 | 0 | if (points && points->npoints > 0 && points->npoints < degree + 1) |
75 | 0 | { |
76 | 0 | lwerror("NURBS: npoints (%u) must be at least degree + 1 (%u)", points->npoints, degree + 1); |
77 | 0 | return NULL; |
78 | 0 | } |
79 | 0 | if (points && weights && nweights != points->npoints) |
80 | 0 | { |
81 | 0 | lwerror("NURBS: nweights (%u) must equal number of control points (%u)", nweights, points->npoints); |
82 | 0 | return NULL; |
83 | 0 | } |
84 | 0 | if (points && knots && nknots != points->npoints + degree + 1) |
85 | 0 | { |
86 | 0 | lwerror("NURBS: nknots (%u) must equal npoints + degree + 1 (%u)", nknots, points->npoints + degree + 1); |
87 | 0 | return NULL; |
88 | 0 | } |
89 | | |
90 | | /* Validate weight values */ |
91 | 0 | if (nweights > 0 && !weights) |
92 | 0 | { |
93 | 0 | lwerror("NURBS: weights is NULL but nweights = %u", nweights); |
94 | 0 | return NULL; |
95 | 0 | } |
96 | 0 | for (uint32_t i = 0; i < nweights; i++) |
97 | 0 | { |
98 | 0 | if (!isfinite(weights[i]) || weights[i] <= 0.0) |
99 | 0 | { |
100 | 0 | lwerror("NURBS: weight[%u] = %g must be finite and > 0", i, weights[i]); |
101 | 0 | return NULL; |
102 | 0 | } |
103 | 0 | } |
104 | | |
105 | | /* Validate knot vector monotonicity */ |
106 | 0 | if (nknots > 0 && !knots) |
107 | 0 | { |
108 | 0 | lwerror("NURBS: knots is NULL but nknots = %u", nknots); |
109 | 0 | return NULL; |
110 | 0 | } |
111 | 0 | for (uint32_t i = 1; i < nknots; i++) |
112 | 0 | { |
113 | 0 | if (!isfinite(knots[i]) || knots[i] < knots[i - 1]) |
114 | 0 | { |
115 | 0 | lwerror("NURBS: knot[%u] = %g must be finite and >= knot[%u] = %g", |
116 | 0 | i, knots[i], i - 1, knots[i - 1]); |
117 | 0 | return NULL; |
118 | 0 | } |
119 | 0 | } |
120 | | |
121 | 0 | result = lwalloc(sizeof(LWNURBSCURVE)); |
122 | 0 | result->type = NURBSCURVETYPE; |
123 | 0 | result->srid = srid; |
124 | 0 | result->bbox = bbox; |
125 | 0 | result->degree = degree; |
126 | 0 | result->nweights = nweights; |
127 | 0 | result->nknots = nknots; |
128 | | |
129 | | /* Inherit dimensional flags from control points */ |
130 | 0 | if (points) { |
131 | 0 | result->flags = points->flags; |
132 | 0 | result->points = points; /* Take ownership - caller must not free */ |
133 | 0 | } else { |
134 | | /* Empty curve: no dimensional information available */ |
135 | 0 | result->flags = 0; |
136 | 0 | result->points = NULL; |
137 | 0 | } |
138 | | |
139 | | /* Deep copy weights array for rational NURBS */ |
140 | 0 | if (weights && nweights > 0) { |
141 | 0 | result->weights = lwalloc(sizeof(double) * nweights); |
142 | 0 | memcpy(result->weights, weights, sizeof(double) * nweights); |
143 | 0 | } else { |
144 | | /* Non-rational NURBS: all weights implicitly 1.0 */ |
145 | 0 | result->weights = NULL; |
146 | 0 | } |
147 | | |
148 | | /* Deep copy knot vector for explicit parameterization */ |
149 | 0 | if (knots && nknots > 0) { |
150 | 0 | result->knots = lwalloc(sizeof(double) * nknots); |
151 | 0 | memcpy(result->knots, knots, sizeof(double) * nknots); |
152 | 0 | } else { |
153 | | /* No knots: uniform parameterization will be generated when needed */ |
154 | 0 | result->knots = NULL; |
155 | 0 | } |
156 | |
|
157 | 0 | return result; |
158 | 0 | } |
159 | | |
160 | | /** |
161 | | * Construct an empty NURBS curve |
162 | | * |
163 | | * Creates a valid but empty NURBS curve with specified dimensional flags. |
164 | | * Empty curves contain no control points but maintain proper structure |
165 | | * for dimensional consistency in PostGIS operations. |
166 | | * |
167 | | * @param srid Spatial reference system identifier |
168 | | * @param hasz True if curve should support Z coordinates |
169 | | * @param hasm True if curve should support M coordinates |
170 | | * @return Empty NURBS curve with specified dimensions |
171 | | */ |
172 | | LWNURBSCURVE * |
173 | | lwnurbscurve_construct_empty(int32_t srid, char hasz, char hasm) |
174 | 0 | { |
175 | 0 | LWNURBSCURVE *result = lwalloc(sizeof(LWNURBSCURVE)); |
176 | 0 | result->type = NURBSCURVETYPE; |
177 | 0 | result->flags = lwflags(hasz, hasm, 0); |
178 | 0 | result->srid = srid; |
179 | 0 | result->bbox = NULL; |
180 | 0 | result->degree = 1; |
181 | 0 | result->points = ptarray_construct_empty(hasz, hasm, 1); |
182 | 0 | result->weights = NULL; |
183 | 0 | result->nweights = 0; |
184 | 0 | result->knots = NULL; |
185 | 0 | result->nknots = 0; |
186 | 0 | return result; |
187 | 0 | } |
188 | | |
189 | | /** |
190 | | * Free NURBS curve memory |
191 | | * |
192 | | * Recursively frees all memory associated with a NURBS curve, |
193 | | * including bounding box, control points, weights, and knot vector. |
194 | | * Safe to call with NULL pointer. |
195 | | * |
196 | | * @param curve NURBS curve to free (can be NULL) |
197 | | */ |
198 | | void |
199 | | lwnurbscurve_free(LWNURBSCURVE *curve) |
200 | 0 | { |
201 | 0 | if (!curve) return; |
202 | | |
203 | 0 | if (curve->bbox) |
204 | 0 | lwfree(curve->bbox); |
205 | |
|
206 | 0 | if (curve->points) |
207 | 0 | ptarray_free(curve->points); |
208 | |
|
209 | 0 | if (curve->weights) |
210 | 0 | lwfree(curve->weights); |
211 | |
|
212 | 0 | if (curve->knots) |
213 | 0 | lwfree(curve->knots); |
214 | |
|
215 | 0 | lwfree(curve); |
216 | 0 | } |
217 | | |
218 | | /** |
219 | | * Deep clone NURBS curve |
220 | | * |
221 | | * Creates a complete independent copy of a NURBS curve. |
222 | | * All arrays (points, weights, knots) are deep-copied, |
223 | | * ensuring the clone can be modified without affecting the original. |
224 | | * |
225 | | * @param curve Source NURBS curve to clone |
226 | | * @return Deep copy of the curve, or NULL if input is NULL |
227 | | */ |
228 | | LWNURBSCURVE * |
229 | | lwnurbscurve_clone_deep(const LWNURBSCURVE *curve) |
230 | 0 | { |
231 | 0 | if (!curve) return NULL; |
232 | | |
233 | | /* Deep clone control points array */ |
234 | 0 | POINTARRAY *points = curve->points ? ptarray_clone_deep(curve->points) : NULL; |
235 | | |
236 | | /* Clone bbox if it exists */ |
237 | 0 | GBOX *bbox = curve->bbox ? gbox_copy(curve->bbox) : NULL; |
238 | | |
239 | | /* lwnurbscurve_construct will deep-copy weights and knots arrays */ |
240 | 0 | LWNURBSCURVE *result = lwnurbscurve_construct(curve->srid, bbox, curve->degree, points, |
241 | 0 | curve->weights, curve->knots, |
242 | 0 | curve->nweights, curve->nknots); |
243 | 0 | if (!result) |
244 | 0 | { |
245 | 0 | ptarray_free(points); |
246 | 0 | if (bbox) lwfree(bbox); |
247 | 0 | } |
248 | 0 | return result; |
249 | 0 | } |
250 | | |
251 | | LWNURBSCURVE * |
252 | | lwnurbscurve_force_dims(const LWNURBSCURVE *curve, int hasz, int hasm, double zval, double mval) |
253 | 0 | { |
254 | 0 | POINTARRAY *points; |
255 | |
|
256 | 0 | if (!curve) |
257 | 0 | return NULL; |
258 | | |
259 | 0 | if (!curve->points || curve->points->npoints == 0) |
260 | 0 | return lwnurbscurve_construct_empty(curve->srid, hasz, hasm); |
261 | | |
262 | 0 | points = ptarray_force_dims(curve->points, hasz, hasm, zval, mval); |
263 | |
|
264 | 0 | LWNURBSCURVE *result = lwnurbscurve_construct(curve->srid, NULL, curve->degree, points, |
265 | 0 | curve->weights, curve->knots, |
266 | 0 | curve->nweights, curve->nknots); |
267 | 0 | if (!result) |
268 | 0 | { |
269 | 0 | ptarray_free(points); |
270 | 0 | } |
271 | 0 | return result; |
272 | 0 | } |
273 | | |
274 | | /** |
275 | | * Generate uniform knot vector for NURBS curve |
276 | | * |
277 | | * Creates a clamped uniform knot vector suitable for standard NURBS curves. |
278 | | * The knot vector has: |
279 | | * - (degree+1) repeated knots at the start (value 0.0) for clamping |
280 | | * - (degree+1) repeated knots at the end (value 1.0) for clamping |
281 | | * - Internal knots uniformly distributed between 0 and 1 |
282 | | * |
283 | | * This ensures the curve starts at the first control point and ends |
284 | | * at the last control point (clamped behavior). |
285 | | * |
286 | | * Mathematical constraint: nknots = npoints + degree + 1 |
287 | | * |
288 | | * @param degree Polynomial degree of the curve |
289 | | * @param npoints Number of control points |
290 | | * @param nknots_out Output parameter for knot vector size |
291 | | * @return Newly allocated uniform knot vector |
292 | | */ |
293 | | static double * |
294 | | lwnurbscurve_generate_uniform_knots(uint32_t degree, uint32_t npoints, uint32_t *nknots_out) |
295 | 0 | { |
296 | 0 | double *knots; |
297 | 0 | uint32_t nknots, i; |
298 | | |
299 | | /* Input validation to prevent underflow */ |
300 | 0 | if (degree == 0 || npoints < degree + 1) { |
301 | 0 | if (nknots_out) *nknots_out = 0; |
302 | 0 | return NULL; |
303 | 0 | } |
304 | | |
305 | 0 | nknots = npoints + degree + 1; |
306 | |
|
307 | 0 | knots = lwalloc(sizeof(double) * nknots); |
308 | 0 | if (!knots) { |
309 | 0 | *nknots_out = 0; |
310 | 0 | return NULL; |
311 | 0 | } |
312 | | |
313 | | /* Clamp start: first (degree+1) knots set to 0.0 */ |
314 | 0 | for (i = 0; i <= degree; i++) { |
315 | 0 | knots[i] = 0.0; |
316 | 0 | } |
317 | | |
318 | | /* Clamp end: last (degree+1) knots set to 1.0 */ |
319 | 0 | for (i = nknots - degree - 1; i < nknots; i++) { |
320 | 0 | knots[i] = 1.0; |
321 | 0 | } |
322 | | |
323 | | /* Distribute internal knots uniformly in (0,1) interval */ |
324 | 0 | if (nknots > 2 * (degree + 1)) { |
325 | 0 | uint32_t internal_knots = nknots - 2 * (degree + 1); |
326 | 0 | for (i = 0; i < internal_knots; i++) { |
327 | | /* Uniform distribution: 1/(n+1), 2/(n+1), ..., n/(n+1) */ |
328 | 0 | knots[degree + 1 + i] = (double)(i + 1) / (internal_knots + 1); |
329 | 0 | } |
330 | 0 | } |
331 | |
|
332 | 0 | *nknots_out = nknots; |
333 | 0 | return knots; |
334 | 0 | } |
335 | | |
336 | | /** |
337 | | * Retrieve a knot vector suitable for WKB serialization. |
338 | | * |
339 | | * Returns a newly-allocated knot vector for the given NURBS curve. If the curve |
340 | | * has an explicit knot vector it is deep-copied; otherwise a clamped uniform |
341 | | * knot vector is generated from the curve degree and number of control points. |
342 | | * |
343 | | * The caller is responsible for freeing the returned array. If `curve` is NULL |
344 | | * or has no control points, `*nknots_out` is set to 0 and NULL is returned. |
345 | | * |
346 | | * @param curve Source NURBS curve. |
347 | | * @param nknots_out Output location which will be set to the number of knots |
348 | | * in the returned array (set to 0 on NULL/empty input). |
349 | | * @return Newly allocated array of knots, or NULL for invalid/empty curves. |
350 | | */ |
351 | | double * |
352 | | lwnurbscurve_get_or_generate_knots(const LWNURBSCURVE *curve, uint32_t *nknots_out) |
353 | 0 | { |
354 | | /* Validate input parameters */ |
355 | 0 | if (!curve || !curve->points) { |
356 | 0 | *nknots_out = 0; |
357 | 0 | return NULL; |
358 | 0 | } |
359 | | |
360 | | /* Use explicit knot vector if available */ |
361 | 0 | if (curve->knots && curve->nknots > 0) { |
362 | 0 | double *knots = lwalloc(sizeof(double) * curve->nknots); |
363 | 0 | memcpy(knots, curve->knots, sizeof(double) * curve->nknots); |
364 | 0 | *nknots_out = curve->nknots; |
365 | 0 | return knots; |
366 | 0 | } |
367 | | |
368 | | /* Generate uniform knot vector on demand */ |
369 | 0 | return lwnurbscurve_generate_uniform_knots(curve->degree, curve->points->npoints, nknots_out); |
370 | 0 | } |
371 | | |
372 | | /** |
373 | | * Cast a NURBS curve to the generic LWGEOM type. |
374 | | * |
375 | | * This performs a safe, no-op pointer cast from LWNURBSCURVE to LWGEOM |
376 | | * so the curve can be used with APIs expecting LWGEOM without changing |
377 | | * ownership or copying data. |
378 | | * |
379 | | * @param obj NURBS curve to cast (may be NULL) |
380 | | * @return The same pointer value cast to LWGEOM*, or NULL if `obj` is NULL |
381 | | */ |
382 | | LWGEOM * |
383 | | lwnurbscurve_as_lwgeom(const LWNURBSCURVE *obj) |
384 | 0 | { |
385 | 0 | return (LWGEOM *)obj; |
386 | 0 | } |
387 | | |
388 | | /** |
389 | | * Cast a generic LWGEOM to a NURBS curve. |
390 | | * |
391 | | * This performs a safe type check and cast from LWGEOM to LWNURBSCURVE. |
392 | | * Returns NULL if the input is not a NURBS curve type. |
393 | | * |
394 | | * @param lwgeom Geometry to cast (may be NULL) |
395 | | * @return The same pointer cast to LWNURBSCURVE*, or NULL if not a NURBSCURVE |
396 | | */ |
397 | | LWNURBSCURVE * |
398 | | lwgeom_as_lwnurbscurve(const LWGEOM *lwgeom) |
399 | 0 | { |
400 | 0 | if ( lwgeom == NULL ) return NULL; |
401 | 0 | if ( lwgeom->type == NURBSCURVETYPE ) |
402 | 0 | return (LWNURBSCURVE *)lwgeom; |
403 | 0 | else return NULL; |
404 | 0 | } |
405 | | |
406 | | /** |
407 | | * Get the control points from a NURBS curve. |
408 | | * |
409 | | * Returns the POINTARRAY containing the control points that define |
410 | | * the NURBS curve. The returned pointer points directly to the curve's |
411 | | * internal data structure and should not be modified or freed. |
412 | | * |
413 | | * @param curve NURBS curve to extract control points from (must not be NULL) |
414 | | * @return Pointer to the control points array, or NULL if curve is NULL |
415 | | */ |
416 | | POINTARRAY * |
417 | | lwnurbscurve_get_control_points(const LWNURBSCURVE *curve) |
418 | 0 | { |
419 | 0 | if ( curve == NULL ) return NULL; |
420 | 0 | return curve->points; |
421 | 0 | } |
422 | | |
423 | | /** |
424 | | * Find the knot span index for parameter u (Algorithm A2.1 from "The NURBS Book"). |
425 | | * |
426 | | * Uses binary search to find the knot span [u_i, u_{i+1}) containing parameter u. |
427 | | * This is the index i such that knots[i] <= u < knots[i+1]. |
428 | | * |
429 | | * For a NURBS curve with n control points and degree p, the knot vector has |
430 | | * n + p + 1 knots. Valid parameter values are in [knots[p], knots[n]]. |
431 | | * |
432 | | * @param degree polynomial degree of the NURBS curve |
433 | | * @param u parameter value to locate |
434 | | * @param knots knot vector array |
435 | | * @param npoints number of control points |
436 | | * @return knot span index i where knots[i] <= u < knots[i+1] |
437 | | */ |
438 | | static uint32_t |
439 | | lwnurbscurve_find_span(uint32_t degree, double u, const double *knots, uint32_t npoints) |
440 | 0 | { |
441 | 0 | uint32_t low, high, mid; |
442 | | |
443 | | /* Special case: u at end of parameter range */ |
444 | 0 | if (u >= knots[npoints]) { |
445 | 0 | return npoints - 1; |
446 | 0 | } |
447 | | |
448 | | /* Special case: u at start of parameter range */ |
449 | 0 | if (u <= knots[degree]) { |
450 | 0 | return degree; |
451 | 0 | } |
452 | | |
453 | | /* Binary search for the knot span */ |
454 | 0 | low = degree; |
455 | 0 | high = npoints; |
456 | 0 | mid = (low + high) / 2; |
457 | |
|
458 | 0 | while (u < knots[mid] || u >= knots[mid + 1]) { |
459 | 0 | if (u < knots[mid]) { |
460 | 0 | high = mid; |
461 | 0 | } else { |
462 | 0 | low = mid; |
463 | 0 | } |
464 | 0 | mid = (low + high) / 2; |
465 | 0 | } |
466 | |
|
467 | 0 | return mid; |
468 | 0 | } |
469 | | |
470 | | /** |
471 | | * Evaluates a NURBS curve at parameter t using De Boor's algorithm. |
472 | | * |
473 | | * Implements Algorithm A4.1 from "The NURBS Book" (Piegl & Tiller). |
474 | | * This iterative algorithm is O(p^2) where p is the degree, compared to |
475 | | * O(2^p) for the naive recursive Cox-de Boor approach. |
476 | | * |
477 | | * For rational NURBS, uses homogeneous coordinates (wx, wy, wz, w) throughout |
478 | | * the computation, then projects back to Cartesian coordinates at the end. |
479 | | * |
480 | | * @param curve NURBS curve to evaluate |
481 | | * @param t parameter value (typically in [0,1]) |
482 | | * @return point on the curve at parameter t, or empty point if curve is invalid |
483 | | */ |
484 | | LWPOINT * |
485 | | lwnurbscurve_evaluate(const LWNURBSCURVE *curve, double t) |
486 | 0 | { |
487 | 0 | POINT4D result; |
488 | 0 | uint32_t j, k; |
489 | 0 | double *knots; |
490 | 0 | uint32_t nknots; |
491 | 0 | char hasz, hasm; |
492 | 0 | LWPOINT *lwpoint; |
493 | 0 | uint32_t span; |
494 | 0 | uint32_t degree; |
495 | 0 | POINT4D *temp; /* Temporary array for De Boor algorithm */ |
496 | 0 | double alpha; |
497 | 0 | double *weights; /* Temporary weights array for De Boor algorithm */ |
498 | | |
499 | | /* Validate input */ |
500 | 0 | if (!curve) |
501 | 0 | return lwpoint_construct_empty(SRID_UNKNOWN, 0, 0); |
502 | 0 | if (!curve->points || curve->points->npoints == 0) |
503 | 0 | return lwpoint_construct_empty(curve->srid, FLAGS_GET_Z(curve->flags), FLAGS_GET_M(curve->flags)); |
504 | | |
505 | | /* Get dimensional flags */ |
506 | 0 | hasz = FLAGS_GET_Z(curve->flags); |
507 | 0 | hasm = FLAGS_GET_M(curve->flags); |
508 | 0 | degree = curve->degree; |
509 | | |
510 | | /* Clamp normalized parameter t to [0,1]. Endpoints are still evaluated |
511 | | * against the knot vector, since unclamped NURBS do not generally pass |
512 | | * through their first or last control point. */ |
513 | 0 | if (t < 0.0) |
514 | 0 | t = 0.0; |
515 | 0 | else if (t > 1.0) |
516 | 0 | t = 1.0; |
517 | | |
518 | | /* Get knot vector for evaluation */ |
519 | 0 | knots = lwnurbscurve_get_or_generate_knots(curve, &nknots); |
520 | 0 | if (!knots || nknots == 0) { |
521 | 0 | lwfree(knots); |
522 | 0 | return lwpoint_construct_empty(curve->srid, hasz, hasm); |
523 | 0 | } |
524 | | |
525 | | /* Remap parameter from [0,1] to knot vector range [knots[degree], knots[npoints]] */ |
526 | 0 | t = knots[degree] + t * (knots[curve->points->npoints] - knots[degree]); |
527 | | |
528 | | /* Find the knot span containing parameter t (Algorithm A2.1) */ |
529 | 0 | span = lwnurbscurve_find_span(degree, t, knots, curve->points->npoints); |
530 | | |
531 | | /* Allocate temporary arrays for De Boor iteration (degree+1 points) */ |
532 | 0 | temp = lwalloc(sizeof(POINT4D) * (degree + 1)); |
533 | 0 | weights = lwalloc(sizeof(double) * (degree + 1)); |
534 | | |
535 | | /* Initialize temp array with control points and weights. |
536 | | * For rational curves: store (w*x, w*y, w*z, w) in homogeneous coordinates |
537 | | * For non-rational: weights are 1.0 */ |
538 | 0 | for (j = 0; j <= degree; j++) { |
539 | 0 | uint32_t cp_idx = span - degree + j; |
540 | 0 | POINT4D cp; |
541 | |
|
542 | 0 | getPoint4d_p(curve->points, cp_idx, &cp); |
543 | 0 | weights[j] = (curve->weights && cp_idx < curve->nweights) ? |
544 | 0 | curve->weights[cp_idx] : 1.0; |
545 | | |
546 | | /* Store in homogeneous coordinates (w*P) */ |
547 | 0 | temp[j].x = cp.x * weights[j]; |
548 | 0 | temp[j].y = cp.y * weights[j]; |
549 | 0 | temp[j].z = hasz ? cp.z * weights[j] : 0.0; |
550 | 0 | temp[j].m = hasm ? cp.m * weights[j] : 0.0; |
551 | 0 | } |
552 | | |
553 | | /* De Boor iteration (Algorithm A4.1) in homogeneous space */ |
554 | 0 | for (k = 1; k <= degree; k++) { |
555 | 0 | for (j = degree; j >= k; j--) { |
556 | 0 | uint32_t knot_idx = span - degree + j; |
557 | 0 | double denom = knots[knot_idx + degree - k + 1] - knots[knot_idx]; |
558 | |
|
559 | 0 | if (denom != 0.0) { |
560 | 0 | alpha = (t - knots[knot_idx]) / denom; |
561 | | |
562 | | /* Linear interpolation in homogeneous space */ |
563 | 0 | temp[j].x = (1.0 - alpha) * temp[j-1].x + alpha * temp[j].x; |
564 | 0 | temp[j].y = (1.0 - alpha) * temp[j-1].y + alpha * temp[j].y; |
565 | 0 | if (hasz) temp[j].z = (1.0 - alpha) * temp[j-1].z + alpha * temp[j].z; |
566 | 0 | if (hasm) temp[j].m = (1.0 - alpha) * temp[j-1].m + alpha * temp[j].m; |
567 | | |
568 | | /* Also interpolate weights */ |
569 | 0 | weights[j] = (1.0 - alpha) * weights[j-1] + alpha * weights[j]; |
570 | 0 | } |
571 | 0 | } |
572 | 0 | } |
573 | | |
574 | | /* Result is in temp[degree], stored in homogeneous coordinates (w*P) |
575 | | * Project back to Cartesian by dividing by weight */ |
576 | 0 | result = temp[degree]; |
577 | 0 | if (weights[degree] != 0.0 && weights[degree] != 1.0) { |
578 | 0 | result.x /= weights[degree]; |
579 | 0 | result.y /= weights[degree]; |
580 | 0 | if (hasz) result.z /= weights[degree]; |
581 | 0 | if (hasm) result.m /= weights[degree]; |
582 | 0 | } |
583 | |
|
584 | 0 | lwfree(weights); |
585 | |
|
586 | 0 | lwfree(temp); |
587 | 0 | lwfree(knots); |
588 | |
|
589 | 0 | lwpoint = lwpoint_construct(curve->srid, NULL, ptarray_construct_empty(hasz, hasm, 1)); |
590 | 0 | ptarray_append_point(lwpoint->point, &result, LW_TRUE); |
591 | 0 | return lwpoint; |
592 | 0 | } |
593 | | |
594 | | /** |
595 | | * Converts a NURBS curve to a LineString by uniform sampling. |
596 | | * |
597 | | * Evaluates the NURBS curve at uniformly distributed parameter values |
598 | | * to create a piecewise linear approximation. |
599 | | * |
600 | | * @param curve NURBS curve to convert |
601 | | * @param num_segments number of segments in the resulting LineString |
602 | | * @return LineString approximation of the NURBS curve |
603 | | */ |
604 | | LWLINE * |
605 | | lwnurbscurve_to_linestring(const LWNURBSCURVE *curve, uint32_t num_segments) |
606 | 0 | { |
607 | 0 | POINTARRAY *pts; |
608 | 0 | uint32_t i; |
609 | 0 | char hasz, hasm; |
610 | | |
611 | | /* Get dimensional flags */ |
612 | 0 | hasz = curve ? FLAGS_GET_Z(curve->flags) : 0; |
613 | 0 | hasm = curve ? FLAGS_GET_M(curve->flags) : 0; |
614 | | |
615 | | /* Validate input */ |
616 | 0 | if (!curve || !curve->points || curve->points->npoints == 0) |
617 | 0 | return lwline_construct_empty(curve ? curve->srid : SRID_UNKNOWN, hasz, hasm); |
618 | | |
619 | | /* Ensure minimum number of segments */ |
620 | 0 | if (num_segments < 2) num_segments = 2; |
621 | | |
622 | | /* Create point array for result */ |
623 | 0 | pts = ptarray_construct_empty(hasz, hasm, num_segments + 1); |
624 | | |
625 | | /* Sample the curve at uniform parameter intervals */ |
626 | 0 | for (i = 0; i <= num_segments; i++) { |
627 | 0 | double t = (double)i / num_segments; |
628 | 0 | LWPOINT *pt = lwnurbscurve_evaluate(curve, t); |
629 | 0 | POINT4D p4d; |
630 | |
|
631 | 0 | if (pt && pt->point && pt->point->npoints > 0) { |
632 | 0 | getPoint4d_p(pt->point, 0, &p4d); |
633 | 0 | ptarray_append_point(pts, &p4d, LW_TRUE); |
634 | 0 | } |
635 | |
|
636 | 0 | if (pt) lwpoint_free(pt); |
637 | 0 | } |
638 | |
|
639 | | return lwline_construct(curve->srid, NULL, pts); |
640 | 0 | } |