#define BLANK_AI_AICONTROLLER_HPP_
 
 #include "../app/IntervalTimer.hpp"
-#include "../model/geometry.hpp"
+#include "../geometry/primitive.hpp"
 #include "../world/EntityController.hpp"
 
 #include <glm/glm.hpp>
 
 #include "IdleState.hpp"
 #include "RoamState.hpp"
 
-#include "../model/geometry.hpp"
+#include "../geometry/distance.hpp"
+#include "../geometry/rotation.hpp"
 #include "../rand/GaloisLFSR.hpp"
 #include "../world/Entity.hpp"
 #include "../world/World.hpp"
 
 #include "NetworkedInput.hpp"
 
 #include "../app/init.hpp"
+#include "../geometry/distance.hpp"
 #include "../io/WorldSave.hpp"
 #include "../net/Packet.hpp"
 #include "../world/Chunk.hpp"
 
--- /dev/null
+#ifndef BLANK_GEOMETRY_CONST_HPP_
+#define BLANK_GEOMETRY_CONST_HPP_
+
+
+namespace blank {
+
+constexpr float PI = 3.141592653589793238462643383279502884;
+constexpr float PI_0p25 = PI * 0.25f;
+constexpr float PI_0p5 = PI * 0.5f;
+constexpr float PI_1p5 = PI * 1.5f;
+constexpr float PI_2p0 = PI * 2.0f;
+
+constexpr float PI_inv = 1.0f / PI;
+constexpr float PI_0p5_inv = 1.0f / PI_0p5;
+
+}
+
+#endif
 
--- /dev/null
+#ifndef BLANK_GEOMETRY_DISTANCE_HPP_
+#define BLANK_GEOMETRY_DISTANCE_HPP_
+
+#include <algorithm>
+#include <limits>
+#include <glm/glm.hpp>
+
+
+namespace blank {
+
+inline float length_squared(const glm::vec3 &v) noexcept {
+       return dot(v, v);
+}
+
+inline float distance_squared(const glm::vec3 &a, const glm::vec3 &b) noexcept {
+       return length_squared(a - b);
+}
+
+template <class T>
+inline bool iszero(const T &v) noexcept {
+       return length_squared(v) < std::numeric_limits<typename T::value_type>::epsilon();
+}
+
+template<class T>
+T manhattan_distance(const glm::tvec3<T> &a, const glm::tvec3<T> &b) noexcept {
+       glm::tvec3<T> diff(abs(a - b));
+       return diff.x + diff.y + diff.z;
+}
+
+template<class T>
+T manhattan_radius(const glm::tvec3<T> &v) noexcept {
+       glm::tvec3<T> a(abs(v));
+       return std::max(a.x, std::max(a.y, a.z));
+}
+
+}
+
+#endif
 
