فهرست منبع

Committing everything done up to this point for math and util
math:
angle:
opaque typedefs for degree and radian, implicit transformation between them.
sin and cos for radians
vector:
a generic vector object
a test suite + project rules for cxxtest that validates vector.hpp
matrix:
a generic 2D matrix object
a test suite project rules for cxxtest that validates most of matrix.hpp
helper generator methods for things like identity, diagonal, translation, scale, and rotation matrices
shape:
line, circle, square, rectangle, quad
implicit conversion from square -> rectangle -> quad
util:
macro.h: basic macros like string-ize and concat
scope_exit.hpp: declarative cleanup of resources without needing a wrapper class
expect.hpp: validate a boolean-convertable precondition or throw an exception

Samuel Jaffe 9 سال پیش
کامیت
dcfb6b5e89

+ 13 - 0
game.xcworkspace/contents.xcworkspacedata

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+   version = "1.0">
+   <FileRef
+      location = "group:util/gameutils.xcodeproj">
+   </FileRef>
+   <FileRef
+      location = "group:graphics/graphics.xcodeproj">
+   </FileRef>
+   <FileRef
+      location = "group:math/math.xcodeproj">
+   </FileRef>
+</Workspace>

+ 19 - 0
math/angle.cpp

@@ -0,0 +1,19 @@
+//
+//  angle.cpp
+//  math
+//
+//  Created by Sam Jaffe on 8/19/16.
+//
+
+#include "angle.hpp"
+
+#include <cmath>
+
+namespace math {
+  radian::radian(double v) : value(v) {}
+  radian::radian(degree d) : value(d.value / M_2_PI) {}
+  radian::operator degree() const { return { value * M_2_PI }; }
+  
+  double sin(radian r) { return std::sin(r.value); }
+  double cos(radian r) { return std::cos(r.value); }
+}

+ 24 - 0
math/angle.hpp

@@ -0,0 +1,24 @@
+//
+//  angle.hpp
+//  math
+//
+//  Created by Sam Jaffe on 8/19/16.
+//
+
+#pragma once
+
+namespace math {
+  struct degree {
+    double value;
+  };
+  
+  struct radian {
+    radian(double v);
+    radian(degree d);
+    operator degree() const;
+    double value;
+  };
+  
+  double sin(radian r);
+  double cos(radian r);
+}

+ 32 - 0
math/common.cpp

@@ -0,0 +1,32 @@
+//
+//  common.cpp
+//  math
+//
+//  Created by Sam Jaffe on 8/20/16.
+//
+
+#include "common.hpp"
+#include "angle.hpp"
+#include "shape.hpp"
+#include "vector.hpp"
+
+namespace math {
+  vec2 rotate(vec2 const & c, vec2 const & p, radian r) {
+    vec2 trans = p - c;
+    vec2 vcos = trans * static_cast<float>(cos(r));
+    vec2 vsin = trans * static_cast<float>(sin(r));
+    return {
+      vcos[0] - vsin[1] + c[0],
+      vsin[0] - vcos[1] + c[1]
+    };
+  }
+  
+  quad rotate(vec2 const & c, quad const & q, radian r) {
+    return {
+      rotate(c, q.ll, r),
+      rotate(c, q.lr, r),
+      rotate(c, q.ur, r),
+      rotate(c, q.ul, r)
+    };
+  }
+}

+ 15 - 0
math/common.hpp

@@ -0,0 +1,15 @@
+//
+//  common.hpp
+//  math
+//
+//  Created by Sam Jaffe on 8/20/16.
+//
+
+#pragma once
+
+#include "math_fwd.hpp"
+
+namespace math {
+  vec2 rotate(vec2 const & center, vec2 const & point, radian r);
+  quad rotate(vec2 const & center, quad const & q, radian r);
+}

+ 503 - 0
math/math.xcodeproj/project.pbxproj

