]> git.seodisparate.com - jumpartifact.com_demo_0/commitdiff
Impl 3d_helpers, unit tests, basic 3D demo
authorStephen Seo <seo.disparate@gmail.com>
Wed, 2 Aug 2023 10:59:06 +0000 (19:59 +0900)
committerStephen Seo <seo.disparate@gmail.com>
Wed, 2 Aug 2023 10:59:06 +0000 (19:59 +0900)
3d_helpers facilitate usage of transformation matrices.

Unit tests ensure the 3d_helpers are correct.

Currently, the program rotates a Cube mesh in x, y, and z axes.

Makefile
src/3d_helpers.cc [new file with mode: 0644]
src/3d_helpers.h [new file with mode: 0644]
src/game.cc
src/main.cc
src/screen_trunner.cc [new file with mode: 0644]
src/screen_trunner.h [new file with mode: 0644]
src/test/test.cc [new file with mode: 0644]
wasm_build/Makefile

index 16658a1d41f5debb0a89b612fcb0ad6c50b00e9b..7d95aca2ae48865635ef3ad34edc4069b4b714a6 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -13,25 +13,38 @@ SOURCES = \
                src/main.cc \
                src/game.cc \
                src/screen.cc \
-               src/screen_test.cc
+               src/screen_test.cc \
+               src/screen_trunner.cc \
+               src/3d_helpers.cc
 
 HEADERS = \
                src/game.h \
                src/screen.h \
-               src/screen_test.h
+               src/screen_test.h \
+               src/screen_trunner.h \
+               src/3d_helpers.h
 
 OBJECTS = $(addprefix ${OBJDIR}/,$(subst .cc,.cc.o,${SOURCES}))
 
+TEST_SOURCES = \
+                       src/test/test.cc
+
+TEST_OBJECTS = $(addprefix ${OBJDIR}/,$(subst .cc,.cc.o,${TEST_SOURCES}))
+
 all: | format demo_0
 
 demo_0: ${OBJECTS}
        ${CXX} ${CXX_FLAGS} ${LINKER_FLAGS} -o $@ $^
 
+test: $(filter-out ${OBJDIR}/src/main.cc.o,${OBJECTS}) ${TEST_OBJECTS}
+       ${CXX} ${CXX_FLAGS} ${LINKER_FLAGS} -o $@ $^
+
 .PHONY: clean format
 
 clean:
        rm -rf ${OBJDIR}
        rm -f demo_0
+       rm -f test
 
 format:
        clang-format -i --style=google ${HEADERS} ${SOURCES}