--- /dev/null
+#include "const.hpp"
+#include "distance.hpp"
+#include "primitive.hpp"
+#include "rotation.hpp"
+
+#include <limits>
+#include <glm/gtx/matrix_cross_product.hpp>
+#include <glm/gtx/optimum_pow.hpp>
+#include <glm/gtx/transform.hpp>
+
+
+namespace blank {
+
+glm::mat3 find_rotation(const glm::vec3 &a, const glm::vec3 &b) noexcept {
+       glm::vec3 v(cross(a, b));
+       if (iszero(v)) {
+               // a and b are parallel
+               if (iszero(a - b)) {
+                       // a and b are identical
+                       return glm::mat3(1.0f);
+               } else {
+                       // a and b are opposite
+                       // create arbitrary unit vector perpendicular to a and
+                       // rotate 180° around it
+                       glm::vec3 arb(a);
+                       if (std::abs(a.x - 1.0f) > std::numeric_limits<float>::epsilon()) {
+                               arb.x += 1.0f;
+                       } else {
+                               arb.y += 1.0f;
+                       }
+                       glm::vec3 axis(normalize(cross(a, arb)));
+                       return glm::mat3(glm::rotate(PI, axis));
+               }
+       }
+       float mv = length_squared(v);
+       float c = dot(a, b);
+       float f = (1 - c) / mv;
+       glm::mat3 vx(matrixCross3(v));
+       return glm::mat3(1.0f) + vx + (pow2(vx) * f);
+}
+
+bool Intersection(
+       const Ray &ray,
+       const AABB &aabb,
+       const glm::mat4 &M,
+       float *dist,
+       glm::vec3 *normal
+) noexcept {
+       float t_min = 0.0f;
+       float t_max = std::numeric_limits<float>::infinity();
+       const glm::vec3 aabb_pos(M[3].x, M[3].y, M[3].z);
+       const glm::vec3 delta = aabb_pos - ray.orig;
+
+       glm::vec3 t1(t_min, t_min, t_min), t2(t_max, t_max, t_max);
+
+       for (int i = 0; i < 3; ++i) {
+               const glm::vec3 axis(M[i].x, M[i].y, M[i].z);
+               const float e = glm::dot(axis, delta);
+               const float f = glm::dot(axis, ray.dir);
+
+               if (std::abs(f) > std::numeric_limits<float>::epsilon()) {
+                       t1[i] = (e + aabb.min[i]) / f;
+                       t2[i] = (e + aabb.max[i]) / f;
+
+                       t_min = std::max(t_min, std::min(t1[i], t2[i]));
+                       t_max = std::min(t_max, std::max(t1[i], t2[i]));
+
+                       if (t_max < t_min) {
+                               return false;
+                       }
+               } else {
+                       if (aabb.min[i] - e > 0.0f || aabb.max[i] - e < 0.0f) {
+                               return false;
+                       }
+               }
+       }
+
+       glm::vec3 min_all(min(t1, t2));
+
+       if (dist) {
+               *dist = t_min;
+       }
+       if (normal) {
+               if (min_all.x > min_all.y) {
+                       if (min_all.x > min_all.z) {
+                               normal->x = t2.x < t1.x ? 1 : -1;
+                       } else {
+                               normal->z = t2.z < t1.z ? 1 : -1;
+                       }
+               } else if (min_all.y > min_all.z) {
+                       normal->y = t2.y < t1.y ? 1 : -1;
+               } else {
+                       normal->z = t2.z < t1.z ? 1 : -1;
+               }
+       }
+       return true;
+}
+
+
+bool Intersection(
+       const AABB &a_box,
+       const glm::mat4 &a_m,
+       const AABB &b_box,
+       const glm::mat4 &b_m,
+       float &depth,
+       glm::vec3 &normal
+) noexcept {
+       glm::vec3 a_corners[8] = {
+               glm::vec3(a_m * glm::vec4(a_box.min.x, a_box.min.y, a_box.min.z, 1)),
+               glm::vec3(a_m * glm::vec4(a_box.min.x, a_box.min.y, a_box.max.z, 1)),
+               glm::vec3(a_m * glm::vec4(a_box.min.x, a_box.max.y, a_box.min.z, 1)),
+               glm::vec3(a_m * glm::vec4(a_box.min.x, a_box.max.y, a_box.max.z, 1)),
+               glm::vec3(a_m * glm::vec4(a_box.max.x, a_box.min.y, a_box.min.z, 1)),
+               glm::vec3(a_m * glm::vec4(a_box.max.x, a_box.min.y, a_box.max.z, 1)),
+               glm::vec3(a_m * glm::vec4(a_box.max.x, a_box.max.y, a_box.min.z, 1)),
+               glm::vec3(a_m * glm::vec4(a_box.max.x, a_box.max.y, a_box.max.z, 1)),
+       };
+
+       glm::vec3 b_corners[8] = {
+               glm::vec3(b_m * glm::vec4(b_box.min.x, b_box.min.y, b_box.min.z, 1)),
+               glm::vec3(b_m * glm::vec4(b_box.min.x, b_box.min.y, b_box.max.z, 1)),
+               glm::vec3(b_m * glm::vec4(b_box.min.x, b_box.max.y, b_box.min.z, 1)),
+               glm::vec3(b_m * glm::vec4(b_box.min.x, b_box.max.y, b_box.max.z, 1)),
+               glm::vec3(b_m * glm::vec4(b_box.max.x, b_box.min.y, b_box.min.z, 1)),
+               glm::vec3(b_m * glm::vec4(b_box.max.x, b_box.min.y, b_box.max.z, 1)),
+               glm::vec3(b_m * glm::vec4(b_box.max.x, b_box.max.y, b_box.min.z, 1)),
+               glm::vec3(b_m * glm::vec4(b_box.max.x, b_box.max.y, b_box.max.z, 1)),
+       };
+
+       glm::vec3 axes[6] = {
+               glm::vec3(a_m * glm::vec4(1, 0, 0, 0)),
+               glm::vec3(a_m * glm::vec4(0, 1, 0, 0)),
+               glm::vec3(a_m * glm::vec4(0, 0, 1, 0)),
+               glm::vec3(b_m * glm::vec4(1, 0, 0, 0)),
+               glm::vec3(b_m * glm::vec4(0, 1, 0, 0)),
+               glm::vec3(b_m * glm::vec4(0, 0, 1, 0)),
+       };
+
+       depth = std::numeric_limits<float>::infinity();
+       int min_axis = 0;
+
+       int cur_axis = 0;
+       for (const glm::vec3 &axis : axes) {
+               float a_min = std::numeric_limits<float>::infinity();
+               float a_max = -std::numeric_limits<float>::infinity();
+               for (const glm::vec3 &corner : a_corners) {
+                       float val = glm::dot(corner, axis);
+                       a_min = std::min(a_min, val);
+                       a_max = std::max(a_max, val);
+               }
+
+               float b_min = std::numeric_limits<float>::infinity();
+               float b_max = -std::numeric_limits<float>::infinity();
+               for (const glm::vec3 &corner : b_corners) {
+                       float val = glm::dot(corner, axis);
+                       b_min = std::min(b_min, val);
+                       b_max = std::max(b_max, val);
+               }
+
+               if (a_max < b_min || b_max < a_min) return false;
+
+               float overlap = std::min(a_max, b_max) - std::max(a_min, b_min);
+               if (overlap < depth) {
+                       depth = overlap;
+                       min_axis = cur_axis;
+               }
+
+               ++cur_axis;
+       }
+
+       normal = axes[min_axis];
+       return true;
+}
+
+
+bool CullTest(const AABB &box, const glm::mat4 &MVP) noexcept {
+       // transform corners into clip space
+       glm::vec4 corners[8] = {
+               { box.min.x, box.min.y, box.min.z, 1.0f },
+               { box.min.x, box.min.y, box.max.z, 1.0f },
+               { box.min.x, box.max.y, box.min.z, 1.0f },
+               { box.min.x, box.max.y, box.max.z, 1.0f },
+               { box.max.x, box.min.y, box.min.z, 1.0f },
+               { box.max.x, box.min.y, box.max.z, 1.0f },
+               { box.max.x, box.max.y, box.min.z, 1.0f },
+               { box.max.x, box.max.y, box.max.z, 1.0f },
+       };
+       for (glm::vec4 &corner : corners) {
+               corner = MVP * corner;
+               corner /= corner.w;
+       }
+
+       int hits[6] = { 0, 0, 0, 0, 0, 0 };
+
+       // check how many corners lie outside
+       for (const glm::vec4 &corner : corners) {
+               if (corner.x >  1.0f) ++hits[0];
+               if (corner.x < -1.0f) ++hits[1];
+               if (corner.y >  1.0f) ++hits[2];
+               if (corner.y < -1.0f) ++hits[3];
+               if (corner.z >  1.0f) ++hits[4];
+               if (corner.z < -1.0f) ++hits[5];
+       }
+
+       // if all corners are outside any given clip plane, the test is true
+       for (int hit : hits) {
+               if (hit == 8) return true;
+       }
+
+       // otherwise the box might still get culled completely, but can't say for sure ;)
+       return false;
+}
+
+}
 