@@ -0,0 +1,503 @@
+// !$*UTF8*$!
+{
+	archiveVersion = 1;
+	classes = {
+	};
+	objectVersion = 46;
+	objects = {
+
+/* Begin PBXBuildFile section */
+		CD3786271CF9F67B00BE89B2 /* vector.hpp in Headers */ = {isa = PBXBuildFile; fileRef = CD3786261CF9F67B00BE89B2 /* vector.hpp */; };
+		CD37862D1CFA121C00BE89B2 /* matrix.hpp in Headers */ = {isa = PBXBuildFile; fileRef = CD37862C1CFA121C00BE89B2 /* matrix.hpp */; };
+		CD3AC71E1D2C0AF8002B4BB0 /* shape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CD3AC71C1D2C0AF8002B4BB0 /* shape.cpp */; };
+		CD3AC71F1D2C0AF8002B4BB0 /* shape.hpp in Headers */ = {isa = PBXBuildFile; fileRef = CD3AC71D1D2C0AF8002B4BB0 /* shape.hpp */; };
+		CD3C80881D6645C600ACC795 /* vector_tc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CD3C80871D6645C600ACC795 /* vector_tc.cpp */; };
+		CD3C808A1D66469400ACC795 /* math_fwd.hpp in Headers */ = {isa = PBXBuildFile; fileRef = CD3AC6FA1D2C0558002B4BB0 /* math_fwd.hpp */; };
+		CD3C809A1D6654C200ACC795 /* matrix_tc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CD3C80981D6654C200ACC795 /* matrix_tc.cpp */; };
+		CD3C809F1D675AB100ACC795 /* angle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CD3C809D1D675AB100ACC795 /* angle.cpp */; };
+		CD3C80A01D675AB100ACC795 /* angle.hpp in Headers */ = {isa = PBXBuildFile; fileRef = CD3C809E1D675AB100ACC795 /* angle.hpp */; };
+		CD3C80B11D67B79B00ACC795 /* libmath.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = CD3786181CF9F61100BE89B2 /* libmath.dylib */; };
+		CD3C80B91D6884D700ACC795 /* matrix_helpers.hpp in Headers */ = {isa = PBXBuildFile; fileRef = CD3C80B81D6884D700ACC795 /* matrix_helpers.hpp */; };
+		CD3C80BC1D68902300ACC795 /* common.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CD3C80BA1D68902300ACC795 /* common.cpp */; };
+		CD3C80BD1D68902300ACC795 /* common.hpp in Headers */ = {isa = PBXBuildFile; fileRef = CD3C80BB1D68902300ACC795 /* common.hpp */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXCopyFilesBuildPhase section */
+		CD3C807E1D6645AB00ACC795 /* CopyFiles */ = {
+			isa = PBXCopyFilesBuildPhase;
+			buildActionMask = 2147483647;
+			dstPath = /usr/share/man/man1/;
+			dstSubfolderSpec = 0;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 1;
+		};
+		CD3C808F1D6654A400ACC795 /* CopyFiles */ = {
+			isa = PBXCopyFilesBuildPhase;
+			buildActionMask = 2147483647;
+			dstPath = /usr/share/man/man1/;
+			dstSubfolderSpec = 0;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 1;
+		};
+/* End PBXCopyFilesBuildPhase section */
+
+/* Begin PBXFileReference section */
+		CD3786181CF9F61100BE89B2 /* libmath.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libmath.dylib; sourceTree = BUILT_PRODUCTS_DIR; };
+		CD3786261CF9F67B00BE89B2 /* vector.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = vector.hpp; sourceTree = "<group>"; };
+		CD37862C1CFA121C00BE89B2 /* matrix.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = matrix.hpp; sourceTree = "<group>"; };
+		CD3AC6FA1D2C0558002B4BB0 /* math_fwd.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = math_fwd.hpp; sourceTree = "<group>"; };
+		CD3AC71C1D2C0AF8002B4BB0 /* shape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = shape.cpp; sourceTree = "<group>"; };
+		CD3AC71D1D2C0AF8002B4BB0 /* shape.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = shape.hpp; sourceTree = "<group>"; };
+		CD3C807A1D66440A00ACC795 /* vector.t.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = vector.t.h; sourceTree = "<group>"; };
+		CD3C80801D6645AB00ACC795 /* vector_tc */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = vector_tc; sourceTree = BUILT_PRODUCTS_DIR; };
+		CD3C80871D6645C600ACC795 /* vector_tc.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = vector_tc.cpp; sourceTree = "<group>"; };
+		CD3C80911D6654A400ACC795 /* matrix_tc */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = matrix_tc; sourceTree = BUILT_PRODUCTS_DIR; };
+		CD3C80981D6654C200ACC795 /* matrix_tc.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = matrix_tc.cpp; sourceTree = "<group>"; };
+		CD3C80991D6654C200ACC795 /* matrix.t.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = matrix.t.h; sourceTree = "<group>"; };
+		CD3C809D1D675AB100ACC795 /* angle.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = angle.cpp; sourceTree = "<group>"; };
+		CD3C809E1D675AB100ACC795 /* angle.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = angle.hpp; sourceTree = "<group>"; };
+		CD3C80B81D6884D700ACC795 /* matrix_helpers.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = matrix_helpers.hpp; sourceTree = "<group>"; };
+		CD3C80BA1D68902300ACC795 /* common.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = common.cpp; sourceTree = "<group>"; };
+		CD3C80BB1D68902300ACC795 /* common.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = common.hpp; sourceTree = "<group>"; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+		CD3786151CF9F61100BE89B2 /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		CD3C807D1D6645AB00ACC795 /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		CD3C808E1D6654A400ACC795 /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				CD3C80B11D67B79B00ACC795 /* libmath.dylib in Frameworks */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+		CD37860F1CF9F61100BE89B2 = {
+			isa = PBXGroup;
+			children = (
+				CD3C80791D66440200ACC795 /* test */,
+				CD3786321CFA304800BE89B2 /* src */,
+				CD3786191CF9F61100BE89B2 /* Products */,
+			);
+			sourceTree = "<group>";
+		};
+		CD3786191CF9F61100BE89B2 /* Products */ = {
+			isa = PBXGroup;
+			children = (
+				CD3786181CF9F61100BE89B2 /* libmath.dylib */,
+				CD3C80801D6645AB00ACC795 /* vector_tc */,
+				CD3C80911D6654A400ACC795 /* matrix_tc */,
+			);
+			name = Products;
+			sourceTree = "<group>";
+		};
+		CD3786321CFA304800BE89B2 /* src */ = {
+			isa = PBXGroup;
+			children = (
+				CD3786261CF9F67B00BE89B2 /* vector.hpp */,
+				CD3AC6FA1D2C0558002B4BB0 /* math_fwd.hpp */,
+				CD37862C1CFA121C00BE89B2 /* matrix.hpp */,
+				CD3C80B81D6884D700ACC795 /* matrix_helpers.hpp */,
+				CD3AC71C1D2C0AF8002B4BB0 /* shape.cpp */,
+				CD3AC71D1D2C0AF8002B4BB0 /* shape.hpp */,
+				CD3C809D1D675AB100ACC795 /* angle.cpp */,
+				CD3C809E1D675AB100ACC795 /* angle.hpp */,
+				CD3C80BA1D68902300ACC795 /* common.cpp */,
+				CD3C80BB1D68902300ACC795 /* common.hpp */,
+			);
+			name = src;
+			sourceTree = "<group>";
+		};
+		CD3C80791D66440200ACC795 /* test */ = {
+			isa = PBXGroup;
+			children = (
+				CD3C807A1D66440A00ACC795 /* vector.t.h */,
+				CD3C80991D6654C200ACC795 /* matrix.t.h */,
+				CD3C80871D6645C600ACC795 /* vector_tc.cpp */,
+				CD3C80981D6654C200ACC795 /* matrix_tc.cpp */,
+			);
+			name = test;
+			sourceTree = "<group>";
+		};
+/* End PBXGroup section */
+
+/* Begin PBXHeadersBuildPhase section */
+		CD3786161CF9F61100BE89B2 /* Headers */ = {
+			isa = PBXHeadersBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				CD3786271CF9F67B00BE89B2 /* vector.hpp in Headers */,
+				CD3AC71F1D2C0AF8002B4BB0 /* shape.hpp in Headers */,
+				CD37862D1CFA121C00BE89B2 /* matrix.hpp in Headers */,
+				CD3C80A01D675AB100ACC795 /* angle.hpp in Headers */,
+				CD3C808A1D66469400ACC795 /* math_fwd.hpp in Headers */,
+				CD3C80BD1D68902300ACC795 /* common.hpp in Headers */,
+				CD3C80B91D6884D700ACC795 /* matrix_helpers.hpp in Headers */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXHeadersBuildPhase section */
+
+/* Begin PBXNativeTarget section */
+		CD3786171CF9F61100BE89B2 /* math */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = CD3786231CF9F61100BE89B2 /* Build configuration list for PBXNativeTarget "math" */;
+			buildPhases = (
+				CD3786141CF9F61100BE89B2 /* Sources */,
+				CD3786151CF9F61100BE89B2 /* Frameworks */,
+				CD3786161CF9F61100BE89B2 /* Headers */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+			);
+			name = math;
+			productName = math;
+			productReference = CD3786181CF9F61100BE89B2 /* libmath.dylib */;
+			productType = "com.apple.product-type.library.dynamic";
+		};
+		CD3C807F1D6645AB00ACC795 /* vector_tc */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = CD3C80841D6645AB00ACC795 /* Build configuration list for PBXNativeTarget "vector_tc" */;
+			buildPhases = (
+				CD3C80891D6645ED00ACC795 /* ShellScript */,
+				CD3C807C1D6645AB00ACC795 /* Sources */,
+				CD3C807D1D6645AB00ACC795 /* Frameworks */,
+				CD3C807E1D6645AB00ACC795 /* CopyFiles */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+			);
+			name = vector_tc;
+			productName = vector_tc;
+			productReference = CD3C80801D6645AB00ACC795 /* vector_tc */;
+			productType = "com.apple.product-type.tool";
+		};
+		CD3C80901D6654A400ACC795 /* matrix_tc */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = CD3C80951D6654A400ACC795 /* Build configuration list for PBXNativeTarget "matrix_tc" */;
+			buildPhases = (
+				CD3C809B1D66556900ACC795 /* ShellScript */,
+				CD3C808D1D6654A400ACC795 /* Sources */,
+				CD3C808E1D6654A400ACC795 /* Frameworks */,
+				CD3C808F1D6654A400ACC795 /* CopyFiles */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+			);
+			name = matrix_tc;
+			productName = matrix_tc;
+			productReference = CD3C80911D6654A400ACC795 /* matrix_tc */;
+			productType = "com.apple.product-type.tool";
+		};
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+		CD3786101CF9F61100BE89B2 /* Project object */ = {
+			isa = PBXProject;
+			attributes = {
+				LastUpgradeCheck = 0720;
+				ORGANIZATIONNAME = "Sam Jaffe";
+				TargetAttributes = {
+					CD3786171CF9F61100BE89B2 = {
+						CreatedOnToolsVersion = 7.2.1;
+					};
+					CD3C807F1D6645AB00ACC795 = {
+						CreatedOnToolsVersion = 7.2.1;
+					};
+					CD3C80901D6654A400ACC795 = {
+						CreatedOnToolsVersion = 7.2.1;
+					};
+				};
+			};
+			buildConfigurationList = CD3786131CF9F61100BE89B2 /* Build configuration list for PBXProject "math" */;
+			compatibilityVersion = "Xcode 3.2";
+			developmentRegion = English;
+			hasScannedForEncodings = 0;
+			knownRegions = (
+				en,
+			);
+			mainGroup = CD37860F1CF9F61100BE89B2;
+			productRefGroup = CD3786191CF9F61100BE89B2 /* Products */;
+			projectDirPath = "";
+			projectRoot = "";
+			targets = (
+				CD3786171CF9F61100BE89B2 /* math */,
+				CD3C807F1D6645AB00ACC795 /* vector_tc */,
+				CD3C80901D6654A400ACC795 /* matrix_tc */,
+			);
+		};
+/* End PBXProject section */
+
+/* Begin PBXShellScriptBuildPhase section */
+		CD3C80891D6645ED00ACC795 /* ShellScript */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputPaths = (
+				"$(SRCROOT)/vector.t.h",
+			);
+			outputPaths = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "cxxtestgen --error-printer -o vector_tc.cpp vector.t.h";
+		};
+		CD3C809B1D66556900ACC795 /* ShellScript */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputPaths = (
+				"$(SRCROOT)/matrix.t.h",
+			);
+			outputPaths = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "cxxtestgen --error-printer -o matrix_tc.cpp matrix.t.h";
+		};
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+		CD3786141CF9F61100BE89B2 /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				CD3AC71E1D2C0AF8002B4BB0 /* shape.cpp in Sources */,
+				CD3C80BC1D68902300ACC795 /* common.cpp in Sources */,
+				CD3C809F1D675AB100ACC795 /* angle.cpp in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		CD3C807C1D6645AB00ACC795 /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				CD3C80881D6645C600ACC795 /* vector_tc.cpp in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		CD3C808D1D6654A400ACC795 /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				CD3C809A1D6654C200ACC795 /* matrix_tc.cpp in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXSourcesBuildPhase section */
+
+/* Begin XCBuildConfiguration section */
+		CD3786211CF9F61100BE89B2 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				CODE_SIGN_IDENTITY = "-";
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = dwarf;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				ENABLE_TESTABILITY = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu99;
+				GCC_DYNAMIC_NO_PIC = NO;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_OPTIMIZATION_LEVEL = 0;
+				GCC_PREPROCESSOR_DEFINITIONS = (
+					"DEBUG=1",
+					"$(inherited)",
+				);
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				MACOSX_DEPLOYMENT_TARGET = 10.10;
+				MTL_ENABLE_DEBUG_INFO = YES;
+				ONLY_ACTIVE_ARCH = YES;
+				SDKROOT = macosx;
+			};
+			name = Debug;
+		};
+		CD3786221CF9F61100BE89B2 /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				CODE_SIGN_IDENTITY = "-";
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+				ENABLE_NS_ASSERTIONS = NO;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu99;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				MACOSX_DEPLOYMENT_TARGET = 10.10;
+				MTL_ENABLE_DEBUG_INFO = NO;
+				SDKROOT = macosx;
+			};
+			name = Release;
+		};
+		CD3786241CF9F61100BE89B2 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				DYLIB_COMPATIBILITY_VERSION = 1;
+				DYLIB_CURRENT_VERSION = 1;
+				EXECUTABLE_PREFIX = lib;
+				GCC_ENABLE_CPP_EXCEPTIONS = YES;
+				GCC_ENABLE_CPP_RTTI = YES;
+				GCC_SYMBOLS_PRIVATE_EXTERN = YES;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				USER_HEADER_SEARCH_PATHS = ..;
+			};
+			name = Debug;
+		};
+		CD3786251CF9F61100BE89B2 /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				DYLIB_COMPATIBILITY_VERSION = 1;
+				DYLIB_CURRENT_VERSION = 1;
+				EXECUTABLE_PREFIX = lib;
+				GCC_ENABLE_CPP_EXCEPTIONS = YES;
+				GCC_ENABLE_CPP_RTTI = YES;
+				GCC_SYMBOLS_PRIVATE_EXTERN = YES;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				USER_HEADER_SEARCH_PATHS = ..;
+			};
+			name = Release;
+		};
+		CD3C80851D6645AB00ACC795 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				HEADER_SEARCH_PATHS = /usr/local/include;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				USER_HEADER_SEARCH_PATHS = ..;
+			};
+			name = Debug;
+		};
+		CD3C80861D6645AB00ACC795 /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				GCC_GENERATE_TEST_COVERAGE_FILES = YES;
+				GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES;
+				HEADER_SEARCH_PATHS = /usr/local/include;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				USER_HEADER_SEARCH_PATHS = ..;
+			};
+			name = Release;
+		};
+		CD3C80961D6654A400ACC795 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				GCC_GENERATE_TEST_COVERAGE_FILES = YES;
+				GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES;
+				HEADER_SEARCH_PATHS = /usr/local/include;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				USER_HEADER_SEARCH_PATHS = ..;
+			};
+			name = Debug;
+		};
+		CD3C80971D6654A400ACC795 /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				GCC_GENERATE_TEST_COVERAGE_FILES = NO;
+				GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = NO;
+				HEADER_SEARCH_PATHS = /usr/local/include;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				USER_HEADER_SEARCH_PATHS = ..;
+			};
+			name = Release;
+		};
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+		CD3786131CF9F61100BE89B2 /* Build configuration list for PBXProject "math" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				CD3786211CF9F61100BE89B2 /* Debug */,
+				CD3786221CF9F61100BE89B2 /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		CD3786231CF9F61100BE89B2 /* Build configuration list for PBXNativeTarget "math" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				CD3786241CF9F61100BE89B2 /* Debug */,
+				CD3786251CF9F61100BE89B2 /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		CD3C80841D6645AB00ACC795 /* Build configuration list for PBXNativeTarget "vector_tc" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				CD3C80851D6645AB00ACC795 /* Debug */,
+				CD3C80861D6645AB00ACC795 /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		CD3C80951D6654A400ACC795 /* Build configuration list for PBXNativeTarget "matrix_tc" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				CD3C80961D6654A400ACC795 /* Debug */,
+				CD3C80971D6654A400ACC795 /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+/* End XCConfigurationList section */
+	};
+	rootObject = CD3786101CF9F61100BE89B2 /* Project object */;
+}

