Skip to content

Commit

Permalink
Vector 2 angle, inplace array methods
Browse files Browse the repository at this point in the history
  • Loading branch information
EliCDavis committed Dec 3, 2023
1 parent 5588ce6 commit e265a51
Show file tree
Hide file tree
Showing 8 changed files with 184 additions and 12 deletions.
27 changes: 26 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,31 @@ Collection of **generic, immutable** vector math functions I've written overtime

Has support for both Vector2 (x, y), Vector3 (x, y, z), and Vector4 (x, y, z, w) functions.

## API

| Function | Vector2 | Vector3 | Vector4 | Description |
|---------------|---------|---------|---------|--------------------------------------------------------|
| Abs |||| Returns a vector with each component's absolute value |
| Add |||| Component Wise Addition |
| Angle ||| | Returns the angle between two vectors |
| Ceil |||| Ceils each vectors component to the nearest integer |
| Clamp |||| Clamps each component between two values |
| Cross | || | Returns the cross product between two vectors |
| Dot |||| Returns the dot product between two vectors |
| Flip |||| Scales the vector by -1 |
| Floor |||| Floors each vectors component |
| Format |||| Build a string with vector data |
| Length |||| Returns the length of the vector |
| LengthSquared |||| Returns the squared length of the vector |
| MaxComponent |||| Returns the vectors largest component |
| Midpoint |||| Finds the mid point between two vectors |
| MinComponent |||| Returns the vectors smallest component |
| Normalize |||| Returns the normalized vector |
| Round |||| Rounds each vectors component to the nearest integer |
| Scale |||| Scales the vector by some constant |
| Sqrt |||| Returns a vector with each component's square root |
| Sub |||| Component Wise Subtraction |

## Example

Below is an example on how to implement the different sign distance field functions in a generic fashion to work for both int, int64, float32, and float64.
Expand All @@ -34,7 +59,7 @@ func Box[T vector.Number](pos vector3.Vector[T], bounds vector3.Vector[T]) Field
// https://www.youtube.com/watch?v=62-pRVZuS5c
return func(v vector3.Vector[T]) float64 {
q := v.Sub(pos).Abs().Sub(halfBounds)
inside := math.Min(math.Max(q.X(), math.Max(q.Y(), q.Z())), 0)
inside := math.Min(float64(q.MaxComponent()), 0)
return vector3.Max(q, vector3.Zero[T]()).Length() + inside
}
}
Expand Down
4 changes: 2 additions & 2 deletions vector3/util.go → util.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package vector3
package vector

import "math"

func clamp(f, min, max float64) float64 {
func Clamp(f, min, max float64) float64 {
return math.Max(math.Min(f, max), min)
}
8 changes: 8 additions & 0 deletions vector2/vector2.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,14 @@ func (v Vector[T]) YX() Vector[T] {
}
}

func (v Vector[T]) Angle(other Vector[T]) float64 {
denominator := math.Sqrt(v.LengthSquared() * other.LengthSquared())
if denominator < 1e-15 {
return 0.
}
return math.Acos(vector.Clamp(v.Dot(other)/denominator, -1., 1.))
}

// Midpoint returns the midpoint between this vector and the vector passed in.
func (v Vector[T]) Midpoint(o Vector[T]) Vector[T] {
return o.Add(v).Scale(0.5)
Expand Down
25 changes: 25 additions & 0 deletions vector2/vector2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -384,3 +384,28 @@ func TestContainsNaN(t *testing.T) {
})
}
}

func TestAngle(t *testing.T) {
tests := map[string]struct {
a vector2.Float64
b vector2.Float64
angle float64
}{
"up => down: Pi": {
a: vector2.Up[float64](),
b: vector2.Down[float64](),
angle: math.Pi,
},
"up => right: Pi": {
a: vector2.Up[float64](),
b: vector2.Right[float64](),
angle: math.Pi / 2,
},
}

for name, tc := range tests {
t.Run(name, func(t *testing.T) {
assert.InDelta(t, tc.angle, tc.a.Angle(tc.b), 0.000001)
})
}
}
46 changes: 41 additions & 5 deletions vector3/array.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,17 @@ func (v3a Array[T]) Add(other Vector[T]) (out Array[T]) {
return
}

func (v3a Array[T]) AddInplace(other Vector[T]) Array[T] {
for i, v := range v3a {
v3a[i] = Vector[T]{
v.x + other.x,
v.y + other.y,
v.z + other.z,
}
}
return v3a
}

