/src/gdal/ogr/ogrsf_frmts/dgn/dgnstroke.cpp
Line | Count | Source |
1 | | /****************************************************************************** |
2 | | * |
3 | | * Project: Microstation DGN Access Library |
4 | | * Purpose: Code to stroke Arcs/Ellipses into polylines. |
5 | | * Author: Frank Warmerdam, warmerdam@pobox.com |
6 | | * |
7 | | ****************************************************************************** |
8 | | * Copyright (c) 2001, Avenza Systems Inc, http://www.avenza.com/ |
9 | | * |
10 | | * SPDX-License-Identifier: MIT |
11 | | ****************************************************************************/ |
12 | | |
13 | | #include "dgnlibp.h" |
14 | | #include <cmath> |
15 | | |
16 | | constexpr double DEG_TO_RAD = M_PI / 180.0; |
17 | | |
18 | | /************************************************************************/ |
19 | | /* ComputePointOnArc() */ |
20 | | /************************************************************************/ |
21 | | |
22 | | static void ComputePointOnArc2D(double dfPrimary, double dfSecondary, |
23 | | double dfAxisRotation, double dfAngle, |
24 | | double *pdfX, double *pdfY) |
25 | | |
26 | 7.87M | { |
27 | | // dfAxisRotation and dfAngle are supposed to be in Radians |
28 | 7.87M | const double dfCosRotation = cos(dfAxisRotation); |
29 | 7.87M | const double dfSinRotation = sin(dfAxisRotation); |
30 | 7.87M | const double dfEllipseX = dfPrimary * cos(dfAngle); |
31 | 7.87M | const double dfEllipseY = dfSecondary * sin(dfAngle); |
32 | | |
33 | 7.87M | *pdfX = dfEllipseX * dfCosRotation - dfEllipseY * dfSinRotation; |
34 | 7.87M | *pdfY = dfEllipseX * dfSinRotation + dfEllipseY * dfCosRotation; |
35 | 7.87M | } |
36 | | |
37 | | /************************************************************************/ |
38 | | /* DGNStrokeArc() */ |
39 | | /************************************************************************/ |
40 | | |
41 | | /** |
42 | | * Generate a polyline approximation of an arc. |
43 | | * |
44 | | * Produce a series of equidistant (actually equi-angle) points along |
45 | | * an arc. Currently this only works for 2D arcs (and ellipses). |
46 | | * |
47 | | * @param hFile the DGN file to which the arc belongs (currently not used). |
48 | | * @param psArc the arc to be approximated. |
49 | | * @param nPoints the number of points to use to approximate the arc. |
50 | | * @param pasPoints the array of points into which to put the results. |
51 | | * There must be room for at least nPoints points. |
52 | | * |
53 | | * @return TRUE on success or FALSE on failure. |
54 | | */ |
55 | | |
56 | | int DGNStrokeArc(CPL_UNUSED DGNHandle hFile, DGNElemArc *psArc, int nPoints, |
57 | | DGNPoint *pasPoints) |
58 | 189k | { |
59 | 189k | if (nPoints < 2) |
60 | 0 | return FALSE; |
61 | | |
62 | 189k | if (psArc->primary_axis == 0.0 || psArc->secondary_axis == 0.0) |
63 | 63.5k | { |
64 | 63.5k | CPLError(CE_Warning, CPLE_AppDefined, |
65 | 63.5k | "Zero primary or secondary axis in DGNStrokeArc()."); |
66 | 63.5k | return FALSE; |
67 | 63.5k | } |
68 | | |
69 | 125k | const double dfAngleStep = psArc->sweepang / (nPoints - 1); |
70 | 7.99M | for (int i = 0; i < nPoints; i++) |
71 | 7.87M | { |
72 | 7.87M | const double dfAngle = (psArc->startang + dfAngleStep * i) * DEG_TO_RAD; |
73 | | |
74 | 7.87M | ComputePointOnArc2D(psArc->primary_axis, psArc->secondary_axis, |
75 | 7.87M | psArc->rotation * DEG_TO_RAD, dfAngle, |
76 | 7.87M | &(pasPoints[i].x), &(pasPoints[i].y)); |
77 | 7.87M | pasPoints[i].x += psArc->origin.x; |
78 | 7.87M | pasPoints[i].y += psArc->origin.y; |
79 | 7.87M | pasPoints[i].z = psArc->origin.z; |
80 | 7.87M | } |
81 | | |
82 | 125k | return TRUE; |
83 | 189k | } |
84 | | |
85 | | /************************************************************************/ |
86 | | /* DGNStrokeCurve() */ |
87 | | /************************************************************************/ |
88 | | |
89 | | /** |
90 | | * Generate a polyline approximation of an curve. |
91 | | * |
92 | | * Produce a series of equidistant points along a microstation curve element. |
93 | | * Currently this only works for 2D. |
94 | | * |
95 | | * @param hFile the DGN file to which the arc belongs (currently not used). |
96 | | * @param psCurve the curve to be approximated. |
97 | | * @param nPoints the number of points to use to approximate the curve. |
98 | | * @param pasPoints the array of points into which to put the results. |
99 | | * There must be room for at least nPoints points. |
100 | | * |
101 | | * @return TRUE on success or FALSE on failure. |
102 | | */ |
103 | | |
104 | | int DGNStrokeCurve(CPL_UNUSED DGNHandle hFile, DGNElemMultiPoint *psCurve, |
105 | | int nPoints, DGNPoint *pasPoints) |
106 | 12.9k | { |
107 | 12.9k | const int nDGNPoints = psCurve->num_vertices; |
108 | | |
109 | 12.9k | if (nDGNPoints < 6) |
110 | 4.19k | return FALSE; |
111 | | |
112 | 8.76k | if (nPoints < nDGNPoints - 4) |
113 | 0 | return FALSE; |
114 | | |
115 | | /* -------------------------------------------------------------------- */ |
116 | | /* Compute the Compute the slopes/distances of the segments. */ |
117 | | /* -------------------------------------------------------------------- */ |
118 | 8.76k | double *padfMx = |
119 | 8.76k | static_cast<double *>(CPLMalloc(sizeof(double) * nDGNPoints)); |
120 | 8.76k | double *padfMy = |
121 | 8.76k | static_cast<double *>(CPLMalloc(sizeof(double) * nDGNPoints)); |
122 | 8.76k | double *padfD = |
123 | 8.76k | static_cast<double *>(CPLMalloc(sizeof(double) * nDGNPoints)); |
124 | 8.76k | double *padfTx = |
125 | 8.76k | static_cast<double *>(CPLMalloc(sizeof(double) * nDGNPoints)); |
126 | 8.76k | double *padfTy = |
127 | 8.76k | static_cast<double *>(CPLMalloc(sizeof(double) * nDGNPoints)); |
128 | | |
129 | 8.76k | double dfTotalD = 0.0; |
130 | | |
131 | 8.76k | DGNPoint *pasDGNPoints = psCurve->vertices; |
132 | | |
133 | 5.82M | for (int k = 0; k < nDGNPoints - 1; k++) |
134 | 5.81M | { |
135 | | /* coverity[overrun-local] */ |
136 | 5.81M | padfD[k] = sqrt((pasDGNPoints[k + 1].x - pasDGNPoints[k].x) * |
137 | 5.81M | (pasDGNPoints[k + 1].x - pasDGNPoints[k].x) + |
138 | 5.81M | (pasDGNPoints[k + 1].y - pasDGNPoints[k].y) * |
139 | 5.81M | (pasDGNPoints[k + 1].y - pasDGNPoints[k].y)); |
140 | 5.81M | if (padfD[k] == 0.0) |
141 | 4.92M | { |
142 | 4.92M | padfD[k] = 0.0001; |
143 | 4.92M | padfMx[k] = 0.0; |
144 | 4.92M | padfMy[k] = 0.0; |
145 | 4.92M | } |
146 | 889k | else |
147 | 889k | { |
148 | 889k | padfMx[k] = (pasDGNPoints[k + 1].x - pasDGNPoints[k].x) / padfD[k]; |
149 | 889k | padfMy[k] = (pasDGNPoints[k + 1].y - pasDGNPoints[k].y) / padfD[k]; |
150 | 889k | } |
151 | | |
152 | 5.81M | if (k > 1 && k < nDGNPoints - 3) |
153 | 5.78M | dfTotalD += padfD[k]; |
154 | 5.81M | } |
155 | | |
156 | | /* -------------------------------------------------------------------- */ |
157 | | /* Compute the Tx, and Ty coefficients for each segment. */ |
158 | | /* -------------------------------------------------------------------- */ |
159 | 5.79M | for (int k = 2; k < nDGNPoints - 2; k++) |
160 | 5.79M | { |
161 | 5.79M | if (fabs(padfMx[k + 1] - padfMx[k]) == 0.0 && |
162 | 4.93M | fabs(padfMx[k - 1] - padfMx[k - 2]) == 0.0) |
163 | 4.65M | { |
164 | 4.65M | padfTx[k] = (padfMx[k] + padfMx[k - 1]) / 2; |
165 | 4.65M | } |
166 | 1.13M | else |
167 | 1.13M | { |
168 | 1.13M | padfTx[k] = (padfMx[k - 1] * fabs(padfMx[k + 1] - padfMx[k]) + |
169 | 1.13M | padfMx[k] * fabs(padfMx[k - 1] - padfMx[k - 2])) / |
170 | 1.13M | (std::abs(padfMx[k + 1] - padfMx[k]) + |
171 | 1.13M | std::abs(padfMx[k - 1] - padfMx[k - 2])); |
172 | 1.13M | } |
173 | | |
174 | 5.79M | if (fabs(padfMy[k + 1] - padfMy[k]) == 0.0 && |
175 | 4.91M | fabs(padfMy[k - 1] - padfMy[k - 2]) == 0.0) |
176 | 4.63M | { |
177 | 4.63M | padfTy[k] = (padfMy[k] + padfMy[k - 1]) / 2; |
178 | 4.63M | } |
179 | 1.15M | else |
180 | 1.15M | { |
181 | 1.15M | padfTy[k] = (padfMy[k - 1] * fabs(padfMy[k + 1] - padfMy[k]) + |
182 | 1.15M | padfMy[k] * fabs(padfMy[k - 1] - padfMy[k - 2])) / |
183 | 1.15M | (std::abs(padfMy[k + 1] - padfMy[k]) + |
184 | 1.15M | std::abs(padfMy[k - 1] - padfMy[k - 2])); |
185 | 1.15M | } |
186 | 5.79M | } |
187 | | |
188 | | /* -------------------------------------------------------------------- */ |
189 | | /* Determine a step size in D. We scale things so that we have */ |
190 | | /* roughly equidistant steps in D, but assume we also want to */ |
191 | | /* include every node along the way. */ |
192 | | /* -------------------------------------------------------------------- */ |
193 | 8.76k | double dfStepSize = dfTotalD / (nPoints - (nDGNPoints - 4) - 1); |
194 | | |
195 | | /* ==================================================================== */ |
196 | | /* Process each of the segments. */ |
197 | | /* ==================================================================== */ |
198 | 8.76k | double dfD = dfStepSize; |
199 | 8.76k | int iOutPoint = 0; |
200 | | |
201 | 5.79M | for (int k = 2; k < nDGNPoints - 3; k++) |
202 | 5.78M | { |
203 | | /* -------------------------------------------------------------------- |
204 | | */ |
205 | | /* Compute the "x" coefficients for this segment. */ |
206 | | /* -------------------------------------------------------------------- |
207 | | */ |
208 | 5.78M | const double dfCx = padfTx[k]; |
209 | 5.78M | const double dfBx = |
210 | 5.78M | (3.0 * (pasDGNPoints[k + 1].x - pasDGNPoints[k].x) / padfD[k] - |
211 | 5.78M | 2.0 * padfTx[k] - padfTx[k + 1]) / |
212 | 5.78M | padfD[k]; |
213 | 5.78M | const double dfAx = |
214 | 5.78M | (padfTx[k] + padfTx[k + 1] - |
215 | 5.78M | 2 * (pasDGNPoints[k + 1].x - pasDGNPoints[k].x) / padfD[k]) / |
216 | 5.78M | (padfD[k] * padfD[k]); |
217 | | |
218 | | /* -------------------------------------------------------------------- |
219 | | */ |
220 | | /* Compute the Y coefficients for this segment. */ |
221 | | /* -------------------------------------------------------------------- |
222 | | */ |
223 | 5.78M | const double dfCy = padfTy[k]; |
224 | 5.78M | const double dfBy = |
225 | 5.78M | (3.0 * (pasDGNPoints[k + 1].y - pasDGNPoints[k].y) / padfD[k] - |
226 | 5.78M | 2.0 * padfTy[k] - padfTy[k + 1]) / |
227 | 5.78M | padfD[k]; |
228 | 5.78M | const double dfAy = |
229 | 5.78M | (padfTy[k] + padfTy[k + 1] - |
230 | 5.78M | 2 * (pasDGNPoints[k + 1].y - pasDGNPoints[k].y) / padfD[k]) / |
231 | 5.78M | (padfD[k] * padfD[k]); |
232 | | |
233 | | /* -------------------------------------------------------------------- |
234 | | */ |
235 | | /* Add the start point for this segment. */ |
236 | | /* -------------------------------------------------------------------- |
237 | | */ |
238 | 5.78M | pasPoints[iOutPoint].x = pasDGNPoints[k].x; |
239 | 5.78M | pasPoints[iOutPoint].y = pasDGNPoints[k].y; |
240 | 5.78M | pasPoints[iOutPoint].z = 0.0; |
241 | 5.78M | iOutPoint++; |
242 | | |
243 | | /* -------------------------------------------------------------------- |
244 | | */ |
245 | | /* Step along, adding intermediate points. */ |
246 | | /* -------------------------------------------------------------------- |
247 | | */ |
248 | 29.1M | while (dfD < padfD[k] && iOutPoint < nPoints - (nDGNPoints - k - 1)) |
249 | 23.3M | { |
250 | 23.3M | pasPoints[iOutPoint].x = dfAx * dfD * dfD * dfD + dfBx * dfD * dfD + |
251 | 23.3M | dfCx * dfD + pasDGNPoints[k].x; |
252 | 23.3M | pasPoints[iOutPoint].y = dfAy * dfD * dfD * dfD + dfBy * dfD * dfD + |
253 | 23.3M | dfCy * dfD + pasDGNPoints[k].y; |
254 | 23.3M | pasPoints[iOutPoint].z = 0.0; |
255 | 23.3M | iOutPoint++; |
256 | | |
257 | 23.3M | dfD += dfStepSize; |
258 | 23.3M | } |
259 | | |
260 | 5.78M | dfD -= padfD[k]; |
261 | 5.78M | } |
262 | | |
263 | | /* -------------------------------------------------------------------- */ |
264 | | /* Add the start point for this segment. */ |
265 | | /* -------------------------------------------------------------------- */ |
266 | 35.0k | while (iOutPoint < nPoints) |
267 | 26.2k | { |
268 | 26.2k | pasPoints[iOutPoint].x = pasDGNPoints[nDGNPoints - 3].x; |
269 | 26.2k | pasPoints[iOutPoint].y = pasDGNPoints[nDGNPoints - 3].y; |
270 | 26.2k | pasPoints[iOutPoint].z = 0.0; |
271 | 26.2k | iOutPoint++; |
272 | 26.2k | } |
273 | | |
274 | | /* -------------------------------------------------------------------- */ |
275 | | /* Cleanup. */ |
276 | | /* -------------------------------------------------------------------- */ |
277 | 8.76k | CPLFree(padfMx); |
278 | 8.76k | CPLFree(padfMy); |
279 | 8.76k | CPLFree(padfD); |
280 | 8.76k | CPLFree(padfTx); |
281 | 8.76k | CPLFree(padfTy); |
282 | | |
283 | 8.76k | return TRUE; |
284 | 8.76k | } |
285 | | |
286 | | /************************************************************************/ |
287 | | /* main() */ |
288 | | /* */ |
289 | | /* test mainline */ |
290 | | /************************************************************************/ |
291 | | #ifdef notdef |
292 | | int main(int argc, char **argv) |
293 | | |
294 | | { |
295 | | if (argc != 5) |
296 | | { |
297 | | printf( // ok |
298 | | "Usage: stroke primary_axis secondary_axis axis_rotation angle\n"); |
299 | | exit(1); |
300 | | } |
301 | | |
302 | | const double dfPrimary = CPLAtof(argv[1]); |
303 | | const double dfSecondary = CPLAtof(argv[2]); |
304 | | const double dfAxisRotation = CPLAtof(argv[3]) / 180 * M_PI; |
305 | | const double dfAngle = CPLAtof(argv[4]) / 180 * M_PI; |
306 | | |
307 | | double dfX = 0.0; |
308 | | double dfY = 0.0; |
309 | | ComputePointOnArc2D(dfPrimary, dfSecondary, dfAxisRotation, dfAngle, &dfX, |
310 | | &dfY); |
311 | | |
312 | | printf("X=%.2f, Y=%.2f\n", dfX, dfY); // ok |
313 | | |
314 | | return 0; |
315 | | } |
316 | | |
317 | | #endif |