Browse Source

Added Transform objects to love.math (resolves issue #1228).

- Added love.math.newTransform.
- Added love.graphics.applyTransform and love.graphics.replaceTransform.
- love.graphics.draw/print/printf, Text:add/addf, and SpriteBatch:add can accept a Transform argument.
- love.graphics.push can accept an optional Transform as a second argument, which will apply the Transform after pushing the matrix stack,
- Shader:send can accept Transform objects when sending to a mat4 uniform variable.

--HG--
branch : minor
Alex Szpakowski 8 years ago
parent
commit
14aa6965b3

+ 4 - 0
CMakeLists.txt

@@ -655,6 +655,8 @@ set(LOVE_SRC_MODULE_MATH
 	src/modules/math/MathModule.h
 	src/modules/math/RandomGenerator.cpp
 	src/modules/math/RandomGenerator.h
+	src/modules/math/Transform.cpp
+	src/modules/math/Transform.h
 	src/modules/math/wrap_BezierCurve.cpp
 	src/modules/math/wrap_BezierCurve.h
 	src/modules/math/wrap_CompressedData.cpp
@@ -663,6 +665,8 @@ set(LOVE_SRC_MODULE_MATH
 	src/modules/math/wrap_Math.h
 	src/modules/math/wrap_RandomGenerator.cpp
 	src/modules/math/wrap_RandomGenerator.h
+	src/modules/math/wrap_Transform.cpp
+	src/modules/math/wrap_Transform.h
 )
 
 source_group("modules\\math" FILES ${LOVE_SRC_MODULE_MATH})

+ 20 - 0
platform/xcode/liblove.xcodeproj/project.pbxproj

@@ -895,6 +895,12 @@
 		FA4F2BB31DE1E4B800CA37D7 /* RecordingDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA4F2BA21DE1E36400CA37D7 /* RecordingDevice.cpp */; };
 		FA4F2BB41DE1E4BD00CA37D7 /* RecordingDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA4F2BAA1DE1E37000CA37D7 /* RecordingDevice.cpp */; };
 		FA4F2BB51DE1E4C300CA37D7 /* wrap_RecordingDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA4F2BA41DE1E36400CA37D7 /* wrap_RecordingDevice.cpp */; };
+		FA4F2BE31DE6650600CA37D7 /* Transform.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA4F2BDF1DE6650600CA37D7 /* Transform.cpp */; };
+		FA4F2BE41DE6650600CA37D7 /* Transform.h in Headers */ = {isa = PBXBuildFile; fileRef = FA4F2BE01DE6650600CA37D7 /* Transform.h */; };
+		FA4F2BE51DE6650600CA37D7 /* wrap_Transform.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA4F2BE11DE6650600CA37D7 /* wrap_Transform.cpp */; };
+		FA4F2BE61DE6650600CA37D7 /* wrap_Transform.h in Headers */ = {isa = PBXBuildFile; fileRef = FA4F2BE21DE6650600CA37D7 /* wrap_Transform.h */; };
+		FA4F2BE71DE6650D00CA37D7 /* Transform.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA4F2BDF1DE6650600CA37D7 /* Transform.cpp */; };
+		FA4F2BE81DE6651000CA37D7 /* wrap_Transform.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA4F2BE11DE6650600CA37D7 /* wrap_Transform.cpp */; };
 		FA56D9BC1C208A0200D8D3C7 /* libmodplug.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FA56D9BA1C2089EE00D8D3C7 /* libmodplug.a */; };
 		FA577AB016C7507900860150 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA577A7916C71A1700860150 /* Cocoa.framework */; };
 		FA577AC216C7512D00860150 /* FreeType.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA577A6716C719D900860150 /* FreeType.framework */; };
@@ -1600,6 +1606,10 @@
 		FA4F2BAB1DE1E37000CA37D7 /* RecordingDevice.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RecordingDevice.h; sourceTree = "<group>"; };
 		FA4F2BAE1DE1E37B00CA37D7 /* RecordingDevice.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RecordingDevice.cpp; sourceTree = "<group>"; };
 		FA4F2BAF1DE1E37B00CA37D7 /* RecordingDevice.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RecordingDevice.h; sourceTree = "<group>"; };
+		FA4F2BDF1DE6650600CA37D7 /* Transform.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Transform.cpp; sourceTree = "<group>"; };
+		FA4F2BE01DE6650600CA37D7 /* Transform.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Transform.h; sourceTree = "<group>"; };
+		FA4F2BE11DE6650600CA37D7 /* wrap_Transform.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_Transform.cpp; sourceTree = "<group>"; };
+		FA4F2BE21DE6650600CA37D7 /* wrap_Transform.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_Transform.h; sourceTree = "<group>"; };
 		FA56D9BA1C2089EE00D8D3C7 /* libmodplug.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libmodplug.a; sourceTree = "<group>"; };
 		FA577A6716C719D900860150 /* FreeType.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FreeType.framework; path = /Library/Frameworks/FreeType.framework; sourceTree = "<absolute>"; };
 		FA577A6D16C719EA00860150 /* Lua.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Lua.framework; path = /Library/Frameworks/Lua.framework; sourceTree = "<absolute>"; };
@@ -2559,6 +2569,8 @@
 				FA0B7C041A95902C000E1D17 /* MathModule.h */,
 				FA0B7C051A95902C000E1D17 /* RandomGenerator.cpp */,
 				FA0B7C061A95902C000E1D17 /* RandomGenerator.h */,
+				FA4F2BDF1DE6650600CA37D7 /* Transform.cpp */,
+				FA4F2BE01DE6650600CA37D7 /* Transform.h */,
 				FA0B7C071A95902C000E1D17 /* wrap_BezierCurve.cpp */,
 				FA0B7C081A95902C000E1D17 /* wrap_BezierCurve.h */,
 				FAB17BEE1ABFB37500F9BA27 /* wrap_CompressedData.cpp */,
@@ -2569,6 +2581,8 @@
 				FA0B7C0B1A95902C000E1D17 /* wrap_RandomGenerator.cpp */,
 				FA0B7C0C1A95902C000E1D17 /* wrap_RandomGenerator.h */,
 				FA2E9BFE1C19E00C0004A1EE /* wrap_RandomGenerator.lua */,
