From cabaee680e66651f2a9a6e72996e9f21d47c274f Mon Sep 17 00:00:00 2001
From: Jacek Olszak <jacekolszak@gmail.com>
Date: Tue, 12 Feb 2019 14:57:03 +0100
Subject: [PATCH] #159 Fix unproject for rotated matrix

---
 geometry.go      | 12 +++++-------
 geometry_test.go | 29 +++++++++++++++++++++++++++++
 2 files changed, 34 insertions(+), 7 deletions(-)

diff --git a/geometry.go b/geometry.go
index 72d148e..621a0b4 100644
--- a/geometry.go
+++ b/geometry.go
@@ -401,13 +401,11 @@ func (m Matrix) Project(u Vec) Vec {
 
 // Unproject does the inverse operation to Project.
 //
-// It turns out that multiplying a vector by the inverse matrix of m can be nearly-accomplished by
-// subtracting the translate part of the matrix and multplying by the inverse of the top-left 2x2
-// matrix, and the inverse of a 2x2 matrix is simple enough to just be inlined in the computation.
-//
 // Time complexity is O(1).
 func (m Matrix) Unproject(u Vec) Vec {
-	d := (m[0] * m[3]) - (m[1] * m[2])
-	u.X, u.Y = (u.X-m[4])/d, (u.Y-m[5])/d
-	return Vec{u.X*m[3] - u.Y*m[1], u.Y*m[0] - u.X*m[2]}
+	det := m[0]*m[3] - m[2]*m[1]
+	return Vec{
+		m[3]/det*u.X - m[2]/det*u.Y + m[2]*m[5] - m[3]*m[4],
+		-m[1]/det*u.X + m[0]/det*u.Y + m[1]*m[4] - m[0]*m[5],
+	}
 }
diff --git a/geometry_test.go b/geometry_test.go
index e1c1a6f..f97b8cd 100644
--- a/geometry_test.go
+++ b/geometry_test.go
@@ -2,6 +2,8 @@ package pixel_test
 
 import (
 	"fmt"
+	"github.com/stretchr/testify/assert"
+	"math"
 	"testing"
 
 	"github.com/faiface/pixel"
@@ -77,3 +79,30 @@ func TestResizeRect(t *testing.T) {
 		})
 	}
 }
+
+func TestMatrix_Unproject(t *testing.T) {
+	t.Run("for rotated matrix", func(t *testing.T) {
+		matrix := pixel.IM.Rotated(pixel.ZV, math.Pi/2)
+		unprojected := matrix.Unproject(pixel.V(0, 1))
+		assert.InDelta(t, unprojected.X, 1, 0.01)
+		assert.InDelta(t, unprojected.Y, 0, 0.01)
+	})
+	t.Run("for moved matrix", func(t *testing.T) {
+		matrix := pixel.IM.Moved(pixel.V(5, 5))
+		unprojected := matrix.Unproject(pixel.V(0, 0))
+		assert.InDelta(t, unprojected.X, -5, 0.01)
+		assert.InDelta(t, unprojected.Y, -5, 0.01)
+	})
+	t.Run("for scaled matrix", func(t *testing.T) {
+		matrix := pixel.IM.Scaled(pixel.ZV, 2)
+		unprojected := matrix.Unproject(pixel.V(4, 4))
+		assert.InDelta(t, unprojected.X, 2, 0.01)
+		assert.InDelta(t, unprojected.Y, 2, 0.01)
+	})
+	t.Run("for singular matrix", func(t *testing.T) {
+		matrix := pixel.Matrix{0, 0, 0, 0, 0, 0}
+		unprojected := matrix.Unproject(pixel.ZV)
+		assert.True(t, math.IsNaN(unprojected.X))
+		assert.True(t, math.IsNaN(unprojected.Y))
+	})
+}
-- 
GitLab