func (v3a Array[T]) Sub(other Vector[T]) (out Array[T]) {
out = make(Array[T], len(v3a))

Expand All @@ -43,27 +54,52 @@ func (v3a Array[T]) Sub(other Vector[T]) (out Array[T]) {
return
}

func (v3a Array[T]) Distance() float64 {
func (v3a Array[T]) SubInplace(other Vector[T]) Array[T] {
for i, v := range v3a {
v3a[i] = Vector[T]{
v.x - other.x,
v.y - other.y,
v.z - other.z,
}
}
return v3a
}

func (v3a Array[T]) Distance() (total float64) {
if len(v3a) < 2 {
return 0
return
}
total := 0.
for i := 1; i < len(v3a); i++ {
total += v3a[i].Distance(v3a[i-1])
}
return total
return
}

func (v3a Array[T]) Scale(t float64) (out Array[T]) {
out = make(Array[T], len(v3a))

for i, v := range v3a {
out[i] = v.Scale(t)
out[i] = Vector[T]{
x: T(float64(v.x) * t),
y: T(float64(v.y) * t),
z: T(float64(v.z) * t),
}
}

return
}

func (v3a Array[T]) ScaleInplace(t float64) Array[T] {
for i, v := range v3a {
v3a[i] = Vector[T]{
x: T(float64(v.x) * t),
y: T(float64(v.y) * t),
z: T(float64(v.z) * t),
}
}
return v3a
}

func (v3a Array[T]) DivByConstant(t float64) (out Array[T]) {
out = make(Array[T], len(v3a))

Expand Down
53 changes: 53 additions & 0 deletions vector3/array_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,41 @@ func TestArray_Add(t *testing.T) {
}
}

func TestArray_AddInplace(t *testing.T) {
// ARRANGE ================================================================
arr := vector3.Float64Array([]vector3.Float64{
vector3.New(0., 0., 0.),
vector3.New(1., 0., 0.),
vector3.New(2., 0., 0.),
})
add := vector3.New(1., 2., 3.)

// ACT ====================================================================
arr.AddInplace(add)

// ASSERT =================================================================
assert.Equal(t, vector3.New(1., 2., 3.), arr[0])
assert.Equal(t, vector3.New(2., 2., 3.), arr[1])
assert.Equal(t, vector3.New(3., 2., 3.), arr[2])
}

func TestArray_ScaleInplace(t *testing.T) {
// ARRANGE ================================================================
arr := vector3.Float64Array([]vector3.Float64{
vector3.New(0., 0., 0.),
vector3.New(1., 0., 0.),
vector3.New(2., 0., 0.),
})

// ACT ====================================================================
arr.ScaleInplace(2)

// ASSERT =================================================================
assert.Equal(t, vector3.New(0., 0., 0.), arr[0])
assert.Equal(t, vector3.New(2., 0., 0.), arr[1])
assert.Equal(t, vector3.New(4., 0., 0.), arr[2])
}

func TestArray_Sub(t *testing.T) {
// ARRANGE ================================================================
arr := vector3.Float64Array([]vector3.Float64{
Expand All @@ -167,6 +202,24 @@ func TestArray_Sub(t *testing.T) {
}
}

func TestArray_SubInplace(t *testing.T) {
// ARRANGE ================================================================
arr := vector3.Float64Array([]vector3.Float64{
vector3.New(0., 0., 0.),
vector3.New(1., 0., 0.),
vector3.New(2., 0., 0.),
})
sub := vector3.New(1., 2., 3.)

// ACT ====================================================================
arr.SubInplace(sub)

// ASSERT =================================================================
assert.Equal(t, vector3.New(-1., -2., -3.), arr[0])
assert.Equal(t, vector3.New(0., -2., -3.), arr[1])
assert.Equal(t, vector3.New(1., -2., -3.), arr[2])
}

func TestArray_ContainsNaN_True(t *testing.T) {
// ARRANGE ================================================================
arr := vector3.Float64Array([]vector3.Float64{
Expand Down
8 changes: 4 additions & 4 deletions vector3/vector3.go
Original file line number Diff line number Diff line change
Expand Up @@ -440,9 +440,9 @@ func (v Vector[T]) Abs() Vector[T] {

func (v Vector[T]) Clamp(min, max T) Vector[T] {
return Vector[T]{
x: T(clamp(float64(v.x), float64(min), float64(max))),
y: T(clamp(float64(v.y), float64(min), float64(max))),
z: T(clamp(float64(v.z), float64(min), float64(max))),
x: T(vector.Clamp(float64(v.x), float64(min), float64(max))),
y: T(vector.Clamp(float64(v.y), float64(min), float64(max))),
z: T(vector.Clamp(float64(v.z), float64(min), float64(max))),
}
}

Expand Down Expand Up @@ -579,7 +579,7 @@ func (v Vector[T]) Angle(other Vector[T]) float64 {
if denominator < 1e-15 {
return 0.
}
return math.Acos(clamp(v.Dot(other)/denominator, -1., 1.))
return math.Acos(vector.Clamp(v.Dot(other)/denominator, -1., 1.))
}

func (v Vector[T]) NearZero() bool {
Expand Down
25 changes: 25 additions & 0 deletions vector3/vector3_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,31 @@ func TestToFloat64(t *testing.T) {
assert.Equal(t, float64(3), out.Z())
}

func TestAngle(t *testing.T) {
tests := map[string]struct {
a vector3.Float64
b vector3.Float64
angle float64
}{
"up => down: Pi": {
a: vector3.Up[float64](),
b: vector3.Down[float64](),
angle: math.Pi,
},
"up => right: Pi": {
a: vector3.Up[float64](),
b: vector3.Right[float64](),
angle: math.Pi / 2,
},
}

for name, tc := range tests {
t.Run(name, func(t *testing.T) {
assert.InDelta(t, tc.angle, tc.a.Angle(tc.b), 0.000001)
})
}
}

func TestMaxComponent(t *testing.T) {
assert.Equal(t, 4., vector3.New(-2., 3., 4.).MaxComponent())
}
Expand Down

0 comments on commit e265a51

Please sign in to comment.