+				FA4F2BE11DE6650600CA37D7 /* wrap_Transform.cpp */,
+				FA4F2BE21DE6650600CA37D7 /* wrap_Transform.h */,
 			);
 			path = math;
 			sourceTree = "<group>";
@@ -3191,6 +3205,7 @@
 				FA0B7E6B1A95902C000E1D17 /* wrap_PulleyJoint.h in Headers */,
 				FA0B7E051A95902C000E1D17 /* Contact.h in Headers */,
 				FA0B7A691A958EA3000E1D17 /* b2Island.h in Headers */,
+				FA4F2BE41DE6650600CA37D7 /* Transform.h in Headers */,
 				FA0B7E0E1A95902C000E1D17 /* Fixture.h in Headers */,
 				FA0B7A401A958EA3000E1D17 /* b2ChainShape.h in Headers */,
 				217DFBE81D9F6D490055D849 /* inet.h in Headers */,
@@ -3266,6 +3281,7 @@
 				217DFBF71D9F6D490055D849 /* options.h in Headers */,
 				FA0B7E3B1A95902C000E1D17 /* World.h in Headers */,
 				FA0B7DC31A95902C000E1D17 /* wrap_Joystick.h in Headers */,
+				FA4F2BE61DE6650600CA37D7 /* wrap_Transform.h in Headers */,
 				FA0B7EE71A95902D000E1D17 /* Window.h in Headers */,
 				FA0B7E651A95902C000E1D17 /* wrap_PolygonShape.h in Headers */,
 				FA0B7AC91A958EA3000E1D17 /* win32.h in Headers */,
@@ -3501,6 +3517,7 @@
 				FA0B7AAB1A958EA3000E1D17 /* b2WeldJoint.cpp in Sources */,
 				FA0B7EC21A95902C000E1D17 /* threads.cpp in Sources */,
 				FA0B79371A958E3B000E1D17 /* macosx.mm in Sources */,
+				FA4F2BE81DE6651000CA37D7 /* wrap_Transform.cpp in Sources */,
 				FA0B7D011A95902C000E1D17 /* Filesystem.cpp in Sources */,
 				FA0B7ED91A95902D000E1D17 /* wrap_Timer.cpp in Sources */,
 				FA0B7DD11A95902C000E1D17 /* love.cpp in Sources */,
@@ -3583,6 +3600,7 @@
 				FA0B7D681A95902C000E1D17 /* wrap_ParticleSystem.cpp in Sources */,
 				FA0B7AD51A958EA3000E1D17 /* unix.c in Sources */,
 				FAB17BE71ABFAA9000F9BA27 /* lz4.c in Sources */,
+				FA4F2BE71DE6650D00CA37D7 /* Transform.cpp in Sources */,
 				FA0B7D651A95902C000E1D17 /* wrap_Mesh.cpp in Sources */,
 				FA0B7A531A958EA3000E1D17 /* b2Math.cpp in Sources */,
 				FA0B7CDA1A95902C000E1D17 /* Pool.cpp in Sources */,
@@ -3861,6 +3879,7 @@
 				FA0B7EDF1A95902D000E1D17 /* wrap_Touch.cpp in Sources */,
 				FA0B794A1A958E3B000E1D17 /* wrap_Data.cpp in Sources */,
 				217DFBFB1D9F6D490055D849 /* serial.c in Sources */,
+				FA4F2BE51DE6650600CA37D7 /* wrap_Transform.cpp in Sources */,
 				217DFC0D1D9F6D490055D849 /* unixudp.c in Sources */,
 				FA0B7CDC1A95902C000E1D17 /* Source.cpp in Sources */,
 				FA0B7DC41A95902C000E1D17 /* wrap_JoystickModule.cpp in Sources */,
@@ -4045,6 +4064,7 @@
 				FA0B7CDF1A95902C000E1D17 /* Source.cpp in Sources */,
 				FA0B7ECE1A95902C000E1D17 /* wrap_LuaThread.cpp in Sources */,
 				FA0B79431A958E3B000E1D17 /* Variant.cpp in Sources */,
+				FA4F2BE31DE6650600CA37D7 /* Transform.cpp in Sources */,
 				FA0B7EA01A95902C000E1D17 /* Sound.cpp in Sources */,
 				FA0B7DE51A95902C000E1D17 /* Cursor.cpp in Sources */,
 				FA0B7EDB1A95902D000E1D17 /* Touch.cpp in Sources */,

+ 6 - 0
src/common/Matrix.cpp

@@ -73,6 +73,12 @@ Matrix4::Matrix4()
 {
 	setIdentity();
 }
