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

Created 'MonotonePolygon' #95

Merged
merged 37 commits into from
Jan 18, 2021
Merged

Created 'MonotonePolygon' #95

merged 37 commits into from
Jan 18, 2021

Conversation

1ndy
Copy link

@1ndy 1ndy commented Jan 10, 2021

I'd like to attempt random monotone polygon generation

TODO:

  • Generate points in counter-clockwise order. Going counter-clockwise from a minimum point to a maximum point means the 'right-half' should be in between the extremes and the 'left-half' should be after.
  • Generalize the function to accept a list of points instead of randomly generating them. All in all, the type signatures should look like this:
randomMonotone :: RandomGen g => Int -> Rand g (SimplePolygon () Rational)
randomMonotoneDirected :: RandomGen g => Int -> Vector 2 Rational -> Rand g (SimplePolygon () Rational)
monotoneFrom :: (Ord r, Num r) => Vector 2 r -> [Point 2 r -> SimplePolygon () r

randomMonotone should generate a random Vector and call randomMonotoneDirected.
randomMonotoneDirected should generate a set of random points and call randomMonotoneFrom. Splitting up the functions like this makes it a lot easier to test them.

@lemmih
Copy link
Collaborator

lemmih commented Jan 10, 2021

Excellent!

To rehash the algorithm:

  1. Create N Point 2 Rational (N >= 3)
  2. Create a random Vector 2 Rational
  3. Find the extremes (min and max) of the points when sorted in the direction of the vector.
    We already have code for this. See maximumBy (cmpExtreme vector) and minimumBy (cmpExtreme vector).
  4. Take out the two extremal points from the set.
  5. Partition the remaining points according to whether they're on the left side or right side of the imaginary line between the two extremal points.
  6. Sort the two partitioned sets, one in the direction of the vector and one in the opposite direction.
  7. Connect the points, starting from the minimal extreme point, going through the set of points that are increasing in the direction of the vector, then to the maximal point, and finally down through the points that are decreasing in the direction of the vector.

I'll add some type-signatures and tests to your branch.

@lemmih
Copy link
Collaborator

lemmih commented Jan 10, 2021

@1ndy
Copy link
Author

1ndy commented Jan 10, 2021

Box is checked. Thanks for giving me a starting point, I'll get started tomorrow

@lemmih lemmih mentioned this pull request Jan 10, 2021
@lemmih
Copy link
Collaborator

lemmih commented Jan 13, 2021

For random Rational points, see this code:

granularity :: Integer
granularity = 10000000

-- Random point between screenTop/screenBottom.
genPoint :: RandomGen g => Rand g (Point 2 Rational)
genPoint = do
  x <- liftRand $ randomR (0, granularity)
  y <- liftRand $ randomR (0, granularity)
  pure $ Point2
    ((x % granularity) * screenHeight - screenTop)
    ((y % granularity) * screenHeight - screenTop)

It has to be in the Rand monad and cannot just be a Point 2 Rational.

@lemmih
Copy link
Collaborator

lemmih commented Jan 13, 2021

Also, you can use the ClosedLineSegment constructor to make a line. But the line is actually not needed. It can be imaginary. We just need to partition the points according to which side they're on. We can test this by just using the 'min' and 'max' points you already found.

@lemmih
Copy link
Collaborator

lemmih commented Jan 13, 2021

Your solution is very close to being correct. It just has to be monadic and use do. Like this:

randomMonotone :: RandomGen g => Int -> Vector 2 Rational -> Rand g (SimplePolygon () Rational)
randomMonotone nVertices direction = do
  -- 1, skip 2 in this function bc `direction` is given
  points <- replicateM nVertices genPoint
  -- 3
  let min = Data.Geometry.Polygon.Core.minimumBy (cmpExtreme direction) points
      max = Data.Geometry.Polygon.Core.maximumBy (cmpExtreme direction) points
  -- rest of your code here.

@lemmih
Copy link
Collaborator

lemmih commented Jan 13, 2021

Another hint: There's a built-in function for testing which side a point is on.

toTheLeft :: Point 2 r -> Bool
toTheLeft x = ccw min max x == CCW

You can then use Data.List.partition :: (a -> Bool) -> [a] -> ([a], [a]) to split the points in two.

@lemmih
Copy link
Collaborator

lemmih commented Jan 13, 2021

This code:

        polygon = SimplePolygon $ Data.CircularSeq.fromList (min : leftHalf : max : rightHalf)

Can be written as:

        polygon = fromPoints ([min] ++ leftHalf ++ [max] ++ rightHalf)

Let me know if I'm micro-managing too much and you'd rather want some peace and quiet. :)

