Skip to content

Commit eb6fdfb

Browse files
committed
Implement deviation metric callback functionality.
1 parent 61ada83 commit eb6fdfb

File tree

4 files changed

+148
-56
lines changed

4 files changed

+148
-56
lines changed

DeviationMetrics.c

+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
DistanceMetrics.c
3+
08/29/2015
4+
Authors: Michael Casebolt, Brett Casebolt
5+
*/
6+
7+
/*
8+
The MIT License (MIT)
9+
10+
Copyright (c) 2015 Michael Casebolt and Brett Casebolt
11+
12+
Permission is hereby granted, free of charge, to any person obtaining a copy
13+
of this software and associated documentation files (the "Software"), to deal
14+
in the Software without restriction, including without limitation the rights
15+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16+
copies of the Software, and to permit persons to whom the Software is
17+
furnished to do so, subject to the following conditions:
18+
19+
The above copyright notice and this permission notice shall be included in
20+
all copies or substantial portions of the Software.
21+
22+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
28+
THE SOFTWARE.
29+
*/
30+
31+
// This file uses lines up to 100 characters long. If this 100 character line fits then you're good.
32+
33+
#include "PathCompacter.h"
34+
35+
// This one just returns the shortest distance to the infinite extension of the line segment.
36+
static double perpendicularDistance(DVector2D start, DVector2D end, DVector2D mid,
37+
double dSquareSegmentLength)
38+
{
39+
double dArea;
40+
41+
dArea = 0.5 * (start.dX * (mid.dY - end.dY) +
42+
mid.dX * (end.dY - start.dY) +
43+
end.dX * (start.dY - mid.dY));
44+
45+
return dArea * dArea / dSquareSegmentLength;
46+
}
47+
DeviationMetric perpendicularDistanceDeviationMetric = &perpendicularDistance;
48+
49+
static double shortestDistanceToSegment(DVector2D start, DVector2D end, DVector2D mid,
50+
double dSquareSegmentLength)
51+
{
52+
double dAX, dAY, dBX, dBY, dCX, dCY, dAdotB, dBdotC, dArea;
53+
54+
// Start->End forms vector A.
55+
dAX = end.dX - start.dX;
56+
dAY = end.dY - start.dY;
57+
58+
// Start->Mid forms vector B.
59+
dBX = mid.dX - start.dX;
60+
dBY = mid.dY - start.dY;
61+
62+
// End->Mid forms vector C;
63+
dCX = mid.dX - end.dX;
64+
dCY = mid.dY - end.dY;
65+
66+
// Find the dot product of A and B.
67+
dAdotB = dAX * dBX + dAY * dBY;
68+
69+
// Find the dot product of B and C;
70+
dBdotC = dBX * dCX + dBY * dCY;
71+
72+
// If the signs are different, the closest point on the segment is not an endpoint.
73+
if (dAdotB > 0.0 && dBdotC < 0.0)
74+
{
75+
dArea = 0.5 * (start.dX * (mid.dY - end.dY) +
76+
mid.dX * (end.dY - start.dY) +
77+
end.dX * (start.dY - mid.dY));
78+
79+
return dArea * dArea / dSquareSegmentLength;
80+
}
81+
82+
// Otherwise, figure out which endpoint it is closer to.
83+
else
84+
{
85+
if (dAdotB < 0.0 && dBdotC < 0.0)
86+
{
87+
// It is closer to the start point.
88+
return dAX * dAX + dAY * dAY;
89+
}
90+
else
91+
{
92+
// It is closer to the end point.
93+
return dCX * dCX + dCY * dCY;
94+
}
95+
}
96+
}
97+
DeviationMetric shortestDistanceToSegmentDeviationMetric = &shortestDistanceToSegment;

PathCompacter.c

+24-29
Original file line numberDiff line numberDiff line change
@@ -55,16 +55,17 @@ typedef enum CompactPathResultCode
5555
#define FAILURE 0
5656
#define SUCCESS 1
5757

