From fbf40a44553fe9b22e658bc53ffa68bca15b9f84 Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Sat, 10 Aug 2024 17:04:13 +0900 Subject: [PATCH] vector: bug fix: isPointCloseToSegment didn't work when two p0 and p1 are the same Closes #3061 --- vector/export_test.go | 32 ++++++++++++++++ vector/path.go | 7 +++- vector/path_test.go | 88 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 vector/export_test.go create mode 100644 vector/path_test.go diff --git a/vector/export_test.go b/vector/export_test.go new file mode 100644 index 000000000..69c419826 --- /dev/null +++ b/vector/export_test.go @@ -0,0 +1,32 @@ +// Copyright 2024 The Ebitengine Authors +// +// 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 vector + +type Point struct { + X, Y float32 +} + +func IsPointCloseToSegment(p, p0, p1 Point, allow float32) bool { + return isPointCloseToSegment(point{ + x: p.X, + y: p.Y, + }, point{ + x: p0.X, + y: p0.Y, + }, point{ + x: p1.X, + y: p1.Y, + }, allow) +} diff --git a/vector/path.go b/vector/path.go index 3146267ac..20dab7d68 100644 --- a/vector/path.go +++ b/vector/path.go @@ -232,12 +232,17 @@ func lineForTwoPoints(p0, p1 point) (a, b, c float32) { } // isPointCloseToSegment detects the distance between a segment (x0, y0)-(x1, y1) and a point (x, y) is less than allow. +// If p0 and p1 are the same, isPointCloseToSegment returns true when the distance between p0 and p is less than allow. func isPointCloseToSegment(p, p0, p1 point, allow float32) bool { + if p0 == p1 { + return allow*allow >= (p0.x-p.x)*(p0.x-p.x)+(p0.y-p.y)*(p0.y-p.y) + } + a, b, c := lineForTwoPoints(p0, p1) // The distance between a line ax+by+c=0 and (x0, y0) is // |ax0 + by0 + c| / √(a² + b²) - return allow*allow*(a*a+b*b) > (a*p.x+b*p.y+c)*(a*p.x+b*p.y+c) + return allow*allow*(a*a+b*b) >= (a*p.x+b*p.y+c)*(a*p.x+b*p.y+c) } // crossingPointForTwoLines returns a crossing point for two lines. diff --git a/vector/path_test.go b/vector/path_test.go new file mode 100644 index 000000000..daa32da91 --- /dev/null +++ b/vector/path_test.go @@ -0,0 +1,88 @@ +// Copyright 2024 The Ebitengine Authors +// +// 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 vector_test + +import ( + "testing" + + "github.com/hajimehoshi/ebiten/v2/vector" +) + +func TestIsPointCloseToSegment(t *testing.T) { + testCases := []struct { + p vector.Point + p0 vector.Point + p1 vector.Point + allow float32 + want bool + }{ + { + p: vector.Point{0.5, 0.5}, + p0: vector.Point{0, 0}, + p1: vector.Point{1, 0}, + allow: 1, + want: true, + }, + { + p: vector.Point{0.5, 1.5}, + p0: vector.Point{0, 0}, + p1: vector.Point{1, 0}, + allow: 1, + want: false, + }, + { + p: vector.Point{0.5, 0.5}, + p0: vector.Point{0, 0}, + p1: vector.Point{1, 1}, + allow: 0, + want: true, + }, + { + p: vector.Point{0, 1}, + p0: vector.Point{0, 0}, + p1: vector.Point{1, 1}, + allow: 0.7, + want: false, + }, + { + p: vector.Point{0, 1}, + p0: vector.Point{0, 0}, + p1: vector.Point{1, 1}, + allow: 0.8, + want: true, + }, + { + // p0 and p1 are the same. + p: vector.Point{0, 1}, + p0: vector.Point{0.5, 0.5}, + p1: vector.Point{0.5, 0.5}, + allow: 0.7, + want: false, + }, + { + // p0 and p1 are the same. + p: vector.Point{0, 1}, + p0: vector.Point{0.5, 0.5}, + p1: vector.Point{0.5, 0.5}, + allow: 0.8, + want: true, + }, + } + for _, tc := range testCases { + if got := vector.IsPointCloseToSegment(tc.p, tc.p0, tc.p1, tc.allow); got != tc.want { + t.Errorf("got: %v, want: %v", got, tc.want) + } + } +}