diff --git a/.gitignore b/.gitignore index 93fa848..1b57dab 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ test/test_tuples test/test_canvas test/test_matrices +test/test_transformations *.ppm \ No newline at end of file diff --git a/float.h b/float.h index 963704c..1b20cac 100644 --- a/float.h +++ b/float.h @@ -2,8 +2,10 @@ #include +#include "math.h" + inline static bool float_equal(float a, float b) { const float epsilon = 0.00001; - return __builtin_fabs(a - b) < epsilon; + return fabsf(a - b) < epsilon; } diff --git a/math.h b/math.h new file mode 100644 index 0000000..d7953fb --- /dev/null +++ b/math.h @@ -0,0 +1,6 @@ +#pragma once + +#define sqrtf __builtin_sqrtf +#define fabsf __builtin_fabsf +#define cosf __builtin_cosf +#define sinf __builtin_sinf diff --git a/test/run.sh b/test/run.sh index 04d963d..b0c7a9d 100644 --- a/test/run.sh +++ b/test/run.sh @@ -2,7 +2,7 @@ set -eux -for name in tuples canvas matrices; do +for name in tuples canvas matrices transformations; do gcc -g -gdwarf-5 \ -Wall -Werror -Wfatal-errors \ -I. \ diff --git a/test/test_transformations.c b/test/test_transformations.c new file mode 100644 index 0000000..b8ff5df --- /dev/null +++ b/test/test_transformations.c @@ -0,0 +1,234 @@ +#include +#include + +#include "transformations.h" +#include "runner.h" + +static bool transformations_test_0(const char ** scenario) +{ + *scenario = "Multiplying by a translation matrix"; + + struct mat4x4 transform = translation(5.0f, -3.0f, 2.0f); + struct tuple p = point(-3.0f, 4.0f, 5.0f); + + return tuple_equal(mat4x4_mul_t(&transform, &p), point(2.0f, 1.0f, 7.0f)); +} + +static bool transformations_test_1(const char ** scenario) +{ + *scenario = "Multiplying by the inverse of a translation matrix"; + + struct mat4x4 transform = translation(5.0f, -3.0f, 2.0f); + struct mat4x4 inv = mat4x4_inverse(&transform); + struct tuple p = point(-3.0f, 4.0f, 5.0f); + + return tuple_equal(mat4x4_mul_t(&inv, &p), point(-8.0f, 7.0f, 3.0f)); +} + +static bool transformations_test_2(const char ** scenario) +{ + *scenario = "Translation does not affect vectors"; + + struct mat4x4 transform = translation(5.0f, -3.0f, 2.0f); + struct tuple v = vector(-3.0f, 4.0f, 5.0f); + + return tuple_equal(mat4x4_mul_t(&transform, &v), v); +} + +static bool transformations_test_3(const char ** scenario) +{ + *scenario = "A scaling matrix applied to a point"; + + struct mat4x4 transform = scaling(2.0f, 3.0f, 4.0f); + struct tuple p = point(-4.0f, 6.0f, 8.0f); + + return tuple_equal(mat4x4_mul_t(&transform, &p), point(-8.0f, 18.0f, 32.0f)); +} + +static bool transformations_test_4(const char ** scenario) +{ + *scenario = "A scaling matrix applied to a vector"; + + struct mat4x4 transform = scaling(2.0f, 3.0f, 4.0f); + struct tuple v = vector(-4.0f, 6.0f, 8.0f); + + return tuple_equal(mat4x4_mul_t(&transform, &v), vector(-8.0f, 18.0f, 32.0f)); +} + +static bool transformations_test_5(const char ** scenario) +{ + *scenario = "Multiplying by the inverse of a scaling matrix"; + + struct mat4x4 transform = scaling(2.0f, 3.0f, 4.0f); + struct mat4x4 inv = mat4x4_inverse(&transform); + struct tuple v = vector(-4.0f, 6.0f, 8.0f); + + return tuple_equal(mat4x4_mul_t(&inv, &v), vector(-2.0f, 2.0f, 2.0f)); +} + +static bool transformations_test_6(const char ** scenario) +{ + *scenario = "Reflection is scaling by a negative value"; + + struct mat4x4 transform = scaling(-1.0f, 1.0f, 1.0f); + struct tuple p = point(2.0f, 3.0f, 4.0f); + + return tuple_equal(mat4x4_mul_t(&transform, &p), point(-2.0f, 3.0f, 4.0f)); +} + +static bool transformations_test_7(const char ** scenario) +{ + *scenario = "Rotating a point around the x axis"; + + struct tuple p = point(0.0f, 1.0f, 0.0f); + struct mat4x4 half_quarter = rotation_x(tau / 8); + struct mat4x4 full_quarter = rotation_x(tau / 4); + + return + tuple_equal(mat4x4_mul_t(&half_quarter, &p), point(0.0f, 0.7071067811865476, 0.7071067811865476)) && + tuple_equal(mat4x4_mul_t(&full_quarter, &p), point(0.0f, 0.0f, 1.0f)); +} + +static bool transformations_test_8(const char ** scenario) +{ + *scenario = "Rotating a point around the y axis"; + + struct tuple p = point(0.0f, 0.0f, 1.0f); + struct mat4x4 half_quarter = rotation_y(tau / 8); + struct mat4x4 full_quarter = rotation_y(tau / 4); + + return + tuple_equal(mat4x4_mul_t(&half_quarter, &p), point(0.7071067811865476, 0.0f, 0.7071067811865476)) && + tuple_equal(mat4x4_mul_t(&full_quarter, &p), point(1.0f, 0.0f, 0.0f)); +} + +static bool transformations_test_9(const char ** scenario) +{ + *scenario = "Rotating a point around the z axis"; + + struct tuple p = point(0.0f, 1.0f, 0.0f); + struct mat4x4 half_quarter = rotation_z(tau / 8); + struct mat4x4 full_quarter = rotation_z(tau / 4); + + return + tuple_equal(mat4x4_mul_t(&half_quarter, &p), point(-0.7071067811865476, 0.7071067811865476, 0.0f)) && + tuple_equal(mat4x4_mul_t(&full_quarter, &p), point(-1.0f, 0.0f, 0.0f)); +} + +static bool transformations_test_10(const char ** scenario) +{ + *scenario = "A shearing transformation moves x in proportion to y"; + + struct mat4x4 transform = shearing(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f); + struct tuple p = point(2.0f, 3.0f, 4.0f); + + return tuple_equal(mat4x4_mul_t(&transform, &p), point(5.0f, 3.0f, 4.0f)); +} + +static bool transformations_test_11(const char ** scenario) +{ + *scenario = "A shearing transformation moves x in proportion to z"; + + struct mat4x4 transform = shearing(0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f); + struct tuple p = point(2.0f, 3.0f, 4.0f); + + return tuple_equal(mat4x4_mul_t(&transform, &p), point(6.0f, 3.0f, 4.0f)); +} + +static bool transformations_test_12(const char ** scenario) +{ + *scenario = "A shearing transformation moves y in proportion to x"; + + struct mat4x4 transform = shearing(0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f); + struct tuple p = point(2.0f, 3.0f, 4.0f); + + return tuple_equal(mat4x4_mul_t(&transform, &p), point(2.0f, 5.0f, 4.0f)); +} + +static bool transformations_test_13(const char ** scenario) +{ + *scenario = "A shearing transformation moves y in proportion to z"; + + struct mat4x4 transform = shearing(0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f); + struct tuple p = point(2.0f, 3.0f, 4.0f); + + return tuple_equal(mat4x4_mul_t(&transform, &p), point(2.0f, 7.0f, 4.0f)); +} + +static bool transformations_test_14(const char ** scenario) +{ + *scenario = "A shearing transformation moves z in proportion to x"; + + struct mat4x4 transform = shearing(0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f); + struct tuple p = point(2.0f, 3.0f, 4.0f); + + return tuple_equal(mat4x4_mul_t(&transform, &p), point(2.0f, 3.0f, 6.0f)); +} + +static bool transformations_test_15(const char ** scenario) +{ + *scenario = "A shearing transformation moves z in proportion to y"; + + struct mat4x4 transform = shearing(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f); + struct tuple p = point(2.0f, 3.0f, 4.0f); + + return tuple_equal(mat4x4_mul_t(&transform, &p), point(2.0f, 3.0f, 7.0f)); +} + +static bool transformations_test_16(const char ** scenario) +{ + *scenario = "Individual transforms are applied in sequence"; + + struct tuple p = point(1.0f, 0.0f, 1.0f); + struct mat4x4 a = rotation_x(tau / 4.0f); + struct mat4x4 b = scaling(5.0f, 5.0f, 5.0f); + struct mat4x4 c = translation(10.0f, 5.0f, 7.0f); + + struct tuple p2 = mat4x4_mul_t(&a, &p); + struct tuple p3 = mat4x4_mul_t(&b, &p2); + struct tuple p4 = mat4x4_mul_t(&c, &p3); + + return + tuple_equal(p2, point(1.0f, -1.0f, 0.0f)) && + tuple_equal(p3, point(5.0f, -5.0f, 0.0f)) && + tuple_equal(p4, point(15.0f, 0.0f, 7.0f)); +} + +static bool transformations_test_17(const char ** scenario) +{ + *scenario = "Chained transformations must be applied in reverse order"; + + struct tuple p = point(1.0f, 0.0f, 1.0f); + struct mat4x4 a = rotation_x(tau / 4.0f); + struct mat4x4 b = scaling(5.0f, 5.0f, 5.0f); + struct mat4x4 c = translation(10.0f, 5.0f, 7.0f); + + struct mat4x4 t1 = mat4x4_mul_m(&c, &b); + struct mat4x4 t2 = mat4x4_mul_m(&t1, &a); + struct tuple p1 = mat4x4_mul_t(&t2, &p); + + return tuple_equal(p1, point(15.0f, 0.0f, 7.0f)); +} + +test_t transformations_tests[] = { + transformations_test_0, + transformations_test_1, + transformations_test_2, + transformations_test_3, + transformations_test_4, + transformations_test_5, + transformations_test_6, + transformations_test_7, + transformations_test_8, + transformations_test_9, + transformations_test_10, + transformations_test_11, + transformations_test_12, + transformations_test_13, + transformations_test_14, + transformations_test_15, + transformations_test_16, + transformations_test_17, +}; + +RUNNER(transformations_tests) diff --git a/transformations.h b/transformations.h new file mode 100644 index 0000000..d9c5e9a --- /dev/null +++ b/transformations.h @@ -0,0 +1,59 @@ +#pragma once + +#include "math.h" +#include "matrices.h" + +inline static struct mat4x4 translation(float x, float y, float z) +{ + return mat4x4(1.0f, 0.0f, 0.0f, x, + 0.0f, 1.0f, 0.0f, y, + 0.0f, 0.0f, 1.0f, z, + 0.0f, 0.0f, 0.0f, 1.0f); +} + +inline static struct mat4x4 scaling(float x, float y, float z) +{ + return mat4x4(x, 0.0f, 0.0f, 0.0f, + 0.0f, y, 0.0f, 0.0f, + 0.0f, 0.0f, z, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f); +} + +static const float tau = 6.283185307179586; + +inline static struct mat4x4 rotation_x(float r) +{ + return mat4x4(1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, cosf(r), -sinf(r), 0.0f, + 0.0f, sinf(r), cosf(r), 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f); +} + +inline static struct mat4x4 rotation_y(float r) +{ + return mat4x4( cosf(r), 0.0f, sinf(r), 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + -sinf(r), 0.0f, cosf(r), 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f); +} + +inline static struct mat4x4 rotation_z(float r) +{ + return mat4x4( cosf(r), -sinf(r), 0.0f, 0.0f, + sinf(r), cosf(r), 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f); +} + +inline static struct mat4x4 shearing(float xy, + float xz, + float yx, + float yz, + float zx, + float zy) +{ + return mat4x4(1.0f, xy, xz, 0.0f, + yx, 1.0f, yz, 0.0f, + zx, zy, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f); +} diff --git a/tuples.h b/tuples.h index 8dd8e64..202895b 100644 --- a/tuples.h +++ b/tuples.h @@ -1,5 +1,6 @@ #pragma once +#include "math.h" #include "float.h" struct tuple { @@ -113,10 +114,10 @@ inline static struct tuple tuple_div(struct tuple a, float s) inline static float tuple_magnitude(struct tuple a) { - return __builtin_sqrtf(a.x * a.x + - a.y * a.y + - a.z * a.z + - a.w * a.w); + return sqrtf(a.x * a.x + + a.y * a.y + + a.z * a.z + + a.w * a.w); } inline static struct tuple tuple_normalize(struct tuple a)