@1ndy
Copy link
Author

1ndy commented Jan 13, 2021 via email

@1ndy
Copy link
Author

1ndy commented Jan 15, 2021

Sorry for all the commits. I am getting an error while building hgeometry-ipe so this is the only way I can check my code right now

@noinia
Copy link
Owner

noinia commented Jan 15, 2021

What error do you get when building hgeometry-ipe?

As a workaround (i.e. so that locally you can at least properly compile stuff), you can probably also remove hgeometry-ipe from the cabal.project file as well as disable tests. Not really an ideal solution though.

@lemmih
Copy link
Collaborator

lemmih commented Jan 16, 2021

I will push some visualization code to make debugging easier.

@lemmih
Copy link
Collaborator

lemmih commented Jan 16, 2021

Pushed.
If you go to the hgeometry-showcase folder and run stack repl hgeometry-showcase, it'll give you a GHCi prompt. In GHCi run :main random_monotone and an animation will appear in a browser window. It only works with stack and not cabal.

@lemmih
Copy link
Collaborator

lemmih commented Jan 16, 2021

Ah, I made a mistake in isMonotone :D Will push a fix.

@lemmih
Copy link
Collaborator

lemmih commented Jan 16, 2021

I love it! Your code works really well:

random_monotone.mp4

@lemmih
Copy link
Collaborator

lemmih commented Jan 16, 2021

I added two TODO items to your original post.

@1ndy
Copy link
Author

1ndy commented Jan 17, 2021

Hey that's pretty cool! I'll work on those changes, I like the idea of a generalized randomMonotoneFrom function, though since it doesn't use the random monad maybe we should just call it monotoneFrom? I'll export it as the former for now

@lemmih
Copy link
Collaborator

lemmih commented Jan 17, 2021

Hey that's pretty cool! I'll work on those changes, I like the idea of a generalized randomMonotoneFrom function, though since it doesn't use the random monad maybe we should just call it monotoneFrom? I'll export it as the former for now

Oh yeah, monotoneFrom is a much better name.

@lemmih
Copy link
Collaborator

lemmih commented Jan 17, 2021

I've deleted some of the code I copy&pasted into your module at the start of the PR. It's been replaced by a better and more standard way of generating random points. Your code is unchanged except for a few type signatures.

@1ndy
Copy link
Author

1ndy commented Jan 17, 2021

Looks good, are we ready to move it to the proper directory and submit the PR?

@lemmih
Copy link
Collaborator

lemmih commented Jan 18, 2021

Yes, everything looks good. I'll move the module, write a QuickCheck test, and mark the PR as ready.

@lemmih lemmih marked this pull request as ready for review January 18, 2021 01:56
@lemmih
Copy link
Collaborator

lemmih commented Jan 18, 2021

Alright, looks like everything is in order:

I'll go ahead and merge this PR. Thank you so much, @1ndy. You've improved HGeometry. I hope we'll see more algorithms from you in the future.

@1ndy
Copy link
Author

1ndy commented Jan 18, 2021

Thanks for all your help!!

@lemmih lemmih merged commit 7b5deb1 into noinia:master Jan 18, 2021
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.

3 participants