Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement shapeutil_visit_crossing_edge_pairs #135

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

missinglink
Copy link
Contributor

This PR implements shapeutil_visit_crossing_edge_pairs which is required to resolve #108.

If this is something you'd be interested in merging then let me know.
All the tests (so far) are passing, it just needs to be cleaned up a bit.

@missinglink
Copy link
Contributor Author

Worth noting an oddity between the CPP and Go implementations, the edge crossings Crossing enum is in a different order, numeric comparisons from the CPP code don't port to Go 🤷‍♂️

DoNotCross = -1
MaybeCross = 0
Cross = 1
const (
	// Cross means the edges cross.
	Cross Crossing = iota
	// MaybeCross means two vertices from different edges are the same.
	MaybeCross
	// DoNotCross means the edges do not cross.
	DoNotCross
)

Copy link
Collaborator

@jmr jmr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We would like to add this functionality.

Can you explain your basic process for converting this? Is it a straightforward translation of the C++ code?

Where did you do something differently from C++?

// VisitCrossingEdgePairs finds all crossing edge pairs in an index.
func VisitCrossingEdgePairs(index *ShapeIndex, crossingType CrossingType, visitor EdgePairVisitor) bool {
needAdjacent := crossingType == CrossingTypeAll
for it := index.Iterator(); !it.Done(); it.Next() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two lines below.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It calls visitCrossings in a loop. Could this be a single call to VisitCrossings, like the C++ does? It looks like you're reimplementing VisitCrossings.

@missinglink
Copy link
Contributor Author

missinglink commented Apr 2, 2025

Where did you do something differently from C++

The main obstacle to a 1:1 port are CPP function overloading, this necessitates some function renaming as Golang doesn't support overloading.

There are numerous differences, I'm not going to even try to list them all, most notably is the one I commented about above, different struct definitions between the two, use of pointers in CPP but values in Go, porting stdlib fiction calls, etc.

Functionally the code is the same, you can probably ask an AI to summarize if you're not comfortable reviewing by eye.

crosser.RestartAt(b.Edge.V0)
}
sign := crosser.ChainCrossingSign(b.Edge.V1)
// missinglink: enum ordering is reversed compared to C++
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what to get out of "missinglink:"? Is this

// Note that enum ordering is reversed compared to C++
// TODO(missinglink): Make them match.

?

}

func FindCrossingError(shape Shape, a, b ShapeEdge, isInterior bool) error {
ap := shape.ChainPosition(int(a.ID.EdgeID))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason not to have is_polygon in the error messages?

polygon := makePolygon(tc.polygonStr, true)
testHasCrossingPermutations(t, polygon.loops, 0, tc.hasCrossing)
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do these test cases match up with the C++ coverage? I don't see a FindSelfIntersection test.

TEST(GetCrossingEdgePairs, NoIntersectionsOneIndex) {
TEST(GetCrossingEdgePairs, NoIntersectionsTwoIndexes) {
TEST(GetCrossingEdgePairs, EdgeGridOneIndex) {
TEST(GetCrossingEdgePairs, EdgeGridTwoIndexes) {
TEST(FindSelfIntersection, Basic) {


import "fmt"

type ShapeEdgeVector []ShapeEdge
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be package private since it's only used in private methods


import "fmt"

type ShapeEdgeVector []ShapeEdge
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Go slice types of types should generally be named as the plural of the type. e.g. shapeEdges []ShapeEdge.

(also Vector is used through s2 already as an X,Y,Z object, so the name would be somewhat confusing that the defined type has no ShapeEdge but no Vector part.)

return true
}

// Visits all pairs of crossing edges in the given S2ShapeIndex, terminating
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://go.dev/doc/comment#func

Func comments should start with the name of the function. (here and all others)


type EdgePairVisitor func(a, b ShapeEdge, isInterior bool) bool

// getShapeEdges returns all edges in the given S2ShapeIndexCell.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace all C++ type names with the Go counterparts in comments.

e.g. S2ShapeIndexCell -> ShapeIndexCell

return true
}

func FindCrossingError(shape Shape, a, b ShapeEdge, isInterior bool) error {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be private and needs the corresponding doc comments from c++

}
}

func testHasCrossingPermutations(t *testing.T, loops []*Loop, i int, hasCrossing bool) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add the c++ function comments to this

}

func TestGetCrossingEdgePairsGrid(t *testing.T) {
kGridSize := 10.0
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const gridSize = 10.0

t.Errorf("Fail")
}
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are none of the two index versions of the tests. Can you add a TODO for them to be added?


index.Add(&shape)
if len(getCrossingEdgePairsBruteForce(index, CrossingTypeAll)) != 112 {
t.Errorf("Fail")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

error messages should be more descriptive than just 'fail'

"testing"
)

type EdgePair struct {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be private

if isInterior {
if ap.ChainID != bp.ChainID {
return fmt.Errorf(
"Loop %d edge %d crosses loop %d edge %d",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here and elsewhere: Error messages should start with lower-case, since they may be wrapped with more context (https://google.github.io/styleguide/go/decisions.html#error-strings)

// However, the vertical lines do not reach the top line as it curves on the
// surface of the sphere: despite "epsilon" those 9 are not even very close
// to intersecting. Thus 9 * 12 = 108 interior and four more at the corners
// when CrossingType::ALL is used.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CrossingTypeAll

index.Add(polygon)

if hasCrossing != FindSelfIntersection(index) {
t.Error("Test failed: expected and actual crossing results do not match")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Idiomatic Go style is like

if got := FindSelfIntersection(index); got != hasCrossing {
  t.Errorf("FindSelfIntersection got %v, want %v", got, hasCrossing)
}

It would be nice to include something about the loops (FindSelfIntersection(%s)), though there isn't a loopToString textformat function yet.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Loop validate() fails to adequately detect duplicate vertices
4 participants