diff --git a/constants.go b/constants.go index bb4d276..11fd790 100644 --- a/constants.go +++ b/constants.go @@ -5,3 +5,13 @@ import ( ) const radToDegFactor float64 = 180 / math.Pi + +const degToRadFactor float64 = math.Pi / 180 + +type axis int + +const ( + xAxis axis = iota + yAxis + zAxis +) diff --git a/rotateDeg.go b/rotateDeg.go new file mode 100644 index 0000000..da32c5e --- /dev/null +++ b/rotateDeg.go @@ -0,0 +1,128 @@ +package govec + +import "math" + +// V2F + +// RotateDeg rotates the vector counterclockwise by the specified number of degrees and returns a new vector. +func (v V2F[T]) RotateDeg(degrees float64) V2F[T] { + d := degrees * degToRadFactor + + return V2F[T]{ + X: T(math.Cos(d)*float64(v.X) - math.Sin(d)*float64(v.Y)), + Y: T(math.Sin(d)*float64(v.X) + math.Cos(d)*float64(v.Y)), + } +} + +// RotateDegInPlace modifies v by rotating the vector counterclockwise by the specified number of degrees. +func (v *V2F[T]) RotateDegInPlace(degrees float64) { + d := degrees * degToRadFactor + + x1 := T(math.Cos(d)*float64(v.X) - math.Sin(d)*float64(v.Y)) + y1 := T(math.Sin(d)*float64(v.X) + math.Cos(d)*float64(v.Y)) + + v.X = x1 + v.Y = y1 +} + +// V2I + +// RotateDeg rotates the vector counterclockwise by the specified number of degrees and returns a new V2F vector. +func (v V2I[T]) RotateDeg(degrees float64) V2F[float64] { + d := degrees * degToRadFactor + + t := V2F[float64]{ + X: math.Cos(d)*float64(v.X) - math.Sin(d)*float64(v.Y), + Y: math.Sin(d)*float64(v.X) + math.Cos(d)*float64(v.Y), + } + + return t +} + +// V3F + +// RotateDeg rotates the vector counterclockwise by the specified number of degrees, around axis and returns a new vector. +func (v V3F[T]) RotateDeg(degrees float64, axis axis) V3F[T] { + d := degrees * degToRadFactor + + switch axis { + case zAxis: + return V3F[T]{ + X: T(math.Cos(d)*float64(v.X) - math.Sin(d)*float64(v.Y)), + Y: T(math.Sin(d)*float64(v.X) + math.Cos(d)*float64(v.Y)), + Z: v.Z, + } + case yAxis: + return V3F[T]{ + X: T(math.Cos(d)*float64(v.X) - math.Sin(d)*float64(v.Y)), + Y: v.Y, + Z: T(-math.Sin(d)*float64(v.X) + math.Cos(d)*float64(v.Z)), + } + default: + return V3F[T]{ + X: v.X, + Y: T(math.Cos(d)*float64(v.Y) - math.Sin(d)*float64(v.Z)), + Z: T(-math.Sin(d)*float64(v.Z) + math.Cos(d)*float64(v.Z)), + } + } +} + +// RotateDegInPlace modifies v by rotating the vector counterclockwise by the specified number of degrees, around axis. +func (v *V3F[T]) RotateDegInPlace(degrees float64, axis axis) { + d := degrees * degToRadFactor + + switch axis { + case zAxis: + x := T(math.Cos(d)*float64(v.X) - math.Sin(d)*float64(v.Y)) + y := T(math.Sin(d)*float64(v.X) + math.Cos(d)*float64(v.Y)) + z := T(float64(v.Z)) + + v.X = x + v.Y = y + v.Z = z + case yAxis: + x := T(math.Cos(d)*float64(v.X) - math.Sin(d)*float64(v.Y)) + y := T(float64(v.Y)) + z := T(-math.Sin(d)*float64(v.X) + math.Cos(d)*float64(v.Z)) + + v.X = x + v.Y = y + v.Z = z + default: + x := T(float64(v.X)) + y := T(math.Cos(d)*float64(v.Y) - math.Sin(d)*float64(v.Z)) + z := T(-math.Sin(d)*float64(v.Z) + math.Cos(d)*float64(v.Z)) + + v.X = x + v.Y = y + v.Z = z + } +} + +// V3I + +// RotateDeg rotates the vector counterclockwise by the specified number of degrees, around axis and returns a new V3F vector. +func (v V3I[T]) RotateDeg(degrees float64, axis axis) V3F[float64] { + d := degrees * degToRadFactor + + switch axis { + case zAxis: + return V3F[float64]{ + X: math.Cos(d)*float64(v.X) - math.Sin(d)*float64(v.Y), + Y: math.Sin(d)*float64(v.X) + math.Cos(d)*float64(v.Y), + Z: float64(v.Z), + } + case yAxis: + return V3F[float64]{ + X: math.Cos(d)*float64(v.X) - math.Sin(d)*float64(v.Y), + Y: float64(v.Y), + Z: -math.Sin(d)*float64(v.X) + math.Cos(d)*float64(v.Z), + } + default: + return V3F[float64]{ + X: float64(v.X), + Y: math.Cos(d)*float64(v.Y) - math.Sin(d)*float64(v.Z), + Z: -math.Sin(d)*float64(v.Z) + math.Cos(d)*float64(v.Z), + } + } +} diff --git a/rotateDeg_test.go b/rotateDeg_test.go new file mode 100644 index 0000000..25c54ea --- /dev/null +++ b/rotateDeg_test.go @@ -0,0 +1,236 @@ +package govec + +import ( + "testing" +) + +func TestV2F_RotateDeg(t *testing.T) { + type expected struct { + X float64 + Y float64 + } + + tt := []struct { + name string + degrees float64 + expected expected + }{ + {name: "0", expected: expected{X: 0, Y: 1}}, + {name: "90", degrees: 90, expected: expected{X: -1, Y: 0}}, + {name: "180", degrees: 180, expected: expected{X: 0, Y: -1}}, + } + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + v1 := V2F[float64]{X: 0, Y: 1} + v2 := v1.RotateDeg(tc.degrees) + + if !almostEqual(v2.X, tc.expected.X, 1e-10) { + t.Errorf("V2F RotateDeg failed expected %v: got %v", tc.expected.X, v2.X) + } + + if !almostEqual(v2.Y, tc.expected.Y, 1e-10) { + t.Errorf("V2F RotateDeg failed expected %v: got %v", tc.expected.Y, v2.Y) + } + }) + } +} + +func TestV2I_RotateDeg(t *testing.T) { + type expected struct { + X float64 + Y float64 + } + + tt := []struct { + name string + degrees float64 + expected expected + }{ + {name: "0", expected: expected{X: 0, Y: 1}}, + {name: "90", degrees: 90, expected: expected{X: -1, Y: 0}}, + {name: "180", degrees: 180, expected: expected{X: 0, Y: -1}}, + } + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + v1 := V2I[int]{X: 0, Y: 1} + v2 := v1.RotateDeg(tc.degrees) + + if !almostEqual(v2.X, tc.expected.X, 1e-10) { + t.Errorf("V2I RotateDeg failed expected %v: got %v", tc.expected.X, v2.X) + } + + if !almostEqual(v2.Y, tc.expected.Y, 1e-10) { + t.Errorf("V2I RotateDeg failed expected %v: got %v", tc.expected.Y, v2.Y) + } + }) + } +} + +func TestV2F_RotateDegInPlace(t *testing.T) { + type expected struct { + X float64 + Y float64 + } + + tt := []struct { + name string + degrees float64 + expected expected + }{ + {name: "0", expected: expected{X: 0, Y: 1}}, + {name: "90", degrees: 90, expected: expected{X: -1, Y: 0}}, + {name: "180", degrees: 180, expected: expected{X: 0, Y: -1}}, + } + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + v1 := V2F[float64]{X: 0, Y: 1} + v1.RotateDegInPlace(tc.degrees) + + if !almostEqual(v1.X, tc.expected.X, 1e-10) { + t.Errorf("V2F RotateDegInPlace failed expected %v: got %v", tc.expected.X, v1.X) + } + + if !almostEqual(v1.Y, tc.expected.Y, 1e-10) { + t.Errorf("V2F RotateDegInPlace failed expected %v: got %v", tc.expected.Y, v1.Y) + } + }) + } +} + +func TestV3F_RotateDeg(t *testing.T) { + type expected struct { + X float64 + Y float64 + Z float64 + } + + tt := []struct { + name string + degrees float64 + axis axis + expected expected + }{ + {name: "z0", axis: zAxis, expected: expected{X: 0, Y: 1, Z: 0}}, + {name: "z90", degrees: 90, axis: zAxis, expected: expected{X: -1, Y: 0, Z: 0}}, + {name: "z180", degrees: 180, axis: zAxis, expected: expected{X: 0, Y: -1, Z: 0}}, + + {name: "y0", axis: yAxis, expected: expected{X: 0, Y: 1, Z: 0}}, + {name: "y90", degrees: 90, axis: yAxis, expected: expected{X: -1, Y: 1, Z: 0}}, + {name: "y180", degrees: 180, axis: yAxis, expected: expected{X: 0, Y: 1, Z: 0}}, + + {name: "x0", axis: xAxis, expected: expected{X: 0, Y: 1, Z: 0}}, + {name: "x90", degrees: 90, axis: xAxis, expected: expected{X: 0, Y: 0, Z: 0}}, + {name: "x180", degrees: 180, axis: xAxis, expected: expected{X: 0, Y: -1, Z: 0}}, + } + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + v1 := V3F[float64]{X: 0, Y: 1, Z: 0} + v2 := v1.RotateDeg(tc.degrees, tc.axis) + + if !almostEqual(v2.X, tc.expected.X, 1e-10) { + t.Errorf("V3F RotateDeg failed expected %v: got %v", tc.expected.X, v2.X) + } + + if !almostEqual(v2.Y, tc.expected.Y, 1e-10) { + t.Errorf("V3F RotateDeg failed expected %v: got %v", tc.expected.Y, v2.Y) + } + + if !almostEqual(v2.Z, tc.expected.Z, 1e-10) { + t.Errorf("V3F RotateDeg failed expected %v: got %v", tc.expected.Z, v2.Z) + } + }) + } +} + +func TestV3I_RotateDeg(t *testing.T) { + type expected struct { + X float64 + Y float64 + Z float64 + } + + tt := []struct { + name string + degrees float64 + axis axis + expected expected + }{ + {name: "z0", axis: zAxis, expected: expected{X: 0, Y: 1, Z: 0}}, + {name: "z90", degrees: 90, axis: zAxis, expected: expected{X: -1, Y: 0, Z: 0}}, + {name: "z180", degrees: 180, axis: zAxis, expected: expected{X: 0, Y: -1, Z: 0}}, + + {name: "y0", axis: yAxis, expected: expected{X: 0, Y: 1, Z: 0}}, + {name: "y90", degrees: 90, axis: yAxis, expected: expected{X: -1, Y: 1, Z: 0}}, + {name: "y180", degrees: 180, axis: yAxis, expected: expected{X: 0, Y: 1, Z: 0}}, + + {name: "x0", axis: xAxis, expected: expected{X: 0, Y: 1, Z: 0}}, + {name: "x90", degrees: 90, axis: xAxis, expected: expected{X: 0, Y: 0, Z: 0}}, + {name: "x180", degrees: 180, axis: xAxis, expected: expected{X: 0, Y: -1, Z: 0}}, + } + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + v1 := V3I[int]{X: 0, Y: 1, Z: 0} + v2 := v1.RotateDeg(tc.degrees, tc.axis) + + if !almostEqual(v2.X, tc.expected.X, 1e-10) { + t.Errorf("V3I RotateDeg failed expected %v: got %v", tc.expected.X, v2.X) + } + + if !almostEqual(v2.Y, tc.expected.Y, 1e-10) { + t.Errorf("V3I RotateDeg failed expected %v: got %v", tc.expected.Y, v2.Y) + } + + if !almostEqual(v2.Z, tc.expected.Z, 1e-10) { + t.Errorf("V3I RotateDeg failed expected %v: got %v", tc.expected.Z, v2.Z) + } + }) + } +} + +func TestV3F_RotateDegInPlace(t *testing.T) { + type expected struct { + X float64 + Y float64 + Z float64 + } + + tt := []struct { + name string + degrees float64 + axis axis + expected expected + }{ + {name: "z0", axis: zAxis, expected: expected{X: 0, Y: 1, Z: 0}}, + {name: "z90", degrees: 90, axis: zAxis, expected: expected{X: -1, Y: 0, Z: 0}}, + {name: "z180", degrees: 180, axis: zAxis, expected: expected{X: 0, Y: -1, Z: 0}}, + + {name: "y0", axis: yAxis, expected: expected{X: 0, Y: 1, Z: 0}}, + {name: "y90", degrees: 90, axis: yAxis, expected: expected{X: -1, Y: 1, Z: 0}}, + {name: "y180", degrees: 180, axis: yAxis, expected: expected{X: 0, Y: 1, Z: 0}}, + + {name: "x0", axis: xAxis, expected: expected{X: 0, Y: 1, Z: 0}}, + {name: "x90", degrees: 90, axis: xAxis, expected: expected{X: 0, Y: 0, Z: 0}}, + {name: "x180", degrees: 180, axis: xAxis, expected: expected{X: 0, Y: -1, Z: 0}}, + } + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + v1 := V3F[float64]{X: 0, Y: 1, Z: 0} + v1.RotateDegInPlace(tc.degrees, tc.axis) + + if !almostEqual(v1.X, tc.expected.X, 1e-10) { + t.Errorf("V3F RotateDegInPlace failed expected %v: got %v", tc.expected.X, v1.X) + } + + if !almostEqual(v1.Y, tc.expected.Y, 1e-10) { + t.Errorf("V3F RotateDegInPlace failed expected %v: got %v", tc.expected.Y, v1.Y) + } + + if !almostEqual(v1.Z, tc.expected.Z, 1e-10) { + t.Errorf("V3F RotateDegInPlace failed expected %v: got %v", tc.expected.Z, v1.Z) + } + }) + } +}