csg 0.1.3 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,9 @@
1
+ #include "mesh.h"
2
+ #include "poly.h"
3
+
4
+ #ifndef __CMD_AUDIT_H
5
+ #define __CMD_AUDIT_H
6
+
7
+ int cmd_audit(int argc, char *argv[]);
8
+
9
+ #endif
@@ -1,11 +1,13 @@
1
1
  #include <string.h>
2
2
  #include "dbg.h"
3
3
 
4
+ #include "util.h"
4
5
  #include "commands.h"
5
6
  #include "mesh.h"
6
7
  #include "bsp.h"
7
8
  #include "bsp_mesh.h"
8
9
  #include "export.h"
10
+ #include "cmd_audit.h"
9
11
 
10
12
  typedef bsp_node_t* (*bsp_binary_op)(bsp_node_t *, bsp_node_t *);
11
13
 
@@ -26,14 +28,14 @@ bsp_node_t* bsp_binary_operation(char *path1, char *path2, bsp_binary_op op) {
26
28
  check(file1 != NULL, "Failed to read mesh from '%s'", path1);
27
29
  log_info("Loaded file: %s %d facets", path1, file1->poly_count(file1));
28
30
  bsp1 = mesh_to_bsp(file1);
29
- check_mem(bsp1);
31
+ assert_mem(bsp1);
30
32
 
31
33
  // Read 2
32
34
  file2 = mesh_read_file(path2);
33
35
  check(file2 != NULL, "Failed to read mesh from '%s'", path2);
34
36
  log_info("Loaded file: %s %d facets", path2, file2->poly_count(file2));
35
37
  bsp2 = mesh_to_bsp(file2);
36
- check_mem(bsp2);
38
+ assert_mem(bsp2);
37
39
 
38
40
  // Operate
39
41
  result = op(bsp1, bsp2);
@@ -89,11 +91,54 @@ MAKE_CSG_COMMAND(intersect);
89
91
  MAKE_CSG_COMMAND(union);
90
92
  MAKE_CSG_COMMAND(subtract);
91
93
 
94
+ // Commands that exist only when built with `DEBUG` defined
95
+ // these generally don't do anything useful
96
+ #ifdef DEBUG
97
+ int cmd_DEBUG_bsp(int argc, char **argv) {
98
+ const char *suffix = ".bsp.stl";
99
+ char *name = argv[0];
100
+ char *out_name = NULL;
101
+ mesh_t *in = NULL;
102
+ mesh_t *out = NULL;
103
+ bsp_node_t *bsp = NULL;
104
+ check(argc >= 1, "Too few args");
105
+ assert_mem(out_name = calloc(strlen(name) + strlen(suffix) + 1, 1));
106
+ check(sprintf(out_name, "%s%s", name, suffix) == (strlen(name) + strlen(suffix)), "Failed to build out name.");
107
+
108
+ check(in = mesh_read_file(name), "Failed to READ");
109
+ check(bsp = in->to_bsp(in), "Failed to BSP");
110
+ check(out = bsp_to_mesh(bsp, 0), "Failed to BSP->mesh wrap");
111
+ bsp = NULL; // Make it obvs that out now holds the ref
112
+
113
+ log_info("Read: [%s]", argv[0]);
114
+ log_info("Poly Count: [%d]", in->poly_count(in));
115
+ log_info("BSP: [%p]", out);
116
+ log_info("BSP Poly Count: [%d]", out->poly_count(out));
117
+ log_info("Write: [%s](%d)", out_name, out->write(out, out_name, "STL"));
118
+
119
+ if(out != NULL) out->destroy(out);
120
+ if(in != NULL) in->destroy(in);
121
+ if(out_name != NULL) free(out_name);
122
+ return 0;
123
+ error:
124
+ if(out != NULL) out->destroy(out);
125
+ if(in != NULL) in->destroy(in);
126
+ if(out_name != NULL) free(out_name);
127
+ return -1;
128
+ }
129
+ #endif
130
+
92
131
  // Available commands
93
132
  const cmd_t commands[] = {
94
133
  {"intersect", "Intersect two geometries", cmd_intersect},
95
134
  {"subtract", "Subtract two geometries", cmd_subtract},
96
135
  {"union", "Union two geometries", cmd_union},
136
+ {"audit", "Audit a mesh on disk for errors", cmd_audit},
137
+
138
+ #ifdef DEBUG
139
+ {"bsp", "Identity through BSP", cmd_DEBUG_bsp},
140
+ #endif
141
+
97
142
  {NULL, NULL, NULL}
98
143
  };
99
144
 
@@ -1,9 +1,9 @@
1
+ #include "util.h"
1
2
  #include "export.h"
2
3
  #include "bsp_mesh.h"
3
4
 
4
5
  stl_object *stl_from_polys(klist_t(poly) *polygons) {
5
6
  stl_object *stl = stl_alloc(NULL, polygons->size);
6
- check_mem(stl);
7
7
 
8
8
  kliter_t(poly) *iter = kl_begin(polygons);
9
9
  stl_facet *facet = stl->facets;
@@ -49,13 +49,11 @@ bsp_node_t *stl_to_bsp(stl_object *stl) {
49
49
  poly = poly_make_triangle(stl->facets[i].vertices[0],
50
50
  stl->facets[i].vertices[1],
51
51
  stl->facets[i].vertices[2]);
52
- check_mem(polys);
53
52
  *kl_pushp(poly, polys) = poly;
54
53
  }
55
54
  check(polys->size == stl->facet_count, "Wrong number of faces generated.");
56
55
 
57
56
  tree = bsp_build(NULL, polys, 1);
58
- check_mem(tree);
59
57
 
60
58
  kl_destroy(poly, polys);
61
59
  return tree;
@@ -77,33 +75,56 @@ mesh_t* bsp_to_mesh(bsp_node_t* bsp, int copy) {
77
75
  return NEW(bsp_mesh_t, "BSP", input);
78
76
  }
79
77
 
80
- klist_t(poly)* polys_to_tris(klist_t(poly) *dst, klist_t(poly) *src) {
78
+ klist_t(poly) *poly_to_tris(klist_t(poly)* dst, poly_t *poly) {
81
79
  klist_t(poly) *result = dst;
82
80
  if(result == NULL) result = kl_init(poly);
83
81
 
84
- kliter_t(poly) *iter = NULL;
85
- for(iter = kl_begin(src); iter != kl_end(src); iter = kl_next(iter)) {
86
- poly_t *poly = kl_val(iter);
87
- int vertex_count = poly_vertex_count(poly);
82
+ int vertex_count = poly_vertex_count(poly);
88
83
 
89
- // Copy triangles, split higher-vertex polygons into triangle fans.
90
- if(vertex_count == 3) {
91
- poly_t *copy = clone_poly(poly);
92
- check_mem(copy);
93
- *kl_pushp(poly, result) = copy;
94
- }
95
- else if(vertex_count > 3) {
96
- float3 *v_cur, *v_prev;
97
- for(int i = 2; i < vertex_count; i++) {
98
- v_cur = &poly->vertices[i];
99
- v_prev = &poly->vertices[i - 1];
100
- poly_t *tri = poly_make_triangle(poly->vertices[0], *v_prev, *v_cur);
101
- check_mem(tri);
84
+ // Copy triangles, split higher-vertex polygons into triangle fans.
85
+ if(vertex_count == 3) {
86
+ *kl_pushp(poly, result) = clone_poly(poly);
87
+ }
88
+ else if(vertex_count > 3) {
89
+ float3 *v_cur, *v_prev;
90
+ for(int i = 2; i < vertex_count; i++) {
91
+ v_cur = &poly->vertices[i];
92
+ v_prev = &poly->vertices[i - 1];
93
+ poly_t *tri = poly_make_triangle(poly->vertices[0], *v_prev, *v_cur);
94
+
95
+ // If we don't create a valid polygon, don't include it in the result.
96
+ if(tri != NULL) {
102
97
  *kl_pushp(poly, result) = tri;
103
98
  }
104
- } else {
105
- sentinel("polygon(%p) has less than three(%d) vertices.", poly, poly_vertex_count(poly));
99
+ else {
100
+ #ifdef DEBUG
101
+ log_warn("Failed to build triangle:\n(%f, %f, %f)\n(%f, %f, %f)\n(%f, %f, %f)",
102
+ FLOAT3_FORMAT(poly->vertices[0]), FLOAT3_FORMAT(*v_prev), FLOAT3_FORMAT(*v_cur));
103
+ log_warn("Original:");
104
+ poly_print(poly, dbg_get_log());
105
+ #endif
106
+ }
106
107
  }
108
+ } else {
109
+ sentinel("polygon(%p) has less than three(%d) vertices.", poly, poly_vertex_count(poly));
110
+ }
111
+
112
+ return result;
113
+ error:
114
+ if(result != dst) if(result != NULL) kl_destroy(poly, result);
115
+ return NULL;
116
+ }
117
+
118
+ klist_t(poly)* polys_to_tris(klist_t(poly) *dst, klist_t(poly) *src) {
119
+ klist_t(poly) *result = dst;
120
+ if(result == NULL) result = kl_init(poly);
121
+
122
+ kliter_t(poly) *iter = NULL;
123
+ for(iter = kl_begin(src); iter != kl_end(src); iter = kl_next(iter)) {
124
+ poly_t *poly = kl_val(iter);
125
+ check(poly_to_tris(result, poly) != NULL,
126
+ "Failed to tesselate %p(%d) into triangles.",
127
+ poly, poly_vertex_count(poly));
107
128
  }
108
129
 
109
130
  return result;
@@ -15,6 +15,7 @@ stl_object *bsp_to_stl(bsp_node_t *tree);
15
15
  bsp_node_t *stl_to_bsp(stl_object *stl);
16
16
  bsp_node_t *mesh_to_bsp(mesh_t *mesh);
17
17
  mesh_t* bsp_to_mesh(bsp_node_t *tree, int copy);
18
+ klist_t(poly) *poly_to_tris(klist_t(poly)* dst, poly_t *src);
18
19
  klist_t(poly)* polys_to_tris(klist_t(poly)* dst, klist_t(poly)* src);
19
20
 
20
21
  #endif
data/src/poly.c CHANGED
@@ -1,14 +1,16 @@
1
+ #include <stdio.h>
1
2
  #include <assert.h>
3
+ #include <stdbool.h>
2
4
 
3
5
  #include "poly.h"
6
+ #include "util.h"
7
+ #include "export.h"
4
8
 
5
9
  poly_t *alloc_poly(void) {
6
10
  poly_t *poly = malloc(sizeof(poly_t));
7
- check_mem(poly);
11
+ assert_mem(poly);
8
12
  poly_init(poly);
9
13
  return poly;
10
- error:
11
- return NULL;
12
14
  }
13
15
 
14
16
  void free_poly(poly_t *p, int free_self) {
@@ -20,6 +22,24 @@ void free_poly(poly_t *p, int free_self) {
20
22
  if(free_self) free(p);
21
23
  }
22
24
 
25
+ void poly_print(poly_t *p, FILE *stream) {
26
+ fprintf(stream, "Poly(%p) Verts: %d Area: %f:\n", p, poly_vertex_count(p), poly_area(p));
27
+ for(int i = 0; i < poly_vertex_count(p); i++) {
28
+ fprintf(stream,"\tV[%d]: (%f, %f, %f)\n", i, FLOAT3_FORMAT(p->vertices[i]));
29
+ }
30
+ }
31
+
32
+ void poly_print_with_plane_info(poly_t *p, poly_t *plane, FILE *stream) {
33
+ fprintf(stream, "Poly(%p) w(%f) Verts: %d Area: %f:\n", p, p->w, poly_vertex_count(p), poly_area(p));
34
+ for(int i = 0; i < poly_vertex_count(p); i++) {
35
+ float3 diff = FLOAT3_INIT;
36
+ f3_sub(&diff, p->vertices[i], plane->vertices[0]);
37
+ float distance = f3_dot(plane->normal, diff);
38
+ fprintf(stream,"\tV[%d]: (%f, %f, %f) [%s] - %f from plane\n",
39
+ i, FLOAT3_FORMAT(p->vertices[i]), poly_classify_vertex_string(plane, p->vertices[i]), distance);
40
+ }
41
+ }
42
+
23
43
  poly_t *poly_init(poly_t *poly) {
24
44
  poly->vertex_count = 0;
25
45
  poly->vertex_max = POLY_MAX_VERTS;
@@ -28,8 +48,7 @@ poly_t *poly_init(poly_t *poly) {
28
48
  }
29
49
 
30
50
  poly_t *clone_poly(poly_t *poly) {
31
- poly_t *copy = NULL;
32
- check_mem(copy = alloc_poly());
51
+ poly_t *copy = alloc_poly();
33
52
  memcpy(copy, poly, sizeof(poly_t));
34
53
 
35
54
  // Either point the clone at its own copied
@@ -41,14 +60,11 @@ poly_t *clone_poly(poly_t *poly) {
41
60
  // We can lean on the `copy->*` memebers
42
61
  // since they would have been memcpy'd over
43
62
  copy->vertices = malloc(poly_vertex_max(copy) * sizeof(float3));
44
- check_mem(copy->vertices);
63
+ assert_mem(copy->vertices);
45
64
  memcpy(copy->vertices, poly->vertices, poly_vertex_max(copy) * sizeof(float3));
46
65
  }
47
66
 
48
67
  return copy;
49
- error:
50
- if(copy != NULL) free_poly(copy, 1);
51
- return NULL;
52
68
  }
53
69
 
54
70
  int poly_update(poly_t *poly) {
@@ -70,6 +86,65 @@ int poly_update(poly_t *poly) {
70
86
  return 0;
71
87
  }
72
88
 
89
+ // Return two times the area of a triangle.
90
+ // Avoids the division in half unless it's required to avoid
91
+ // failing `f > 0.0` when area is used as a predicate
92
+ float poly_triangle_2area(poly_t *triangle) {
93
+ if(poly_vertex_count(triangle) != 3) return NAN;
94
+
95
+ return triangle_2area(
96
+ triangle->vertices[0],
97
+ triangle->vertices[1],
98
+ triangle->vertices[2]
99
+ );
100
+ }
101
+
102
+ // The actual area of a triangle `triangle`
103
+ // Works through poly_triangle_2area
104
+ float poly_triangle_area(poly_t *triangle) {
105
+ return 0.5 * poly_triangle_2area(triangle);
106
+ }
107
+
108
+ float poly_area(poly_t *poly) {
109
+ return poly_2area(poly) / 2.0;
110
+ }
111
+
112
+ float poly_2area(poly_t *poly) {
113
+ float area2 = 0.0;
114
+ const int vertex_count = poly_vertex_count(poly);
115
+
116
+ // Sanity check that we have at least a polygon
117
+ if(vertex_count < 3) return NAN;
118
+
119
+ // Before we get into this tesselating bullshit, is this just a triangle?
120
+ if(vertex_count == 3) return poly_triangle_2area(poly);
121
+
122
+ // Break the poly into a triangle fan and sum the 2areas of the components
123
+ // Note that i = 2 on first iteration so that `i - 1` is defined and != 0
124
+ // This starts the loop on the first triangle in the poly.
125
+ // Since we're only caring about the magnitude of the cross inside
126
+ // triangle_2area, the vertex order doesn't matter.
127
+ for(int i = 2; i < vertex_count; i++) {
128
+ area2 += triangle_2area(
129
+ poly->vertices[0], // Root vertex
130
+ poly->vertices[i - 1], // Previous vertex
131
+ poly->vertices[i] // Current vertex
132
+ );
133
+ }
134
+
135
+
136
+ return area2;
137
+ }
138
+
139
+ bool poly_has_area(poly_t *poly) {
140
+ float area = poly_2area(poly);
141
+ check_debug(!isnan(area), "Polygon(%p) area is NaN", poly);
142
+
143
+ return area > 0.0;
144
+ error:
145
+ return false;
146
+ }
147
+
73
148
  int poly_vertex_count(poly_t *poly) {
74
149
  return poly->vertex_count;
75
150
  }
@@ -91,7 +166,7 @@ int poly_vertex_expand(poly_t *poly) {
91
166
  // Not using realloc because the original buffer may be struct-owned
92
167
  int new_size = poly->vertex_max * 2;
93
168
  float3 *new_verts = malloc(new_size * sizeof(float3));
94
- check_mem(new_verts);
169
+ assert_mem(new_verts);
95
170
 
96
171
  memcpy(new_verts, poly->vertices, poly->vertex_max * sizeof(float3));
97
172
  poly->vertex_max = new_size;
@@ -105,17 +180,28 @@ int poly_vertex_expand(poly_t *poly) {
105
180
  poly->vertices = new_verts;
106
181
 
107
182
  return 0;
108
- error:
109
- if(new_verts != NULL) free(new_verts);
110
- return -1;
111
183
  }
112
184
 
113
- // add a vertex to the end of the polygon vertex list
114
- int poly_push_vertex(poly_t *poly, float3 v) {
185
+ // add a vertex to the end of the polygon vertex list, if
186
+ // `guard` is true, a check will be performed to reject
187
+ // verts that cause 0-length edges to appear.
188
+ bool poly_push_vertex_guarded(poly_t *poly, float3 v, bool guard) {
115
189
  if(poly_vertex_available(poly) == 0) {
116
190
  poly_vertex_expand(poly);
117
191
  }
118
192
 
193
+ // We only need to perform zero-length-edge checks if we are
194
+ // actually going to create an edge through this push.
195
+ if(guard && (poly_vertex_count(poly) > 0)) {
196
+ int last_idx = poly_vertex_count(poly) - 1;
197
+ bool duplicate_first = !(f3_distance2(poly->vertices[0], v) > 0.0);
198
+ bool duplicate_last = !(f3_distance2(poly->vertices[last_idx], v) > 0.0);
199
+
200
+ // Fail out the addition if we're adding a duplucate first or last vertex
201
+ // as the new last vert. This would create an edge of length zero.
202
+ if(duplicate_first || duplicate_last) return false;
203
+ }
204
+
119
205
  // Dat assignment copy
120
206
  poly->vertices[poly->vertex_count][0] = v[0];
121
207
  poly->vertices[poly->vertex_count][1] = v[1];
@@ -126,9 +212,20 @@ int poly_push_vertex(poly_t *poly, float3 v) {
126
212
  check(poly_update(poly) == 0, "Failed to update polygon during poly_push_vertex(%p)", poly);
127
213
  }
128
214
 
129
- return 0;
215
+ return true;
130
216
  error:
131
- return -1;
217
+ return false;
218
+ }
219
+
220
+ // The default interface to pushing a vertex, force the guard to on
221
+ bool poly_push_vertex(poly_t *poly, float3 v) {
222
+ return poly_push_vertex_guarded(poly, v, true);
223
+ }
224
+
225
+ // Unsafe poly push, forces the guard off, allowing 0-length edges
226
+ // to form. Useful in the `audit` command
227
+ bool poly_push_vertex_unsafe(poly_t *poly, float3 v) {
228
+ return poly_push_vertex_guarded(poly, v, false);
132
229
  }
133
230
 
134
231
  int poly_classify_vertex(poly_t *poly, float3 v) {
@@ -138,6 +235,21 @@ int poly_classify_vertex(poly_t *poly, float3 v) {
138
235
  return COPLANAR;
139
236
  }
140
237
 
238
+ const char* poly_classify_vertex_string(poly_t *poly, float3 v) {
239
+ const char *classification = "UNKNOWN";
240
+ switch(poly_classify_vertex(poly, v)) {
241
+ case FRONT:
242
+ classification = "FRONT";
243
+ break;
244
+ case BACK:
245
+ classification = "BACK";
246
+ break;
247
+ case COPLANAR:
248
+ classification = "COPLANAR";
249
+ }
250
+ return classification;
251
+ }
252
+
141
253
  int poly_classify_poly(poly_t *this, poly_t *other) {
142
254
  int front, back;
143
255
  int count = poly_vertex_count(other);
@@ -180,6 +292,10 @@ int poly_split(poly_t *divider, poly_t *poly, poly_t **front, poly_t **back) {
180
292
  int count = poly_vertex_count(poly);
181
293
  for(i = 0; i < count; i++) {
182
294
  j = (i + 1) % count;
295
+
296
+ // Fill v_cur[..] and v_next[..] with the values of
297
+ // the current (i) and next (j) vertex (x,y,z) data
298
+ // from `poly`
183
299
  for(int k = 0; k < 3; k++) {
184
300
  v_cur[k] = poly->vertices[i][k];
185
301
  v_next[k] = poly->vertices[j][k];
@@ -204,31 +320,38 @@ int poly_split(poly_t *divider, poly_t *poly, poly_t **front, poly_t **back) {
204
320
  float t = divider->w;
205
321
  t = t - f3_dot(divider->normal, v_cur);
206
322
  t = t / f3_dot(divider->normal, diff);
323
+ t = clampf(t, 0.0, 1.0);
207
324
 
208
325
  float3 mid_f = {v_cur[0], v_cur[1], v_cur[2]};
209
326
  f3_interpolate(&mid_f, v_cur, v_next, t);
210
327
 
211
- check(poly_push_vertex(*front, mid_f) == 0,
212
- "Failed to push midpoint to front poly(%p)", front);
213
- check(poly_push_vertex(*back, mid_f) == 0,
214
- "Failed to push midpoint to back poly(%p):", back);
328
+ poly_push_vertex(*front, mid_f);
329
+ poly_push_vertex(*back, mid_f);
215
330
  }
216
331
  }
217
332
 
333
+ // Clear any polygons that are not finished by this point
334
+ if((*front != NULL) && (poly_vertex_count(*front) < 3)) {
335
+ free_poly(*front, true);
336
+ *front = NULL;
337
+ }
338
+
339
+ if((*back != NULL) && (poly_vertex_count(*back) < 3)) {
340
+ free_poly(*back, true);
341
+ *back = NULL;
342
+ }
343
+
218
344
  return 0;
219
- error:
220
- return -1;
221
345
  }
222
346
 
223
- poly_t *poly_make_triangle(float3 a, float3 b, float3 c) {
224
- poly_t *p = NULL;
225
- check_mem(p = alloc_poly());
347
+ poly_t *poly_make_triangle_guarded(float3 a, float3 b, float3 c, bool guard) {
348
+ poly_t *p = alloc_poly();
226
349
 
227
- check(poly_push_vertex(p, a) == 0,
350
+ check_debug(poly_push_vertex_guarded(p, a, guard),
228
351
  "Failed to add vertex a to poly(%p): (%f, %f, %f)", p, FLOAT3_FORMAT(a));
229
- check(poly_push_vertex(p, b) == 0,
352
+ check_debug(poly_push_vertex_guarded(p, b, guard),
230
353
  "Failed to add vertex b to poly(%p): (%f, %f, %f)", p, FLOAT3_FORMAT(b));
231
- check(poly_push_vertex(p, c) == 0,
354
+ check_debug(poly_push_vertex_guarded(p, c, guard),
232
355
  "Failed to add vertex c to poly(%p): (%f, %f, %f)", p, FLOAT3_FORMAT(c));
233
356
 
234
357
  return p;
@@ -237,6 +360,14 @@ error:
237
360
  return NULL;
238
361
  }
239
362
 
363
+ poly_t *poly_make_triangle(float3 a, float3 b, float3 c) {
364
+ return poly_make_triangle_guarded(a, b, c, true);
365
+ }
366
+
367
+ poly_t *poly_make_triangle_unsafe(float3 a, float3 b, float3 c) {
368
+ return poly_make_triangle_guarded(a, b, c, false);
369
+ }
370
+
240
371
  poly_t *poly_invert(poly_t *poly) {
241
372
  f3_scale(&poly->normal, -1.0);
242
373
  poly->w *= -1.0;
@@ -262,3 +393,45 @@ poly_t *poly_invert(poly_t *poly) {
262
393
 
263
394
  return poly;
264
395
  }
396
+
397
+ // Compute the length of the lognest edge squared
398
+ float poly_max_edge_length2(poly_t *poly) {
399
+ const int count = poly_vertex_count(poly);
400
+ float longest = -INFINITY;
401
+
402
+ for(int i = 0; i < count; i++) {
403
+ int j = (i + 1) % count;
404
+ float d2 = f3_distance2(poly->vertices[i], poly->vertices[j]);
405
+
406
+ longest = (d2 > longest) ? d2 : longest;
407
+ }
408
+
409
+ return longest;
410
+ }
411
+
412
+ float poly_min_edge_length2(poly_t *poly) {
413
+ const int count = poly_vertex_count(poly);
414
+ float min = INFINITY;
415
+
416
+ for(int i = 0; i < count; i++) {
417
+ int j = (i + 1) % count;
418
+ float d2 = f3_distance2(poly->vertices[i], poly->vertices[j]);
419
+
420
+ min = (d2 < min) ? d2 : min;
421
+ }
422
+
423
+ return min;
424
+ }
425
+
426
+ float triangle_2area(float3 a, float3 b, float3 c) {
427
+ float3 b_a = FLOAT3_INIT;
428
+ float3 c_a = FLOAT3_INIT;
429
+
430
+ f3_sub(&b_a, b, a);
431
+ f3_sub(&c_a, c, a);
432
+
433
+ float3 cross = FLOAT3_INIT;
434
+ f3_cross(&cross, b_a, c_a);
435
+
436
+ return f3_magnitude(&cross);
437
+ }