--- /dev/null
+#ifndef BLANK_GEOMETRY_PRIMITIVE_HPP_
+#define BLANK_GEOMETRY_PRIMITIVE_HPP_
+
+#include <algorithm>
+#include <glm/glm.hpp>
+
+
+namespace blank {
+
+struct AABB {
+       glm::vec3 min;
+       glm::vec3 max;
+
+       void Adjust() noexcept {
+               if (max.x < min.x) std::swap(max.x, min.x);
+               if (max.y < min.y) std::swap(max.y, min.y);
+               if (max.z < min.z) std::swap(max.z, min.z);
+       }
+
+       glm::vec3 Center() const noexcept {
+               return min + (max - min) * 0.5f;
+       }
+};
+
+struct Ray {
+       glm::vec3 orig;
+       glm::vec3 dir;
+};
+
+bool Intersection(
+       const Ray &,
+       const AABB &,
+       const glm::mat4 &M,
+       float *dist = nullptr,
+       glm::vec3 *normal = nullptr) noexcept;
+
+bool Intersection(
+       const AABB &a_box,
+       const glm::mat4 &a_m,
+       const AABB &b_box,
+       const glm::mat4 &b_m,
+       float &depth,
+       glm::vec3 &normal) noexcept;
+
+bool CullTest(const AABB &box, const glm::mat4 &MVP) noexcept;
+
+}
+
+#endif
 
--- /dev/null
+#ifndef BLANK_GEOMETRY_ROTATION_HPP_
+#define BLANK_GEOMETRY_ROTATION_HPP_
+
+#include <glm/glm.hpp>
+
+
+namespace blank {
+
+glm::mat3 find_rotation(const glm::vec3 &a, const glm::vec3 &b) noexcept;
+
+}
+
+#endif
 
 #include "SkyBoxMesh.hpp"
 #include "SpriteMesh.hpp"
 
-#include "../model/geometry.hpp"
+#include "../geometry/primitive.hpp"
 
 #include <algorithm>
 #include <iostream>
 
 #include "Viewport.hpp"
 
 #include "../app/init.hpp"
-#include "../model/geometry.hpp"
+#include "../geometry/const.hpp"
 
 #include <GL/glew.h>
 #include <glm/gtc/matrix_transform.hpp>
 
 #ifndef BLAMK_MODEL_PART_HPP_
 #define BLAMK_MODEL_PART_HPP_
 
-#include "geometry.hpp"
-
 #include <cstdint>
 #include <list>
 #include <memory>
 
 #define BLANK_MODEL_SHAPE_HPP_
 
 #include "CollisionBounds.hpp"
-#include "geometry.hpp"
+#include "../geometry/primitive.hpp"
 #include "../graphics/BlockMesh.hpp"
 #include "../graphics/EntityMesh.hpp"
 #include "../world/Block.hpp"
 
 #define BLANK_MODEL_BOUNDS_HPP_
 
 #include "CollisionBounds.hpp"
-#include "geometry.hpp"
+#include "../geometry/primitive.hpp"
 
 #include <vector>
 #include <glm/glm.hpp>
 