+
+
+Matrix4::Matrix4(const float elements[16])
+{
+	memcpy(e, elements, sizeof(float) * 16);
+}
 	
 Matrix4::Matrix4(float t00, float t10, float t01, float t11, float x, float y)
 {

+ 9 - 0
src/common/Matrix.h

@@ -52,6 +52,12 @@ public:
 	 **/
 	Matrix4(float t00, float t10, float t01, float t11, float x, float y);
 
+	/**
+	 * Creates a new matrix from the specified elements. Be sure to pass
+	 * exactly 16 elements in!
+	 **/
+	Matrix4(const float elements[16]);
+
 	/**
 	 * Creates a new matrix set to a transformation.
 	 **/
@@ -182,6 +188,9 @@ public:
 	template <typename V>
 	void transform(V *dst, const V *src, int size) const;
 
+	/**
+	 * Computes and returns the inverse of the matrix.
+	 **/
 	Matrix4 inverse() const;
 
 	/**

+ 2 - 1
src/common/types.cpp

@@ -68,7 +68,8 @@ static const TypeBits *createTypeFlags()
 	// Math.
 	b[MATH_RANDOM_GENERATOR_ID] = (one << MATH_RANDOM_GENERATOR_ID) | b[OBJECT_ID];
 	b[MATH_BEZIER_CURVE_ID] = (one << MATH_BEZIER_CURVE_ID) | b[OBJECT_ID];
-	b[MATH_COMPRESSED_DATA_ID] = (one <<MATH_COMPRESSED_DATA_ID) | b[DATA_ID];
+	b[MATH_COMPRESSED_DATA_ID] = (one << MATH_COMPRESSED_DATA_ID) | b[DATA_ID];
+	b[MATH_TRANSFORM_ID] = (one << MATH_TRANSFORM_ID) | b[OBJECT_ID];
 
 	// Audio.
 	b[AUDIO_SOURCE_ID] = (one << AUDIO_SOURCE_ID) | b[OBJECT_ID];

+ 1 - 0
src/common/types.h

@@ -70,6 +70,7 @@ enum Type
 	MATH_RANDOM_GENERATOR_ID,
 	MATH_BEZIER_CURVE_ID,
 	MATH_COMPRESSED_DATA_ID,
+	MATH_TRANSFORM_ID,
 
 	// Audio
 	AUDIO_SOURCE_ID,

+ 10 - 0
src/modules/graphics/opengl/Graphics.cpp

@@ -2092,6 +2092,16 @@ void Graphics::origin()
 	pixelSizeStack.back() = 1;
 }
 
+void Graphics::applyTransform(love::math::Transform *transform)
+{
+	gl.getTransform() *= transform->getMatrix();
+}
+
+void Graphics::replaceTransform(love::math::Transform *transform)
+{
+	gl.getTransform() = transform->getMatrix();
+}
+
 Vector Graphics::transformPoint(Vector point)
 {
 	Vector p;

+ 6 - 0
src/modules/graphics/opengl/Graphics.h

@@ -38,6 +38,8 @@
 
 #include "video/VideoStream.h"
 
+#include "math/Transform.h"
+
 #include "Font.h"
 #include "Image.h"
 #include "graphics/Quad.h"
@@ -488,6 +490,10 @@ public:
 	void translate(float x, float y);
 	void shear(float kx, float ky);
 	void origin();
+
+	void applyTransform(love::math::Transform *transform);
+	void replaceTransform(love::math::Transform *transform);
+
 	Vector transformPoint(Vector point);
 	Vector inverseTransformPoint(Vector point);
 

+ 82 - 52
src/modules/graphics/opengl/wrap_Graphics.cpp

@@ -1667,24 +1667,37 @@ int w_draw(lua_State *L)
 		startidx = 2;
 	}
 
-	float x  = (float) luaL_optnumber(L, startidx + 0, 0.0);
-	float y  = (float) luaL_optnumber(L, startidx + 1, 0.0);
-	float a  = (float) luaL_optnumber(L, startidx + 2, 0.0);
-	float sx = (float) luaL_optnumber(L, startidx + 3, 1.0);
-	float sy = (float) luaL_optnumber(L, startidx + 4, sx);
-	float ox = (float) luaL_optnumber(L, startidx + 5, 0.0);
-	float oy = (float) luaL_optnumber(L, startidx + 6, 0.0);
-	float kx = (float) luaL_optnumber(L, startidx + 7, 0.0);
-	float ky = (float) luaL_optnumber(L, startidx + 8, 0.0);
-
-	Matrix4 m(x, y, a, sx, sy, ox, oy, kx, ky);
+	if (luax_istype(L, startidx, MATH_TRANSFORM_ID))
+	{
+		math::Transform *tf = luax_totype<math::Transform>(L, startidx, MATH_TRANSFORM_ID);
+		luax_catchexcept(L, [&]() {
+			if (texture && quad)
+				instance()->drawq(texture, quad, tf->getMatrix());
+			else
+				instance()->draw(drawable, tf->getMatrix());
+		});
+	}
+	else
+	{
+		float x  = (float) luaL_optnumber(L, startidx + 0, 0.0);
+		float y  = (float) luaL_optnumber(L, startidx + 1, 0.0);
+		float a  = (float) luaL_optnumber(L, startidx + 2, 0.0);
+		float sx = (float) luaL_optnumber(L, startidx + 3, 1.0);
+		float sy = (float) luaL_optnumber(L, startidx + 4, sx);
+		float ox = (float) luaL_optnumber(L, startidx + 5, 0.0);
+		float oy = (float) luaL_optnumber(L, startidx + 6, 0.0);
+		float kx = (float) luaL_optnumber(L, startidx + 7, 0.0);
+		float ky = (float) luaL_optnumber(L, startidx + 8, 0.0);
 
-	luax_catchexcept(L, [&]() {
-		if (texture && quad)
-			instance()->drawq(texture, quad, m);
-		else if (drawable)
-			instance()->draw(drawable, m);
-	});
+		Matrix4 m(x, y, a, sx, sy, ox, oy, kx, ky);
+
+		luax_catchexcept(L, [&]() {
+			if (texture && quad)
+				instance()->drawq(texture, quad, m);
+			else if (drawable)
+				instance()->draw(drawable, m);
+		});
+	}
 
 	return 0;
 }
@@ -1694,19 +1707,27 @@ int w_print(lua_State *L)
 	std::vector<Font::ColoredString> str;
 	luax_checkcoloredstring(L, 1, str);
 
-	float x = (float)luaL_optnumber(L, 2, 0.0);
-	float y = (float)luaL_optnumber(L, 3, 0.0);
-	float angle = (float)luaL_optnumber(L, 4, 0.0f);
-	float sx = (float)luaL_optnumber(L, 5, 1.0f);
-	float sy = (float)luaL_optnumber(L, 6, sx);
-	float ox = (float)luaL_optnumber(L, 7, 0.0f);
-	float oy = (float)luaL_optnumber(L, 8, 0.0f);
-	float kx = (float)luaL_optnumber(L, 9, 0.0f);
-	float ky = (float)luaL_optnumber(L, 10, 0.0f);
+	if (luax_istype(L, 2, MATH_TRANSFORM_ID))
+	{
+		math::Transform *tf = luax_totype<math::Transform>(L, 2, MATH_TRANSFORM_ID);
+		luax_catchexcept(L, [&](){ instance()->print(str, tf->getMatrix()); });
+	}
+	else
+	{
+		float x = (float)luaL_optnumber(L, 2, 0.0);
+		float y = (float)luaL_optnumber(L, 3, 0.0);
+		float angle = (float)luaL_optnumber(L, 4, 0.0f);
+		float sx = (float)luaL_optnumber(L, 5, 1.0f);
+		float sy = (float)luaL_optnumber(L, 6, sx);
+		float ox = (float)luaL_optnumber(L, 7, 0.0f);
+		float oy = (float)luaL_optnumber(L, 8, 0.0f);
+		float kx = (float)luaL_optnumber(L, 9, 0.0f);
+		float ky = (float)luaL_optnumber(L, 10, 0.0f);
 
-	Matrix4 m(x, y, angle, sx, sy, ox, oy, kx, ky);
+		Matrix4 m(x, y, angle, sx, sy, ox, oy, kx, ky);
 
-	luax_catchexcept(L, [&](){ instance()->print(str, m); });
+		luax_catchexcept(L, [&](){ instance()->print(str, m); });
+	}
 	return 0;
 }
 
@@ -1715,36 +1736,38 @@ int w_printf(lua_State *L)
 	std::vector<Font::ColoredString> str;
 	luax_checkcoloredstring(L, 1, str);
 
-	float x = (float)luaL_checknumber(L, 2);
-	float y = (float)luaL_checknumber(L, 3);
-	float wrap = (float)luaL_checknumber(L, 4);
-
-	float angle = 0.0f;
-	float sx = 1.0f, sy = 1.0f;
-	float ox = 0.0f, oy = 0.0f;
-	float kx = 0.0f, ky = 0.0f;
-
 	Font::AlignMode align = Font::ALIGN_LEFT;
+	Matrix4 m;
 
-	if (lua_gettop(L) >= 5)
+	int formatidx = 4;
+
+	if (luax_istype(L, 2, MATH_TRANSFORM_ID))
 	{
-		if (!lua_isnil(L, 5))
-		{
-			const char *str = luaL_checkstring(L, 5);
-			if (!Font::getConstant(str, align))
-				return luaL_error(L, "Incorrect alignment: %s", str);
-		}
+		math::Transform *tf = luax_totype<math::Transform>(L, 2, MATH_TRANSFORM_ID);
+		m = tf->getMatrix();
+		formatidx = 3;
+	}
+	else
+	{
+		float x = (float)luaL_checknumber(L, 2);
+		float y = (float)luaL_checknumber(L, 3);
+
+		float angle = (float) luaL_optnumber(L, 6, 0.0f);
+		float sx = (float) luaL_optnumber(L, 7, 1.0f);
+		float sy = (float) luaL_optnumber(L, 8, sx);
+		float ox = (float) luaL_optnumber(L, 9, 0.0f);
+		float oy = (float) luaL_optnumber(L, 10, 0.0f);
+		float kx = (float) luaL_optnumber(L, 11, 0.0f);
+		float ky = (float) luaL_optnumber(L, 12, 0.0f);
 
-		angle = (float) luaL_optnumber(L, 6, 0.0f);
-		sx = (float) luaL_optnumber(L, 7, 1.0f);
-		sy = (float) luaL_optnumber(L, 8, sx);
-		ox = (float) luaL_optnumber(L, 9, 0.0f);
-		oy = (float) luaL_optnumber(L, 10, 0.0f);
-		kx = (float) luaL_optnumber(L, 11, 0.0f);
-		ky = (float) luaL_optnumber(L, 12, 0.0f);
+		m = Matrix4(x, y, angle, sx, sy, ox, oy, kx, ky);
 	}
 
-	Matrix4 m(x, y, angle, sx, sy, ox, oy, kx, ky);
+	float wrap = (float)luaL_checknumber(L, formatidx);
+
+	const char *astr = lua_isnoneornil(L, formatidx + 1) ? nullptr : luaL_checkstring(L, formatidx + 1);
+	if (astr != nullptr && !Font::getConstant(astr, align))
+		return luaL_error(L, "Incorrect alignment: %s", astr);
 
 	luax_catchexcept(L, [&](){ instance()->printf(str, wrap, align, m); });
 	return 0;
@@ -2048,6 +2071,13 @@ int w_push(lua_State *L)
 		return luaL_error(L, "Invalid graphics stack type: %s", sname);
 
 	luax_catchexcept(L, [&](){ instance()->push(stype); });
+
+	if (luax_istype(L, 2, MATH_TRANSFORM_ID))
+	{
+		math::Transform *t = luax_totype<math::Transform>(L, 2, MATH_TRANSFORM_ID);
+		instance()->applyTransform(t);
+	}
+
 	return 0;
 }
 

+ 8 - 0
src/modules/graphics/opengl/wrap_Shader.cpp

@@ -21,6 +21,7 @@
 #include "wrap_Shader.h"
 #include "graphics/wrap_Texture.h"
 #include "math/MathModule.h"
+#include "math/Transform.h"
 
 #include <string>
 #include <algorithm>
@@ -164,6 +165,13 @@ int w_Shader_sendMatrices(lua_State *L, int startidx, Shader *shader, const Shad
 
 	for (int i = 0; i < count; i++)
 	{
+		if (columns == 4 && rows == 4 && luax_istype(L, startidx + i, MATH_TRANSFORM_ID))
+		{
+			math::Transform *t = luax_totype<math::Transform>(L, startidx + i, MATH_TRANSFORM_ID);
+			memcpy(&values[i * 16], t->getMatrix().getElements(), sizeof(float) * 16);
+			continue;
+		}
+
 		luaL_checktype(L, startidx + i, LUA_TTABLE);
 
 		lua_rawgeti(L, startidx + i, 1);

+ 32 - 18
src/modules/graphics/opengl/wrap_SpriteBatch.cpp

@@ -23,6 +23,7 @@
 #include "Image.h"
 #include "Canvas.h"
 #include "graphics/wrap_Texture.h"
+#include "math/wrap_Transform.h"
 
 // C++
 #include <typeinfo>
@@ -51,24 +52,37 @@ static inline int w_SpriteBatch_add_or_set(lua_State *L, SpriteBatch *t, int sta
 	else if (lua_isnil(L, startidx) && !lua_isnoneornil(L, startidx + 1))
 		return luax_typerror(L, startidx, "Quad");
 
-	float x  = (float) luaL_optnumber(L, startidx + 0, 0.0);
-	float y  = (float) luaL_optnumber(L, startidx + 1, 0.0);
-	float a  = (float) luaL_optnumber(L, startidx + 2, 0.0);
-	float sx = (float) luaL_optnumber(L, startidx + 3, 1.0);
-	float sy = (float) luaL_optnumber(L, startidx + 4, sx);
-	float ox = (float) luaL_optnumber(L, startidx + 5, 0.0);
-	float oy = (float) luaL_optnumber(L, startidx + 6, 0.0);
-	float kx = (float) luaL_optnumber(L, startidx + 7, 0.0);
-	float ky = (float) luaL_optnumber(L, startidx + 8, 0.0);
-
-	Matrix4 m(x, y, a, sx, sy, ox, oy, kx, ky);
-
-	luax_catchexcept(L, [&]() {
-		if (quad)
-			index = t->addq(quad, m, index);
-		else
-			index = t->add(m, index);
-	});
+	if (luax_istype(L, startidx, MATH_TRANSFORM_ID))
+	{
+		math::Transform *tf = luax_totype<math::Transform>(L, startidx, MATH_TRANSFORM_ID);
+		luax_catchexcept(L, [&]() {
+			if (quad)
+				index = t->addq(quad, tf->getMatrix(), index);
+			else
+				index = t->add(tf->getMatrix(), index);
+		});
+	}
+	else
+	{
+		float x  = (float) luaL_optnumber(L, startidx + 0, 0.0);
+		float y  = (float) luaL_optnumber(L, startidx + 1, 0.0);
+		float a  = (float) luaL_optnumber(L, startidx + 2, 0.0);
+		float sx = (float) luaL_optnumber(L, startidx + 3, 1.0);
+		float sy = (float) luaL_optnumber(L, startidx + 4, sx);
+		float ox = (float) luaL_optnumber(L, startidx + 5, 0.0);
+		float oy = (float) luaL_optnumber(L, startidx + 6, 0.0);
+		float kx = (float) luaL_optnumber(L, startidx + 7, 0.0);
+		float ky = (float) luaL_optnumber(L, startidx + 8, 0.0);
+
+		Matrix4 m(x, y, a, sx, sy, ox, oy, kx, ky);
+
+		luax_catchexcept(L, [&]() {
+			if (quad)
+				index = t->addq(quad, m, index);
+			else
+				index = t->add(m, index);
+		});
+	}
 
 	return index;
 }

+ 47 - 28
src/modules/graphics/opengl/wrap_Text.cpp

@@ -19,6 +19,7 @@
  **/
 
 #include "wrap_Text.h"
+#include "math/wrap_Transform.h"
 
 namespace love
 {
@@ -126,24 +127,33 @@ int w_Text_add(lua_State *L)
 {
 	Text *t = luax_checktext(L, 1);
 
+	int index = 0;
+
 	std::vector<Font::ColoredString> text;
 	luax_checkcoloredstring(L, 2, text);
 
-	float x  = (float) luaL_optnumber(L, 3, 0.0);
-	float y  = (float) luaL_optnumber(L, 4, 0.0);
-	float a  = (float) luaL_optnumber(L, 5, 0.0);
-	float sx = (float) luaL_optnumber(L, 6, 1.0);
-	float sy = (float) luaL_optnumber(L, 7, sx);
-	float ox = (float) luaL_optnumber(L, 8, 0.0);
-	float oy = (float) luaL_optnumber(L, 9, 0.0);
-	float kx = (float) luaL_optnumber(L, 10, 0.0);
-	float ky = (float) luaL_optnumber(L, 11, 0.0);
-
-	Matrix4 m(x, y, a, sx, sy, ox, oy, kx, ky);
-	int index = 0;
-	luax_catchexcept(L, [&](){ index = t->add(text, m); });
-	lua_pushnumber(L, index + 1);
+	if (luax_istype(L, 3, MATH_TRANSFORM_ID))
+	{
+		math::Transform *tf = luax_totype<math::Transform>(L, 3, MATH_TRANSFORM_ID);
+		luax_catchexcept(L, [&](){ index = t->add(text, tf->getMatrix()); });
+	}
+	else
+	{
+		float x  = (float) luaL_optnumber(L, 3, 0.0);
+		float y  = (float) luaL_optnumber(L, 4, 0.0);
+		float a  = (float) luaL_optnumber(L, 5, 0.0);
+		float sx = (float) luaL_optnumber(L, 6, 1.0);
+		float sy = (float) luaL_optnumber(L, 7, sx);
+		float ox = (float) luaL_optnumber(L, 8, 0.0);
+		float oy = (float) luaL_optnumber(L, 9, 0.0);
+		float kx = (float) luaL_optnumber(L, 10, 0.0);
+		float ky = (float) luaL_optnumber(L, 11, 0.0);
+
+		Matrix4 m(x, y, a, sx, sy, ox, oy, kx, ky);
+		luax_catchexcept(L, [&](){ index = t->add(text, m); });
+	}
 
+	lua_pushnumber(L, index + 1);
 	return 1;
 }
 
@@ -151,6 +161,8 @@ int w_Text_addf(lua_State *L)
 {
 	Text *t = luax_checktext(L, 1);
 
+	int index = 0;
+
 	std::vector<Font::ColoredString> text;
 	luax_checkcoloredstring(L, 2, text);
 
@@ -162,21 +174,28 @@ int w_Text_addf(lua_State *L)
 	if (!Font::getConstant(alignstr, align))
 		return luaL_error(L, "Invalid align mode: %s", alignstr);
 
-	float x  = (float) luaL_optnumber(L, 5, 0.0);
-	float y  = (float) luaL_optnumber(L, 6, 0.0);
-	float a  = (float) luaL_optnumber(L, 7, 0.0);
-	float sx = (float) luaL_optnumber(L, 8, 1.0);
-	float sy = (float) luaL_optnumber(L, 9, sx);
-	float ox = (float) luaL_optnumber(L, 10, 0.0);
-	float oy = (float) luaL_optnumber(L, 11, 0.0);
-	float kx = (float) luaL_optnumber(L, 12, 0.0);
-	float ky = (float) luaL_optnumber(L, 13, 0.0);
-
-	Matrix4 m(x, y, a, sx, sy, ox, oy, kx, ky);
-	int index = 0;
-	luax_catchexcept(L, [&](){ index = t->addf(text, wrap, align, m); });
-	lua_pushnumber(L, index + 1);
+	if (luax_istype(L, 5, MATH_TRANSFORM_ID))
+	{
+		math::Transform *tf = luax_totype<math::Transform>(L, 5, MATH_TRANSFORM_ID);
+		luax_catchexcept(L, [&](){ index = t->addf(text, wrap, align, tf->getMatrix()); });
+	}
+	else
+	{
+		float x  = (float) luaL_optnumber(L, 5, 0.0);
+		float y  = (float) luaL_optnumber(L, 6, 0.0);
+		float a  = (float) luaL_optnumber(L, 7, 0.0);
+		float sx = (float) luaL_optnumber(L, 8, 1.0);
+		float sy = (float) luaL_optnumber(L, 9, sx);
+		float ox = (float) luaL_optnumber(L, 10, 0.0);
+		float oy = (float) luaL_optnumber(L, 11, 0.0);
+		float kx = (float) luaL_optnumber(L, 12, 0.0);
+		float ky = (float) luaL_optnumber(L, 13, 0.0);
+
+		Matrix4 m(x, y, a, sx, sy, ox, oy, kx, ky);
+		luax_catchexcept(L, [&](){ index = t->addf(text, wrap, align, m); });
+	}
 
+	lua_pushnumber(L, index + 1);
 	return 1;
 }
 

+ 11 - 0
src/modules/math/MathModule.cpp

@@ -25,6 +25,7 @@
 #include "common/int.h"
 #include "common/StringMap.h"
 #include "BezierCurve.h"
+#include "Transform.h"
 
 // STL
 #include <cmath>
@@ -374,6 +375,16 @@ BezierCurve *Math::newBezierCurve(const std::vector<Vector> &points)
 	return new BezierCurve(points);
 }
 
+Transform *Math::newTransform()
+{
+	return new Transform();
+}
+
+Transform *Math::newTransform(float x, float y, float a, float sx, float sy, float ox, float oy, float kx, float ky)
+{
+	return new Transform(x, y, a, sx, sy, ox, oy, kx, ky);
+}
+
 std::string hash(HashFunction::Function function, Data *input)
 {
 	return hash(function, (const char*) input->getData(), input->getSize());

+ 4 - 0
src/modules/math/MathModule.h

@@ -45,6 +45,7 @@ namespace math
 {
 
 class BezierCurve;
+class Transform;
 
 struct Triangle
 {
@@ -176,6 +177,9 @@ public:
 	 **/
 	BezierCurve *newBezierCurve(const std::vector<Vector> &points);
 
+	Transform *newTransform();
+	Transform *newTransform(float x, float y, float a, float sx, float sy, float ox, float oy, float kx, float ky);
+
 	// Implements Module.
 	virtual ModuleType getModuleType() const
 	{

+ 131 - 0
src/modules/math/Transform.cpp

@@ -0,0 +1,131 @@
+/**
+ * Copyright (c) 2006-2016 LOVE Development Team
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty.  In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ *    claim that you wrote the original software. If you use this software
+ *    in a product, an acknowledgment in the product documentation would be
+ *    appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ *    misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ **/
+
+#include "Transform.h"
+
+namespace love
+{
+namespace math
+{
+
+Transform::Transform()
+	: matrix()
+	, inverseDirty(true)
+	, inverseMatrix()
+{
+}
+
+Transform::Transform(const Matrix4 &m)
+	: matrix(m)
+	, inverseDirty(true)
+	, inverseMatrix()
+{
+}
+
+Transform::Transform(float x, float y, float a, float sx, float sy, float ox, float oy, float kx, float ky)
+	: matrix(x, y, a, sx, sy, ox, oy, kx, ky)
+	, inverseDirty(true)
+	, inverseMatrix()
+{
+}
+
+Transform::~Transform()
+{
+}
+
+Transform *Transform::clone()
+{
+	return new Transform(*this);
+}
+
+Transform *Transform::inverse()
+{
+	return new Transform(getInverseMatrix());
+}
+
+void Transform::apply(Transform *other)
+{
+	matrix *= other->getMatrix();
+	inverseDirty = true;
+}
+
+void Transform::translate(float x, float y)
+{
+	matrix.translate(x, y);
+	inverseDirty = true;
+}
+
+void Transform::rotate(float angle)
+{
+	matrix.rotate(angle);
+	inverseDirty = true;
+}
+
+void Transform::scale(float x, float y)
+{
+	matrix.scale(x, y);
+	inverseDirty = true;
+}
+
+void Transform::shear(float x, float y)
+{
+	matrix.shear(x, y);
+	inverseDirty = true;
+}
+
+void Transform::reset()
+{
+	matrix.setIdentity();
+	inverseDirty = true;
+}
+
+void Transform::setTransformation(float x, float y, float a, float sx, float sy, float ox, float oy, float kx, float ky)
+{
+	matrix.setTransformation(x, y, a, sx, sy, ox, oy, kx, ky);
+	inverseDirty = true;
+}
+
+love::Vector Transform::transformPoint(love::Vector p) const
+{
+	love::Vector result;
+	matrix.transform(&result, &p, 1);
+	return result;
+}
+
+love::Vector Transform::inverseTransformPoint(love::Vector p)
+{
+	love::Vector result;
+	getInverseMatrix().transform(&result, &p, 1);
+	return result;
+}
+
+const Matrix4 &Transform::getMatrix() const
+{
+	return matrix;
+}
+
+void Transform::setMatrix(const Matrix4 &m)
+{
+	matrix = m;
+	inverseDirty = true;
+}
+
+} // math
+} // love

+ 83 - 0
src/modules/math/Transform.h

@@ -0,0 +1,83 @@
+/**
+ * Copyright (c) 2006-2016 LOVE Development Team
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty.  In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ *    claim that you wrote the original software. If you use this software
+ *    in a product, an acknowledgment in the product documentation would be
+ *    appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ *    misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ **/
+
+#pragma once
+
+// LOVE
+#include "common/Object.h"
+#include "common/Matrix.h"
+#include "common/Vector.h"
+
+namespace love
+{
+namespace math
+{
+
+class Transform : public Object
+{
+public:
+
+	Transform();
+	Transform(const Matrix4 &m);
+	Transform(float x, float y, float a, float sx, float sy, float ox, float oy, float kx, float ky);
+
+	virtual ~Transform();
+
+	Transform *clone();
+	Transform *inverse();
+
+	void apply(Transform *other);
+
+	void translate(float x, float y);
+	void rotate(float angle);
+	void scale(float x, float y);
+	void shear(float x, float y);
+
+	void reset();
+	void setTransformation(float x, float y, float a, float sx, float sy, float ox, float oy, float kx, float ky);
+
+	love::Vector transformPoint(love::Vector p) const;
+	love::Vector inverseTransformPoint(love::Vector p);
+
+	const Matrix4 &getMatrix() const;
+	void setMatrix(const Matrix4 &m);
+
+private:
+
+	inline const Matrix4 &getInverseMatrix()
+	{
+		if (inverseDirty)
+		{
+			inverseDirty = false;
+			inverseMatrix = matrix.inverse();
+		}
+
+		return inverseMatrix;
+	}
+	
+	Matrix4 matrix;
+	bool inverseDirty;
+	Matrix4 inverseMatrix;
+
+}; // Transform
+
+
+} // math
+} // love

+ 29 - 0
src/modules/math/wrap_Math.cpp

@@ -22,8 +22,10 @@
 #include "wrap_RandomGenerator.h"
 #include "wrap_BezierCurve.h"
 #include "wrap_CompressedData.h"
+#include "wrap_Transform.h"
 #include "MathModule.h"
 #include "BezierCurve.h"
+#include "Transform.h"
 #include "common/b64.h"
 
 #include <cmath>
@@ -118,6 +120,31 @@ int w_newBezierCurve(lua_State *L)
 	return 1;
 }
 
+int w_newTransform(lua_State *L)
+{
+	Transform *t = nullptr;
+
+	if (lua_isnoneornil(L, 1))
+		t = Math::instance.newTransform();
+	else
+	{
+		float x =  (float) luaL_checknumber(L, 1);
+		float y =  (float) luaL_checknumber(L, 2);
+		float a =  (float) luaL_optnumber(L, 3, 0.0);
+		float sx = (float) luaL_optnumber(L, 4, 1.0);
+		float sy = (float) luaL_optnumber(L, 5, sx);
+		float ox = (float) luaL_optnumber(L, 6, 0.0);
+		float oy = (float) luaL_optnumber(L, 7, 0.0);
+		float kx = (float) luaL_optnumber(L, 8, 0.0);
+		float ky = (float) luaL_optnumber(L, 9, 0.0);
+		t = Math::instance.newTransform(x, y, a, sx, sy, ox, oy, kx, ky);
+	}
+
+	luax_pushtype(L, MATH_TRANSFORM_ID, t);
+	t->release();
+	return 1;
+}
+
 int w_triangulate(lua_State *L)
 {
 	std::vector<love::Vector> vertices;
@@ -507,6 +534,7 @@ static const luaL_Reg functions[] =
 	{ "_getRandomGenerator", w__getRandomGenerator },
 	{ "newRandomGenerator", w_newRandomGenerator },
 	{ "newBezierCurve", w_newBezierCurve },
+	{ "newTransform", w_newTransform },
 	{ "triangulate", w_triangulate },
 	{ "isConvex", w_isConvex },
 	{ "gammaToLinear", w_gammaToLinear },
@@ -525,6 +553,7 @@ static const lua_CFunction types[] =
 	luaopen_randomgenerator,
 	luaopen_beziercurve,
 	luaopen_compresseddata,
+	luaopen_transform,
 	0
 };
 

+ 305 - 0
src/modules/math/wrap_Transform.cpp

@@ -0,0 +1,305 @@
+/**
+ * Copyright (c) 2006-2016 LOVE Development Team
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty.  In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ *    claim that you wrote the original software. If you use this software
+ *    in a product, an acknowledgment in the product documentation would be
+ *    appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ *    misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ **/
+
+#include "wrap_Transform.h"
+
+namespace love
+{
+namespace math
+{
+
+Transform *luax_checktransform(lua_State *L, int idx)
+{
+	return luax_checktype<Transform>(L, idx, MATH_TRANSFORM_ID);
+}
+
+int w_Transform_clone(lua_State *L)
+{
+	Transform *t = luax_checktransform(L, 1);
+	Transform *newtransform = t->clone();
+	luax_pushtype(L, MATH_TRANSFORM_ID, newtransform);
+	newtransform->release();
+	return 1;
+}
+
+int w_Transform_inverse(lua_State *L)
+{
+	Transform *t = luax_checktransform(L, 1);
+	Transform *inverse = t->inverse();
+	luax_pushtype(L, MATH_TRANSFORM_ID, inverse);
+	inverse->release();
+	return 1;
+}
+
+int w_Transform_apply(lua_State *L)
+{
+	Transform *t = luax_checktransform(L, 1);
+	Transform *other = luax_checktransform(L, 2);
+	t->apply(other);
+	lua_pushvalue(L, 1);
+	return 1;
+}
+
+int w_Transform_translate(lua_State *L)
+{
+	Transform *t = luax_checktransform(L, 1);
+	float x = (float) luaL_checknumber(L, 2);
+	float y = (float) luaL_checknumber(L, 3);
+	t->translate(x, y);
+	lua_pushvalue(L, 1);
+	return 1;
+}
+
+int w_Transform_rotate(lua_State *L)
+{
+	Transform *t = luax_checktransform(L, 1);
+	float angle = (float) luaL_checknumber(L, 2);
+	t->rotate(angle);
+	lua_pushvalue(L, 1);
+	return 1;
+}
+
+int w_Transform_scale(lua_State *L)
+{
+	Transform *t = luax_checktransform(L, 1);
+	float sx = (float) luaL_checknumber(L, 2);
+	float sy = (float) luaL_optnumber(L, 3, sx);
+	t->scale(sx, sy);
+	lua_pushvalue(L, 1);
+	return 1;
+}
+
+int w_Transform_shear(lua_State *L)
+{
+	Transform *t = luax_checktransform(L, 1);
+	float kx = (float) luaL_checknumber(L, 2);
+	float ky = (float) luaL_checknumber(L, 3);
+	t->shear(kx, ky);
+	lua_pushvalue(L, 1);
+	return 1;
+}
+
+int w_Transform_reset(lua_State *L)
+{
+	Transform *t = luax_checktransform(L, 1);
+	t->reset();
+	lua_pushvalue(L, 1);
+	return 1;
+}
+
+int w_Transform_setTransformation(lua_State *L)
+{
+	Transform *t = luax_checktransform(L, 1);
+	float x =  (float) luaL_optnumber(L, 2, 0.0);
+	float y =  (float) luaL_optnumber(L, 3, 0.0);
+	float a =  (float) luaL_optnumber(L, 4, 0.0);
+	float sx = (float) luaL_optnumber(L, 5, 1.0);
+	float sy = (float) luaL_optnumber(L, 6, sx);
+	float ox = (float) luaL_optnumber(L, 7, 0.0);
+	float oy = (float) luaL_optnumber(L, 8, 0.0);
+	float kx = (float) luaL_optnumber(L, 9, 0.0);
+	float ky = (float) luaL_optnumber(L, 10, 0.0);
+	t->setTransformation(x, y, a, sx, sy, ox, oy, kx, ky);
+	lua_pushvalue(L, 1);
+	return 1;
+}
+
+int w_Transform_setMatrix(lua_State *L)
+{
+	Transform *t = luax_checktransform(L, 1);
+
+	bool columnmajor = false;
+
+	int idx = 2;
+	if (lua_isboolean(L, idx))
+	{
+		columnmajor = lua_toboolean(L, idx);
+		idx++;
+	}
+
+	float elements[16];
+
+	if (lua_istable(L, idx))
+	{
+		lua_rawgeti(L, idx, 1);
+		bool tableoftables = lua_istable(L, -1);
+		lua_pop(L, 1);
+
+		if (tableoftables)
+		{
+			if (columnmajor)
+			{
+				for (int column = 0; column < 4; column++)
+				{
+					lua_rawgeti(L, idx, column + 1);
+
+					for (int row = 0; row < 4; row++)
+					{
+						lua_rawgeti(L, -(row + 1), row + 1);
+						elements[column * 4 + row] = (float) luaL_checknumber(L, -1);
+					}
+
+					lua_pop(L, 4 + 1);
+				}
+			}
+			else
+			{
+				for (int row = 0; row < 4; row++)
+				{
+					lua_rawgeti(L, idx, row + 1);
+
+					for (int column = 0; column < 4; column++)
+					{
+						// The table has the matrix elements laid out in row-major
+						// order, but we need to store them column-major in memory.
+						lua_rawgeti(L, -(column + 1), column + 1);
+						elements[column * 4 + row] = (float) luaL_checknumber(L, -1);
+					}
+
+					lua_pop(L, 4 + 1);
+				}
+			}
+		}
+		else
+		{
+			if (columnmajor)
+			{
+				for (int column = 0; column < 4; column++)
+				{
+					for (int row = 0; row < 4; row++)
+					{
+						lua_rawgeti(L, idx, column * 4 + row + 1);
+						elements[column * 4 + row] = (float) luaL_checknumber(L, -1);
+					}
+				}
+			}
+			else
+			{
+				for (int column = 0; column < 4; column++)
+				{
+					for (int row = 0; row < 4; row++)
+					{
+						// The table has the matrix elements laid out in row-major
+						// order, but we need to store them column-major in memory.
+						lua_rawgeti(L, idx, row * 4 + column + 1);
+						elements[column * 4 + row] = (float) luaL_checknumber(L, -1);
+					}
+				}
+			}
+
+			lua_pop(L, 16);
+		}
+	}
+	else
+	{
+		if (columnmajor)
+		{
+			for (int i = 0; i < 16; i++)
+				elements[i] = (float) luaL_checknumber(L, idx + i);
+		}
+		else
+		{
+			for (int column = 0; column < 4; column++)
+			{
+				for (int row = 0; row < 4; row++)
+					elements[column * 4 + row] = (float) luaL_checknumber(L, row * 4 + column + idx);
+			}
+		}
+	}
+
+	t->setMatrix(Matrix4(elements));
+	lua_pushvalue(L, 1);
+	return 1;
+}
+
+int w_Transform_getMatrix(lua_State *L)
+{
+	Transform *t = luax_checktransform(L, 1);
+	const float *elements = t->getMatrix().getElements();
+
+	// We want to push elements in row-major order, but they're stored column-
+	// major.
+	for (int row = 0; row < 4; row++)
+	{
+		for (int column = 0; column < 4; column++)
+			lua_pushnumber(L, elements[column * 4 + row]);
+	}
+
+	return 16;
+}
+
+int w_Transform_transformPoint(lua_State *L)
+{
+	Transform *t = luax_checktransform(L, 1);
+	love::Vector p;
+	p.x = (float) luaL_checknumber(L, 2);
+	p.y = (float) luaL_checknumber(L, 3);
+	p = t->transformPoint(p);
+	lua_pushnumber(L, p.x);
+	lua_pushnumber(L, p.y);
+	return 2;
+}
+
+int w_Transform_inverseTransformPoint(lua_State *L)
+{
+	Transform *t = luax_checktransform(L, 1);
+	love::Vector p;
+	p.x = (float) luaL_checknumber(L, 2);
+	p.y = (float) luaL_checknumber(L, 3);
+	p = t->inverseTransformPoint(p);
+	lua_pushnumber(L, p.x);
+	lua_pushnumber(L, p.y);
+	return 2;
+}
+
+int w_Transform__mul(lua_State *L)
+{
+	Transform *t1 = luax_checktransform(L, 1);
+	Transform *t2 = luax_checktransform(L, 2);
+	Transform *t3 = new Transform(t1->getMatrix() * t2->getMatrix());
+	luax_pushtype(L, MATH_TRANSFORM_ID, t3);
+	t3->release();
+	return 1;
+}
+
+static const luaL_Reg functions[] =
+{
+	{ "clone", w_Transform_clone },
+	{ "inverse", w_Transform_inverse },
+	{ "apply", w_Transform_apply },
+	{ "translate", w_Transform_translate },
+	{ "rotate", w_Transform_rotate },
+	{ "scale", w_Transform_scale },
+	{ "shear", w_Transform_shear },
+	{ "reset", w_Transform_reset },
+	{ "setTransformation", w_Transform_setTransformation },
+	{ "transformPoint", w_Transform_transformPoint },
+	{ "inverseTransformPoint", w_Transform_inverseTransformPoint },
+	{ "__mul", w_Transform__mul },
+	{ 0, 0 }
+};
+
+extern "C" int luaopen_transform(lua_State *L)
+{
+	return luax_register_type(L, MATH_TRANSFORM_ID, "Transform", functions, nullptr);
+}
+
+} // math
+} // love

+ 35 - 0
src/modules/math/wrap_Transform.h

@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) 2006-2016 LOVE Development Team
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty.  In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ *    claim that you wrote the original software. If you use this software
+ *    in a product, an acknowledgment in the product documentation would be
+ *    appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ *    misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ **/
+
+#pragma once
+
+// LOVE
+#include "Transform.h"
+#include "common/runtime.h"
+
+namespace love
+{
+namespace math
+{
+
+extern "C" int luaopen_transform(lua_State *L);
+
+} // math
+} // love