+ 40 - 0
math/math_fwd.hpp

@@ -0,0 +1,40 @@
+//
+//  vector_typedefs.hpp
+//  math
+//
+//  Created by Sam Jaffe on 7/5/16.
+//
+
+#pragma once
+
+#include <cstdint>
+
+namespace math {
+  namespace vector {
+    template <typename, size_t> class vector;
+  }
+  namespace matrix {
+    template <typename, size_t, size_t> class matrix;
+    
+    template <typename T, size_t N>
+    using square_matrix = matrix<T, N, N>;
+  }
+  
+  struct line;
+  struct circle;
+  struct quad;
+  struct rectangle;
+  struct square;
+  
+  struct degree;
+  struct radian;
+}
+
+namespace math {
+  using vec2i = vector::vector<int, 2>;
+  using vec2  = vector::vector<float, 2>;
+  using vec3  = vector::vector<float, 3>;
+  using rgba  = vector::vector<uint8_t, 4>;
+  
+  using matr4 = matrix::matrix<float, 4, 4>;
+}

+ 265 - 0
math/matrix.hpp

@@ -0,0 +1,265 @@
+//
+//  matrix.hpp
+//  math
+//
+//  Created by Sam Jaffe on 5/28/16.
+//
+
+#pragma once
+
+#include "vector.hpp"
+
+#include "util/expect.hpp"
+
+namespace math { namespace matrix {
+  
+  template <typename T>
+  struct is_matrix { static const constexpr bool value = false; };
+  
+  template <typename T, std::size_t R, std::size_t C>
+  class matrix;
+  template <typename T, size_t R, size_t C>
+  struct is_matrix<matrix<T, R, C> > { static const constexpr bool value = true; };
+  
+  namespace concat_strategy {
+    struct {} horizonal;
+    using horizontal_concat_t = decltype(horizonal);
+    struct {} vertical;
+    using vertical_concat_t = decltype(vertical);
+    struct {} diagonal;
+    using diagonal_concat_t = decltype(diagonal);
+  };
+  
+#define MATRIX_DISABLE_IF_MATRIX(_type, t, r, c) \
+typename std::enable_if<!is_matrix<_type>::value, matrix<t, r, c> >::type
+  
+#define MATRIX_FOR_EACH_RANGE(i, i_max, j, j_max) for (size_t i = 0; i < i_max; ++i) for (size_t j = 0; j < j_max; ++j)
+#define MATRIX_FOR_EACH(i, j) MATRIX_FOR_EACH_RANGE(i, R, j, C)
+  
+  template <typename T, std::size_t R, std::size_t C>
+  class matrix {
+  public:
+    using value_type = T;
+    
+    template <typename M>
+    using mul_t = decltype(std::declval<T>()*std::declval<M>());
+    template <typename M>
+    using div_t = decltype(std::declval<T>()/std::declval<M>());
+  public:
+    
+    template <typename S>
+    class row_reference {
+    public:
+      row_reference(S ( & h )[C]) : _handle(h) {}
+      row_reference(row_reference const &) = delete;
+
+      row_reference & operator=(row_reference const & other) {
+        return operator=<S>(other);
+      }
+      template <typename S2>
+      row_reference & operator=(row_reference<S2> const & other) {
+        VECTOR_FOR_EACH_RANGE(i, C) { _handle[i] = other[i]; }
+        return *this;
+      }
+
+      S const & operator[](std::size_t col) const { return _handle[col]; }
+      S & operator[](std::size_t col) { return _handle[col]; }
+      S const & at(std::size_t col) const {
+        expects_e(col < C, std::out_of_range, "column index out of range");
+        return operator[](col);
+      }
+      S & at(std::size_t col) {
+        expects_e(col < C, std::out_of_range, "column index out of range");
+        return operator[](col);
+      }
+      
+    private:
+      S ( & _handle )[C];
+    };
+    
+    matrix() = default;
+    matrix(std::array<std::array<T, C>, R> const & init) {
+      MATRIX_FOR_EACH(i, j) {
+        _data[i][j] = init[i][j];
+      }
+    }
+    matrix(std::initializer_list<std::array<T, C>> const & init) {
+      expects(init.size() == R, "initializer size mismatch");
+      size_t i = 0;
+      for (auto it = init.begin(), end = init.end(); it != end && i < R; ++it, ++i) {
+        for (size_t j = 0; j < C; ++j) {
+          _data[i][j] = (*it)[j];
+        }
+      }
+    }
+    template <size_t N>
+    matrix(vector::vector<typename std::enable_if<C == 1 && N == R, T>::type, N> const & other) {
+      VECTOR_FOR_EACH(i) {
+        _data[i][0] = other[i];
+      }
+    }
+    matrix(matrix const& other) {
+      *this = other;
+    }
+    matrix(matrix && other) {
+      *this = std::move(other);
+    }
+    matrix & operator=(matrix const& other) {
+      MATRIX_FOR_EACH(i, j) {
+        _data[i][j] = other._data[i][j];
+      }
+      return *this;
+    }
+    matrix & operator=(matrix && other) {
+      MATRIX_FOR_EACH(i, j) {
+        _data[i][j] = std::move(other._data[i][j]);
+      }
+      return *this;
+    }
+    
+    template <size_t R2, size_t C2>
+    matrix(matrix<T, R2, C2> const & other) {
+      MATRIX_FOR_EACH_RANGE(i, std::min(R, R2), j, std::min(C, C2)) {
+        _data[i][j] = other(i, j);
+      }
+    }
+    
+    matrix<T, C, R> transpose() const {
+      matrix<T, C, R> out;
+      MATRIX_FOR_EACH(i, j) { out(j,i) = _data[i][j]; }
+      return out;
+    }
+    
+    template <size_t C2>
+    matrix<T, R, C + C2> concat(matrix<T, R, C2> const & other, concat_strategy::horizontal_concat_t) const {
+      matrix<T, R, C + C2> accum{*this};
+      MATRIX_FOR_EACH_RANGE(i, R, j, C2) { accum(i, j + C) = other(i, j); }
+      return accum;
+    }
+    
+    template <size_t R2>
+    matrix<T, R + R2, C> concat(matrix<T, R2, C> const & other, concat_strategy::vertical_concat_t) const {
+      matrix<T, R + R2, C> accum{*this};
+      MATRIX_FOR_EACH_RANGE(i, R2, j, C) { accum(i + R, j) = other(i, j); }
+      return accum;
+    }
+    
+    template <size_t R2, size_t C2>
+    matrix<T, R + R2, C + C2> concat(matrix<T, R2, C2> const & other, concat_strategy::diagonal_concat_t) const {
+      matrix<T, R + R2, C + C2> accum{*this};
+      MATRIX_FOR_EACH_RANGE(i, R2, j, C2) { accum(i + R, j + C) = other(i, j); }
+      return accum;
+    }
+    
+    T const & operator()(std::size_t row, std::size_t col) const {
+      return _data[row][col];
+    }
+    T & operator()(std::size_t row, std::size_t col) {
+      return _data[row][col];
+    }
+    row_reference<const T> operator[](std::size_t row) const {
+      return { _data[row] };
+    }
+    row_reference<T> operator[](std::size_t row) {
+      return { _data[row] };
+    }
+    row_reference<const T> at(std::size_t row) const {
+      expects_e(row >= R, std::out_of_range, "row index out of range");
+      return operator[](row);
+    }
+    row_reference<T> at(std::size_t row) {
+      expects_e(row >= R, std::out_of_range, "row index out of range");
+      return operator[](row);
+    }
+    value_type const & at(std::size_t row, std::size_t col) const {
+      expects_e(row < R && col < C, std::out_of_range, "coordinates out of range");
+      return _data[row][col];
+    }
+    value_type & at(std::size_t row, std::size_t col) {
+      expects_e(row < R && col < C, std::out_of_range, "coordinates out of range");
+      return _data[row][col];
+    }
+    
+    matrix& operator+=(matrix const & other) {
+      MATRIX_FOR_EACH(i, j) {
+        _data[i][j] += other[i][j];
+      }
+      return *this;
+    }
+    matrix operator+(matrix const & other) const {
+      return matrix{*this} += other;
+    }
+    matrix& operator-=(matrix const & other) {
+      MATRIX_FOR_EACH(i, j) {
+        _data[i][j] -= other[i][j];
+      }
+      return *this;
+    }
+    matrix operator-(matrix const & other) const {
+      return matrix{*this} -= other;
+    }
+    
+    vector::vector<T, C> operator*(vector::vector<T, C> const & vec) const {
+      vector::vector<T, C> rval;
+      MATRIX_FOR_EACH(i, j) {
+        rval[i] += _data[i][j] * vec[j];
+      }
+      return rval;
+    }
+    
+    template <std::size_t C2>
+    matrix<T, R, C2> operator*(matrix<T, C, C2> const & other) const {
+      matrix<T, R, C2> rval;
+      MATRIX_FOR_EACH(i, j) {
+        for (size_t k = 0; k < C2; ++k) {
+          rval[i][k] += _data[i][j] * other[j][k];
+        }
+      }
+      return rval;
+    }
+    
+    matrix<T, R, C>& operator*=(T c) {
+      MATRIX_FOR_EACH(i, j) {
+        _data[i][j] *= c;
+      }
+      return *this;
+    }
+    
+    template <typename M>
+    MATRIX_DISABLE_IF_MATRIX(M, mul_t<M>, R, C) operator*(M c) const {
+      return matrix<mul_t<M>, R, C>{*this} *= c;
+    }
+    
+    template <typename M>
+    friend MATRIX_DISABLE_IF_MATRIX(M, mul_t<M>, R, C) operator*(M c, matrix const& matr) {
+      return matrix<mul_t<M>, R, C>{matr} *= c;
+    }
+    
+    template <typename M>
+    matrix<div_t<M>, R, C>& operator/=(M c) {
+      MATRIX_FOR_EACH(i, j) {
+        _data[i][j] /= c;
+      }
+      return *this;
+    }
+    template <typename M>
+    matrix<div_t<M>, R, C> operator/(M c) const {
+      return matrix<mul_t<M>, R, C>{*this} /= c;
+    }
+    
+    bool operator==(matrix const & other) const {
+      MATRIX_FOR_EACH(i, j) {
+        if (_data[i][j] != other._data[i][j]) {
+          return false;
+        }
+      }
+      return true;
+    }
+    bool operator!=(matrix const & other) const {
+      return !operator==(other);
+    }
+  private:
+    value_type _data[R][C] = {value_type()};
+  };
+
+} }

