1 /* SPDX-License-Identifier: MIT */
2 
3 #include <stdio.h>
4 #include <graphene.h>
5 #include <mutest.h>
6 
7 static void
triangle_init_from_point3d(mutest_spec_t * spec)8 triangle_init_from_point3d (mutest_spec_t *spec)
9 {
10   graphene_point3d_t a = GRAPHENE_POINT3D_INIT ( 0.f,  1.f, 0.f);
11   graphene_point3d_t b = GRAPHENE_POINT3D_INIT ( 1.f, -1.f, 0.f);
12   graphene_point3d_t c = GRAPHENE_POINT3D_INIT (-1.f, -1.f, 0.f);
13   graphene_point3d_t zero = GRAPHENE_POINT3D_INIT_ZERO;
14   graphene_point3d_t check_a, check_b, check_c;
15   graphene_triangle_t *t;
16 
17   t = graphene_triangle_init_from_point3d (graphene_triangle_alloc (), &a, &b, &c);
18   graphene_triangle_get_points (t, &check_a, &check_b, &check_c);
19   mutest_expect ("triangle.a to match first point",
20                  mutest_bool_value (graphene_point3d_equal (&check_a, &a)),
21                  mutest_to_be_true,
22                  NULL);
23   mutest_expect ("triangle.b to match second point",
24                  mutest_bool_value (graphene_point3d_equal (&check_b, &b)),
25                  mutest_to_be_true,
26                  NULL);
27   mutest_expect ("triangle.c to match third point",
28                  mutest_bool_value (graphene_point3d_equal (&check_c, &c)),
29                  mutest_to_be_true,
30                  NULL);
31   mutest_expect ("a unit triangle to have an area of 2",
32                  mutest_float_value (graphene_triangle_get_area (t)),
33                  mutest_to_be_close_to, 2.0, 0.0001,
34                  NULL);
35 
36   graphene_triangle_init_from_point3d (t, NULL, NULL, NULL);
37   graphene_triangle_get_points (t, &check_a, &check_b, &check_c);
38   mutest_expect ("triangle.a to be 0 when initialized to NULL",
39                  mutest_bool_value (graphene_point3d_equal (&check_a, &zero)),
40                  mutest_to_be_true,
41                  NULL);
42   mutest_expect ("triangle.b to be 0 when initialized to NULL",
43                  mutest_bool_value (graphene_point3d_equal (&check_b, &zero)),
44                  mutest_to_be_true,
45                  NULL);
46   mutest_expect ("triangle.c to be 0 when initialized to NULL",
47                  mutest_bool_value (graphene_point3d_equal (&check_c, &zero)),
48                  mutest_to_be_true,
49                  NULL);
50   mutest_expect ("degenerate triangle to have an area of zero",
51                  mutest_float_value (graphene_triangle_get_area (t)),
52                  mutest_to_be_close_to, 0.0, 0.00001,
53                  NULL);
54 
55   graphene_triangle_free (t);
56 }
57 
58 static void
triangle_init_from_vec3(mutest_spec_t * spec)59 triangle_init_from_vec3 (mutest_spec_t *spec)
60 {
61   graphene_vec3_t a, b, c;
62   graphene_vec3_t check_a, check_b, check_c;
63   graphene_triangle_t *t;
64 
65   graphene_vec3_init (&a, 0, 1, 0);
66   graphene_vec3_init (&b, 1, -1, 0);
67   graphene_vec3_init (&c, -1, -1, 0);
68 
69   t = graphene_triangle_init_from_vec3 (graphene_triangle_alloc (), &a, &b, &c);
70   graphene_triangle_get_vertices (t, &check_a, &check_b, &check_c);
71   mutest_expect ("triangle.a to match first vector",
72                  mutest_bool_value (graphene_vec3_equal (&check_a, &a)),
73                  mutest_to_be_true,
74                  NULL);
75   mutest_expect ("triangle.b to match second vector",
76                  mutest_bool_value (graphene_vec3_equal (&check_b, &b)),
77                  mutest_to_be_true,
78                  NULL);
79   mutest_expect ("triangle.c to match third vector",
80                  mutest_bool_value (graphene_vec3_equal (&check_c, &c)),
81                  mutest_to_be_true,
82                  NULL);
83   mutest_expect ("a unit triangle to have an area of 2",
84                  mutest_float_value (graphene_triangle_get_area (t)),
85                  mutest_to_be_close_to, 2.0, 0.0001,
86                  NULL);
87 
88   graphene_triangle_init_from_vec3 (t, NULL, NULL, NULL);
89   graphene_triangle_get_vertices (t, &check_a, &check_b, &check_c);
90   mutest_expect ("triangle.a to be 0 when initialized to NULL",
91                  mutest_bool_value (graphene_vec3_equal (&check_a, graphene_vec3_zero ())),
92                  mutest_to_be_true,
93                  NULL);
94   mutest_expect ("triangle.b to be 0 when initialized to NULL",
95                  mutest_bool_value (graphene_vec3_equal (&check_b, graphene_vec3_zero ())),
96                  mutest_to_be_true,
97                  NULL);
98   mutest_expect ("triangle.c to be 0 when initialized to NULL",
99                  mutest_bool_value (graphene_vec3_equal (&check_c, graphene_vec3_zero ())),
100                  mutest_to_be_true,
101                  NULL);
102   mutest_expect ("degenerate triangle to have an area of zero",
103                  mutest_float_value (graphene_triangle_get_area (t)),
104                  mutest_to_be_close_to, 0.0, 0.00001,
105                  NULL);
106 
107   graphene_triangle_free (t);
108 }
109 
110 static void
triangle_init_from_float(mutest_spec_t * spec)111 triangle_init_from_float (mutest_spec_t *spec)
112 {
113   graphene_vec3_t a, b, c;
114   graphene_vec3_t check_a, check_b, check_c;
115   graphene_triangle_t *t;
116   float v[9] = { 0.f, 1.f, 0.f, 1.f, -1.f, 0.f, -1.f, -1.f, 0.f };
117 
118   graphene_vec3_init_from_float (&a, v);
119   graphene_vec3_init_from_float (&b, v + 3);
120   graphene_vec3_init_from_float (&c, v + 6);
121 
122   t = graphene_triangle_init_from_float (graphene_triangle_alloc (), v, v + 3, v + 6);
123   graphene_triangle_get_vertices (t, &check_a, &check_b, &check_c);
124   mutest_expect ("triangle.a to match first vector",
125                  mutest_bool_value (graphene_vec3_equal (&check_a, &a)),
126                  mutest_to_be_true,
127                  NULL);
128   mutest_expect ("triangle.b to match second vector",
129                  mutest_bool_value (graphene_vec3_equal (&check_b, &b)),
130                  mutest_to_be_true,
131                  NULL);
132   mutest_expect ("triangle.c to match third vector",
133                  mutest_bool_value (graphene_vec3_equal (&check_c, &c)),
134                  mutest_to_be_true,
135                  NULL);
136   mutest_expect ("a unit triangle to have an area of 2",
137                  mutest_float_value (graphene_triangle_get_area (t)),
138                  mutest_to_be_close_to, 2.0, 0.0001,
139                  NULL);
140 
141   graphene_triangle_free (t);
142 }
143 
144 static void
triangle_contains_point(mutest_spec_t * spec)145 triangle_contains_point (mutest_spec_t *spec)
146 {
147   graphene_point3d_t a = GRAPHENE_POINT3D_INIT ( 0.f,  1.f, 0.f);
148   graphene_point3d_t b = GRAPHENE_POINT3D_INIT ( 1.f, -1.f, 0.f);
149   graphene_point3d_t c = GRAPHENE_POINT3D_INIT (-1.f, -1.f, 0.f);
150   graphene_triangle_t *t;
151   graphene_point3d_t zero = GRAPHENE_POINT3D_INIT_ZERO;
152   graphene_point3d_t one = GRAPHENE_POINT3D_INIT (1.f, 1.f, 1.f);
153   graphene_point3d_t midpoint;
154 
155   t = graphene_triangle_init_from_point3d (graphene_triangle_alloc (), &a, &b, &c);
156   graphene_triangle_get_midpoint (t, &midpoint);
157 
158   mutest_expect ("triangle contains (0, 0, 0)",
159                  mutest_bool_value (graphene_triangle_contains_point (t, &zero)),
160                  mutest_to_be_true,
161                  NULL);
162   mutest_expect ("triangle does not contain (1, 1, 1)",
163                  mutest_bool_value (graphene_triangle_contains_point (t, &one)),
164                  mutest_to_be_false,
165                  NULL);
166   mutest_expect ("triangle contains its midpoint",
167                  mutest_bool_value (graphene_triangle_contains_point (t, &midpoint)),
168                  mutest_to_be_true,
169                  NULL);
170 
171   graphene_triangle_free (t);
172 }
173 
174 static void
triangle_plane(mutest_spec_t * spec)175 triangle_plane (mutest_spec_t *spec)
176 {
177   graphene_triangle_t t;
178   graphene_plane_t p;
179   graphene_point3d_t a, b, c;
180   graphene_vec3_t t_norm, p_norm;
181 
182   graphene_triangle_init_from_point3d (&t, NULL, NULL, NULL);
183   graphene_triangle_get_plane (&t, &p);
184   graphene_triangle_get_points (&t, &a, &b, &c);
185 
186   mutest_expect ("degenerate triangle point A sits on the triangle's plane",
187                  mutest_float_value (graphene_plane_distance (&p, &a)),
188                  mutest_to_be_close_to, 0.0, 0.0001,
189                  NULL);
190   mutest_expect ("degenerate triangle point B sits on the triangle's plane",
191                  mutest_float_value (graphene_plane_distance (&p, &b)),
192                  mutest_to_be_close_to, 0.0, 0.0001,
193                  NULL);
194   mutest_expect ("degenerate triangle point C sits on the triangle's plane",
195                  mutest_float_value (graphene_plane_distance (&p, &c)),
196                  mutest_to_be_close_to, 0.0, 0.0001,
197                  NULL);
198 
199   graphene_triangle_get_normal (&t, &t_norm);
200   graphene_plane_get_normal (&p, &p_norm);
201 
202   mutest_expect ("degenerate triangle normal is zero vector",
203                  mutest_bool_value (graphene_vec3_equal (&t_norm, graphene_vec3_zero ())),
204                  mutest_to_be_true,
205                  NULL);
206   mutest_expect ("degenerate triangle normal is equal to the plane normal",
207                  mutest_bool_value (graphene_vec3_equal (&t_norm, &p_norm)),
208                  mutest_to_be_true,
209                  NULL);
210 
211   graphene_point3d_init (&a, 0.f, 0.f, 0.f);
212   graphene_point3d_init (&b, 1.f, 0.f, 0.f);
213   graphene_point3d_init (&c, 0.f, 1.f, 0.f);
214   graphene_triangle_init_from_point3d (&t, &a, &b, &c);
215   graphene_triangle_get_plane (&t, &p);
216 
217   mutest_expect ("unit triangle point A sits on the triangle's plane",
218                  mutest_float_value (graphene_plane_distance (&p, &a)),
219                  mutest_to_be_close_to, 0.0, 0.0001,
220                  NULL);
221   mutest_expect ("unit triangle point B sits on the triangle's plane",
222                  mutest_float_value (graphene_plane_distance (&p, &b)),
223                  mutest_to_be_close_to, 0.0, 0.0001,
224                  NULL);
225   mutest_expect ("unit triangle point C sits on the triangle's plane",
226                  mutest_float_value (graphene_plane_distance (&p, &c)),
227                  mutest_to_be_close_to, 0.0, 0.0001,
228                  NULL);
229 
230   graphene_triangle_get_normal (&t, &t_norm);
231   graphene_plane_get_normal (&p, &p_norm);
232   mutest_expect ("unit triangle normal is equal to the plane normal",
233                  mutest_bool_value (graphene_vec3_near (&t_norm, &p_norm, 0.0001f)),
234                  mutest_to_be_true,
235                  NULL);
236 
237   graphene_point3d_init (&a, 2.f, 0.f, 0.f);
238   graphene_point3d_init (&b, 0.f, 0.f, 0.f);
239   graphene_point3d_init (&c, 0.f, 0.f, 2.f);
240   graphene_triangle_init_from_point3d (&t, &a, &b, &c);
241   graphene_triangle_get_plane (&t, &p);
242 
243   mutest_expect ("triangle point A sits on the triangle's plane",
244                  mutest_float_value (graphene_plane_distance (&p, &a)),
245                  mutest_to_be_close_to, 0.0, 0.0001,
246                  NULL);
247   mutest_expect ("triangle point B sits on the triangle's plane",
248                  mutest_float_value (graphene_plane_distance (&p, &b)),
249                  mutest_to_be_close_to, 0.0, 0.0001,
250                  NULL);
251   mutest_expect ("triangle point C sits on the triangle's plane",
252                  mutest_float_value (graphene_plane_distance (&p, &c)),
253                  mutest_to_be_close_to, 0.0, 0.0001,
254                  NULL);
255 
256   graphene_triangle_get_normal (&t, &t_norm);
257   graphene_vec3_normalize (&t_norm, &t_norm);
258   graphene_plane_get_normal (&p, &p_norm);
259   mutest_expect ("triangle normal is equal to the plane normal",
260                  mutest_bool_value (graphene_vec3_near (&t_norm, &p_norm, 0.0001f)),
261                  mutest_to_be_true,
262                  NULL);
263 }
264 
265 static void
triangle_barycoords(mutest_spec_t * spec)266 triangle_barycoords (mutest_spec_t *spec)
267 {
268   graphene_triangle_t t;
269   graphene_point3d_t a, b, c, p;
270   graphene_vec2_t barycoords;
271   graphene_vec2_t check;
272 
273   graphene_point3d_init (&a, 0.f, 0.f, 0.f);
274   graphene_point3d_init (&b, 1.f, 0.f, 0.f);
275   graphene_point3d_init (&c, 1.f, 1.f, 0.f);
276   graphene_triangle_init_from_point3d (&t, &a, &b, &c);
277 
278   struct {
279     const char *description;
280     graphene_point3d_t point;
281     float uv[2];
282   } border_points[] = {
283     { "unit triangle point A",
284       GRAPHENE_POINT3D_INIT (0.f, 0.f, 0.f),
285       { 0.f, 0.f },
286     },
287     { "unit triangle point B",
288       GRAPHENE_POINT3D_INIT (1.f, 0.f, 0.f),
289       { 0.f, 1.f },
290     },
291     { "unit triangle point C",
292       GRAPHENE_POINT3D_INIT (1.f, 1.f, 0.f),
293       { 1.f, 0.f },
294     },
295   };
296 
297   /* Border */
298   for (int i = 0; i < (sizeof (border_points) / sizeof (border_points[0])); i++)
299     {
300       char desc[128];
301 
302       snprintf (desc, 128, "barycoords for %s to exist", border_points[i].description);
303 
304       mutest_expect (desc,
305                      mutest_bool_value (graphene_triangle_get_barycoords (&t, &(border_points[i].point), &barycoords)),
306                      mutest_to_be_true,
307                      NULL);
308 
309       snprintf (desc, 128, "barycoords for %s to be (%g, %g)",
310                 border_points[i].description,
311                 border_points[i].uv[0],
312                 border_points[i].uv[1]);
313       graphene_vec2_init_from_float (&check, border_points[i].uv);
314       mutest_expect (desc,
315                      mutest_bool_value (graphene_vec2_near (&barycoords, &check, 0.0001f)),
316                      mutest_to_be_true,
317                      NULL);
318     }
319 
320   /* Inside */
321   struct {
322     graphene_point3d_t point;
323     float uv[2];
324   } inside_points[] = {
325     { GRAPHENE_POINT3D_INIT (0.5f, 0.0f, 0.0f), { 0.0f, 0.5f } },
326     { GRAPHENE_POINT3D_INIT (0.5f, 0.5f, 0.0f), { 0.5f, 0.0f } },
327     { GRAPHENE_POINT3D_INIT (1.0f, 0.5f, 0.0f), { 0.5f, 0.5f } },
328   };
329 
330   for (int i = 0; i < (sizeof (inside_points) / sizeof (inside_points[0])); i++)
331     {
332       char desc[128];
333 
334       snprintf (desc, 128, "barycoords for inside point (%g, %g, %g) to exists",
335                 inside_points[i].point.x,
336                 inside_points[i].point.y,
337                 inside_points[i].point.z);
338       mutest_expect (desc,
339                      mutest_bool_value (graphene_triangle_get_barycoords (&t, &(inside_points[i].point), &barycoords)),
340                      mutest_to_be_true,
341                      NULL);
342 
343       snprintf (desc, 128, "barycoords for inside point (%g, %g, %g) to be (%g, %g)",
344                 inside_points[i].point.x,
345                 inside_points[i].point.y,
346                 inside_points[i].point.z,
347                 inside_points[i].uv[0],
348                 inside_points[i].uv[1]);
349       graphene_vec2_init_from_float (&check, inside_points[i].uv);
350       mutest_expect (desc,
351                      mutest_bool_value (graphene_vec2_near (&barycoords, &check, 0.0001f)),
352                      mutest_to_be_true,
353                      NULL);
354     }
355 
356   /* Outside the triangle, same plane */
357   struct {
358     graphene_point3d_t point;
359     float uv[2];
360   } outside_points[] = {
361     { GRAPHENE_POINT3D_INIT (2.0f, 1.0f, 0.0f), { 1.0f, 1.0f } },
362     { GRAPHENE_POINT3D_INIT (-1.0f, 0.0f, 0.0f), { 0.0f, -1.0f } },
363   };
364 
365   for (int i = 0; i < (sizeof (outside_points) / sizeof (outside_points[0])); i++)
366     {
367       char desc[128];
368 
369       snprintf (desc, 128, "barycoords for outside point (%g, %g, %g) to exists",
370                 outside_points[i].point.x,
371                 outside_points[i].point.y,
372                 outside_points[i].point.z);
373       mutest_expect (desc,
374                      mutest_bool_value (graphene_triangle_get_barycoords (&t, &(outside_points[i].point), &barycoords)),
375                      mutest_to_be_true,
376                      NULL);
377 
378       snprintf (desc, 128, "barycoords for outside point (%g, %g, %g) to be (%g, %g)",
379                 outside_points[i].point.x,
380                 outside_points[i].point.y,
381                 outside_points[i].point.z,
382                 outside_points[i].uv[0],
383                 outside_points[i].uv[1]);
384       graphene_vec2_init_from_float (&check, outside_points[i].uv);
385       mutest_expect (desc,
386                      mutest_bool_value (graphene_vec2_near (&barycoords, &check, 0.0001f)),
387                      mutest_to_be_true,
388                      NULL);
389     }
390 
391   /* Outside the triangle plane */
392   /* FIXME is that normal? */
393   graphene_point3d_init (&p, 0.f, 0.f, 1.f);
394   mutest_expect ("barycoords for point outside triangle plane to exist",
395                  mutest_bool_value (graphene_triangle_get_barycoords (&t, &p, &barycoords)),
396                  mutest_to_be_true,
397                  NULL);
398   graphene_vec2_init (&check, 0.f, 0.f);
399   mutest_expect ("barycoords for point outside triangle plane to be (0, 0)",
400                  mutest_bool_value (graphene_vec2_near (&barycoords, &check, 0.0001f)),
401                  mutest_to_be_true,
402                  NULL);
403 }
404 
405 static void
triangle_area(mutest_spec_t * spec)406 triangle_area (mutest_spec_t *spec)
407 {
408   graphene_triangle_t t;
409   graphene_point3d_t a, b, c;
410 
411   /* Counterclockwise */
412   graphene_point3d_init (&a, 0.f, 0.f, 0.f);
413   graphene_point3d_init (&b, 1.f, 0.f, 0.f);
414   graphene_point3d_init (&c, 1.f, 1.f, 0.f);
415 
416   graphene_triangle_init_from_point3d (&t, &a, &b, &c);
417   mutest_expect ("area of unit triangle constructed counterclockwise to be 0.5",
418                  mutest_float_value (graphene_triangle_get_area (&t)),
419                  mutest_to_be_close_to, 0.5, 0.0001,
420                  NULL);
421 
422   /* Clockwise (positive too) */
423   graphene_triangle_init_from_point3d (&t, &a, &c, &b);
424   mutest_expect ("area of unit triangle constructed clockwise to be 0.5",
425                  mutest_float_value (graphene_triangle_get_area (&t)),
426                  mutest_to_be_close_to, 0.5, 0.0001,
427                  NULL);
428 }
429 
430 static void
triangle_suite(mutest_suite_t * suite)431 triangle_suite (mutest_suite_t *suite)
432 {
433   mutest_it ("initializes from points", triangle_init_from_point3d);
434   mutest_it ("initializes from vectors", triangle_init_from_vec3);
435   mutest_it ("initialized from float arrays", triangle_init_from_float);
436   mutest_it ("contains points", triangle_contains_point);
437   mutest_it ("defines planes", triangle_plane);
438   mutest_it ("defines barycoords", triangle_barycoords);
439   mutest_it ("defines areas", triangle_area);
440 }
441 
442 MUTEST_MAIN (
443   mutest_describe ("graphene_triangle_t", triangle_suite);
444 )
445