Skip to content

Commit 6042697

Browse files
committed
fix bug for semi-arc polygon generation not completing full arc
1 parent f722cd1 commit 6042697

File tree

6 files changed

+100
-8
lines changed

6 files changed

+100
-8
lines changed

md2/polygon.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ func arcCenterFrom2points(p1, p2 Vec, r float64) (Vec, float64, error) {
225225
} else if maxChordLen-chordLen <= -semiArcTol {
226226
return Vec{}, 0, errBadArc
227227
} else if ms1.EqualWithinAbs(sinTheta, 1, semiArcTol) {
228-
return Scale(0.5, Add(p1, p2)), math.Copysign(math.Pi/2, r), nil
228+
return Scale(0.5, Add(p1, p2)), math.Copysign(math.Pi, r), nil
229229
}
230230
chordThetaDiv2 := math.Asin(sinTheta)
231231
diffTo90 := chordThetaDiv2 - math.Pi/2

md2/polygon_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@
55
package md2
66

77
import (
8+
"strconv"
89
"testing"
910

1011
math "math"
12+
"github.com/soypat/geometry/internal"
13+
ms1 "github.com/soypat/geometry/md1"
1114
)
1215

1316
var testoffsets = []Vec{{-1, -2}, {-2, 1}, {2, -1}, {}, {1, 0}, {0, 1}, {1, 1}}
@@ -187,3 +190,44 @@ func TestArc_invalidArc(t *testing.T) {
187190
}
188191
}
189192
}
193+
194+
func TestArcUniformity(t *testing.T) {
195+
const tol = internal.Smallfloat64
196+
var cases = []struct {
197+
start, end Vec
198+
radius float64
199+
}{
200+
{start: Vec{0, 0}, end: Vec{0, 4}, radius: 2},
201+
}
202+
var poly PolygonBuilder
203+
var buf []Vec
204+
var err error
205+
for itest, test := range cases {
206+
for facets := 3; facets <= 3; facets++ {
207+
t.Run("case="+strconv.Itoa(itest)+" facets="+strconv.Itoa(facets), func(t *testing.T) {
208+
poly.Reset()
209+
poly.Add(test.start)
210+
poly.Add(test.end).Arc(test.radius, facets)
211+
buf, err = poly.AppendVecs(buf[:0])
212+
if err != nil {
213+
t.Fatal(err)
214+
}
215+
center, angle, err := arcCenterFrom2points(test.start, test.end, test.radius)
216+
if err != nil {
217+
t.Errorf("getting center: %s", err)
218+
}
219+
anglePerFacet := angle / float64(facets)
220+
dir0 := Sub(test.start, center)
221+
for i, v := range buf {
222+
dir := Sub(v, center)
223+
cosangle := Cos(dir, dir0)
224+
gotAngle := math.Acos(cosangle)
225+
wantAngle := float64(i) * anglePerFacet
226+
if !ms1.EqualWithinAbs(gotAngle, wantAngle, tol) {
227+
t.Errorf("%d/%d facet-dir want angle swept %f, got %f", i+1, len(buf), wantAngle, gotAngle)
228+
}
229+
}
230+
})
231+
}
232+
}
233+
}

md3/md3_test.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@ package md3
66

77
import (
88
"fmt"
9-
"math"
109
"math/rand"
1110
"testing"
1211

12+
math "math"
13+
1314
"github.com/soypat/geometry/internal"
1415
)
1516