+ 107 - 0
math/matrix.t.h

@@ -0,0 +1,107 @@
+//
+//  matrix_tc.h
+//  math
+//
+//  Created by Sam Jaffe on 8/18/16.
+//
+#pragma once
+
+#include <cxxtest/TestSuite.h>
+
+#include "matrix.hpp"
+#include "matrix_helpers.hpp"
+#include "angle.hpp"
+
+class matrix_TestSuite : public CxxTest::TestSuite {
+public:
+  using matr2i = math::matrix::matrix<int, 2, 2>;
+  using matr2 = math::matrix::matrix<double, 2, 2>;
+public:
+  void test_matrix_equals() const {
+    using math::matrix::identity;
+    TS_ASSERT_EQUALS((identity<int, 2>()), (matr2i{{1,0},{0,1}}));
+  }
+  
+  void test_matrix_sum() const {
+    auto iden = math::matrix::identity<int, 2>();
+    auto result = matr2i{{2,0},{0,2}};
+    TS_ASSERT_EQUALS(iden + iden, result);
+  }
+  
+  void test_matrix_default_zero() const {
+    auto zero = matr2i{{0,0},{0,0}};
+    TS_ASSERT_EQUALS(matr2i{}, zero);
+  }
+
+  void test_matrix_subtract() const {
+    auto zero = matr2i{{0,0},{0,0}};
+    auto iden = math::matrix::identity<int, 2>();
+    TS_ASSERT_EQUALS(iden-iden, zero);
+  }
+  
+//  void test_matrix_negate() const {
+//    auto iden = math::matrix::identity<int, 2>();
+//    TS_ASSERT_EQUALS(-iden, (matr2i{{-1,0},{0,-1}}));
+//  }
+  
+  void test_matrix_scaling() const {
+    auto iden = math::matrix::identity<double, 2>();
+    TS_ASSERT_EQUALS(2*iden, (matr2{{2.0,0.0},{0.0,2.0}}));
+    TS_ASSERT_EQUALS(iden*2, (matr2{{2.0,0.0},{0.0,2.0}}));
+    TS_ASSERT_EQUALS(iden/2.0, (matr2{{0.5,0.0},{0.0,0.5}}));
+  }
+  
+  void test_matrix_multiplication_same_dim() const {
+    auto A = matr2i{{1,2},{2,3}};
+    auto B = matr2i{{1,0},{1,1}};
+    TS_ASSERT_EQUALS(A*B, (matr2i{{3,2},{5,3}}));
+    TS_ASSERT_EQUALS(B*A, (matr2i{{1,2},{3,5}}));
+  }
+  
+  void test_matrix_multiplication_diff_dim() const {
+    auto A = math::matrix::matrix<int, 3, 2>{{1,0},{0,1},{1,1}};
+    auto B = math::matrix::matrix<int, 2, 3>{{0,1,0},{1,0,1}};
+    TS_ASSERT_EQUALS(A*B, (math::matrix::matrix<int, 3, 3>{{0,1,0},{1,0,1},{1,1,1}}));
+    TS_ASSERT_EQUALS(B*A, (math::matrix::matrix<int, 2, 2>{{0,1},{2,1}}));
+  }
+  
+  void test_matrix_vector_multiplication() const {
+    auto A = matr2i{{1,0},{1,2}};
+    auto x = math::vector::vector<int, 2>{1,2};
+    TS_ASSERT_EQUALS(A*x, (math::vector::vector<int, 2>{1,5}));
+  }
+  
+  void test_matrix_composition() const {
+    using namespace math::matrix;
+    using vec4 = math::vector::vector<double, 4>;
+    using vec3 = math::vector::vector<double, 3>;
+    auto rot = rotation<4>(math::degree{90}, rotate::X_AXIS);
+    auto mov = translation(vec3{2.0, 2.5, 1.5});
+    auto scl = scalar(vec3{2.0, math::vector::fill});
+    vec4 epsilon{0.00001, math::vector::fill};
+    TS_ASSERT_DELTA((mov * scl * rot * vec4{1,2,3,1}),
+                     (vec4{4.0,-1.5,-4.5,1.0}),
+                    epsilon);
+  }
+  
+  void test_matrix_from_vector() const {
+    using vec3 = math::vector::vector<double, 3>;
+    vec3 v = vec3{1,2,3};
+    math::matrix::matrix<double, 3, 1> m{v};
+    TS_ASSERT_EQUALS(m(0,0), v[0]);
+    TS_ASSERT_EQUALS(m(1,0), v[1]);
+    TS_ASSERT_EQUALS(m(2,0), v[2]);
+  }
+  
+  void test_matrix_init_list_except() const {
+    TS_ASSERT_THROWS((math::matrix::matrix<double, 2, 1>{{1.0}}), std::logic_error);
+  }
+  
+  void test_assign_row() const {
+    matr2i A = math::matrix::identity<int, 2>();
+    matr2i const B = 2 * A;
+    A[0] = B[0];
+    TS_ASSERT_EQUALS(A, (matr2i{{2,0},{0,1}}));
+  }
+private:
+};