+++ /dev/null
-#include "geometry.hpp"
-
-#include <limits>
-#include <glm/gtx/matrix_cross_product.hpp>
-#include <glm/gtx/optimum_pow.hpp>
-#include <glm/gtx/transform.hpp>
-
-
-namespace blank {
-
-glm::mat3 find_rotation(const glm::vec3 &a, const glm::vec3 &b) noexcept {
-       glm::vec3 v(cross(a, b));
-       if (iszero(v)) {
-               // a and b are parallel
-               if (iszero(a - b)) {
-                       // a and b are identical
-                       return glm::mat3(1.0f);
-               } else {
-                       // a and b are opposite
-                       // create arbitrary unit vector perpendicular to a and
-                       // rotate 180° around it
-                       glm::vec3 arb(a);
-                       if (std::abs(a.x - 1.0f) > std::numeric_limits<float>::epsilon()) {
-                               arb.x += 1.0f;
-                       } else {
-                               arb.y += 1.0f;
-                       }
-                       glm::vec3 axis(normalize(cross(a, arb)));
-                       return glm::mat3(glm::rotate(PI, axis));
-               }
-       }
-       float mv = length_squared(v);
-       float c = dot(a, b);
-       float f = (1 - c) / mv;
-       glm::mat3 vx(matrixCross3(v));
-       return glm::mat3(1.0f) + vx + (pow2(vx) * f);
-}
-
-bool Intersection(
-       const Ray &ray,
-       const AABB &aabb,
-       const glm::mat4 &M,
-       float *dist,
-       glm::vec3 *normal
-) noexcept {
-       float t_min = 0.0f;
-       float t_max = std::numeric_limits<float>::infinity();
-       const glm::vec3 aabb_pos(M[3].x, M[3].y, M[3].z);
-       const glm::vec3 delta = aabb_pos - ray.orig;
-
-       glm::vec3 t1(t_min, t_min, t_min), t2(t_max, t_max, t_max);
-
-       for (int i = 0; i < 3; ++i) {
-               const glm::vec3 axis(M[i].x, M[i].y, M[i].z);
-               const float e = glm::dot(axis, delta);
-               const float f = glm::dot(axis, ray.dir);
-
-               if (std::abs(f) > std::numeric_limits<float>::epsilon()) {
-                       t1[i] = (e + aabb.min[i]) / f;
-                       t2[i] = (e + aabb.max[i]) / f;
-
-                       t_min = std::max(t_min, std::min(t1[i], t2[i]));
-                       t_max = std::min(t_max, std::max(t1[i], t2[i]));
-
-                       if (t_max < t_min) {
-                               return false;
-                       }
-               } else {
-                       if (aabb.min[i] - e > 0.0f || aabb.max[i] - e < 0.0f) {
-                               return false;
-                       }
-               }
-       }
-
-       glm::vec3 min_all(min(t1, t2));
-
-       if (dist) {
-               *dist = t_min;
-       }
-       if (normal) {
-               if (min_all.x > min_all.y) {
-                       if (min_all.x > min_all.z) {
-                               normal->x = t2.x < t1.x ? 1 : -1;
-                       } else {
-                               normal->z = t2.z < t1.z ? 1 : -1;
-                       }
-               } else if (min_all.y > min_all.z) {
-                       normal->y = t2.y < t1.y ? 1 : -1;
-               } else {
-                       normal->z = t2.z < t1.z ? 1 : -1;
-               }
-       }
-       return true;
-}
-
-
-bool Intersection(
-       const AABB &a_box,
-       const glm::mat4 &a_m,
-       const AABB &b_box,
-       const glm::mat4 &b_m,
-       float &depth,
-       glm::vec3 &normal
-) noexcept {
-       glm::vec3 a_corners[8] = {
-               glm::vec3(a_m * glm::vec4(a_box.min.x, a_box.min.y, a_box.min.z, 1)),
-               glm::vec3(a_m * glm::vec4(a_box.min.x, a_box.min.y, a_box.max.z, 1)),
-               glm::vec3(a_m * glm::vec4(a_box.min.x, a_box.max.y, a_box.min.z, 1)),
-               glm::vec3(a_m * glm::vec4(a_box.min.x, a_box.max.y, a_box.max.z, 1)),
-               glm::vec3(a_m * glm::vec4(a_box.max.x, a_box.min.y, a_box.min.z, 1)),
-               glm::vec3(a_m * glm::vec4(a_box.max.x, a_box.min.y, a_box.max.z, 1)),
-               glm::vec3(a_m * glm::vec4(a_box.max.x, a_box.max.y, a_box.min.z, 1)),
-               glm::vec3(a_m * glm::vec4(a_box.max.x, a_box.max.y, a_box.max.z, 1)),
-       };
-
-       glm::vec3 b_corners[8] = {
-               glm::vec3(b_m * glm::vec4(b_box.min.x, b_box.min.y, b_box.min.z, 1)),
-               glm::vec3(b_m * glm::vec4(b_box.min.x, b_box.min.y, b_box.max.z, 1)),
-               glm::vec3(b_m * glm::vec4(b_box.min.x, b_box.max.y, b_box.min.z, 1)),
-               glm::vec3(b_m * glm::vec4(b_box.min.x, b_box.max.y, b_box.max.z, 1)),
-               glm::vec3(b_m * glm::vec4(b_box.max.x, b_box.min.y, b_box.min.z, 1)),
-               glm::vec3(b_m * glm::vec4(b_box.max.x, b_box.min.y, b_box.max.z, 1)),
-               glm::vec3(b_m * glm::vec4(b_box.max.x, b_box.max.y, b_box.min.z, 1)),
-               glm::vec3(b_m * glm::vec4(b_box.max.x, b_box.max.y, b_box.max.z, 1)),
-       };
-
-       glm::vec3 axes[6] = {
-               glm::vec3(a_m * glm::vec4(1, 0, 0, 0)),
-               glm::vec3(a_m * glm::vec4(0, 1, 0, 0)),
-               glm::vec3(a_m * glm::vec4(0, 0, 1, 0)),
-               glm::vec3(b_m * glm::vec4(1, 0, 0, 0)),
-               glm::vec3(b_m * glm::vec4(0, 1, 0, 0)),
-               glm::vec3(b_m * glm::vec4(0, 0, 1, 0)),
-       };
-
-       depth = std::numeric_limits<float>::infinity();
-       int min_axis = 0;
-
-       int cur_axis = 0;
-       for (const glm::vec3 &axis : axes) {
-               float a_min = std::numeric_limits<float>::infinity();
-               float a_max = -std::numeric_limits<float>::infinity();
-               for (const glm::vec3 &corner : a_corners) {
-                       float val = glm::dot(corner, axis);
-                       a_min = std::min(a_min, val);
-                       a_max = std::max(a_max, val);
-               }
-
-               float b_min = std::numeric_limits<float>::infinity();
-               float b_max = -std::numeric_limits<float>::infinity();
-               for (const glm::vec3 &corner : b_corners) {
-                       float val = glm::dot(corner, axis);
-                       b_min = std::min(b_min, val);
-                       b_max = std::max(b_max, val);
-               }
-
-               if (a_max < b_min || b_max < a_min) return false;
-
-               float overlap = std::min(a_max, b_max) - std::max(a_min, b_min);
-               if (overlap < depth) {
-                       depth = overlap;
-                       min_axis = cur_axis;
-               }
-
-               ++cur_axis;
-       }
-
-       normal = axes[min_axis];
-       return true;
-}
-
-
-bool CullTest(const AABB &box, const glm::mat4 &MVP) noexcept {
-       // transform corners into clip space
-       glm::vec4 corners[8] = {
-               { box.min.x, box.min.y, box.min.z, 1.0f },
-               { box.min.x, box.min.y, box.max.z, 1.0f },
-               { box.min.x, box.max.y, box.min.z, 1.0f },
-               { box.min.x, box.max.y, box.max.z, 1.0f },
-               { box.max.x, box.min.y, box.min.z, 1.0f },
-               { box.max.x, box.min.y, box.max.z, 1.0f },
-               { box.max.x, box.max.y, box.min.z, 1.0f },
-               { box.max.x, box.max.y, box.max.z, 1.0f },
-       };
-       for (glm::vec4 &corner : corners) {
-               corner = MVP * corner;
-               corner /= corner.w;
-       }
-
-       int hits[6] = { 0, 0, 0, 0, 0, 0 };
-
-       // check how many corners lie outside
-       for (const glm::vec4 &corner : corners) {
-               if (corner.x >  1.0f) ++hits[0];
-               if (corner.x < -1.0f) ++hits[1];
-               if (corner.y >  1.0f) ++hits[2];
-               if (corner.y < -1.0f) ++hits[3];
-               if (corner.z >  1.0f) ++hits[4];
-               if (corner.z < -1.0f) ++hits[5];
-       }
-
-       // if all corners are outside any given clip plane, the test is true
-       for (int hit : hits) {
-               if (hit == 8) return true;
-       }
-
-       // otherwise the box might still get culled completely, but can't say for sure ;)
-       return false;
-}
-
-}
 
