mirror of https://github.com/tidwall/tile38.git
fixed raycast bug
This commit is contained in:
parent
2e41d49c4b
commit
204b3ef724
|
@ -0,0 +1,21 @@
|
|||
Copyright (c) 2016 Josh Baker
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
Contact GitHub API Training Shop Blog About
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
Poly
|
||||
====
|
||||
[![Build Status](https://travis-ci.org/tidwall/poly.svg?branch=master)](https://travis-ci.org/tidwall/poly)
|
||||
[![GoDoc](https://godoc.org/github.com/tidwall/poly?status.svg)](https://godoc.org/github.com/tidwall/poly)
|
||||
|
||||
Polygon detection methods for Go.
|
||||
|
||||
Contact
|
||||
-------
|
||||
Josh Baker [@tidwall](http://twitter.com/tidwall)
|
||||
|
||||
License
|
||||
-------
|
||||
Poly source code is available under the MIT [License](/LICENSE).
|
|
@ -42,10 +42,10 @@ func insideshpext(p Point, shape Polygon, exterior bool) bool {
|
|||
in := false
|
||||
for i := 0; i < len(shape); i++ {
|
||||
res := raycast(p, shape[i], shape[(i+1)%len(shape)])
|
||||
if res == on {
|
||||
if res.on {
|
||||
return exterior
|
||||
}
|
||||
if res == left {
|
||||
if res.in {
|
||||
in = !in
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,6 +34,14 @@ func TestRayInside(t *testing.T) {
|
|||
testRayInside(t, P(1, -0.1), strange, false)
|
||||
}
|
||||
|
||||
func TestRayInside2(t *testing.T) {
|
||||
normal := []Point{P(0, 0), P(4, 3), P(5, 2), P(0, 0)}
|
||||
testRayInside(t, P(1, 2), normal, false)
|
||||
testRayInside(t, P(1, 3), normal, false)
|
||||
testRayInside(t, P(4, 2), normal, true)
|
||||
testRayInside(t, P(2, 1), normal, true)
|
||||
}
|
||||
|
||||
var texterior = Polygon{
|
||||
P(0, 0),
|
||||
P(0, 6),
|
||||
|
@ -74,6 +82,10 @@ func TestRayExteriorHoles(t *testing.T) {
|
|||
{P(8, -3), false},
|
||||
{P(8, 1), false},
|
||||
{P(14, -1), false},
|
||||
|
||||
{P(8, -0.5), true},
|
||||
{P(8, -1.5), true},
|
||||
{P(8, -1), true},
|
||||
}
|
||||
// add the edges, all should be inside
|
||||
for i := 0; i < len(texterior); i++ {
|
||||
|
|
|
@ -9,6 +9,8 @@ func (p Point) Intersects(exterior Polygon, holes []Polygon) bool {
|
|||
func (shape Polygon) Intersects(exterior Polygon, holes []Polygon) bool {
|
||||
return shape.doesIntersects(false, exterior, holes)
|
||||
}
|
||||
|
||||
// LineStringIntersects detects if a polygon intersects a linestring
|
||||
func (shape Polygon) LineStringIntersects(exterior Polygon, holes []Polygon) bool {
|
||||
return shape.doesIntersects(true, exterior, holes)
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
// Package poly provides polygon detection methods.
|
||||
package poly
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Point is simple 3D point
|
||||
// Point is simple 2D point
|
||||
type Point struct {
|
||||
X, Y, Z float64
|
||||
}
|
||||
|
|
|
@ -1,121 +1,95 @@
|
|||
package poly
|
||||
|
||||
// This implementation of the raycast algorithm test if a point is
|
||||
// to the left of a line, or on the segment line. Otherwise it is
|
||||
// assumed that the point is outside of the segment line.
|
||||
import "math"
|
||||
|
||||
type rayres int
|
||||
|
||||
func (r rayres) String() string {
|
||||
switch r {
|
||||
default:
|
||||
return "unknown"
|
||||
case out:
|
||||
return "out"
|
||||
case left:
|
||||
return "left"
|
||||
case on:
|
||||
return "on"
|
||||
}
|
||||
type rayres struct {
|
||||
in, on bool
|
||||
}
|
||||
|
||||
const (
|
||||
out = rayres(0) // outside of the segment.
|
||||
left = rayres(1) // to the left of the segment
|
||||
on = rayres(2) // on segment or vertex, special condition
|
||||
)
|
||||
|
||||
func raycast(p, a, b Point) rayres {
|
||||
// make sure that the point is inside the segment bounds
|
||||
if a.Y < b.Y && (p.Y < a.Y || p.Y > b.Y) {
|
||||
return rayres{false, false}
|
||||
} else if a.Y > b.Y && (p.Y < b.Y || p.Y > a.Y) {
|
||||
return rayres{false, false}
|
||||
}
|
||||
|
||||
// test if point is in on the segment
|
||||
if a.Y == b.Y {
|
||||
// A and B share the same Y plane.
|
||||
if a.X == b.X {
|
||||
// AB is just a point.
|
||||
if p.X == a.X && p.Y == a.Y {
|
||||
return on
|
||||
if p == a {
|
||||
return rayres{false, true}
|
||||
} else {
|
||||
return rayres{false, false}
|
||||
}
|
||||
return out
|
||||
}
|
||||
// AB is a horizontal line.
|
||||
if p.Y != a.Y {
|
||||
// P is not on same Y plane as A and B.
|
||||
return out
|
||||
}
|
||||
// P is on same Y plane as A and B
|
||||
if a.X < b.X {
|
||||
if p.X >= a.X && p.X <= b.X {
|
||||
return on
|
||||
if p.Y == b.Y {
|
||||
// horizontal segment
|
||||
// check if the point in on the line
|
||||
if a.X < b.X {
|
||||
if p.X >= a.X && p.X <= b.X {
|
||||
return rayres{false, true}
|
||||
}
|
||||
} else {
|
||||
if p.X >= b.X && p.X <= a.X {
|
||||
return rayres{false, true}
|
||||
}
|
||||
}
|
||||
if p.X < a.X {
|
||||
return left
|
||||
}
|
||||
}
|
||||
if a.X == b.X && p.X == b.X {
|
||||
// vertical segment
|
||||
// check if the point in on the line
|
||||
if a.Y < b.Y {
|
||||
if p.Y >= a.Y && p.Y <= b.Y {
|
||||
return rayres{false, true}
|
||||
}
|
||||
} else {
|
||||
if p.X >= b.X && p.X <= a.X {
|
||||
return on
|
||||
}
|
||||
if p.X < b.X {
|
||||
return left
|
||||
if p.Y >= b.Y && p.Y <= a.Y {
|
||||
return rayres{false, true}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
if (p.X-a.X)/(b.X-a.X) == (p.Y-a.Y)/(b.Y-a.Y) {
|
||||
return rayres{false, true}
|
||||
}
|
||||
|
||||
if a.X == b.X {
|
||||
// AB is a vertical line.
|
||||
if a.Y > b.Y {
|
||||
// A is above B
|
||||
if p.Y > a.Y || p.Y < b.Y {
|
||||
return out
|
||||
}
|
||||
} else {
|
||||
// B is above A
|
||||
if p.Y > b.Y || p.Y < a.Y {
|
||||
return out
|
||||
}
|
||||
}
|
||||
if p.X == a.X {
|
||||
return on
|
||||
}
|
||||
if p.X < a.X {
|
||||
return left
|
||||
}
|
||||
return out
|
||||
// do the actual raycast here.
|
||||
for p.Y == a.Y || p.Y == b.Y {
|
||||
p.Y = math.Nextafter(p.Y, math.Inf(1))
|
||||
}
|
||||
|
||||
// AB is an angled line
|
||||
if a.Y > b.Y {
|
||||
// swap A and B so that A is below B.
|
||||
a.X, a.Y, b.X, b.Y = b.X, b.Y, a.X, a.Y
|
||||
}
|
||||
if p.Y < a.Y || p.Y > b.Y {
|
||||
return out
|
||||
}
|
||||
if a.X < b.X {
|
||||
if p.X < a.X {
|
||||
return left
|
||||
}
|
||||
if p.X > b.X {
|
||||
return out
|
||||
if a.Y < b.Y {
|
||||
if p.Y < a.Y || p.Y > b.Y {
|
||||
return rayres{false, false}
|
||||
}
|
||||
} else {
|
||||
if p.X < b.X {
|
||||
return left
|
||||
if p.Y < b.Y || p.Y > a.Y {
|
||||
return rayres{false, false}
|
||||
}
|
||||
}
|
||||
if a.X > b.X {
|
||||
if p.X > a.X {
|
||||
return out
|
||||
return rayres{false, false}
|
||||
}
|
||||
if p.X < b.X {
|
||||
return rayres{true, false}
|
||||
}
|
||||
} else {
|
||||
if p.X > b.X {
|
||||
return rayres{false, false}
|
||||
}
|
||||
if p.X < a.X {
|
||||
return rayres{true, false}
|
||||
}
|
||||
}
|
||||
if (p.X == a.X && p.Y == a.Y) || (p.X == b.X && p.Y == b.Y) {
|
||||
// P is on a vertex.
|
||||
return on
|
||||
if a.Y < b.Y {
|
||||
if (p.Y-a.Y)/(p.X-a.X) >= (b.Y-a.Y)/(b.X-a.X) {
|
||||
return rayres{true, false}
|
||||
}
|
||||
} else {
|
||||
if (p.Y-b.Y)/(p.X-b.X) >= (a.Y-b.Y)/(a.X-b.X) {
|
||||
return rayres{true, false}
|
||||
}
|
||||
}
|
||||
v1 := (p.Y - a.Y) / (p.X - a.X)
|
||||
v2 := (b.Y - a.Y) / (b.X - a.X)
|
||||
if v1-v2 == 0 {
|
||||
// P is on a segment
|
||||
return on
|
||||
}
|
||||
if v1 >= v2 {
|
||||
return left
|
||||
}
|
||||
return out
|
||||
return rayres{false, false}
|
||||
}
|
||||
|
|
|
@ -1,66 +0,0 @@
|
|||
package poly
|
||||
|
||||
import "testing"
|
||||
|
||||
func testRayRay(t *testing.T, p, a, b Point, expect rayres) {
|
||||
res := raycast(p, a, b)
|
||||
if res != expect {
|
||||
t.Fatalf("1) %v,%v,%v = %s, expect %s", p, a, b, res, expect)
|
||||
}
|
||||
res = raycast(p, a, b)
|
||||
if res != expect {
|
||||
t.Fatalf("1) %v,%v,%v = %s, expect %s", p, b, a, res, expect)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRayHorizontal(t *testing.T) {
|
||||
for x := float64(-1); x <= 4+1; x++ {
|
||||
expect := on
|
||||
if x < 0 {
|
||||
expect = left
|
||||
} else if x > 4 {
|
||||
expect = out
|
||||
}
|
||||
testRayRay(t, P(x, 0), P(0, 0), P(4, 0), expect)
|
||||
}
|
||||
for x := float64(-1); x <= 4+1; x++ {
|
||||
expect := out
|
||||
testRayRay(t, P(x, -1), P(0, 0), P(4, 0), expect)
|
||||
}
|
||||
for x := float64(-1); x <= 4+1; x++ {
|
||||
expect := out
|
||||
testRayRay(t, P(x, +1), P(0, 0), P(4, 0), expect)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRayVertical(t *testing.T) {
|
||||
for y := float64(-1); y <= 4+1; y++ {
|
||||
expect := on
|
||||
if y < 0 || y > 4 {
|
||||
expect = out
|
||||
}
|
||||
testRayRay(t, P(0, y), P(0, 0), P(0, 4), expect)
|
||||
}
|
||||
for y := float64(-1); y <= 4+1; y++ {
|
||||
expect := left
|
||||
if y < 0 || y > 4 {
|
||||
expect = out
|
||||
}
|
||||
testRayRay(t, P(-1, y), P(0, 0), P(0, 4), expect)
|
||||
}
|
||||
for y := float64(-1); y <= 4+1; y++ {
|
||||
expect := out
|
||||
testRayRay(t, P(+1, y), P(0, 0), P(0, 4), expect)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRayAngled(t *testing.T) {
|
||||
testRayRay(t, P(1, 3), P(0, 4), P(4, 0), on)
|
||||
testRayRay(t, P(0, 4), P(0, 4), P(4, 0), on)
|
||||
testRayRay(t, P(4, 0), P(0, 4), P(4, 0), on)
|
||||
testRayRay(t, P(1, 4), P(0, 4), P(4, 0), out)
|
||||
testRayRay(t, P(5, 0), P(0, 4), P(4, 0), out)
|
||||
testRayRay(t, P(-1, 4), P(0, 4), P(4, 0), left)
|
||||
testRayRay(t, P(3, 0), P(0, 4), P(4, 0), left)
|
||||
testRayRay(t, P(2, 2), P(0, 4), P(4, 0), on)
|
||||
}
|
Loading…
Reference in New Issue