+ 83 - 0
math/matrix_helpers.hpp

@@ -0,0 +1,83 @@
+//
+//  matrix_helpers.hpp
+//  math
+//
+//  Created by Sam Jaffe on 8/20/16.
+//
+
+#pragma once
+
+#include "matrix.hpp"
+
+namespace math { namespace matrix {
+  
+  template <typename T, std::size_t N>
+  matrix<T, N, N> identity() {
+    matrix<T, N, N> rval;
+    VECTOR_FOR_EACH(i) { rval.at(i,i) = 1; }
+    return rval;
+  }
+  
+  template <typename T, std::size_t N>
+  matrix<T, N, N> diagonal(vector::vector<T, N> const & vec) {
+    matrix<T, N, N> rval = identity<T, N>();
+    VECTOR_FOR_EACH(i) { rval.at(i,i) = vec[i]; }
+    return rval;
+  }
+  
+  template <typename T, std::size_t N>
+  matrix<T, N+1, N+1> translation(vector::vector<T, N> const & vec) {
+    matrix<T, N+1, N+1> rval = identity<T, N+1>();
+    VECTOR_FOR_EACH(i) { rval.at(i,N) = vec[i]; }
+    return rval;
+  }
+  
+  template <typename T, std::size_t N>
+  matrix<T, N+1, N+1> scalar(vector::vector<T, N> const & vec) {
+    matrix<T, N+1, N+1> rval = identity<T, N+1>();
+    VECTOR_FOR_EACH(i) { rval.at(i,i) = vec[i]; }
+    return rval;
+  }
+  
+  template <size_t N>
+  struct rotation_t {
+    constexpr rotation_t(size_t f, size_t s)
+    : first(f), second(s) {}
+    size_t first, second;
+  };
+  
+  namespace rotate {
+    constexpr rotation_t<3> const X_AXIS{1,2};
+    constexpr rotation_t<3> const Y_AXIS{2,0};
+    constexpr rotation_t<3> const Z_AXIS{0,1};
+    
+    constexpr rotation_t<3> const ROLL{1,2};
+    constexpr rotation_t<3> const PITCH{2,0};
+    constexpr rotation_t<3> const YAW{0,1};
+    
+    constexpr rotation_t<2> const ROT_2D{0,1};
+  }
+  
+  template <typename T, size_t D>
+  auto rotation(T theta, rotation_t<D> r) -> matrix<decltype(sin(theta)), D, D> {
+    static_assert(D >= 2, "cannot rotate with 1D matrix");
+    using G = decltype(sin(theta));
+    using std::sin;
+    using std::cos;
+    matrix<G, D, D> rval = identity<G, D>();
+    G const vsin = sin(theta);
+    G const vcos = cos(theta);
+    rval.at(r.first, r.first) = vcos;
+    rval.at(r.second, r.second) = vcos;
+    rval.at(r.first, r.second) = -vsin;
+    rval.at(r.second, r.first) = vsin;
+    return rval;
+  }
+  
+  template <size_t D, typename T, size_t RD>
+  auto rotation(T theta, rotation_t<RD> r) -> matrix<decltype(sin(theta)), D, D> {
+    using G = decltype(sin(theta));
+    return rotation(theta, r).concat(identity<G, D - RD>(), concat_strategy::diagonal);
+  }
+  
+} }

+ 34 - 0
math/shape.cpp

@@ -0,0 +1,34 @@
+//
+//  shape.cpp
+//  math
+//
+//  Created by Sam Jaffe on 7/5/16.
+//
+
+#include "shape.hpp"
+
+namespace math {
+  
+  rectangle::operator quad() const {
+    return {
+      origin,
+      origin + vec2{ size.x(), 0.0 },
+      origin + size,
+      origin + vec2{ 0.0, size.y() }
+    };
+  }
+
+  square::operator rectangle() const {
+    return { origin, origin + vec2{size, size} };
+  }
+  
+  square::operator quad() const {
+    return {
+      origin,
+      origin + vec2{size, 0.0 },
+      origin + vec2{size, size},
+      origin + vec2{ 0.0, size}
+    };
+  }
+  
+}

+ 38 - 0
math/shape.hpp

@@ -0,0 +1,38 @@
+//
+//  shape.hpp
+//  math
+//
+//  Created by Sam Jaffe on 7/5/16.
+//
+
+#pragma once
+
+#include "vector.hpp"
+#include "math_fwd.hpp"
+
+namespace math {
+  struct line {
+    vec2 first, second;
+  };
+  
+  struct circle {
+    vec2 center;
+    float radius;
+  };
+  
+  struct quad {
+    vec2 ll, lr, ur, ul;
+  };
+  
+  struct rectangle {
+    operator quad() const;
+    vec2 origin, size;
+  };
+    
+  struct square {
+    operator rectangle() const;
+    operator quad() const;
+    vec2 origin;
+    float size;
+  };  
+}

+ 284 - 0
math/vector.hpp

