mirror of
https://github.com/mum4k/termdash.git
synced 2025-04-25 13:48:50 +08:00
Adding functions that calculate angles.
This commit is contained in:
parent
20c31cb800
commit
6276788be2
224
trig/trig.go
Normal file
224
trig/trig.go
Normal file
@ -0,0 +1,224 @@
|
||||
// Copyright 2019 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package trig implements various trigonometrical calculations.
|
||||
package trig
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"math"
|
||||
"sort"
|
||||
|
||||
"github.com/mum4k/termdash/numbers"
|
||||
)
|
||||
|
||||
// CirclePointAtAngle given an angle in degrees and a circle midpoint and
|
||||
// radius, calculates coordinates of a point on the circle at that angle.
|
||||
// Angles are zero at the X axis and grow counter-clockwise.
|
||||
func CirclePointAtAngle(degrees int, mid image.Point, radius int) image.Point {
|
||||
angle := numbers.DegreesToRadians(degrees)
|
||||
r := float64(radius)
|
||||
x := mid.X + int(numbers.Round(r*math.Cos(angle)))
|
||||
// Y coordinates grow down on the canvas.
|
||||
y := mid.Y - int(numbers.Round(r*math.Sin(angle)))
|
||||
return image.Point{x, y}
|
||||
}
|
||||
|
||||
// CircleAngleAtPoint given a point on a circle and its midpoint,
|
||||
// calculates the angle in degrees.
|
||||
// Angles are zero at the X axis and grow counter-clockwise.
|
||||
func CircleAngleAtPoint(point, mid image.Point) int {
|
||||
adj := float64(point.X - mid.X)
|
||||
opp := float64(mid.Y - point.Y)
|
||||
if opp != 0 {
|
||||
angle := math.Atan2(opp, adj)
|
||||
return numbers.RadiansToDegrees(angle)
|
||||
} else if adj >= 0 {
|
||||
return 0
|
||||
} else {
|
||||
return 180
|
||||
}
|
||||
}
|
||||
|
||||
// PointIsIn asserts whether the provided point is inside of a shape outlined
|
||||
// with the provided points.
|
||||
// Does not verify that the shape is closed or complete, it merely counts the
|
||||
// number of intersections with the shape on one row.
|
||||
func PointIsIn(p image.Point, points []image.Point) bool {
|
||||
maxX := p.X
|
||||
set := map[image.Point]struct{}{}
|
||||
for _, sp := range points {
|
||||
set[sp] = struct{}{}
|
||||
if sp.X > maxX {
|
||||
maxX = sp.X
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := set[p]; ok {
|
||||
// Not inside if it is on the shape.
|
||||
return false
|
||||
}
|
||||
|
||||
byY := map[int][]int{} // maps y->x
|
||||
for p := range set {
|
||||
byY[p.Y] = append(byY[p.Y], p.X)
|
||||
}
|
||||
for y := range byY {
|
||||
sort.Ints(byY[y])
|
||||
}
|
||||
|
||||
set = map[image.Point]struct{}{}
|
||||
for y, xses := range byY {
|
||||
set[image.Point{xses[0], y}] = struct{}{}
|
||||
if len(xses) == 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
for i := 1; i < len(xses); i++ {
|
||||
if xses[i] != xses[i-1]+1 {
|
||||
set[image.Point{xses[i], y}] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
crosses := 0
|
||||
for x := p.X; x <= maxX; x++ {
|
||||
if _, ok := set[image.Point{x, p.Y}]; ok {
|
||||
crosses++
|
||||
}
|
||||
}
|
||||
return crosses%2 != 0
|
||||
}
|
||||
|
||||
const (
|
||||
// MinAngle is the smallest valid angle in degrees.
|
||||
MinAngle = 0
|
||||
// MaxAngle is the largest valid angle in degrees.
|
||||
MaxAngle = 360
|
||||
)
|
||||
|
||||
// angleRange represents a range of angles in degrees.
|
||||
// The range includes all angles such that start <= angle <= end.
|
||||
type angleRange struct {
|
||||
// start is the start if the range.
|
||||
// This is always less or equal to the end.
|
||||
start int
|
||||
|
||||
// end is the end of the range.
|
||||
end int
|
||||
}
|
||||
|
||||
// contains asserts whether the specified angle is in the range.
|
||||
func (ar *angleRange) contains(angle int) bool {
|
||||
return angle >= ar.start && angle <= ar.end
|
||||
}
|
||||
|
||||
// normalizeRange normalizes the start and end angles in degrees into ranges of
|
||||
// angles. Useful for cases where the 0/360 point falls within the range.
|
||||
// E.g:
|
||||
// 0,25 => angleRange{0, 26}
|
||||
// 0,360 => angleRange{0, 361}
|
||||
// 359,20 => angleRange{359, 361}, angleRange{0, 21}
|
||||
func normalizeRange(start, end int) ([]*angleRange, error) {
|
||||
if start < MinAngle || start > MaxAngle {
|
||||
return nil, fmt.Errorf("invalid start angle:%d, must be in range %d <= start <= %d", start, MinAngle, MaxAngle)
|
||||
}
|
||||
if end < MinAngle || end > MaxAngle {
|
||||
return nil, fmt.Errorf("invalid end angle:%d, must be in range %d <= end <= %d", end, MinAngle, MaxAngle)
|
||||
}
|
||||
|
||||
if start == MaxAngle && end == 0 {
|
||||
start, end = end, start
|
||||
}
|
||||
|
||||
if start <= end {
|
||||
return []*angleRange{
|
||||
{start, end},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// The range is crossing the 0/360 degree point.
|
||||
// Break it into multiple ranges.
|
||||
return []*angleRange{
|
||||
{start, MaxAngle},
|
||||
{0, end},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// RangeSize returns the size of the degree range.
|
||||
// E.g:
|
||||
// 0,25 => 25
|
||||
// 359,1 => 2
|
||||
func RangeSize(start, end int) (int, error) {
|
||||
ranges, err := normalizeRange(start, end)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len(ranges) == 1 {
|
||||
return end - start, nil
|
||||
}
|
||||
return MaxAngle - start + end, nil
|
||||
}
|
||||
|
||||
// RangeMid returns an angle that lies in the middle between start and end.
|
||||
// E.g:
|
||||
// 0,10 => 5
|
||||
// 350,10 => 0
|
||||
func RangeMid(start, end int) (int, error) {
|
||||
ranges, err := normalizeRange(start, end)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len(ranges) == 1 {
|
||||
return start + ((end - start) / 2), nil
|
||||
}
|
||||
|
||||
length := MaxAngle - start + end
|
||||
want := length / 2
|
||||
res := start + want
|
||||
return res % MaxAngle, nil
|
||||
}
|
||||
|
||||
// FilterByAngle filters the provided points, returning only those that fall
|
||||
// within the starting and the ending angle on a circle with the provided mid
|
||||
// point.
|
||||
func FilterByAngle(points []image.Point, mid image.Point, start, end int) ([]image.Point, error) {
|
||||
var res []image.Point
|
||||
ranges, err := normalizeRange(start, end)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if mid.X < 0 || mid.Y < 0 {
|
||||
return nil, fmt.Errorf("the mid point %v cannot have negative coordinates", mid)
|
||||
}
|
||||
|
||||
for _, p := range points {
|
||||
angle := CircleAngleAtPoint(p, mid)
|
||||
|
||||
// Edge case, this might mean 0 or 360.
|
||||
// Decide based on where we are starting.
|
||||
if angle == 0 && start > 0 {
|
||||
angle = MaxAngle
|
||||
}
|
||||
|
||||
for _, r := range ranges {
|
||||
if r.contains(angle) {
|
||||
res = append(res, p)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
471
trig/trig_test.go
Normal file
471
trig/trig_test.go
Normal file
@ -0,0 +1,471 @@
|
||||
// Copyright 2019 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package trig
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"testing"
|
||||
|
||||
"github.com/kylelemons/godebug/pretty"
|
||||
)
|
||||
|
||||
func TestCirclePointAtAngleAndAngle(t *testing.T) {
|
||||
tests := []struct {
|
||||
degrees int
|
||||
mid image.Point
|
||||
radius int
|
||||
want image.Point
|
||||
}{
|
||||
{0, image.Point{0, 0}, 1, image.Point{1, 0}},
|
||||
{90, image.Point{0, 0}, 1, image.Point{0, -1}},
|
||||
{180, image.Point{0, 0}, 1, image.Point{-1, 0}},
|
||||
{270, image.Point{0, 0}, 1, image.Point{0, 1}},
|
||||
|
||||
// Non-zero mid point.
|
||||
{0, image.Point{5, 5}, 1, image.Point{6, 5}},
|
||||
{90, image.Point{5, 5}, 1, image.Point{5, 4}},
|
||||
{180, image.Point{5, 5}, 1, image.Point{4, 5}},
|
||||
{270, image.Point{5, 5}, 1, image.Point{5, 6}},
|
||||
{0, image.Point{1, 1}, 1, image.Point{2, 1}},
|
||||
{90, image.Point{1, 1}, 1, image.Point{1, 0}},
|
||||
{180, image.Point{1, 1}, 1, image.Point{0, 1}},
|
||||
{270, image.Point{1, 1}, 1, image.Point{1, 2}},
|
||||
|
||||
// Larger radius.
|
||||
{0, image.Point{0, 0}, 11, image.Point{11, 0}},
|
||||
{90, image.Point{0, 0}, 11, image.Point{0, -11}},
|
||||
{180, image.Point{0, 0}, 11, image.Point{-11, 0}},
|
||||
{270, image.Point{0, 0}, 11, image.Point{0, 11}},
|
||||
|
||||
// Other angles.
|
||||
{27, image.Point{0, 0}, 11, image.Point{10, -5}},
|
||||
{68, image.Point{0, 0}, 11, image.Point{4, -10}},
|
||||
{333, image.Point{2, 2}, 2, image.Point{4, 3}},
|
||||
{153, image.Point{2, 2}, 2, image.Point{0, 1}},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(fmt.Sprintf("CirclePointAtAngle %v %v %v", tc.degrees, tc.mid, tc.radius), func(t *testing.T) {
|
||||
got := CirclePointAtAngle(tc.degrees, tc.mid, tc.radius)
|
||||
if got != tc.want {
|
||||
t.Errorf("CirclePointAtAngle(%v, %v, %v) => %v, want %v", tc.degrees, tc.mid, tc.radius, got, tc.want)
|
||||
}
|
||||
})
|
||||
t.Run(fmt.Sprintf("CircleAngleAtPoint %v %v", tc.want, tc.mid), func(t *testing.T) {
|
||||
got := CircleAngleAtPoint(tc.want, tc.mid)
|
||||
want := tc.degrees
|
||||
if got != want {
|
||||
t.Errorf("CircleAngleAtPoint(%v, %v) => %v, want %v", tc.want, tc.mid, got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPointIsIn(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
point image.Point
|
||||
shape []image.Point
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
desc: "no points provided",
|
||||
point: image.Point{0, 0},
|
||||
shape: nil,
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
desc: "point is on the shape",
|
||||
point: image.Point{0, 0},
|
||||
shape: []image.Point{
|
||||
{0, 0},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
desc: "point is left of the shape",
|
||||
point: image.Point{0, 1},
|
||||
shape: []image.Point{
|
||||
{1, 0}, {2, 0}, {3, 0},
|
||||
{1, 1}, {3, 1},
|
||||
{1, 2}, {2, 2}, {3, 2},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
desc: "point is in a shape whose border gets crossed once",
|
||||
point: image.Point{2, 1},
|
||||
shape: []image.Point{
|
||||
{1, 0}, {2, 0}, {3, 0},
|
||||
{1, 1}, {3, 1},
|
||||
{1, 2}, {2, 2}, {3, 2},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
desc: "point is in an U shape whose border gets crossed multiple times",
|
||||
point: image.Point{1, 1},
|
||||
shape: []image.Point{
|
||||
{0, 0}, {1, 0}, {2, 0}, {3, 0}, {4, 0}, {5, 0}, {6, 0},
|
||||
{0, 1}, {2, 1}, {4, 1}, {6, 1},
|
||||
{0, 2}, {1, 2}, {2, 2}, {3, 2}, {4, 2}, {5, 2},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
desc: "point is in a triangle",
|
||||
point: image.Point{3, 1},
|
||||
shape: []image.Point{
|
||||
{3, 0},
|
||||
{2, 1}, {4, 1},
|
||||
{1, 2}, {2, 2}, {3, 2}, {4, 2}, {5, 2},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
desc: "ignores multiple shape points on the same row",
|
||||
point: image.Point{2, 1},
|
||||
shape: []image.Point{
|
||||
{1, 0}, {2, 0}, {3, 0},
|
||||
{1, 1}, {3, 1}, {4, 1}, {5, 1},
|
||||
{1, 2}, {2, 2}, {3, 2},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
got := PointIsIn(tc.point, tc.shape)
|
||||
if got != tc.want {
|
||||
t.Errorf("PointIsIn => %v, want %v", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRangeSize(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
start int
|
||||
end int
|
||||
want int
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
desc: "invalid start, too small",
|
||||
start: MinAngle - 1,
|
||||
end: MaxAngle,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
desc: "invalid start, too large",
|
||||
start: MaxAngle + 1,
|
||||
end: MaxAngle,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
desc: "invalid end, too small",
|
||||
start: MinAngle,
|
||||
end: MinAngle - 1,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
desc: "invalid end, too large",
|
||||
start: MinAngle,
|
||||
end: MaxAngle + 1,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
desc: "zero range starting at zero",
|
||||
start: 0,
|
||||
end: 0,
|
||||
want: 0,
|
||||
},
|
||||
{
|
||||
desc: "zero range starting at max angle",
|
||||
start: 360,
|
||||
end: 360,
|
||||
want: 0,
|
||||
},
|
||||
{
|
||||
desc: "range with size of one",
|
||||
start: 1,
|
||||
end: 2,
|
||||
want: 1,
|
||||
},
|
||||
{
|
||||
desc: "reverse range with size of 359",
|
||||
start: 2,
|
||||
end: 1,
|
||||
want: 359,
|
||||
},
|
||||
{
|
||||
desc: "range that crosses 360",
|
||||
start: 350,
|
||||
end: 10,
|
||||
want: 20,
|
||||
},
|
||||
{
|
||||
desc: "reverse range that doesn't cross 360",
|
||||
start: 10,
|
||||
end: 350,
|
||||
want: 340,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
got, err := RangeSize(tc.start, tc.end)
|
||||
if (err != nil) != tc.wantErr {
|
||||
t.Errorf("RangeSize => unexpected error: %v, wantErr: %v", err, tc.wantErr)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if got != tc.want {
|
||||
t.Errorf("RangeSize => %v, want %v", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRangeMid(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
start int
|
||||
end int
|
||||
want int
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
desc: "invalid start, too small",
|
||||
start: MinAngle - 1,
|
||||
end: MaxAngle,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
desc: "invalid start, too large",
|
||||
start: MaxAngle + 1,
|
||||
end: MaxAngle,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
desc: "invalid end, too small",
|
||||
start: MinAngle,
|
||||
end: MinAngle - 1,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
desc: "invalid end, too large",
|
||||
start: MinAngle,
|
||||
end: MaxAngle + 1,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
desc: "zero range",
|
||||
start: 0,
|
||||
end: 0,
|
||||
want: 0,
|
||||
},
|
||||
{
|
||||
desc: "one degree range",
|
||||
start: 0,
|
||||
end: 1,
|
||||
want: 0,
|
||||
},
|
||||
{
|
||||
desc: "three degree range",
|
||||
start: 0,
|
||||
end: 3,
|
||||
want: 1,
|
||||
},
|
||||
{
|
||||
desc: "range that crosses 360, mid isn't 360",
|
||||
start: 351,
|
||||
end: 11,
|
||||
want: 1,
|
||||
},
|
||||
{
|
||||
desc: "range that crosses 360, mid is 360",
|
||||
start: 350,
|
||||
end: 10,
|
||||
want: 0,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
got, err := RangeMid(tc.start, tc.end)
|
||||
if (err != nil) != tc.wantErr {
|
||||
t.Errorf("RangeMid => unexpected error: %v, wantErr: %v", err, tc.wantErr)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if got != tc.want {
|
||||
t.Errorf("RangeMid => %v, want %v", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterByAngle(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
points []image.Point
|
||||
mid image.Point
|
||||
start int
|
||||
end int
|
||||
want []image.Point
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
desc: "invalid mid, negative X coordinate",
|
||||
mid: image.Point{-1, 0},
|
||||
start: MinAngle,
|
||||
end: MaxAngle,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
desc: "invalid mid, negative Y coordinate",
|
||||
mid: image.Point{0, -1},
|
||||
start: MinAngle,
|
||||
end: MaxAngle,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
desc: "invalid start, too small",
|
||||
start: MinAngle - 1,
|
||||
end: MaxAngle,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
desc: "invalid start, too large",
|
||||
start: MaxAngle + 1,
|
||||
end: MaxAngle,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
desc: "invalid end, too small",
|
||||
start: MinAngle,
|
||||
end: MinAngle - 1,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
desc: "invalid end, too large",
|
||||
start: MinAngle,
|
||||
end: MaxAngle + 1,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
desc: "full first quadrant",
|
||||
points: []image.Point{
|
||||
{0, 0}, {1, 0}, {2, 0},
|
||||
{0, 1}, {1, 1}, {2, 1},
|
||||
{0, 2}, {1, 2}, {2, 2},
|
||||
},
|
||||
mid: image.Point{1, 1},
|
||||
start: 0,
|
||||
end: 90,
|
||||
want: []image.Point{
|
||||
{1, 0}, {2, 0},
|
||||
{1, 1}, {2, 1},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "partial second quadrant",
|
||||
points: []image.Point{
|
||||
{0, 0}, {1, 0}, {2, 0},
|
||||
{0, 1}, {1, 1}, {2, 1},
|
||||
{0, 2}, {1, 2}, {2, 2},
|
||||
},
|
||||
mid: image.Point{1, 1},
|
||||
start: 130,
|
||||
end: 140,
|
||||
want: []image.Point{
|
||||
{0, 0},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "range crosses 360",
|
||||
points: []image.Point{
|
||||
{0, 0}, {1, 0}, {2, 0},
|
||||
{0, 1}, {1, 1}, {2, 1},
|
||||
{0, 2}, {1, 2}, {2, 2},
|
||||
},
|
||||
mid: image.Point{1, 1},
|
||||
start: 310,
|
||||
end: 50,
|
||||
want: []image.Point{
|
||||
{2, 0},
|
||||
{1, 1}, {2, 1},
|
||||
{2, 2},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "full circle",
|
||||
points: []image.Point{
|
||||
{0, 0}, {1, 0}, {2, 0},
|
||||
{0, 1}, {1, 1}, {2, 1},
|
||||
{0, 2}, {1, 2}, {2, 2},
|
||||
},
|
||||
mid: image.Point{1, 1},
|
||||
start: 0,
|
||||
end: 360,
|
||||
want: []image.Point{
|
||||
{0, 0}, {1, 0}, {2, 0},
|
||||
{0, 1}, {1, 1}, {2, 1},
|
||||
{0, 2}, {1, 2}, {2, 2},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "full circle in reverse",
|
||||
points: []image.Point{
|
||||
{0, 0}, {1, 0}, {2, 0},
|
||||
{0, 1}, {1, 1}, {2, 1},
|
||||
{0, 2}, {1, 2}, {2, 2},
|
||||
},
|
||||
mid: image.Point{1, 1},
|
||||
start: 360,
|
||||
end: 0,
|
||||
want: []image.Point{
|
||||
{0, 0}, {1, 0}, {2, 0},
|
||||
{0, 1}, {1, 1}, {2, 1},
|
||||
{0, 2}, {1, 2}, {2, 2},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
got, err := FilterByAngle(tc.points, tc.mid, tc.start, tc.end)
|
||||
if (err != nil) != tc.wantErr {
|
||||
t.Errorf("FilterByAngle => unexpected error: %v, wantErr: %v", err, tc.wantErr)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if diff := pretty.Compare(tc.want, got); diff != "" {
|
||||
t.Errorf("FilterByAngle => unexpected diff (-want, +got):\n%s", diff)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user