+++ /dev/null
-#ifndef BLANK_MODEL_GEOMETRY_H_
-#define BLANK_MODEL_GEOMETRY_H_
-
-#include <algorithm>
-#include <limits>
-#include <glm/glm.hpp>
-
-
-namespace blank {
-
-constexpr float PI = 3.141592653589793238462643383279502884;
-constexpr float PI_0p25 = PI * 0.25f;
-constexpr float PI_0p5 = PI * 0.5f;
-constexpr float PI_1p5 = PI * 1.5f;
-constexpr float PI_2p0 = PI * 2.0f;
-
-constexpr float PI_inv = 1.0f / PI;
-constexpr float PI_0p5_inv = 1.0f / PI_0p5;
-
-constexpr float DEG_RAD_FACTOR = PI / 180.0f;
-constexpr float RAD_DEG_FACTOR = 180.0f / PI;
-
-constexpr float deg2rad(float d) noexcept {
-       return d * DEG_RAD_FACTOR;
-}
-
-constexpr float rad2deg(float r) noexcept {
-       return r * RAD_DEG_FACTOR;
-}
-
-
-inline float length_squared(const glm::vec3 &v) noexcept {
-       return dot(v, v);
-}
-
-inline float distance_squared(const glm::vec3 &a, const glm::vec3 &b) noexcept {
-       return length_squared(a - b);
-}
-
-
-template <class T>
-inline bool iszero(const T &v) noexcept {
-       return length_squared(v) < std::numeric_limits<typename T::value_type>::epsilon();
-}
-
-
-template<class T>
-T manhattan_distance(const glm::tvec3<T> &a, const glm::tvec3<T> &b) noexcept {
-       glm::tvec3<T> diff(abs(a - b));
-       return diff.x + diff.y + diff.z;
-}
-
-template<class T>
-T manhattan_radius(const glm::tvec3<T> &v) noexcept {
-       glm::tvec3<T> a(abs(v));
-       return std::max(a.x, std::max(a.y, a.z));
-}
-
-
-glm::mat3 find_rotation(const glm::vec3 &a, const glm::vec3 &b) noexcept;
-
-
-struct AABB {
-       glm::vec3 min;
-       glm::vec3 max;
-
-       void Adjust() noexcept {
-               if (max.x < min.x) std::swap(max.x, min.x);
-               if (max.y < min.y) std::swap(max.y, min.y);
-               if (max.z < min.z) std::swap(max.z, min.z);
-       }
-
-       glm::vec3 Center() const noexcept {
-               return min + (max - min) * 0.5f;
-       }
-};
-
-struct Ray {
-       glm::vec3 orig;
-       glm::vec3 dir;
-};
-
-bool Intersection(
-       const Ray &,
-       const AABB &,
-       const glm::mat4 &M,
-       float *dist = nullptr,
-       glm::vec3 *normal = nullptr) noexcept;
-
-bool Intersection(
-       const AABB &a_box,
-       const glm::mat4 &a_m,
-       const AABB &b_box,
-       const glm::mat4 &b_m,
-       float &depth,
-       glm::vec3 &normal) noexcept;
-
-bool CullTest(const AABB &box, const glm::mat4 &MVP) noexcept;
-
-}
-
-#endif
 
 #include "Packet.hpp"
 
 #include "../app/init.hpp"