@@ -0,0 +1,284 @@
+//
+//  vector.hpp
+//  vector
+//
+//  Created by Sam Jaffe on 8/15/16.
+//
+
+#pragma once
+
+#include <cassert>
+#include <cmath>
+#include <cstddef>
+
+#include <array>
+#include <initializer_list>
+#include <stdexcept>
+#include <type_traits>
+
+#include "util/expect.hpp"
+
+namespace math { namespace vector {
+#define VECTOR_ENABLE_IF_LT_N(index, expr) \
+template <bool _ = true> \
+typename std::enable_if<std::size_t(index) < N && _, expr>::type
+  
+#define VECTOR_ENABLE_IF_EQ_N(index, t, n) \
+template <bool _ = true> \
+typename std::enable_if<std::size_t(index) == N && _, vector<t, n> >::type
+
+#define VECTOR_ENABLE_IF_EQ_T(_type, t, n) \
+typename std::enable_if<std::is_same<_type, t>::value, vector<t, n> >::type
+  
+#define VECTOR_DISABLE_IF_VECTOR(_type, t, n) \
+typename std::enable_if<!is_vector<_type>::value, vector<t, n> >::type
+
+#define VECTOR_ACCESS_FN(name, i) \
+VECTOR_ENABLE_IF_LT_N(i, value_type const &) name() const { return _data[i]; } \
+VECTOR_ENABLE_IF_LT_N(i, value_type &) name() { return _data[i]; }
+
+#define VECTOR_FOR_EACH_RANGE(var, end) for (std::size_t var = 0; var < end; ++var)
+#define VECTOR_FOR_EACH(var) VECTOR_FOR_EACH_RANGE(var, N)
+  
+  struct {} fill;
+  using fill_t = decltype(fill);
+  
+  template <typename T>
+  struct is_vector { static const constexpr bool value = false; };
+  
+  template <typename T, std::size_t N>
+  class vector;
+  template <typename T, std::size_t N>
+  struct is_vector<vector<T, N>> { static const constexpr bool value = true; };
+  
+  template <typename T, std::size_t N>
+  class vector {
+  public:
+    using value_type = T;
+  private:
+    using mag_t = decltype(std::sqrt(std::declval<T>()));
+    template <typename M>
+    using mul_t = decltype(std::declval<T>()*std::declval<M>());
+    template <typename M>
+    using div_t = decltype(std::declval<T>()/std::declval<M>());
+  public:
+    
+    // Constructors
+    vector() = default;
+    
+    vector(std::initializer_list<T> && init) {
+      expects(init.size() == N, "initializer size mismatch");
+      std::size_t idx = 0;
+      for ( auto it = init.begin(), end = init.end(); it != end && idx < N; ++it, ++idx ) {
+        _data[idx] = *it;
+      }
+    }
+    
+    vector(std::array<T, N> const & init) {
+      VECTOR_FOR_EACH(i) { _data[i] = init[i]; }
+    }
+    
+    vector(vector const & other) {
+      *this = other;
+    }
+    
+    vector(vector && other) {
+      *this = std::move(other);
+    }
+    
+    // Conversion
+    template <typename T2, std::size_t N2>
+    explicit vector(vector<T2, N2> const & other) {
+      VECTOR_FOR_EACH_RANGE(i, std::min(N, N2)) {
+        _data[i] = static_cast<T>(other[i]);
+      }
+    }
+    
+    vector(T const & v, fill_t) {
+      VECTOR_FOR_EACH(i) { _data[i] = v; }
+    }
+    
+    // Assignment
+    vector& operator=(vector const & other) {
+      VECTOR_FOR_EACH(i) { _data[i] = other[i]; }
+      return *this;
+    }
+    
+    vector& operator=(vector && other) {
+      VECTOR_FOR_EACH(i) { _data[i] = std::move(other._data[i]); }
+      return *this;
+    }
+    
+    // Named Accessors
+    // - Numeric Vector Accessors
+    VECTOR_ACCESS_FN(x, 0)
+    VECTOR_ACCESS_FN(y, 1)
+    VECTOR_ACCESS_FN(z, 2)
+    VECTOR_ACCESS_FN(w, 3)
+    
+    // - Color Vector Accessors
+    VECTOR_ACCESS_FN(r, 0)
+    VECTOR_ACCESS_FN(g, 1)
+    VECTOR_ACCESS_FN(b, 2)
+    VECTOR_ACCESS_FN(a, 3)
+    
+    // Unnamed Accessors
+    value_type const & operator[](std::size_t idx) const {
+      return _data[idx];
+    }
+    
+    value_type & operator[](std::size_t idx) {
+      return _data[idx];
+    }
+    
+    value_type const & at(std::size_t idx) const {
+      expects_e(idx < N, std::out_of_range, "index out of range");
+      return _data[idx];
+    }
+    
+    value_type & at(std::size_t idx) {
+      expects_e(idx < N, std::out_of_range, "index out of range");
+      return _data[idx];
+    }
+    
+    // Mathematical Operations
+    vector& operator+=(vector const & other) {
+      VECTOR_FOR_EACH(i) { _data[i] += other[i]; }
+      return *this;
+    }
+    
+    vector operator+(vector const & other) const {
+      return vector{*this} += other;
+    }
+
+    vector& operator-=(vector const & other) {
+      VECTOR_FOR_EACH(i) { _data[i] -= other[i]; }
+      return *this;
+    }
+    
+    vector operator-(vector const & other) const {
+      return vector{*this} -= other;
+    }
+
+    vector operator-() const {
+      return vector{} -= *this;
+    }
+
+    template <typename M>
+    VECTOR_ENABLE_IF_EQ_T(mul_t<M>, T, N)& operator*=(M c) {
+      VECTOR_FOR_EACH(i) { _data[i] *= c; }
+      return *this;
+    }
+    
+    template <typename M>
+    VECTOR_DISABLE_IF_VECTOR(M, mul_t<M>, N) operator*(M c) const {
+      return vector<mul_t<M>, N>{*this} *= c;
+    }
+    
+    template <typename M>
+    friend VECTOR_DISABLE_IF_VECTOR(M, mul_t<M>, N) operator*(M c, vector<T, N> const & v) {
+      return v * c;
+    }
+    
+    template <typename M>
+    VECTOR_ENABLE_IF_EQ_T(mul_t<M>, T, N)& operator*=(vector<M, N> c) {
+      return vector<mul_t<M>, N>{*this} *= c;
+    }
+    
+    template <typename M>
+    vector<mul_t<M>, N> operator*(vector<M, N> const & other) const {
+      return vector<mul_t<M>, N>{*this} *= other;
+    }
+    
+    template <typename M>
+    VECTOR_ENABLE_IF_EQ_T(div_t<M>, T, N)& operator/=(M c) {
+      expects_e(c != 0, std::domain_error, "divide by zero");
+      VECTOR_FOR_EACH(i) { _data[i] /= c; }
+      return *this;
+    }
+    
+    template <typename M>
+    VECTOR_DISABLE_IF_VECTOR(M, div_t<M>, N) operator/(M c) const {
+      return vector<div_t<M>, N>{*this} /= c;
+    }
+    
+    template <typename M>
+    VECTOR_ENABLE_IF_EQ_T(div_t<M>, T, N)& operator/=(vector<M, N> c) {
+      VECTOR_FOR_EACH(i) { expects_e(c[i] != 0, std::domain_error, "divide by zero"); }
+      VECTOR_FOR_EACH(i) { _data[i] /= c[i]; }
+      return *this;
+    }
+    
+    template <typename M>
+    vector<div_t<M>, N> operator/(vector<M, N> const & other) const {
+      return vector<div_t<M>, N>{*this} /= other;
+    }
+    
+    // Vector Operations
+    value_type dot(vector const & other) const {
+      value_type accum{};
+      VECTOR_FOR_EACH(i) { accum += at(i) * other.at(i); }
+      return accum;
+    }
+    
+    mag_t magnitude() const {
+      return std::sqrt(dot(*this));
+    }
+    
+    vector<mag_t, N> unit() const {
+      return *this / magnitude();
+    }
+    
+    VECTOR_ENABLE_IF_EQ_N(3, T, N) cross(vector const & other) const {
+      return {
+        y()*other.z() - z()*other.y(),
+        z()*other.x() - x()*other.z(),
+        x()*other.y() - y()*other.x()
+      };
+    }
+
+    VECTOR_ENABLE_IF_EQ_N(2, T, 3) cross(vector const & other) const {
+      return { 0, 0, x()*other.y() - y()*other.x() };
+    }
+    
+    vector<mag_t, N> projection(vector const & other) const {
+      vector<mag_t, N> b_p = other.unit();
+      return b_p * vector<mag_t, N>{*this}.dot(b_p);
+    }
+    
+  private:
+    value_type _data[N] = {value_type()};
+  };
+  
+  template <typename T, std::size_t N>
+  vector<T, N> abs(vector<T, N> const & self) {
+    vector<T, N> tmp(self);
+    using std::abs;
+    VECTOR_FOR_EACH(i) { tmp[i] = abs(tmp[i]); }
+    return tmp;
+  }
+
+  template <typename T, std::size_t N>
+  int compare(vector<T, N> const & lhs, vector<T, N> const & rhs) {
+    VECTOR_FOR_EACH(i) {
+      if (lhs[i] < rhs[i]) return -1;
+      else if (lhs[i] > rhs[i]) return 1;
+    }
+    return 0;
+  }
+
+  template <typename T, std::size_t N>
+  bool operator==(vector<T, N> const & lhs, vector<T, N> const & rhs) { return compare(lhs, rhs) == 0; }
+  template <typename T, std::size_t N>
+  bool operator!=(vector<T, N> const & lhs, vector<T, N> const & rhs) { return compare(lhs, rhs) != 0; }
+  template <typename T, std::size_t N>
+  bool operator< (vector<T, N> const & lhs, vector<T, N> const & rhs) { return compare(lhs, rhs) <  0; }
+  template <typename T, std::size_t N>
+  bool operator<=(vector<T, N> const & lhs, vector<T, N> const & rhs) { return compare(lhs, rhs) <= 0; }
+  template <typename T, std::size_t N>
+  bool operator> (vector<T, N> const & lhs, vector<T, N> const & rhs) { return compare(lhs, rhs) >  0; }
+  template <typename T, std::size_t N>
+  bool operator>=(vector<T, N> const & lhs, vector<T, N> const & rhs) { return compare(lhs, rhs) >= 0; }
+} }
+
+using math::vector::abs;