58-
static CompactPathResultCode CompactPathSubproblemSolver(DVector2D *pPointArray,
58+
static CompactPathResultCode compactPathSubproblemSolver(DVector2D *pPointArray,
5959
int iPointsInCurrentPath, DVector2D *pResultPointArray,
60-
int *piPointsInResultPath, int *piDivisionIndex, double dEpsilon);
60+
int *piPointsInResultPath, int *piDivisionIndex, double dEpsilon,
61+
DeviationMetric deviationMetric);
6162

6263
// The call stack will start able to hold this many calls and grow by this amount whenever it needs
6364
// to grow in size.
6465
#define COMPACT_PATH_CALL_STACK_UNIT 2048
6566

6667
// callStackBase is a double pointer because realloc might move the base pointer.
67-
static int CompactPathCallStackPush(CompactPathSubproblemCall **ppCallStackBase,
68+
static int compactPathCallStackPush(CompactPathSubproblemCall **ppCallStackBase,
6869
int *piCallStackCapacity, int *piNumCallsInStack,
6970
CompactPathSubproblemCall *pCall)
7071
{
@@ -89,7 +90,7 @@ static int CompactPathCallStackPush(CompactPathSubproblemCall **ppCallStackBase,
8990
return SUCCESS;
9091
}
9192

92-
static int CompactPathCallStackPop(CompactPathSubproblemCall *pCallStackBase,
93+
static int compactPathCallStackPop(CompactPathSubproblemCall *pCallStackBase,
9394
int *piNumCallsInStack, CompactPathSubproblemCall *pPoppedCall)
9495
{
9596
// Check if there is a call to pop.
@@ -124,8 +125,9 @@ static int CompactPathCallStackPop(CompactPathSubproblemCall *pCallStackBase,
124125
// and resultPointArray, keeping in mind that doing so will likely alter pointArray.
125126
// Returns a true value (1) on successful completion, and returns a false value (0) otherwise.
126127
// On failure, pointsInResultPath and the contents of resultPointArray are undefined.
127-
int CompactPath(DVector2D *pPointArray, int iPointsInCurrentPath,
128-
DVector2D *pResultPointArray, int *piPointsInResultPath, double dEpsilon)
128+
int compactPath(DVector2D *pPointArray, int iPointsInCurrentPath,
129+
DVector2D *pResultPointArray, int *piPointsInResultPath, double dEpsilon,
130+
DeviationMetric deviationMetric)
129131
{
130132
CompactPathSubproblemCall current, firstSubproblem, secondSubproblem;
131133
int iDivisionIndex; // Where should we split the problem into subproblems?
@@ -164,22 +166,22 @@ int CompactPath(DVector2D *pPointArray, int iPointsInCurrentPath,
164166
current.iPointsInCurrentPath = iPointsInCurrentPath;
165167

166168
// Add the first instance to the stack
167-
if (!CompactPathCallStackPush(&pCallStackBase, &iCallStackCapacity, &iNumCallsInStack, &current))
169+
if (!compactPathCallStackPush(&pCallStackBase, &iCallStackCapacity, &iNumCallsInStack, &current))
168170
{
169171
COMPACT_PATH_RETURN(FAILURE);
170172
}
171173

172174
// As long as there are calls on the stack, pop one and process it.
173175
while (iNumCallsInStack > 0)
174176
{
175-
if (!CompactPathCallStackPop(pCallStackBase, &iNumCallsInStack, &current))
177+
if (!compactPathCallStackPop(pCallStackBase, &iNumCallsInStack, &current))
176178
{
177179
COMPACT_PATH_RETURN(FAILURE);
178180
}
179181

180-
subproblemResultCode = CompactPathSubproblemSolver(current.pPointArray,
182+
subproblemResultCode = compactPathSubproblemSolver(current.pPointArray,
181183
current.iPointsInCurrentPath, current.pResultPointArray, &iPointsInResultPath,
182-
&iDivisionIndex, dEpsilon);
184+
&iDivisionIndex, dEpsilon, deviationMetric);
183185

184186
if (subproblemResultCode == COMPACT_PATH_RESULT_CODE_DIVIDE)
185187
{
@@ -199,7 +201,7 @@ int CompactPath(DVector2D *pPointArray, int iPointsInCurrentPath,
199201
secondSubproblem.pPointArray = current.pPointArray + iDivisionIndex;
200202
secondSubproblem.pResultPointArray = current.pResultPointArray + iDivisionIndex;
201203
secondSubproblem.iPointsInCurrentPath = current.iPointsInCurrentPath - iDivisionIndex;
202-
if (!CompactPathCallStackPush(&pCallStackBase, &iCallStackCapacity,
204+
if (!compactPathCallStackPush(&pCallStackBase, &iCallStackCapacity,
203205
&iNumCallsInStack, &secondSubproblem))
204206
{
205207
COMPACT_PATH_RETURN(FAILURE);
@@ -208,7 +210,7 @@ int CompactPath(DVector2D *pPointArray, int iPointsInCurrentPath,
208210
firstSubproblem.pPointArray = current.pPointArray;
209211
firstSubproblem.pResultPointArray = current.pResultPointArray;
210212
firstSubproblem.iPointsInCurrentPath = iDivisionIndex + 1;
211-
if (!CompactPathCallStackPush(&pCallStackBase, &iCallStackCapacity,
213+
if (!compactPathCallStackPush(&pCallStackBase, &iCallStackCapacity,
212214
&iNumCallsInStack, &firstSubproblem))
213215
{
214216
COMPACT_PATH_RETURN(FAILURE);
@@ -251,12 +253,12 @@ int CompactPath(DVector2D *pPointArray, int iPointsInCurrentPath,
251253
// If the result is COMPACT_PATH_RESULT_CODE_SOLVED, it means that the algorithm does not need to
252254
// do any further work on the subproblem, because it is already solved. In this case, divisionIndex
253255
// is not set.
254-
static CompactPathResultCode CompactPathSubproblemSolver(DVector2D *pPointArray,
256+
static CompactPathResultCode compactPathSubproblemSolver(DVector2D *pPointArray,
255257
int iPointsInCurrentPath, DVector2D *pResultPointArray,
256-
int *piPointsInResultPath, int *piDivisionIndex, double dEpsilon)
258+
int *piPointsInResultPath, int *piDivisionIndex, double dEpsilon,
259+
DeviationMetric deviationMetric)
257260
{
258-
double dSquareSegLen, dDX, dDY, dArea, dSquareDeviation, dMaxSquareDeviationInThisSegment,
259-
ax, ay, bx, by, cx, cy;
261+
double dSquareSegLen, dSquareDeviation, dMaxSquareDeviationInThisSegment, dDX, dDY;
260262
int i, iMaxPointIndex;
261263

262264
// If there are fewer than three points provided, the problem is solved already.
@@ -272,22 +274,16 @@ static CompactPathResultCode CompactPathSubproblemSolver(DVector2D *pPointArray,
272274
}
273275

274276
dMaxSquareDeviationInThisSegment = 0.0;
275-
ax = pPointArray[0].dX;
276-
ay = pPointArray[0].dY;
277-
cx = pPointArray[iPointsInCurrentPath-1].dX;
278-
cy = pPointArray[iPointsInCurrentPath-1].dY;
279-
dDX = cx - ax;
280-
dDY = cy - ay;
277+
278+
dDX = pPointArray[iPointsInCurrentPath - 1].dX - pPointArray[0].dX;
279+
dDY = pPointArray[iPointsInCurrentPath - 1].dY - pPointArray[0].dY;
281280
dSquareSegLen = dDX * dDX + dDY * dDY;
282281

283282
for (i = 1; i < iPointsInCurrentPath - 1; ++i)
284283
{
285-
bx = pPointArray[i].dX;
286-
by = pPointArray[i].dY;
287-
// TODO: optionally use the endpoint distance if the triangle is skew.
288-
dArea = ax * (by - cy) + bx * (cy - ay) + cx * (ay - by);
289-
290-
dSquareDeviation = dArea * dArea / dSquareSegLen;
284+
dSquareDeviation = deviationMetric(pPointArray[0],
285+
pPointArray[iPointsInCurrentPath - 1], pPointArray[i], dSquareSegLen);
286+
291287
if (dSquareDeviation > dMaxSquareDeviationInThisSegment)
292288
{
293289
iMaxPointIndex = i;
@@ -311,4 +307,3 @@ static CompactPathResultCode CompactPathSubproblemSolver(DVector2D *pPointArray,
311307
return COMPACT_PATH_RESULT_CODE_DIVIDE;
312308
}
313309
}
314-

PathCompacter.h

+7-7
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ typedef struct DVector2D
4444
// callback does not have to recalculate it every time it gets called.
4545
// These callbacks must return the square of the actual value that they intend to be compared
4646
// against epsilon. This is because squaring often is better than sqrt'ing sometimes.
47-
typedef double (*DeviationMetric)(DVector2D startOfSegment, DVector2D endOfSegment,
48-
DVector2D point, double dSquareSegmentLength);
47+
typedef double (*DeviationMetric)(DVector2D /*startOfSegment*/, DVector2D /*endOfSegment*/,
48+
DVector2D /*point*/, double /*dSquareSegmentLength*/);
4949

5050
// This function iteratively simulates the recursive Ramer-Douglas-Peucker algorithm.
5151
// https://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm
@@ -56,13 +56,13 @@ typedef double (*DeviationMetric)(DVector2D startOfSegment, DVector2D endOfSegme
5656
// and resultPointArray, keeping in mind that doing so will likely alter pointArray.
5757
// Returns a true value (1) on successful completion, and returns a false value (0) otherwise.
5858
// On failure, pointsInResultPath and the contents of resultPointArray are undefined.
59-
extern int compactPath(DVector2D *pPointArray, int iPointsInCurrentPath,
60-
DVector2D *pResultPointArray, int *piPointsInResultPath,
61-
double dEpsilon, DeviationMetric deviationMetric);
59+
int compactPath(DVector2D *pPointArray, int iPointsInCurrentPath,
60+
DVector2D *pResultPointArray, int *piPointsInResultPath,
61+
double dEpsilon, DeviationMetric deviationMetric);
6262

6363
// Declare several metric function implementations.
6464

65-
extern DeviationMetric perpendicularDistanceDeviationMetric;
66-
extern DeviationMetric shortestDistanceToSegmentDeviationMetric;
65+
DeviationMetric perpendicularDistanceDeviationMetric;
66+
DeviationMetric shortestDistanceToSegmentDeviationMetric;
6767

6868
#endif

PathCompacterRecursive.c

+20-20
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,12 @@ THE SOFTWARE.
4141
// array where the next points to be solved should be placed.
4242
// Only CompactPath and CompactPathRecursive should touch this variable.
4343
static DVector2D *pCompacterLocation;
44+
45+
// Save the deviation metric function pointer so that it doesn't have to be copied on the stack
46+
// a bunch of times.
47+
static DeviationMetric currentDeviationMetric;
4448

45-
static void CompactPathRecursive(DVector2D *pPointArray, int iPointsInCurrentPath, double dEpsilon);
49+
static void compactPathRecursive(DVector2D *pPointArray, int iPointsInCurrentPath, double dEpsilon);
4650

4751
// This function iteratively simulates the recursive Ramer-Douglas-Peucker algorithm.
4852
// https://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm
@@ -53,8 +57,9 @@ static void CompactPathRecursive(DVector2D *pPointArray, int iPointsInCurrentPat
5357
// and resultPointArray, keeping in mind that doing so will likely alter pointArray.
5458
// Returns a true value (1) on successful completion, and returns a false value (0) otherwise.
5559
// This function might blow up the stack and crash your program.
56-
int CompactPath(DVector2D *pPointArray, int iPointsInCurrentPath,
57-
DVector2D *pResultPointArray, int *piPointsInResultPath, double dEpsilon)
60+
extern int compactPath(DVector2D *pPointArray, int iPointsInCurrentPath,
61+
DVector2D *pResultPointArray, int *piPointsInResultPath,
62+
double dEpsilon, DeviationMetric deviationMetric)
5863
{
5964
int iFinalSuccessValue;
6065

@@ -74,8 +79,10 @@ int CompactPath(DVector2D *pPointArray, int iPointsInCurrentPath,
7479
*pResultPointArray = *pPointArray;
7580
pCompacterLocation = pResultPointArray + 1;
7681

82+
currentDeviationMetric = deviationMetric;
83+
7784
// Launch the first call of CompactPathRecursive.
78-
CompactPathRecursive(pPointArray, iPointsInCurrentPath, dEpsilon);
85+
compactPathRecursive(pPointArray, iPointsInCurrentPath, dEpsilon);
7986

8087
// Calculate and return through pointer the number of points in the resulting path.
8188
*piPointsInResultPath = pCompacterLocation - pResultPointArray;
@@ -92,10 +99,9 @@ int CompactPath(DVector2D *pPointArray, int iPointsInCurrentPath,
9299
// If the result is COMPACT_PATH_RESULT_CODE_SOLVED, it means that the algorithm does not need to
93100
// do any further work on the subproblem, because it is already solved. In this case, divisionIndex
94101
// is not set.
95-
static void CompactPathRecursive(DVector2D *pPointArray, int iPointsInCurrentPath, double dEpsilon)
102+
static void compactPathRecursive(DVector2D *pPointArray, int iPointsInCurrentPath, double dEpsilon)
96103
{
97-
double dSquareSegLen, dDX, dDY, dArea, dSquareDeviation, dMaxSquareDeviationInThisSegment,
98-
ax, ay, bx, by, cx, cy;
104+
double dSquareSegLen, dDX, dDY, dArea, dSquareDeviation, dMaxSquareDeviationInThisSegment;
99105
int i, iMaxPointIndex;
100106

101107
// If there are fewer than three points provided, the problem is solved already.
@@ -113,22 +119,16 @@ static void CompactPathRecursive(DVector2D *pPointArray, int iPointsInCurrentPat
113119
}
114120

115121
dMaxSquareDeviationInThisSegment = 0.0;
116-
ax = pPointArray[0].dX;
117-
ay = pPointArray[0].dY;
118-
cx = pPointArray[iPointsInCurrentPath-1].dX;
119-
cy = pPointArray[iPointsInCurrentPath-1].dY;
120-
dDX = cx - ax;
121-
dDY = cy - ay;
122+
123+
dDX = pPointArray[iPointsInCurrentPath-1].dX - pPointArray[0].dX;
124+
dDY = pPointArray[iPointsInCurrentPath-1].dY - pPointArray[0].dY;
122125
dSquareSegLen = dDX * dDX + dDY * dDY;
123126

124127
for (i = 1; i < iPointsInCurrentPath - 1; ++i)
125128
{
126-
bx = pPointArray[i].dX;
127-
by = pPointArray[i].dY;
128-
// TODO: optionally use the endpoint distance if the triangle is skew.
129-
dArea = ax * (by - cy) + bx * (cy - ay) + cx * (ay - by);
129+
dSquareDeviation = currentDeviationMetric(pPointArray[0],
130+
pPointArray[iPointsInCurrentPath - 1], pPointArray[i], dSquareSegLen);
130131

131-
dSquareDeviation = dArea * dArea / dSquareSegLen;
132132
if (dSquareDeviation > dMaxSquareDeviationInThisSegment)
133133
{
134134
iMaxPointIndex = i;
@@ -148,9 +148,9 @@ static void CompactPathRecursive(DVector2D *pPointArray, int iPointsInCurrentPat
148148
{
149149
// Split the subproblem. Make sure to keep this left-recursive.
150150

151-
CompactPathRecursive(pPointArray, iMaxPointIndex + 1, dEpsilon);
151+
compactPathRecursive(pPointArray, iMaxPointIndex + 1, dEpsilon);
152152

153-
CompactPathRecursive(pPointArray + iMaxPointIndex, iPointsInCurrentPath - iMaxPointIndex,
153+
compactPathRecursive(pPointArray + iMaxPointIndex, iPointsInCurrentPath - iMaxPointIndex,
154154
dEpsilon);
155155
}
156156

0 commit comments

Comments
 (0)