diff --git a/src/3d_helpers.cc b/src/3d_helpers.cc
new file mode 100644 (file)
index 0000000..ffed809
--- /dev/null
@@ -0,0 +1,128 @@
+#include "3d_helpers.h"
+
+// standard library includes
+#include <cmath>
+
+Matrix get_identity_matrix() {
+  return Matrix{1.0F, 0.0F, 0.0F, 0.0F, 0.0F, 1.0F, 0.0F, 0.0F,
+                0.0F, 0.0F, 1.0F, 0.0F, 0.0F, 0.0F, 0.0F, 1.0F};
+}
+
+// Rotation is
+// cos, -sin,
+// sin, cos
+// probably?
+//
+// About z-axis:
+// cos, -sin, 0, 0,
+// sin,  cos, 0, 0,
+// 0,    0,   1, 0,
+// 0,    0,   0, 1
+
+Matrix get_rotation_matrix_about_z(float radians) {
+  // OpenGL is column-major.
+  return Matrix{std::cos(radians),
+                std::sin(radians),
+                0.0F,
+                0.0F,
+                -std::sin(radians),
+                std::cos(radians),
+                0.0F,
+                0.0F,
+                0.0F,
+                0.0F,
+                1.0F,
+                0.0F,
+                0.0F,
+                0.0F,
+                0.0F,
+                1.0F};
+}
+
+// About y-axis
+//  cos, 0, sin, 0,
+//  0,   1, 0,   0,
+// -sin, 0, cos, 0,
+//  0,   0, 0,   1
+
+Matrix get_rotation_matrix_about_y(float radians) {
+  // OpenGL is column-major.
+  return Matrix{std::cos(radians),
+                0.0F,
+                -std::sin(radians),
+                0.0F,
+                0.0F,
+                1.0F,
+                0.0F,
+                0.0F,
+                std::sin(radians),
+                0.0F,
+                std::cos(radians),
+                0.0F,
+                0.0F,
+                0.0F,
+                0.0F,
+                1.0F};
+}
+
+// About x-axis
+// 1, 0,    0,   0
+// 0, cos, -sin, 0
+// 0, sin,  cos, 0
+// 0, 0,    0,   1
+
+Matrix get_rotation_matrix_about_x(float radians) {
+  // OpenGL is column-major.
+  return Matrix{1.0F,
+                0.0F,
+                0.0F,
+                0.0F,
+                0.0F,
+                std::cos(radians),
+                std::sin(radians),
+                0.0F,
+                0.0F,
+                -std::sin(radians),
+                std::cos(radians),
+                0.0F,
+                0.0F,
+                0.0F,
+                0.0F,
+                1.0F};
+}
+
+Matrix translate_matrix_z(float distance) {
+  return Matrix{1.0F, 0.0F, 0.0F, 0.0F,     0.0F, 1.0F, 0.0F, 0.0F,
+                0.0F, 0.0F, 1.0F, distance, 0.0F, 0.0F, 0.0F, 1.0F};
+}
+
+Matrix operator*(const Matrix &a, const Matrix &b) {
+  return Matrix{a.m0 * b.m0 + a.m1 * b.m4 + a.m2 * b.m8 + a.m3 * b.m12,
+                a.m4 * b.m0 + a.m5 * b.m4 + a.m6 * b.m8 + a.m7 * b.m12,
+                a.m8 * b.m0 + a.m9 * b.m4 + a.m10 * b.m8 + a.m11 * b.m12,
+                a.m12 * b.m0 + a.m13 * b.m4 + a.m14 * b.m8 + a.m15 * b.m12,
+
+                a.m0 * b.m1 + a.m1 * b.m5 + a.m2 * b.m9 + a.m3 * b.m13,
+                a.m4 * b.m1 + a.m5 * b.m5 + a.m6 * b.m9 + a.m7 * b.m13,
+                a.m8 * b.m1 + a.m9 * b.m5 + a.m10 * b.m9 + a.m11 * b.m13,
+                a.m12 * b.m1 + a.m13 * b.m5 + a.m14 * b.m9 + a.m15 * b.m13,
+
+                a.m0 * b.m2 + a.m1 * b.m6 + a.m2 * b.m10 + a.m3 * b.m14,
+                a.m4 * b.m2 + a.m5 * b.m6 + a.m6 * b.m10 + a.m7 * b.m14,
+                a.m8 * b.m2 + a.m9 * b.m6 + a.m10 * b.m10 + a.m11 * b.m14,
+                a.m12 * b.m2 + a.m13 * b.m6 + a.m14 * b.m10 + a.m15 * b.m14,
+
+                a.m0 * b.m3 + a.m1 * b.m7 + a.m2 * b.m11 + a.m3 * b.m15,
+                a.m4 * b.m3 + a.m5 * b.m7 + a.m6 * b.m11 + a.m7 * b.m15,
+                a.m8 * b.m3 + a.m9 * b.m7 + a.m10 * b.m11 + a.m11 * b.m15,
+                a.m12 * b.m3 + a.m13 * b.m7 + a.m14 * b.m11 + a.m15 * b.m15};
+}
+
+Vector4 operator*(const Matrix &m, const Vector4 &v) {
+  return Vector4{
+      m.m0 * v.x + m.m1 * v.y + m.m2 * v.z + m.m3 * v.w,
+      m.m4 * v.x + m.m5 * v.y + m.m6 * v.z + m.m7 * v.w,
+      m.m8 * v.x + m.m9 * v.y + m.m10 * v.z + m.m11 * v.w,
+      m.m12 * v.x + m.m13 * v.y + m.m14 * v.z + m.m15 * v.w,
+  };
+}
diff --git a/src/3d_helpers.h b/src/3d_helpers.h
new file mode 100644 (file)
index 0000000..b77de73
--- /dev/null
@@ -0,0 +1,17 @@
+#ifndef JUMPARTIFACT_DOT_COM_DEMO_0_3D_HELPERS_H_
+#define JUMPARTIFACT_DOT_COM_DEMO_0_3D_HELPERS_H_
+
+// third party includes
+#include <raylib.h>
+
+extern Matrix get_identity_matrix();
+extern Matrix get_rotation_matrix_about_z(float radians);
+extern Matrix get_rotation_matrix_about_y(float radians);
+extern Matrix get_rotation_matrix_about_x(float radians);
+
+extern Matrix translate_matrix_z(float distance);
+
+extern Matrix operator*(const Matrix &a, const Matrix &b);
+extern Vector4 operator*(const Matrix &m, const Vector4 &v);
+
+#endif
index a1fdab1be6223542e167021bb6adb88768162cd5..34824865100611d4b4e6da681071545abc2ed6e0 100644 (file)
@@ -1,11 +1,13 @@
 #include "game.h"
 
 // local includes
-#include "screen_test.h"
+#include "screen_trunner.h"
 
 Game::Game()
     : screen_stack(ScreenStack::new_instance()),