+ 160 - 0
math/vector.t.h

@@ -0,0 +1,160 @@
+#include <cxxtest/TestSuite.h>
+
+#include "vector.hpp"
+
+class vector_TestSuite : public CxxTest::TestSuite {
+public:
+  using vec2i = math::vector::vector<int, 2>;
+  using vec3  = math::vector::vector<double, 3>;
+  using vec3i = math::vector::vector<int, 3>;
+  using vec4i = math::vector::vector<int, 4>;
+
+  void test_vector_badsize_throws() const {
+    TS_ASSERT_THROWS((vec2i{1}), std::logic_error);
+    TS_ASSERT_THROWS((vec2i{1,1,1}), std::logic_error);
+  }
+  
+  void test_vector_equals() const {
+    TS_ASSERT_EQUALS(iota3i(), (vec3i{1, 2, 3}));
+  }
+  
+  void test_vector_notequals() const {
+    TS_ASSERT_DIFFERS(iota3i(), (vec3i{0, 2, 3}));
+  }
+  
+  void test_vector_elems() const {
+    math::vector::vector<int, 3> viota = iota3i();
+    TS_ASSERT_EQUALS(viota[0], 1);
+    TS_ASSERT_EQUALS(viota[1], 2);
+    TS_ASSERT_EQUALS(viota[2], 3);
+  }
+  
+  void test_vector_oob() const {
+    TS_ASSERT_THROWS((iota<int, 3>()).at(3), std::out_of_range);
+  }
+  
+  void test_default_zero() const {
+    TS_ASSERT_EQUALS(vec3i(), (vec3i{0,0,0}));
+  }
+  
+  void test_extends_with_zero() const {
+    TS_ASSERT_EQUALS(vec4i(iota3i()), (vec4i{1,2,3,0}));
+  }
+  
+  void test_vec2_cross() const {
+    TS_ASSERT_EQUALS((vec2i{1,1}.cross(vec2i{-1,1})), (vec3i{0,0,2}));
+  }
+  
+  void test_vec3_cross() const {
+    TS_ASSERT_EQUALS((vec3i{1,1,2}.cross(vec3i{-1,1,1})), (vec3i{-1,-3,2}));
+  }
+  
+  void test_vector_addition_vector() const {
+    TS_ASSERT_EQUALS((vec2i{1,0} + vec2i{0,1}), (vec2i{1,1}))
+  }
+  
+  void test_vector_subtraction_vector() const {
+    TS_ASSERT_EQUALS((vec2i{1,0} - vec2i{0,1}), (vec2i{1,-1}))
+  }
+  
+  void test_vector_multiplication_vector() const {
+    TS_ASSERT_EQUALS((vec2i{1,0}*(vec2i{2,1})), (vec2i{2,0}))
+  }
+  
+  void test_vector_divides_vector() const {
+    TS_ASSERT_EQUALS((vec2i{6,4}/(vec2i{2,1})), (vec2i{3,4}))
+  }
+  
+  void test_divide_by_zero_fails() const {
+    TS_ASSERT_THROWS(vec3{} / 0, std::domain_error);
+    TS_ASSERT_THROWS((vec3{1.0, 1.0, 1.0}/(vec3{1.0, 0.5, 0.0})), std::domain_error);
+  }
+  
+//  void test_vector_addition_value() const {
+//    TS_ASSERT_EQUALS((vec2i{1,0} + 1), (vec2i{2,1}))
+//  }
+//  
+//  void test_vector_subtraction_value() const {
+//    TS_ASSERT_EQUALS((vec2i{1,0} - 1), (vec2i{0,-1}))
+//  }
+  
+  void test_vector_multiplication_value() const {
+    TS_ASSERT_EQUALS((vec2i{1,0} * 3), (vec2i{3,0}))
+  }
+  
+  void test_vector_divides_value() const {
+    TS_ASSERT_EQUALS((vec2i{6,4} / 2), (vec2i{3,2}))
+  }
+  
+//  void test_value_addition_vector() const {
+//    TS_ASSERT_EQUALS(1+iota2i(), (vec2i{2,3}));
+//  }
+//  
+//  void test_value_subtraction_vector() const {
+//    TS_ASSERT_EQUALS(4-iota2i(), (vec2i{3,2}));
+//  }
+  
+  void test_value_multiplication_vector() const {
+    TS_ASSERT_EQUALS(2*iota2i(), (vec2i{2,4}));
+  }
+  
+//  void test_value_divides_vector() const {
+//    TS_ASSERT_EQUALS(4/iota2i(), (vec2i{4,2}));
+//  }
+  
+  void test_length_of_vector() const {
+//    TS_ASSERT_EQUALS(iota3i().lengthSquared(), 14);
+    TS_ASSERT_DELTA(iota3i().magnitude(), std::sqrt(14), 0.00001);
+  }
+  
+  void test_distance_of_vector() const {
+//    TS_ASSERT_EQUALS((iota3i().distanceSquared(vec3i{3, 1, -1})), 21);
+    TS_ASSERT_DELTA((iota3i() - vec3i{3, 1, -1}).magnitude(), std::sqrt(21), 0.00001);
+  }
+  
+  void test_projection_of_vector() const {
+    TS_ASSERT_DELTA((iota3i().projection(vec3i{3, 1, -1})).magnitude(), std::sqrt(4.0/11.0), 0.00001);
+  }
+  
+  void test_dot_product() const {
+    TS_ASSERT_EQUALS((iota3i().dot(vec3i{3, 0, -1})), 0);
+  }
+  
+  void test_unit_vector() const {
+    double sq = std::sqrt(14);
+    vec3 epsilon{0.00001, math::vector::fill};
+    TS_ASSERT_DELTA(iota3i().unit(), (vec3{1/sq, 2/sq, 3/sq}), epsilon);
+  }
+  
+  void test_type_shift() const {
+    TS_ASSERT_EQUALS((vec3i(vec3{1.0, 2.3, 3.9})), iota3i());
+  }
+  
+  void test_swap() const {
+    using std::swap;
+    vec2i a{1, 2}; const vec2i ac = a;
+    vec2i b{5, 7}; const vec2i bc = b;
+    swap(a, b);
+    TS_ASSERT_EQUALS(a, bc);
+    TS_ASSERT_EQUALS(b, ac);
+  }
+  
+  void test_negate_vector() const {
+    TS_ASSERT_EQUALS(-iota3i(), (vec3i{-1,-2,-3}));
+  }
+  
+  void test_absolute_value() const {
+    TS_ASSERT_EQUALS(iota3i(), abs(-iota3i()));
+  }
+  
+private:
+  
+  template <typename T, size_t N> math::vector::vector<T, N> iota() const {
+    math::vector::vector<T, N> rval;
+    for (size_t i = 0; i < N; ++i) rval[i] = static_cast<T>(i+1);
+    return rval;
+  }
+  
+  vec2i iota2i() const { return iota<int, 2>(); }
+  vec3i iota3i() const { return iota<int, 3>(); }
+};

+ 32 - 0
util/expect.hpp

@@ -0,0 +1,32 @@
+//
+//  require.hpp
+//  gameutils
+//
+//  Created by Sam Jaffe on 8/19/16.
+//
+
+#pragma once
+
+#include <stdexcept>
+
+#include "macro.h"
+
+inline namespace precondition {
+  template <typename except>
+  void _expect(bool expr, char const * message) {
+    if (!expr) throw except{message};
+  }
+}
+
+#if defined( __clang__ ) || defined( __GNUC__ )
+# define LOCATION_INFO ". in " STRING(__PRETTY_FUNCTION__) "(" __FILE__ ":" STRING(__LINE__) ")"
+#elif defined( _MSC_VER )
+# define LOCATION_INFO ". in " __FUNCTION__ "(" __FILE__ ":" STRING(__LINE__) ")"
+#else
+# define LOCATION_INFO ". in " __FILE__ ":" STRING(__LINE__)
+#endif
+
+#define error_msg(expr, msg) msg ": " #expr LOCATION_INFO
+
+#define expects(expr, msg) expects_e(expr, std::logic_error, msg)
+#define expects_e(expr, except, msg) precondition::_expect<except>( expr, error_msg(expr, msg) )

+ 267 - 0
util/gameutils.xcodeproj/project.pbxproj