+#include "../geometry/const.hpp"
 #include "../model/Model.hpp"
 #include "../world/Entity.hpp"
 #include "../world/EntityState.hpp"
 
 #include "Server.hpp"
 
 #include "../app/init.hpp"
+#include "../geometry/distance.hpp"
 #include "../io/WorldSave.hpp"
 #include "../model/Model.hpp"
 #include "../world/ChunkIndex.hpp"
 
 #include "../app/init.hpp"
 #include "../audio/Audio.hpp"
 #include "../audio/SoundBank.hpp"
+#include "../geometry/distance.hpp"
 #include "../graphics/Font.hpp"
 #include "../graphics/Viewport.hpp"
 #include "../io/TokenStreamReader.hpp"
 
 void HUD::UpdateOrientation() {
        std::stringstream s;
-       s << std::setprecision(3) << "pitch: " << rad2deg(player.GetEntity().Pitch())
-               << ", yaw: " << rad2deg(player.GetEntity().Yaw());
+       s << std::setprecision(3) << "pitch: " << glm::degrees(player.GetEntity().Pitch())
+               << ", yaw: " << glm::degrees(player.GetEntity().Yaw());
        orientation_text.Set(env.assets.small_ui_font, s.str());
 }
 
 
 
 #include "Block.hpp"
 #include "BlockTypeRegistry.hpp"
-#include "../model/geometry.hpp"
+#include "../geometry/primitive.hpp"
 
 #include <vector>
 #include <glm/glm.hpp>
 
 
 #include "Chunk.hpp"
 #include "EntityState.hpp"
+#include "../geometry/primitive.hpp"
 #include "../model/Instance.hpp"
-#include "../model/geometry.hpp"
 
 #include <cstdint>
 #include <string>
 
 #include "BlockType.hpp"
 #include "BlockTypeRegistry.hpp"
 
-#include "../model/geometry.hpp"
-
 #include <ostream>
 #include <glm/gtx/euler_angles.hpp>
 #include <glm/gtx/transform.hpp>
 
 #include "Generator.hpp"
 #include "WorldCollision.hpp"
 #include "../app/Assets.hpp"
+#include "../geometry/distance.hpp"
 #include "../graphics/BlockLighting.hpp"
 #include "../graphics/BlockMesh.hpp"
 #include "../graphics/Viewport.hpp"
 
 #include "EntityCollision.hpp"
 #include "WorldCollision.hpp"
 #include "../app/Assets.hpp"
+#include "../geometry/const.hpp"
+#include "../geometry/distance.hpp"
 #include "../graphics/Format.hpp"
 #include "../graphics/Viewport.hpp"
 
                                std::cout << "forward:   " << forward << std::endl;
                                std::cout << "facing:    " << facing << std::endl;
                                std::cout << "direction: " << direction << std::endl;
-                               std::cout << "difference: " << rad2deg(relative_difference) << "°" << std::endl;
-                               std::cout << "correction: " << rad2deg(correction) << "°" << std::endl;
+                               std::cout << "difference: " << glm::degrees(relative_difference) << "°" << std::endl;
+                               std::cout << "correction: " << glm::degrees(correction) << "°" << std::endl;
                                std::cout  << std::endl;
                        }
                        // now rotate body by correction and head by -correction
 
