github.com/agnivade/pgm@v0.0.0-20210528073050-e2df0d9cb72d/caliper.go (about) 1 package pgm 2 3 import ( 4 "math" 5 ) 6 7 // Ported from https://github.com/bkiers/RotatingCalipers. 8 9 const sigma = 0.00000000001 10 11 type caliper struct { 12 hull []point 13 pointIndex int 14 currentAngle float64 15 } 16 17 func newCaliper(hull []point, pointIndex int, currentAngle float64) caliper { 18 return caliper{ 19 hull: hull, 20 pointIndex: pointIndex, 21 currentAngle: currentAngle, 22 } 23 } 24 25 func (c *caliper) getAngleNextPoint() float64 { 26 p1 := c.hull[c.pointIndex] 27 p2 := c.hull[(c.pointIndex+1)%len(c.hull)] 28 29 dx := p2.x - p1.x 30 dy := p2.y - p1.y 31 32 angle := math.Atan2(dy, dx) * 180 / math.Pi 33 34 if angle < 0 { 35 return 360 + angle 36 } 37 return angle 38 } 39 40 func (c *caliper) getConstant() float64 { 41 p := c.hull[c.pointIndex] 42 return p.y - (c.getSlope() * p.x) 43 } 44 45 func (c *caliper) getDeltaAngleNextPoint() float64 { 46 angle := c.getAngleNextPoint() 47 48 if angle < 0 { 49 angle = 360 + angle - c.currentAngle 50 } else { 51 angle = angle - c.currentAngle 52 } 53 54 if angle < 0 { 55 return 360 56 } 57 return angle 58 } 59 60 func (c *caliper) getIntersection(d caliper) point { 61 var p point 62 switch { 63 case c.isVertical(): 64 p.x = c.hull[c.pointIndex].x 65 p.y = d.getConstant() 66 case c.isHorizontal(): 67 p.x = d.hull[d.pointIndex].x 68 p.y = c.getConstant() 69 default: 70 p.x = (d.getConstant() - c.getConstant()) / (c.getSlope() - d.getSlope()) 71 p.y = (c.getSlope() * p.x) + c.getConstant() 72 } 73 74 return p 75 } 76 77 func (c *caliper) getSlope() float64 { 78 return math.Tan(c.currentAngle * math.Pi / 180) 79 } 80 81 func (c *caliper) isHorizontal() bool { 82 return (math.Abs(c.currentAngle) < sigma) || (math.Abs(c.currentAngle-180) < sigma) 83 } 84 85 func (c *caliper) isVertical() bool { 86 return (math.Abs(c.currentAngle-90) < sigma) || (math.Abs(c.currentAngle-270) < sigma) 87 } 88 89 func (c *caliper) rotateBy(angle float64) { 90 if c.getDeltaAngleNextPoint() == angle { 91 c.pointIndex = (c.pointIndex + 1) % len(c.hull) 92 } 93 c.currentAngle = math.Mod(c.currentAngle+angle, 360) 94 } 95 96 func getSmallestTheta(i, j, k, l caliper) float64 { 97 thetaI := i.getDeltaAngleNextPoint() 98 thetaJ := j.getDeltaAngleNextPoint() 99 thetaK := k.getDeltaAngleNextPoint() 100 thetaL := l.getDeltaAngleNextPoint() 101 102 if thetaI <= thetaJ && thetaI <= thetaK && thetaI <= thetaL { 103 return thetaI 104 } else if thetaJ <= thetaK && thetaJ <= thetaL { 105 return thetaJ 106 } else if thetaK <= thetaL { 107 return thetaK 108 } else { 109 return thetaL 110 } 111 }