@@ -0,0 +1,267 @@
+// !$*UTF8*$!
+{
+	archiveVersion = 1;
+	classes = {
+	};
+	objectVersion = 46;
+	objects = {
+
+/* Begin PBXBuildFile section */
+		CD3C808B1D6646AC00ACC795 /* scope_exit.hpp in Headers */ = {isa = PBXBuildFile; fileRef = CD3AC7281D2C2922002B4BB0 /* scope_exit.hpp */; };
+		CD3C808C1D6646AF00ACC795 /* identity.hpp in Headers */ = {isa = PBXBuildFile; fileRef = CD3AC7161D2C0794002B4BB0 /* identity.hpp */; };
+		CD3C80B51D67B9CC00ACC795 /* expect.hpp in Headers */ = {isa = PBXBuildFile; fileRef = CD3C80B41D67B9CC00ACC795 /* expect.hpp */; };
+		CD3C80B71D67BD4C00ACC795 /* macro.h in Headers */ = {isa = PBXBuildFile; fileRef = CD3C80B61D67BD4C00ACC795 /* macro.h */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXFileReference section */
+		CD3AC7081D2C0726002B4BB0 /* libgameutils.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libgameutils.dylib; sourceTree = BUILT_PRODUCTS_DIR; };
+		CD3AC7161D2C0794002B4BB0 /* identity.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = identity.hpp; sourceTree = "<group>"; };
+		CD3AC7281D2C2922002B4BB0 /* scope_exit.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = scope_exit.hpp; sourceTree = "<group>"; };
+		CD3C80B41D67B9CC00ACC795 /* expect.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = expect.hpp; sourceTree = "<group>"; };
+		CD3C80B61D67BD4C00ACC795 /* macro.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = macro.h; sourceTree = "<group>"; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+		CD3AC7051D2C0726002B4BB0 /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+		CD3AC6FF1D2C0726002B4BB0 = {
+			isa = PBXGroup;
+			children = (
+				CD3AC70A1D2C0726002B4BB0 /* src */,
+				CD3AC7091D2C0726002B4BB0 /* Products */,
+			);
+			sourceTree = "<group>";
+		};
+		CD3AC7091D2C0726002B4BB0 /* Products */ = {
+			isa = PBXGroup;
+			children = (
+				CD3AC7081D2C0726002B4BB0 /* libgameutils.dylib */,
+			);
+			name = Products;
+			sourceTree = "<group>";
+		};
+		CD3AC70A1D2C0726002B4BB0 /* src */ = {
+			isa = PBXGroup;
+			children = (
+				CD3AC7161D2C0794002B4BB0 /* identity.hpp */,
+				CD3AC7281D2C2922002B4BB0 /* scope_exit.hpp */,
+				CD3C80B61D67BD4C00ACC795 /* macro.h */,
+				CD3C80B41D67B9CC00ACC795 /* expect.hpp */,
+			);
+			name = src;
+			sourceTree = "<group>";
+		};
+/* End PBXGroup section */
+
+/* Begin PBXHeadersBuildPhase section */
+		CD3AC7061D2C0726002B4BB0 /* Headers */ = {
+			isa = PBXHeadersBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				CD3C80B51D67B9CC00ACC795 /* expect.hpp in Headers */,
+				CD3C808C1D6646AF00ACC795 /* identity.hpp in Headers */,
+				CD3C808B1D6646AC00ACC795 /* scope_exit.hpp in Headers */,
+				CD3C80B71D67BD4C00ACC795 /* macro.h in Headers */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXHeadersBuildPhase section */
+
+/* Begin PBXNativeTarget section */
+		CD3AC7071D2C0726002B4BB0 /* gameutils */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = CD3AC7131D2C0726002B4BB0 /* Build configuration list for PBXNativeTarget "gameutils" */;
+			buildPhases = (
+				CD3AC7041D2C0726002B4BB0 /* Sources */,
+				CD3AC7051D2C0726002B4BB0 /* Frameworks */,
+				CD3AC7061D2C0726002B4BB0 /* Headers */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+			);
+			name = gameutils;
+			productName = gameutils;
+			productReference = CD3AC7081D2C0726002B4BB0 /* libgameutils.dylib */;
+			productType = "com.apple.product-type.library.dynamic";
+		};
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+		CD3AC7001D2C0726002B4BB0 /* Project object */ = {
+			isa = PBXProject;
+			attributes = {
+				LastUpgradeCheck = 0720;
+				ORGANIZATIONNAME = "Sam Jaffe";
+				TargetAttributes = {
+					CD3AC7071D2C0726002B4BB0 = {
+						CreatedOnToolsVersion = 7.2.1;
+					};
+				};
+			};
+			buildConfigurationList = CD3AC7031D2C0726002B4BB0 /* Build configuration list for PBXProject "gameutils" */;
+			compatibilityVersion = "Xcode 3.2";
+			developmentRegion = English;
+			hasScannedForEncodings = 0;
+			knownRegions = (
+				en,
+			);
+			mainGroup = CD3AC6FF1D2C0726002B4BB0;
+			productRefGroup = CD3AC7091D2C0726002B4BB0 /* Products */;
+			projectDirPath = "";
+			projectRoot = "";
+			targets = (
+				CD3AC7071D2C0726002B4BB0 /* gameutils */,
+			);
+		};
+/* End PBXProject section */
+
+/* Begin PBXSourcesBuildPhase section */
+		CD3AC7041D2C0726002B4BB0 /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXSourcesBuildPhase section */
+
+/* Begin XCBuildConfiguration section */
+		CD3AC7111D2C0726002B4BB0 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				CODE_SIGN_IDENTITY = "-";
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = dwarf;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				ENABLE_TESTABILITY = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu99;
+				GCC_DYNAMIC_NO_PIC = NO;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_OPTIMIZATION_LEVEL = 0;
+				GCC_PREPROCESSOR_DEFINITIONS = (
+					"DEBUG=1",
+					"$(inherited)",
+				);
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				MACOSX_DEPLOYMENT_TARGET = 10.10;
+				MTL_ENABLE_DEBUG_INFO = YES;
+				ONLY_ACTIVE_ARCH = YES;
+				SDKROOT = macosx;
+			};
+			name = Debug;
+		};
+		CD3AC7121D2C0726002B4BB0 /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				CODE_SIGN_IDENTITY = "-";
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+				ENABLE_NS_ASSERTIONS = NO;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu99;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				MACOSX_DEPLOYMENT_TARGET = 10.10;
+				MTL_ENABLE_DEBUG_INFO = NO;
+				SDKROOT = macosx;
+			};
+			name = Release;
+		};
+		CD3AC7141D2C0726002B4BB0 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				DYLIB_COMPATIBILITY_VERSION = 1;
+				DYLIB_CURRENT_VERSION = 1;
+				EXECUTABLE_PREFIX = lib;
+				GCC_ENABLE_CPP_EXCEPTIONS = YES;
+				GCC_ENABLE_CPP_RTTI = YES;
+				GCC_SYMBOLS_PRIVATE_EXTERN = YES;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+			};
+			name = Debug;
+		};
+		CD3AC7151D2C0726002B4BB0 /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				DYLIB_COMPATIBILITY_VERSION = 1;
+				DYLIB_CURRENT_VERSION = 1;
+				EXECUTABLE_PREFIX = lib;
+				GCC_ENABLE_CPP_EXCEPTIONS = YES;
+				GCC_ENABLE_CPP_RTTI = YES;
+				GCC_SYMBOLS_PRIVATE_EXTERN = YES;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+			};
+			name = Release;
+		};
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+		CD3AC7031D2C0726002B4BB0 /* Build configuration list for PBXProject "gameutils" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				CD3AC7111D2C0726002B4BB0 /* Debug */,
+				CD3AC7121D2C0726002B4BB0 /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		CD3AC7131D2C0726002B4BB0 /* Build configuration list for PBXNativeTarget "gameutils" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				CD3AC7141D2C0726002B4BB0 /* Debug */,
+				CD3AC7151D2C0726002B4BB0 /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+/* End XCConfigurationList section */
+	};
+	rootObject = CD3AC7001D2C0726002B4BB0 /* Project object */;
+}

+ 14 - 0
util/macro.h

@@ -0,0 +1,14 @@
+//
+//  macro.h
+//  gameutils
+//
+//  Created by Sam Jaffe on 8/19/16.
+//
+
+#pragma once
+
+#define CONCAT2(A, B) A##B
+#define CONCAT(A, B) CONCAT2(A, B)
+
+#define STRING2(A) #A
+#define STRING(A) STRING2(A)

+ 24 - 0
util/scope_exit.hpp

@@ -0,0 +1,24 @@
+//
+//  scope_exit.hpp
+//  gameutils
+//
+//  Created by Sam Jaffe on 7/5/16.
+//
+
+#pragma once
+
+#include <functional>
+
+#include "macro.h"
+
+#define scope(type) scope_##type##_t CONCAT(scope_,CONCAT(type,__LINE__) = [&]()
+
+class scope_exit_t {
+public:
+  template <typename F>
+  scope_exit_t(F && fun) : _func(fun) {}
+  scope_exit_t(scope_exit_t && other) : _func(std::move(other._func)) {}
+  ~scope_exit_t() { _func(); }
+private:
+  std::function<void()> _func;
+};