--- /dev/null
+#include "IntersectionTest.hpp"
+
+#include "geometry/const.hpp"
+#include "geometry/primitive.hpp"
+
+#include <limits>
+#include <glm/gtx/io.hpp>
+#include <glm/gtx/transform.hpp>
+
+CPPUNIT_TEST_SUITE_REGISTRATION(blank::test::IntersectionTest);
+
+
+namespace blank {
+namespace test {
+
+void IntersectionTest::setUp() {
+}
+
+void IntersectionTest::tearDown() {
+}
+
+
+void IntersectionTest::testRayBoxIntersection() {
+       Ray ray{ { 0, 0, 0 }, { 1, 0, 0 } }; // at origin, pointing right
+       AABB box{ { -1, -1, -1 }, { 1, 1, 1 } }; // 2x2x2 cube centered around origin
+       glm::mat4 M(1); // no transformation
+
+       const float delta = std::numeric_limits<float>::epsilon();
+
+       float distance = 0;
+       glm::vec3 normal(0);
+
+       CPPUNIT_ASSERT_MESSAGE(
+               "ray at origin not intersecting box at origin",
+               Intersection(ray, box, M, &distance)
+       );
+       CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(
+               "intersection distance way off",
+               0.0f, distance, delta
+       );
+       // normal undefined, so can't test
+
+       // move ray outside the box, but have it still point at it
+       // should be 4 units to the left now
+       ray.orig.x = -5;
+       CPPUNIT_ASSERT_MESSAGE(
+               "ray pointing at box doesn't intersect",
+               Intersection(ray, box, M, &distance, &normal)
+       );
+       CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(
+               "intersection distance way off",
+               4.0f, distance, delta
+       );
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "wrong surface normal at intersection point",
+               glm::vec3(-1, 0, 0), normal
+       );
+
+       // move ray to the other side, so it's pointing away now
+       ray.orig.x = 5;
+       CPPUNIT_ASSERT_MESSAGE(
+               "ray pointing away from box still intersects",
+               !Intersection(ray, box, M)
+       );
+}
+
+void IntersectionTest::testBoxBoxIntersection() {
+       const float delta = std::numeric_limits<float>::epsilon();
+       float depth = 0;
+       glm::vec3 normal(0);
+
+       AABB box{ { -1, -1, -1 }, { 1, 1, 1 } }; // 2x2x2 cube centered around origin
+       glm::mat4 Ma(1); // identity
+       glm::mat4 Mb(1); // identity
+       // they're identical, so should probably intersect ^^
+
+       CPPUNIT_ASSERT_MESSAGE(
+               "identical OBBs don't intersect",
+               Intersection(box, Ma, box, Mb, depth, normal)
+       );
+       // depth is two, but normal can be any
+       // (will probably be the first axis of box a, but any is valid)
+       CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(
+               "penetration depth of coincidental 2x2x2 boxes is not 2",
+               2.0f, depth, delta
+       );
+
+       Ma = glm::translate(glm::vec3(-2, 0, 0)); // 2 to the left
+       Mb = glm::translate(glm::vec3(2, 0, 0)); // 2 to the right
+       CPPUNIT_ASSERT_MESSAGE(
+               "distant OBBs intersect (2 apart, no rotation)",
+               !Intersection(box, Ma, box, Mb, depth, normal)
+       );
+       // depth and normal undefined for non-intersecting objects
+
+       Ma = glm::rotate(PI_0p25, glm::vec3(0, 0, 1)); // rotated 45° around Z
+       Mb = glm::translate(glm::vec3(2.4, 0, 0)); // 2.4 to the right
+       // they should barely touch. intersect by about sqrt(2) - 1.4 if my head works
+       CPPUNIT_ASSERT_MESSAGE(
+               "OBBs don't intersect (one rotated by 45°)",
+               Intersection(box, Ma, box, Mb, depth, normal)
+       );
+       CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(
+               "bad penetration depth (with rotation)",
+               0.01421356237309504880f, depth, delta
+       );
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "bad intersection normal (with rotation)",
+               glm::vec3(1, 0, 0), abs(normal) // normal can be in + or - x, therefore abs()
+       );
+
+       Mb = glm::translate(glm::vec3(3, 0, 0)); // 3 to the right
+       CPPUNIT_ASSERT_MESSAGE(
+               "OBBs intersect (one rotated by 45°)",
+               !Intersection(box, Ma, box, Mb, depth, normal)
+       );
+}
+
+}
+}
 
--- /dev/null
+#ifndef BLANK_TEST_GEOMETRY_INTERSECTIONTEST_H_
+#define BLANK_TEST_GEOMETRY_INTERSECTIONTEST_H_
+
+#include <cppunit/extensions/HelperMacros.h>
+
+
+namespace blank {
+namespace test {
+
+class IntersectionTest
+: public CppUnit::TestFixture {
+
+CPPUNIT_TEST_SUITE(IntersectionTest);
+
+CPPUNIT_TEST(testRayBoxIntersection);
+CPPUNIT_TEST(testBoxBoxIntersection);
+
+CPPUNIT_TEST_SUITE_END();
+
+public:
+       void setUp();
+       void tearDown();
+
+       void testRayBoxIntersection();
+       void testBoxBoxIntersection();
+
+};
+
+}
+}
+
+#endif
 