-      prev_time(std::chrono::steady_clock::now()) {}
+      prev_time(std::chrono::steady_clock::now()) {
+  screen_stack->push_screen<TRunnerScreen>();
+}
 
 void Game::update() {
   auto next_time = std::chrono::steady_clock::now();
index 36e814241756e826eb5db9ead2c3724bc3a14102..b2c18629a291ae3e796b315a0bf34919bfbe2f19 100644 (file)
@@ -39,9 +39,9 @@ void jumpartifact_demo_update(void *ud) {
 int main() {
   InitWindow(800, 800, "Demo");
 
+#ifdef __EMSCRIPTEN__
   Game game{};
 
-#ifdef __EMSCRIPTEN__
   SetWindowSize(call_js_get_canvas_width(), call_js_get_canvas_height());
 
   emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, &game, false,
@@ -49,10 +49,13 @@ int main() {
 
   emscripten_set_main_loop_arg(jumpartifact_demo_update, &game, 0, 1);
 #else
-  SetTargetFPS(60);
+  {
+    Game game{};
+    SetTargetFPS(60);
 
-  while (!WindowShouldClose()) {
-    jumpartifact_demo_update(&game);
+    while (!WindowShouldClose()) {
+      jumpartifact_demo_update(&game);
+    }
   }
 
   CloseAudioDevice();
diff --git a/src/screen_trunner.cc b/src/screen_trunner.cc
new file mode 100644 (file)
index 0000000..5fb1ccb
--- /dev/null
@@ -0,0 +1,119 @@
+#include "screen_trunner.h"
+
+// standard library includes
+#include <raylib.h>
+
+#include <cassert>
+#include <cmath>
+
+// local includes
+#include "3d_helpers.h"
+
+TRunnerScreen::TRunnerScreen(std::weak_ptr<ScreenStack> stack)
+    : Screen(stack),
+      camera{Vector3{0.0F, 0.0F, 0.0F}, Vector3{0.0F, 0.0F, -1.0F},
+             Vector3{0.0F, 1.0F, 0.0F}, 80.0F, CAMERA_PERSPECTIVE},
+      flags(),
+      TEMP_cube(GenMeshCube(2.0F, 2.0F, 2.0F)),
+      TEMP_default_material(LoadMaterialDefault()),
+      TEMP_matrix(get_identity_matrix()),
+      TEMP_offset_matrix(translate_matrix_z(-4.0F)),
+      grid_spacing(1.0F),
+      grid_spacing_modifier(0.0F) {}
+
+TRunnerScreen::~TRunnerScreen() {
+  UnloadMesh(TEMP_cube);
+  UnloadMaterial(TEMP_default_material);
+}
+
+bool TRunnerScreen::update(float dt) {
+  //   grid_spacing_modifier += dt * GRID_SPACING_RATE;
+  //   if (grid_spacing_modifier > 2.0F * PI) {
+  //     grid_spacing_modifier -= 2.0F * PI;
+  //   }
+  //   grid_spacing = std::sin(grid_spacing_modifier) + GRID_SPACING_OFFSET;
+  TEMP_matrix = get_rotation_matrix_about_z(dt) *
+                get_rotation_matrix_about_y(dt * 1.2F) *
+                get_rotation_matrix_about_x(dt * 1.4F) * TEMP_matrix;
+  return false;
+}
+
+bool TRunnerScreen::draw() {
+  BeginDrawing();
+  ClearBackground(PixelToColor(Pixel::PIXEL_SKY));
+  BeginMode3D(camera);
+
+  DrawMesh(TEMP_cube, TEMP_default_material, TEMP_matrix * TEMP_offset_matrix);
+
+  //  for (unsigned int i = 11; i-- > 1;) {
+  //    // upper
+  //    DrawRay(Ray{Vector3{0.0F, 2.0F, -(0.5F + grid_spacing * (float)i)},
+  //                Vector3{1.0F, 0.0F, 0.0F}},
+  //            BLACK);
+  //    DrawRay(Ray{Vector3{0.0F, 2.0F, -(0.5F + grid_spacing * (float)i)},
+  //                Vector3{-1.0F, 0.0F, 0.0F}},
+  //            BLACK);
+  //
+  //    // lower
+  //    DrawRay(Ray{Vector3{0.0F, -2.0F, -(0.5F + grid_spacing * (float)i)},
+  //                Vector3{1.0F, 0.0F, 0.0F}},
+  //            BLACK);
+  //    DrawRay(Ray{Vector3{0.0F, -2.0F, -(0.5F + grid_spacing * (float)i)},
+  //                Vector3{-1.0F, 0.0F, 0.0F}},
+  //            BLACK);
+  //
+  //    // left
+  //    DrawRay(Ray{Vector3{-2.0F, 0.0F, -(0.5F + grid_spacing * (float)i)},
+  //                Vector3{0.0F, 1.0F, 0.0F}},
+  //            BLACK);
+  //    DrawRay(Ray{Vector3{-2.0F, 0.0F, -(0.5F + grid_spacing * (float)i)},
+  //                Vector3{0.0F, -1.0F, 0.0F}},
+  //            BLACK);
+  //
+  //    // right
+  //    DrawRay(Ray{Vector3{2.0F, 0.0F, -(0.5F + grid_spacing * (float)i)},
+  //                Vector3{0.0F, 1.0F, 0.0F}},
+  //            BLACK);
+  //    DrawRay(Ray{Vector3{2.0F, 0.0F, -(0.5F + grid_spacing * (float)i)},
+  //                Vector3{0.0F, -1.0F, 0.0F}},
+  //            BLACK);
+  //  }
+  EndMode3D();
+  EndDrawing();
+
+  return false;
+}
+
+Color TRunnerScreen::PixelToColor(Pixel p) {
+  switch (p) {
+    case PIXEL_BLANK:
+      return Color{0, 0, 0, 0};
+    case PIXEL_BLACK:
+      return Color{0, 0, 0, 255};
+    case PIXEL_RED:
+      return Color{255, 50, 50, 255};
+    case PIXEL_GREEN:
+      return Color{50, 255, 50, 255};
+    case PIXEL_BLUE:
+      return Color{50, 50, 255, 255};
+    case PIXEL_YELLOW:
+      return Color{255, 255, 0, 255};
+    case PIXEL_CYAN:
+      return Color{0, 255, 255, 255};
+    case PIXEL_MAGENTA:
+      return Color{255, 0, 255, 255};
+    case PIXEL_ORANGE:
+      return Color{255, 200, 0, 255};
+    case PIXEL_BROWN:
+      return Color{180, 130, 0, 255};
+    case PIXEL_TURQUOISE:
+      return Color{0, 255, 200, 255};
+    case PIXEL_SKY:
+      return Color{168, 178, 255, 255};
+    case PIXEL_WHITE:
+      return Color{255, 255, 255, 255};
+    default:
+      assert(!"unreachable");
+      return Color{0, 0, 0, 255};
+  }
+}
diff --git a/src/screen_trunner.h b/src/screen_trunner.h
new file mode 100644 (file)
index 0000000..ea62f15
--- /dev/null
@@ -0,0 +1,53 @@
+#ifndef JUMPARTIFACT_DOT_COM_DEMO_0_TANK_RUNNER_H_
+#define JUMPARTIFACT_DOT_COM_DEMO_0_TANK_RUNNER_H_
+
+#include "screen.h"
+
+// standard library includes
+#include <array>
+#include <bitset>
+
+// third party includes
+#include <raylib.h>
+
+constexpr float GRID_SPACING_RATE = 1.0F;
+constexpr float GRID_SPACING_OFFSET = 1.5F;
+
+class TRunnerScreen : public Screen {
+ public:
+  TRunnerScreen(std::weak_ptr<ScreenStack> stack);
+  ~TRunnerScreen() override;
+
+  bool update(float dt) override;
+  bool draw() override;
+
+ private:
+  enum Pixel : unsigned char {
+    PIXEL_BLANK,
+    PIXEL_BLACK,
+    PIXEL_RED,
+    PIXEL_GREEN,
+    PIXEL_BLUE,
+    PIXEL_YELLOW,
+    PIXEL_CYAN,
+    PIXEL_MAGENTA,
+    PIXEL_ORANGE,
+    PIXEL_BROWN,
+    PIXEL_TURQUOISE,
+    PIXEL_SKY,
+    PIXEL_WHITE
+  };
+
+  static Color PixelToColor(Pixel p);
+
+  const Camera3D camera;
+  std::bitset<64> flags;
+  Mesh TEMP_cube;
+  Material TEMP_default_material;
+  Matrix TEMP_matrix;
+  Matrix TEMP_offset_matrix;
+  float grid_spacing;
+  float grid_spacing_modifier;
+};
+
+#endif
diff --git a/src/test/test.cc b/src/test/test.cc
new file mode 100644 (file)
index 0000000..7193f9c
--- /dev/null
@@ -0,0 +1,416 @@
+// standard library includes
+#include <iostream>
+
+// local includes
+#include "../3d_helpers.h"
+
+#define ASSERT_TRUE(v) \
+  if (!(v)) { \
+    std::cerr << "False in ASSERT_TRUE at line " << __LINE__ << "!\n"; \
+  }
+
+#define ASSERT_FALSE(v) \
+  if (v) { \
+    std::cerr << "True in ASSERT_FALSE at line " << __LINE__ << "!\n"; \
+  }
+
+#define ASSERT_FLOAT_EQUALS(f, v) \
+  if ((f) < (v) - 0.1F || (f) > (v) + 0.1F) { \
+    std::cerr << "ASSERT_FLOAT_EQUALS: " << (f) << " is not (roughly) equal to " << (v) << " at line " << __LINE__ << "!\n"; \
+  }
+
+
+int main() {
+  std::cout << "Testing 3d_helpers...\n";
+  {
+    auto identity = get_identity_matrix();
+    ASSERT_TRUE(identity.m0 == 1.0F);
+    ASSERT_TRUE(identity.m1 == 0.0F);
+    ASSERT_TRUE(identity.m2 == 0.0F);
+    ASSERT_TRUE(identity.m3 == 0.0F);
+
+    ASSERT_TRUE(identity.m4 == 0.0F);
+    ASSERT_TRUE(identity.m5 == 1.0F);
+    ASSERT_TRUE(identity.m6 == 0.0F);
+    ASSERT_TRUE(identity.m7 == 0.0F);
+
+    ASSERT_TRUE(identity.m8 == 0.0F);
+    ASSERT_TRUE(identity.m9 == 0.0F);
+    ASSERT_TRUE(identity.m10 == 1.0F);
+    ASSERT_TRUE(identity.m11 == 0.0F);
+
+    ASSERT_TRUE(identity.m12 == 0.0F);
+    ASSERT_TRUE(identity.m13 == 0.0F);
+    ASSERT_TRUE(identity.m14 == 0.0F);
+    ASSERT_TRUE(identity.m15 == 1.0F);
+
+    identity = identity * get_identity_matrix();
+    ASSERT_FLOAT_EQUALS(identity.m0, 1.0F);
+    ASSERT_FLOAT_EQUALS(identity.m1, 0.0F);
+    ASSERT_FLOAT_EQUALS(identity.m2, 0.0F);
+    ASSERT_FLOAT_EQUALS(identity.m3, 0.0F);
+
+    ASSERT_FLOAT_EQUALS(identity.m4, 0.0F);
+    ASSERT_FLOAT_EQUALS(identity.m5, 1.0F);
+    ASSERT_FLOAT_EQUALS(identity.m6, 0.0F);
+    ASSERT_FLOAT_EQUALS(identity.m7, 0.0F);
+
+    ASSERT_FLOAT_EQUALS(identity.m8, 0.0F);
+    ASSERT_FLOAT_EQUALS(identity.m9, 0.0F);
+    ASSERT_FLOAT_EQUALS(identity.m10, 1.0F);
+    ASSERT_FLOAT_EQUALS(identity.m11, 0.0F);
+
+    ASSERT_FLOAT_EQUALS(identity.m12, 0.0F);
+    ASSERT_FLOAT_EQUALS(identity.m13, 0.0F);
+    ASSERT_FLOAT_EQUALS(identity.m14, 0.0F);
+    ASSERT_FLOAT_EQUALS(identity.m15, 1.0F);
+
+    {
+      auto v = Vector4{1.0F, 0.0F, 0.0F, 1.0F};
+      auto v_result = identity * v;
+      ASSERT_FLOAT_EQUALS(v_result.x, 1.0F);
+      ASSERT_FLOAT_EQUALS(v_result.y, 0.0F);
+      ASSERT_FLOAT_EQUALS(v_result.z, 0.0F);
+      ASSERT_FLOAT_EQUALS(v_result.w, 1.0F);
+    }
+    {
+      auto v = Vector4{0.0F, 1.0F, 0.0F, 1.0F};
+      auto v_result = identity * v;
+      ASSERT_FLOAT_EQUALS(v_result.x, 0.0F);
+      ASSERT_FLOAT_EQUALS(v_result.y, 1.0F);
+      ASSERT_FLOAT_EQUALS(v_result.z, 0.0F);
+      ASSERT_FLOAT_EQUALS(v_result.w, 1.0F);
+    }
+    {
+      auto v = Vector4{0.0F, 0.0F, 1.0F, 1.0F};
+      auto v_result = identity * v;
+      ASSERT_FLOAT_EQUALS(v_result.x, 0.0F);
+      ASSERT_FLOAT_EQUALS(v_result.y, 0.0F);
+      ASSERT_FLOAT_EQUALS(v_result.z, 1.0F);
+      ASSERT_FLOAT_EQUALS(v_result.w, 1.0F);
+    }
+  }
+
+  {
+    // 90 degree rotation and identity matrix.
+    auto m = get_rotation_matrix_about_z(PI / 2.0F) * get_identity_matrix();
+    ASSERT_FLOAT_EQUALS(m.m0, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m1, -1.0F);
+    ASSERT_FLOAT_EQUALS(m.m2, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m3, 0.0F);
+
+    ASSERT_FLOAT_EQUALS(m.m4, 1.0F);
+    ASSERT_FLOAT_EQUALS(m.m5, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m6, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m7, 0.0F);
+
+    ASSERT_FLOAT_EQUALS(m.m8, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m9, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m10, 1.0F);
+    ASSERT_FLOAT_EQUALS(m.m11, 0.0F);
+
+    ASSERT_FLOAT_EQUALS(m.m12, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m13, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m14, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m15, 1.0F);
+
+    {
+      auto v = Vector4{1.0F, 0.0F, 0.0F, 1.0F};
+      auto v_result = m * v;
+      ASSERT_FLOAT_EQUALS(v_result.x, 0.0F);
+      ASSERT_FLOAT_EQUALS(v_result.y, 1.0F);
+      ASSERT_FLOAT_EQUALS(v_result.z, 0.0F);
+      ASSERT_FLOAT_EQUALS(v_result.w, 1.0F);
+    }
+    {
+      auto v = Vector4{0.0F, 1.0F, 0.0F, 1.0F};
+      auto v_result = m * v;
+      ASSERT_FLOAT_EQUALS(v_result.x, -1.0F);
+      ASSERT_FLOAT_EQUALS(v_result.y, 0.0F);
+      ASSERT_FLOAT_EQUALS(v_result.z, 0.0F);
+      ASSERT_FLOAT_EQUALS(v_result.w, 1.0F);
+    }
+    {
+      auto v = Vector4{-1.0F, 0.0F, 0.0F, 1.0F};
+      auto v_result = m * v;
+      ASSERT_FLOAT_EQUALS(v_result.x, 0.0F);
+      ASSERT_FLOAT_EQUALS(v_result.y, -1.0F);
+      ASSERT_FLOAT_EQUALS(v_result.z, 0.0F);
+      ASSERT_FLOAT_EQUALS(v_result.w, 1.0F);
+    }
+    {
+      auto v = Vector4{0.0F, -1.0F, 0.0F, 1.0F};
+      auto v_result = m * v;
+      ASSERT_FLOAT_EQUALS(v_result.x, 1.0F);
+      ASSERT_FLOAT_EQUALS(v_result.y, 0.0F);
+      ASSERT_FLOAT_EQUALS(v_result.z, 0.0F);
+      ASSERT_FLOAT_EQUALS(v_result.w, 1.0F);
+    }
+  }
+  {
+    // Double 90 degree rotation about z.
+    auto m = get_rotation_matrix_about_z(PI/2.0F) * get_rotation_matrix_about_z(PI/2.0F);
+    ASSERT_FLOAT_EQUALS(m.m0, -1.0F);
+    ASSERT_FLOAT_EQUALS(m.m1, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m2, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m3, 0.0F);
+
+    ASSERT_FLOAT_EQUALS(m.m4, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m5, -1.0F);
+    ASSERT_FLOAT_EQUALS(m.m6, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m7, 0.0F);
+
+    ASSERT_FLOAT_EQUALS(m.m8, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m9, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m10, 1.0F);
+    ASSERT_FLOAT_EQUALS(m.m11, 0.0F);
+
+    ASSERT_FLOAT_EQUALS(m.m12, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m13, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m14, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m15, 1.0F);
+    {
+      auto v = Vector4{1.0F, 0.0F, 0.0F, 1.0F};
+      auto v_result = m * v;
+      ASSERT_FLOAT_EQUALS(v_result.x, -1.0F);
+      ASSERT_FLOAT_EQUALS(v_result.y, 0.0F);
+      ASSERT_FLOAT_EQUALS(v_result.z, 0.0F);
+      ASSERT_FLOAT_EQUALS(v_result.w, 1.0F);
+    }
+    {
+      auto v = Vector4{0.0F, 1.0F, 0.0F, 1.0F};
+      auto v_result = m * v;
+      ASSERT_FLOAT_EQUALS(v_result.x, 0.0F);
+      ASSERT_FLOAT_EQUALS(v_result.y, -1.0F);
+      ASSERT_FLOAT_EQUALS(v_result.z, 0.0F);
+      ASSERT_FLOAT_EQUALS(v_result.w, 1.0F);
+    }
+    {
+      auto v = Vector4{-1.0F, 0.0F, 0.0F, 1.0F};
+      auto v_result = m * v;
+      ASSERT_FLOAT_EQUALS(v_result.x, 1.0F);
+      ASSERT_FLOAT_EQUALS(v_result.y, 0.0F);
+      ASSERT_FLOAT_EQUALS(v_result.z, 0.0F);
+      ASSERT_FLOAT_EQUALS(v_result.w, 1.0F);
+    }
+    {
+      auto v = Vector4{0.0F, -1.0F, 0.0F, 1.0F};
+      auto v_result = m * v;
+      ASSERT_FLOAT_EQUALS(v_result.x, 0.0F);
+      ASSERT_FLOAT_EQUALS(v_result.y, 1.0F);
+      ASSERT_FLOAT_EQUALS(v_result.z, 0.0F);
+      ASSERT_FLOAT_EQUALS(v_result.w, 1.0F);
+    }
+    // Rotate back 90 degrees.
+    m = get_rotation_matrix_about_z(-PI/2.0F) * m;
+    ASSERT_FLOAT_EQUALS(m.m0, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m1, -1.0F);
+    ASSERT_FLOAT_EQUALS(m.m2, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m3, 0.0F);
+
+    ASSERT_FLOAT_EQUALS(m.m4, 1.0F);
+    ASSERT_FLOAT_EQUALS(m.m5, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m6, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m7, 0.0F);
+
+    ASSERT_FLOAT_EQUALS(m.m8, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m9, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m10, 1.0F);
+    ASSERT_FLOAT_EQUALS(m.m11, 0.0F);
+
+    ASSERT_FLOAT_EQUALS(m.m12, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m13, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m14, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m15, 1.0F);
+  }
+  {
+    // Rotate about y-axis 90 degrees.
+    auto m = get_rotation_matrix_about_y(PI/2.0F);
+    ASSERT_FLOAT_EQUALS(m.m0, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m1, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m2, 1.0F);
+    ASSERT_FLOAT_EQUALS(m.m3, 0.0F);
+
+    ASSERT_FLOAT_EQUALS(m.m4, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m5, 1.0F);
+    ASSERT_FLOAT_EQUALS(m.m6, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m7, 0.0F);
+
+    ASSERT_FLOAT_EQUALS(m.m8, -1.0F);
+    ASSERT_FLOAT_EQUALS(m.m9, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m10, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m11, 0.0F);
+
+    ASSERT_FLOAT_EQUALS(m.m12, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m13, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m14, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m15, 1.0F);
+
+    {
+      auto v = Vector4{1.0F, 0.0F, 0.0F, 1.0F};
+      auto v_result = m * v;
+      ASSERT_FLOAT_EQUALS(v_result.x, 0.0F);
+      ASSERT_FLOAT_EQUALS(v_result.y, 0.0F);
+      ASSERT_FLOAT_EQUALS(v_result.z, -1.0F);
+      ASSERT_FLOAT_EQUALS(v_result.w, 1.0F);
+    }
+    {
+      auto v = Vector4{0.0F, 0.0F, -1.0F, 1.0F};
+      auto v_result = m * v;
+      ASSERT_FLOAT_EQUALS(v_result.x, -1.0F);
+      ASSERT_FLOAT_EQUALS(v_result.y, 0.0F);
+      ASSERT_FLOAT_EQUALS(v_result.z, 0.0F);
+      ASSERT_FLOAT_EQUALS(v_result.w, 1.0F);
+    }
+    {
+      auto v = Vector4{-1.0F, 0.0F, 0.0F, 1.0F};
+      auto v_result = m * v;
+      ASSERT_FLOAT_EQUALS(v_result.x, 0.0F);
+      ASSERT_FLOAT_EQUALS(v_result.y, 0.0F);
+      ASSERT_FLOAT_EQUALS(v_result.z, 1.0F);
+      ASSERT_FLOAT_EQUALS(v_result.w, 1.0F);
+    }
+    {
+      auto v = Vector4{0.0F, 0.0F, 1.0F, 1.0F};
+      auto v_result = m * v;
+      ASSERT_FLOAT_EQUALS(v_result.x, 1.0F);
+      ASSERT_FLOAT_EQUALS(v_result.y, 0.0F);
+      ASSERT_FLOAT_EQUALS(v_result.z, 0.0F);
+      ASSERT_FLOAT_EQUALS(v_result.w, 1.0F);
+    }
+
+    // additional 90 degrees.
+    m = get_rotation_matrix_about_y(PI/2.0F) * m;
+    ASSERT_FLOAT_EQUALS(m.m0, -1.0F);
+    ASSERT_FLOAT_EQUALS(m.m1, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m2, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m3, 0.0F);
+
+    ASSERT_FLOAT_EQUALS(m.m4, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m5, 1.0F);
+    ASSERT_FLOAT_EQUALS(m.m6, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m7, 0.0F);
+
+    ASSERT_FLOAT_EQUALS(m.m8, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m9, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m10, -1.0F);
+    ASSERT_FLOAT_EQUALS(m.m11, 0.0F);
+
+    ASSERT_FLOAT_EQUALS(m.m12, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m13, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m14, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m15, 1.0F);
+    {
+      auto v = Vector4{1.0F, 0.0F, 0.0F, 1.0F};
+      auto v_result = m * v;
+      ASSERT_FLOAT_EQUALS(v_result.x, -1.0F);
+      ASSERT_FLOAT_EQUALS(v_result.y, 0.0F);
+      ASSERT_FLOAT_EQUALS(v_result.z, 0.0F);
+      ASSERT_FLOAT_EQUALS(v_result.w, 1.0F);
+    }
+    {
+      auto v = Vector4{0.0F, 0.0F, -1.0F, 1.0F};
+      auto v_result = m * v;
+      ASSERT_FLOAT_EQUALS(v_result.x, 0.0F);
+      ASSERT_FLOAT_EQUALS(v_result.y, 0.0F);
+      ASSERT_FLOAT_EQUALS(v_result.z, 1.0F);
+      ASSERT_FLOAT_EQUALS(v_result.w, 1.0F);
+    }
+  }
+
+  {
+    // About x-axis 90 degrees.
+    auto m = get_rotation_matrix_about_x(PI/2.0F);
+    ASSERT_FLOAT_EQUALS(m.m0, 1.0F);
+    ASSERT_FLOAT_EQUALS(m.m1, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m2, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m3, 0.0F);
+
+    ASSERT_FLOAT_EQUALS(m.m4, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m5, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m6, -1.0F);
+    ASSERT_FLOAT_EQUALS(m.m7, 0.0F);
+
+    ASSERT_FLOAT_EQUALS(m.m8, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m9, 1.0F);
+    ASSERT_FLOAT_EQUALS(m.m10, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m11, 0.0F);
+
+    ASSERT_FLOAT_EQUALS(m.m12, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m13, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m14, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m15, 1.0F);
+    {
+      auto v = Vector4{0.0F, 1.0F, 0.0F, 1.0F};
+      auto v_result = m * v;
+      ASSERT_FLOAT_EQUALS(v_result.x, 0.0F);
+      ASSERT_FLOAT_EQUALS(v_result.y, 0.0F);
+      ASSERT_FLOAT_EQUALS(v_result.z, 1.0F);
+      ASSERT_FLOAT_EQUALS(v_result.w, 1.0F);
+    }
+    {
+      auto v = Vector4{0.0F, 0.0F, 1.0F, 1.0F};
+      auto v_result = m * v;
+      ASSERT_FLOAT_EQUALS(v_result.x, 0.0F);
+      ASSERT_FLOAT_EQUALS(v_result.y, -1.0F);
+      ASSERT_FLOAT_EQUALS(v_result.z, 0.0F);
+      ASSERT_FLOAT_EQUALS(v_result.w, 1.0F);
+    }
+    {
+      auto v = Vector4{0.0F, -1.0F, 0.0F, 1.0F};
+      auto v_result = m * v;
+      ASSERT_FLOAT_EQUALS(v_result.x, 0.0F);
+      ASSERT_FLOAT_EQUALS(v_result.y, 0.0F);
+      ASSERT_FLOAT_EQUALS(v_result.z, -1.0F);
+      ASSERT_FLOAT_EQUALS(v_result.w, 1.0F);
+    }
+    {
+      auto v = Vector4{0.0F, 0.0F, -1.0F, 1.0F};
+      auto v_result = m * v;
+      ASSERT_FLOAT_EQUALS(v_result.x, 0.0F);
+      ASSERT_FLOAT_EQUALS(v_result.y, 1.0F);
+      ASSERT_FLOAT_EQUALS(v_result.z, 0.0F);
+      ASSERT_FLOAT_EQUALS(v_result.w, 1.0F);
+    }
+    // Additional 90 degrees.
+    m = get_rotation_matrix_about_x(PI/2.0F) * m;
+    ASSERT_FLOAT_EQUALS(m.m0, 1.0F);
+    ASSERT_FLOAT_EQUALS(m.m1, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m2, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m3, 0.0F);
+
+    ASSERT_FLOAT_EQUALS(m.m4, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m5, -1.0F);
+    ASSERT_FLOAT_EQUALS(m.m6, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m7, 0.0F);
+
+    ASSERT_FLOAT_EQUALS(m.m8, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m9, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m10, -1.0F);
+    ASSERT_FLOAT_EQUALS(m.m11, 0.0F);
+
+    ASSERT_FLOAT_EQUALS(m.m12, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m13, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m14, 0.0F);
+    ASSERT_FLOAT_EQUALS(m.m15, 1.0F);
+    {
+      auto v = Vector4{0.0F, 1.0F, 0.0F, 1.0F};
+      auto v_result = m * v;
+      ASSERT_FLOAT_EQUALS(v_result.x, 0.0F);
+      ASSERT_FLOAT_EQUALS(v_result.y, -1.0F);
+      ASSERT_FLOAT_EQUALS(v_result.z, 0.0F);
+      ASSERT_FLOAT_EQUALS(v_result.w, 1.0F);
+    }
+    {
+      auto v = Vector4{0.0F, 0.0F, 1.0F, 1.0F};
+      auto v_result = m * v;
+      ASSERT_FLOAT_EQUALS(v_result.x, 0.0F);
+      ASSERT_FLOAT_EQUALS(v_result.y, 0.0F);
+      ASSERT_FLOAT_EQUALS(v_result.z, -1.0F);
+      ASSERT_FLOAT_EQUALS(v_result.w, 1.0F);
+    }
+  }
+
+  std::cout << "Finished tests.\n";
+  return 0;
+}
index f4ffefea6bbed4466847ab06259265b0921cd7f2..203ef6d6cb9af0d21195c5084a33e1339af4e99c 100644 (file)
@@ -9,13 +9,17 @@ SOURCES = \
                ../src/ems.cc \
                ../src/game.cc \
                ../src/screen.cc \
-               ../src/screen_test.cc
+               ../src/screen_test.cc \
+               ../src/screen_trunner.cc \
+               ../src/3d_helpers.cc
 
 HEADERS = \
                ../src/ems.h \
                ../src/game.h \
                ../src/screen.h \
-               ../src/screen_test.h
+               ../src/screen_test.h \
+               ../src/screen_trunner.h \
+               ../src/3d_helpers.h
 
 CXX = source ${HOME}/git/emsdk/emsdk_env.sh && em++