From 5d64b69b9875657715c134c41ac36f1793aa8459 Mon Sep 17 00:00:00 2001 From: Nicolas Braud-Santoni Date: Tue, 9 Oct 2018 20:46:00 -0400 Subject: [PATCH 1/7] Add a __xor__ dunder for cross-product --- README.md | 5 +++++ ppb_vector/vector2.py | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/README.md b/README.md index 29cd5f1e..198233b8 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,11 @@ Multiply a `Vector2` by another `Vector2` to get the dot product. >>> Vector2(45, 60).length 75.0 +### Cross-product + + >>> Vector(1, 0) ^ Vector(0, 1) + 1 + ### Access Values Convenient access to `Vector2` members via dot notation, indexes, or keys. diff --git a/ppb_vector/vector2.py b/ppb_vector/vector2.py index e13eda4d..596cb49c 100644 --- a/ppb_vector/vector2.py +++ b/ppb_vector/vector2.py @@ -46,6 +46,10 @@ def __rmul__(self, other): if isinstance(other, Number): return Vector2(self.x * other, self.y * other) + def __xor__(self, other): + assert(isinstance(other, Vector2)) + return self.x * other.y - self.y * other.x + def __getitem__(self, item): if hasattr(item, '__index__'): item = item.__index__() From e040d6bb494deb682751f7996aa791d688f7a858 Mon Sep 17 00:00:00 2001 From: Nicolas Braud-Santoni Date: Tue, 9 Oct 2018 20:49:35 -0400 Subject: [PATCH 2/7] Add an angle() method --- README.md | 5 ++++- ppb_vector/vector2.py | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 198233b8..3c129e15 100644 --- a/README.md +++ b/README.md @@ -87,10 +87,13 @@ Multiply a `Vector2` by another `Vector2` to get the dot product. >>> Vector2(45, 60).length 75.0 -### Cross-product +### Cross-product and angle >>> Vector(1, 0) ^ Vector(0, 1) 1 + + >>> Vector(1, 0).angle(Vector(0, 1)) + 90 ### Access Values diff --git a/ppb_vector/vector2.py b/ppb_vector/vector2.py index 596cb49c..2d40def7 100644 --- a/ppb_vector/vector2.py +++ b/ppb_vector/vector2.py @@ -1,4 +1,4 @@ -from math import cos, hypot, radians, sin +from math import asin, cos, degrees, hypot, radians, sin from numbers import Number from collections.abc import Sequence @@ -92,6 +92,9 @@ def __iter__(self): def __neg__(self): return self * -1 + def angle(self, other): + return degrees(asin(self.normalize() ^ other.normalize())) + def rotate(self, degrees): r = radians(degrees) r_cos = cos(r) From d40005b41aa5e089348bcec9d59b8cda614e08ae Mon Sep 17 00:00:00 2001 From: Nicolas Braud-Santoni Date: Tue, 9 Oct 2018 23:56:36 -0400 Subject: [PATCH 3/7] Add tests for angle() --- tests/test_vector2_rotate.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/test_vector2_rotate.py b/tests/test_vector2_rotate.py index 4fe3d4d8..30142840 100644 --- a/tests/test_vector2_rotate.py +++ b/tests/test_vector2_rotate.py @@ -11,9 +11,14 @@ (Vector2(math.pi, math.e), 67, Vector2(-1.27467, 3.95397)) ] +def isclose(x, y, epsilon = 6.5e-5): + d = (x - y) % 360 + return (d < epsilon) or (d > 360 - epsilon) + @pytest.mark.parametrize('input, degrees, expected', data) def test_multiple_rotations(input, degrees, expected): assert input.rotate(degrees) == expected + assert isclose(input.angle(expected), degrees) def test_for_exception(): with pytest.raises(TypeError): From c0d0adbd982eb7f8b668160fa0d1e907f0935094 Mon Sep 17 00:00:00 2001 From: Nicolas Braud-Santoni Date: Wed, 10 Oct 2018 00:54:52 -0400 Subject: [PATCH 4/7] Vector2.angle: Use dot-product rather than cross-product Avoids an issue where opposing vectors get an angle of 0 instead of 180. --- ppb_vector/vector2.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ppb_vector/vector2.py b/ppb_vector/vector2.py index 2d40def7..f9fe90ca 100644 --- a/ppb_vector/vector2.py +++ b/ppb_vector/vector2.py @@ -1,4 +1,4 @@ -from math import asin, cos, degrees, hypot, radians, sin +from math import acos, cos, degrees, hypot, radians, sin from numbers import Number from collections.abc import Sequence @@ -93,7 +93,7 @@ def __neg__(self): return self * -1 def angle(self, other): - return degrees(asin(self.normalize() ^ other.normalize())) + return degrees(acos(self.normalize() * other.normalize())) def rotate(self, degrees): r = radians(degrees) From 4e5efc0923d31d026c2d9295df597357c8b7318e Mon Sep 17 00:00:00 2001 From: Nicolas Braud-Santoni Date: Wed, 10 Oct 2018 22:02:48 -0400 Subject: [PATCH 5/7] fixup! Add a __xor__ dunder for cross-product --- ppb_vector/vector2.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ppb_vector/vector2.py b/ppb_vector/vector2.py index f9fe90ca..04ea9d75 100644 --- a/ppb_vector/vector2.py +++ b/ppb_vector/vector2.py @@ -47,7 +47,6 @@ def __rmul__(self, other): return Vector2(self.x * other, self.y * other) def __xor__(self, other): - assert(isinstance(other, Vector2)) return self.x * other.y - self.y * other.x def __getitem__(self, item): From f0c9b28667866648c9a5427cd459417d027410a8 Mon Sep 17 00:00:00 2001 From: Nicolas Braud-Santoni Date: Wed, 10 Oct 2018 22:12:10 -0400 Subject: [PATCH 6/7] fixup! Add a __xor__ dunder for cross-product --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 3c129e15..18176ab0 100644 --- a/README.md +++ b/README.md @@ -87,13 +87,13 @@ Multiply a `Vector2` by another `Vector2` to get the dot product. >>> Vector2(45, 60).length 75.0 -### Cross-product and angle +### Cross-product + +Take the cross-product between two (2D) vectors. +The result is expressed as a scalar, as it is known to lie on the z-axis. >>> Vector(1, 0) ^ Vector(0, 1) 1 - - >>> Vector(1, 0).angle(Vector(0, 1)) - 90 ### Access Values From 3b22df15637dcd1a3af442c06e89cfb9dbf04707 Mon Sep 17 00:00:00 2001 From: Nicolas Braud-Santoni Date: Wed, 10 Oct 2018 22:12:23 -0400 Subject: [PATCH 7/7] fixup! Add an angle() method --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 18176ab0..d1522d3f 100644 --- a/README.md +++ b/README.md @@ -130,6 +130,16 @@ Rotate a vector in relation to its own origin and return a new `Vector2`. Positive rotation is counter/anti-clockwise. +#### angle(vector) + +Compute the angle between two vectors, expressed as a scalar in degrees. + + >>> Vector(1, 0).angle(Vector(0, 1)) + 90 + +As with `rotate()`, angles are signed, and refer to a direct coordinate system +(i.e. positive rotations are counter-clockwise). + #### normalize() Return the normalized `Vector2` for the given `Vector2`.