+++ /dev/null
-#include "GeometryTest.hpp"
-
-#include "model/geometry.hpp"
-
-#include <limits>
-#include <glm/gtx/io.hpp>
-#include <glm/gtx/transform.hpp>
-
-CPPUNIT_TEST_SUITE_REGISTRATION(blank::test::GeometryTest);
-
-
-namespace blank {
-namespace test {
-
-void GeometryTest::setUp() {
-}
-
-void GeometryTest::tearDown() {
-}
-
-
-void GeometryTest::testRayBoxIntersection() {
-       Ray ray{ { 0, 0, 0 }, { 1, 0, 0 } }; // at origin, pointing right
-       AABB box{ { -1, -1, -1 }, { 1, 1, 1 } }; // 2x2x2 cube centered around origin
-       glm::mat4 M(1); // no transformation
-
-       const float delta = std::numeric_limits<float>::epsilon();
-
-       float distance = 0;
-       glm::vec3 normal(0);
-
-       CPPUNIT_ASSERT_MESSAGE(
-               "ray at origin not intersecting box at origin",
-               Intersection(ray, box, M, &distance)
-       );
-       CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(
-               "intersection distance way off",
-               0.0f, distance, delta
-       );
-       // normal undefined, so can't test
-
-       // move ray outside the box, but have it still point at it
-       // should be 4 units to the left now
-       ray.orig.x = -5;
-       CPPUNIT_ASSERT_MESSAGE(
-               "ray pointing at box doesn't intersect",
-               Intersection(ray, box, M, &distance, &normal)
-       );
-       CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(
-               "intersection distance way off",
-               4.0f, distance, delta
-       );
-       CPPUNIT_ASSERT_EQUAL_MESSAGE(
-               "wrong surface normal at intersection point",
-               glm::vec3(-1, 0, 0), normal
-       );
-
-       // move ray to the other side, so it's pointing away now
-       ray.orig.x = 5;
-       CPPUNIT_ASSERT_MESSAGE(
-               "ray pointing away from box still intersects",
-               !Intersection(ray, box, M)
-       );
-}
-
-void GeometryTest::testBoxBoxIntersection() {
-       const float delta = std::numeric_limits<float>::epsilon();
-       float depth = 0;
-       glm::vec3 normal(0);
-
-       AABB box{ { -1, -1, -1 }, { 1, 1, 1 } }; // 2x2x2 cube centered around origin
-       glm::mat4 Ma(1); // identity
-       glm::mat4 Mb(1); // identity
-       // they're identical, so should probably intersect ^^
-
-       CPPUNIT_ASSERT_MESSAGE(
-               "identical OBBs don't intersect",
-               Intersection(box, Ma, box, Mb, depth, normal)
-       );
-       // depth is two, but normal can be any
-       // (will probably be the first axis of box a, but any is valid)
-       CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(
-               "penetration depth of coincidental 2x2x2 boxes is not 2",
-               2.0f, depth, delta
-       );
-
-       Ma = glm::translate(glm::vec3(-2, 0, 0)); // 2 to the left
-       Mb = glm::translate(glm::vec3(2, 0, 0)); // 2 to the right
-       CPPUNIT_ASSERT_MESSAGE(
-               "distant OBBs intersect (2 apart, no rotation)",
-               !Intersection(box, Ma, box, Mb, depth, normal)
-       );
-       // depth and normal undefined for non-intersecting objects
-
-       Ma = glm::rotate(PI_0p25, glm::vec3(0, 0, 1)); // rotated 45° around Z
-       Mb = glm::translate(glm::vec3(2.4, 0, 0)); // 2.4 to the right
-       // they should barely touch. intersect by about sqrt(2) - 1.4 if my head works
-       CPPUNIT_ASSERT_MESSAGE(
-               "OBBs don't intersect (one rotated by 45°)",
-               Intersection(box, Ma, box, Mb, depth, normal)
-       );
-       CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(
-               "bad penetration depth (with rotation)",
-               0.01421356237309504880f, depth, delta
-       );
-       CPPUNIT_ASSERT_EQUAL_MESSAGE(
-               "bad intersection normal (with rotation)",
-               glm::vec3(1, 0, 0), abs(normal) // normal can be in + or - x, therefore abs()
-       );
-
-       Mb = glm::translate(glm::vec3(3, 0, 0)); // 3 to the right
-       CPPUNIT_ASSERT_MESSAGE(
-               "OBBs intersect (one rotated by 45°)",
-               !Intersection(box, Ma, box, Mb, depth, normal)
-       );
-}
-
-}
-}
 
+++ /dev/null
-#ifndef BLANK_TEST_MODEL_GEOMETRYTEST_H_
-#define BLANK_TEST_MODEL_GEOMETRYTEST_H_
-
-#include <cppunit/extensions/HelperMacros.h>
-
-
-namespace blank {
-namespace test {
-
-class GeometryTest
-: public CppUnit::TestFixture {
-
-CPPUNIT_TEST_SUITE(GeometryTest);
-
-CPPUNIT_TEST(testRayBoxIntersection);
-CPPUNIT_TEST(testBoxBoxIntersection);
-
-CPPUNIT_TEST_SUITE_END();
-
-public:
-       void setUp();
-       void tearDown();
-
-       void testRayBoxIntersection();
-       void testBoxBoxIntersection();
-
-};
-
-}
-}
-
-#endif
 
 #include "PacketTest.hpp"
 
+#include "geometry/const.hpp"
 #include "model/Model.hpp"
 #include "world/Entity.hpp"
 
 
 #ifndef BLANK_TEST_NET_PACKETTEST_HPP_
 #define BLANK_TEST_NET_PACKETTEST_HPP_
 
-#include "model/geometry.hpp"
+#include "geometry/primitive.hpp"
 #include "net/Packet.hpp"
 #include "world/EntityState.hpp"