@@ -36,14 +37,16 @@ func TestRotationConversions(t *testing.T) {
3637
Rotation(2*math.Pi, Vec{X: 1}),
3738
Rotation(0, Vec{X: 1}),
3839
}
40+
maxAngle := 5 * math.Pi / 180
41+
angleTol := math.Cos(maxAngle / 2)
3942
rng := newRNG(1)
4043
for _, rot := range rotations {
4144
angle, axis := rot.Rotation()
4245
gotrot := Rotation(angle, axis)
4346
if !EqualQuat(rot, gotrot, tol) {
4447
t.Errorf("want %v, got %v", rot, gotrot)
4548
}
46-
if !rot.EqualOrientation(gotrot, tol) {
49+
if !rot.EqualOrientation(gotrot, angleTol) {
4750
t.Errorf("not equal orientations %v, got %v", rot, gotrot)
4851
}
4952
m3 := rot.RotationMat3()

md3/rotation.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -124,13 +124,14 @@ func RotationBetweenVecs(start, dest Vec) Quat {
124124
}
125125

126126
// EqualOrientation returns whether the quaternions represents the same orientation with a given tolerence
127-
func (q1 Quat) EqualOrientation(q2 Quat, tolsq float64) bool {
128-
n1sq, n2sq := q1.Dot(q1), q2.Dot(q2)
127+
func (q1 Quat) EqualOrientation(q2 Quat, tol float64) bool {
128+
n1sq := q1.Dot(q1)
129+
n2sq := q2.Dot(q2)
129130
if n1sq == 0 || n2sq == 0 {
130131
return false // Degenerate quaternion.
131132
}
132-
dot := math.Abs(q1.Dot(q2) / (n1sq * n2sq))
133-
return dot > 1-tolsq
133+
d := q1.Dot(q2)
134+
return d*d >= tol*tol*(n1sq*n2sq)
134135
}
135136

136137
/*

ms2/polygon.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ func arcCenterFrom2points(p1, p2 Vec, r float32) (Vec, float32, error) {
221221
} else if maxChordLen-chordLen <= -semiArcTol {
222222
return Vec{}, 0, errBadArc
223223
} else if ms1.EqualWithinAbs(sinTheta, 1, semiArcTol) {
224-
return Scale(0.5, Add(p1, p2)), math.Copysign(math.Pi/2, r), nil
224+
return Scale(0.5, Add(p1, p2)), math.Copysign(math.Pi, r), nil
225225
}
226226
chordThetaDiv2 := math.Asin(sinTheta)
227227
diffTo90 := chordThetaDiv2 - math.Pi/2

ms2/polygon_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
package ms2
22

33
import (
4+
"strconv"
45
"testing"
56

67
math "github.com/chewxy/math32"
8+
"github.com/soypat/geometry/internal"
9+
"github.com/soypat/geometry/ms1"
710
)
811

912
var testoffsets = []Vec{{-1, -2}, {-2, 1}, {2, -1}, {}, {1, 0}, {0, 1}, {1, 1}}
@@ -183,3 +186,44 @@ func TestArc_invalidArc(t *testing.T) {
183186
}
184187
}
185188
}
189+
190+
func TestArcUniformity(t *testing.T) {
191+
const tol = internal.Smallfloat32
192+
var cases = []struct {
193+
start, end Vec
194+
radius float32
195+
}{
196+
{start: Vec{0, 0}, end: Vec{0, 4}, radius: 2},
197+
}
198+
var poly PolygonBuilder
199+
var buf []Vec
200+
var err error
201+
for itest, test := range cases {
202+
for _, facets := range []int{2, 3, 5, 9} {
203+
t.Run("case="+strconv.Itoa(itest)+" facets="+strconv.Itoa(facets), func(t *testing.T) {
204+
poly.Reset()
205+
poly.Add(test.start)
206+
poly.Add(test.end).Arc(test.radius, facets)
207+
buf, err = poly.AppendVecs(buf[:0])
208+
if err != nil {
209+
t.Fatal(err)
210+
}
211+
center, angle, err := arcCenterFrom2points(test.start, test.end, test.radius)
212+
if err != nil {
213+
t.Errorf("getting center: %s", err)
214+
}
215+
anglePerFacet := angle / float32(facets)
216+
dir0 := Sub(test.start, center)
217+
for i, v := range buf {
218+
dir := Sub(v, center)
219+
cosangle := Cos(dir, dir0)
220+
gotAngle := math.Acos(cosangle)
221+
wantAngle := float32(i) * anglePerFacet
222+
if !ms1.EqualWithinAbs(gotAngle, wantAngle, tol) {
223+
t.Errorf("%d/%d facet-dir want angle swept %f, got %f", i+1, len(buf), wantAngle, gotAngle)
224+
}
225+
}
226+
})
227+
}
228+
}
229+
}

0 commit comments

Comments
 (0)