2 Copyright (c) 2021 Ashley Rose Hale (LadyHavoc)
4 Permission is hereby granted, free of charge, to any person obtaining a copy
5 of this software and associated documentation files (the "Software"), to deal
6 in the Software without restriction, including without limitation the rights
7 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 copies of the Software, and to permit persons to whom the Software is
9 furnished to do so, subject to the following conditions:
11 The above copyright notice and this permission notice shall be included in
12 all copies or substantial portions of the Software.
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 typedef struct convex_builder_state_s
27 // planes chosen to describe the volume
31 // corners of the solid described by the planes chosen so far
33 float corners[128][3];
35 // provided point cloud which we are trying to find an optimal fit for
37 const float* points3f;
39 // we consider points to be equivalent if they are within this distance
42 convex_builder_state_t;
44 float convex_normal_distance(const float *normal3f, int numpoints, const float* points3f)
49 best = points3f[0] * normal3f[0] + points3f[1] * normal3f[1] + points3f[2] * normal3f[2];
50 for (i = 1; i < numpoints; i++)
52 d = points3f[i * 3 + 0] * normal3f[0] + points3f[i * 3 + 1] * normal3f[1] + points3f[i * 3 + 2] * normal3f[2];
59 void convex_builder_initialize_for_point_cloud(convex_builder_state_t* b, int numpoints, const float* points3f)
64 // we'll be continuing to read the points provided by the caller
65 b->numpoints = numpoints;
66 b->points3f = points3f;
68 // figure out the bounding box first as a starting point, this can be a
69 // reasonable fit on its own, but more importantly it ensures we never
70 // produce an unbounded solid
71 aabb[0][0] = aabb[1][0] = points3f[0];
72 aabb[0][1] = aabb[1][1] = points3f[1];
73 aabb[0][2] = aabb[1][2] = points3f[2];
75 for (i = 0; i < numpoints; i++)
77 for (j = 0; j < 3; j++)
79 e = fabs(points3f[i * 3 + j]) * (1.0f / 1048576.0f);
82 if (aabb[0][j] > points3f[i * 3 + j])
83 aabb[0][j] = points3f[i * 3 + j];
84 if (aabb[0][j] < points3f[i * 3 + j])
85 aabb[0][j] = points3f[i * 3 + j];
89 for (i = 0; i < 6; i++)
94 b->planes[i * 2 + 0][i] = 1;
95 b->planes[i * 2 + 0][3] = aabb[1][i];
96 b->planes[i * 2 + 1][i] = -1;
97 b->planes[i * 2 + 1][3] = -aabb[0][i];
100 // create the corners of the box
102 for (i = 0; i < 2; i++)
104 for (j = 0; j < 2; j++)
106 for (k = 0; k < 2; k++)
108 b->corners[i * 4 + j * 2 + k][0] = aabb[i][0];
109 b->corners[i * 4 + j * 2 + k][1] = aabb[j][1];
110 b->corners[i * 4 + j * 2 + k][2] = aabb[k][2];
117 void convex_builder_pick_best_planes(convex_builder_state_t* b, int maxplanes)
122 float aabb[2][3], ca[3], cb[3], cn[3], plane[2][4], p[3][3], d[2];
123 float volume = 0, clen2, inv;
125 // iterate all possible planes we could construct from the
127 for (i = 0; i < b->numpoints - 2; i++)
129 for (j = i + 1; j < b->numpoints - 1; j++)
131 for (k = j + 1; k < b->numpoints; k++)
133 // for each unique triplet of points [i,j,k] we visit only the
134 // canonical ordering i<j<k, so we have to produce two opposite
135 // planes; it would be worse to visit all orderings of [i,j,k]
136 // because that would produce 6 planes using 6 cross products,
137 // this way we produce two planes using one cross product.
139 // calculate the edge directions
140 for (l = 0; l < 3; l++)
142 p[0][l] = b->points3f[i * 3 + l];
143 p[1][l] = b->points3f[j * 3 + l];
144 p[2][l] = b->points3f[k * 3 + l];
145 ca[l] = p[1][l] - p[0][l];
146 cb[l] = p[2][l] - p[0][l];
148 // cross product to produce a normal; this is not unit length,
149 // its length is the volume of the triangle *2
150 cn[0] = ca[1] * cb[2] - ca[2] * cb[1];
151 cn[1] = ca[2] * cb[0] - ca[0] * cb[2];
152 cn[2] = ca[0] * cb[1] - ca[1] * cb[0];
153 clen2 = cn[0] * cn[0] + cn[1] * cn[1] + cn[2] * cn[2];
156 // we can't do anything with a degenerate plane
159 // normalize the plane normal
160 inv = 1.0f / sqrt(clen2);
161 for (l = 0; l < 3; l++)
163 plane[0][l] = cn[l] * inv;
164 plane[1][l] = plane[0][l] * -1.0f;
166 // calculate the plane distance of the point triplet
167 plane[0][3] = convex_normal_distance(plane[0], 3, p);
168 plane[1][3] = plane[0][3] * -1.0f;
169 for (l = 0; l < 2; l++)
171 // reject the plane if it puts any points outside of the solid
172 d[l] = convex_normal_distance(plane[l], b->numpoints, b->points3f);
173 if (d[l] - plane[l][3] > b->epsilon)
175 // measure how much this plane carves the volume
183 void convex_planes_for_point_cloud(int* outnumplanes, float* outplanes4f, int maxplanes, int numpoints, float* points3f)
185 // The algorithm here is starting with a suboptimal fit such as an axis-aligned bounding box, and then attempting to carve the largest portions of it away by picking better planes (i.e. largest volume removed) from triangles composed of the arbitrary points, so this means we need a way to measure volume of the carved space.
186 convex_builder_state_t b;
188 // return early if there are no points, rather than crash
193 // first we create a box from the points
194 convex_builder_initialize_for_point_cloud(&b, numpoints, points3f);
196 // optimize the convex solid as best we can
197 convex_builder_pick_best_planes(&b, maxplanes);