Skip to content

Commit

Permalink
Another attempt at a proper intersection Trace in clipTo
Browse files Browse the repository at this point in the history
  • Loading branch information
bergey committed Jan 2, 2014
1 parent a97d217 commit f4aba13
Showing 1 changed file with 34 additions and 8 deletions.
42 changes: 34 additions & 8 deletions src/Diagrams/TwoD/Path.hs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import Data.Typeable

import Data.AffineSpace
import Data.Default.Class
import Data.Monoid.Inf
import Data.VectorSpace

import Diagrams.Combinators (withEnvelope, withTrace)
Expand Down Expand Up @@ -168,8 +169,8 @@ instance Renderable (Path R2) b => TrailLike (QDiagram b R2 Any) where
--
-- * Names can be assigned to the path's vertices
--
-- 'StrokeOpts' is an instance of 'Default', so @stroke' 'with' {
-- ... }@ syntax may be used.
-- 'StrokeOpts' is an instance of 'Default', so @stroke' ('with' &
-- ... )@ syntax may be used.
stroke' :: (Renderable (Path R2) b, IsName a) => StrokeOpts a -> Path R2 -> Diagram b R2
stroke' opts path
| null (pLines ^. unwrapped) = mkP pLoops
Expand Down Expand Up @@ -362,20 +363,45 @@ instance Transformable Clip where
clipBy :: (HasStyle a, V a ~ R2) => Path R2 -> a -> a
clipBy = applyTAttr . Clip . (:[])

-- | Clip a diagram to the given path setting its envelope to the pointwise
-- minimum of the envelopes of the diagram and path. XXX The trace is left
-- unchanged but should probably be the trace of the intersection of the
-- clip path and diagram.
-- | Clip a diagram to the given path setting its envelope to the
-- pointwise minimum of the envelopes of the diagram and path. The
-- trace consists of those parts of the original diagram's trace
-- which fall within the clipping path, or parts of the path's trace
-- within the original diagram.
clipTo :: (Renderable (Path R2) b) => Path R2 -> Diagram b R2 -> Diagram b R2
clipTo p d = toEnvelope $ clipBy p d
clipTo p d = setTrace intersectionTrace . toEnvelope $ clipBy p d
where
envP = appEnvelope . getEnvelope $ p
envD = appEnvelope . getEnvelope $ d
toEnvelope = case (envP, envD) of
(Just eP, Just eD) -> setEnvelope . mkEnvelope $ \v -> min (eP v) (eD v)
(_, _) -> id
intersectionTrace = Trace tryTrace
-- Find the first Trace result that is part of the intersection
tryTrace pt v = let
-- locate the point corresponding to a trace distance
newPt d = pt .+^ v ^* d
-- handle an intersection with the trace of d
dTest dDist = if testPt (newPt dDist) pQuery
then (Finite dDist) else tryTrace (newPt dDist) v
-- handle an intersection with the trace of p
pTest pDist = if testPt (newPt pDist) (query d)
then (Finite pDist) else tryTrace (newPt pDist) v
in
case (appTrace (getTrace p) pt v, appTrace (getTrace d) pt v) of
-- No intersections
(Infinity, Infinity) -> Infinity
-- One intersection, test if it counts, recurse if not
(Infinity, Finite dDist) -> dTest dDist
(Finite pDist, Infinity) -> pTest pDist
-- Two intersections, use the nearest or recurse
(Finite pDist, Finite dDist) ->
if pDist < dDist then pTest pDist else dTest dDist
-- Check if pt is inside the Path / Diagram
testPt pt q = getAny $ runQuery q pt
pQuery = Query $ Any . flip (runFillRule Winding) p

-- | Clip a diagram to the clip path taking the envelope and trace of the clip
-- path.
clipped :: (Renderable (Path R2) b) => Path R2 -> Diagram b R2 -> Diagram b R2
clipped p = (withTrace p) . (withEnvelope p) . (clipBy p)
clipped p = (withTrace p) . (withEnvelope p) . (clipBy p)

8 comments on commit f4aba13

@byorgey
Copy link
Member

@byorgey byorgey commented on f4aba13 Jan 2, 2014

Choose a reason for hiding this comment

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

For this to work I think we would have to change traces to return a list of all intersections. (Which might not be a bad idea anyway.)

@bergey
Copy link
Member Author

@bergey bergey commented on f4aba13 Jan 2, 2014

Choose a reason for hiding this comment

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

Getting the list of intersections in the Trace would make this code much simpler. I believe you that it's necessary. Right now my code finds the same intersection over and over again, which isn't good. I also need to review under what circumstances Traces give negative distances, before I write any more code dealing with Traces.

@jeffreyrosenbluth
Copy link
Member

Choose a reason for hiding this comment

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

What should we do with clipTo in the meantime:

  1. set the envelope and leave the trace alone
  2. set the envelope properly and set the trace to the trace of the clip path
  3. leave it out

@byorgey
Copy link
Member

@byorgey byorgey commented on f4aba13 Jan 2, 2014

Choose a reason for hiding this comment

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

Why do we need to do anything with clipTo "in the meantime"? Why not just do it right in the first place? Changing traces to return a list of intersections should not actually be that hard (...fingers crossed). Basically:

  • Change the type of Trace
  • Change instances of Trace to stop throwing information away (e.g. it might be as simple as replacing a call to minimum with a call to sort!).
  • Change the Semigroup for Trace to do a merge on sorted lists
  • Implement functions like traceP etc. using head.

Due to laziness, this won't even incur much performance overhead compared to what we have now: taking the head of some merged sorted lists only has to do a few comparisons and never has to look at most of the list contents.

The reason this is necessary for implementing clipTo is this: imagine that a ray hits the border of a diagram in four places. Currently, we can only ever get the first and last intersections out of the trace. But if those intersections lie outside the clipping path, they don't count, and there is no way to find the other two. The key point which I think @bergey was missing is that calling trace always returns the intersection with the smallest t-value, even if that intersection is "behind you" (i.e. if the t-value is negative). This is a bit unintuitive at times (it's not how raytracers typically work) but it makes all the math work out really nicely. I should add that if we switch to returning a list, we can easily implement functions that do work in the intuitive way you would expect, simply by filtering out negative t-values.

@jeffreyrosenbluth
Copy link
Member

Choose a reason for hiding this comment

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

Your right, we don't have to do anything in the meantime, but I do think before we go ahead and refactor traces we should rethink "negative" traces.

@byorgey
Copy link
Member

@byorgey byorgey commented on f4aba13 Jan 2, 2014

Choose a reason for hiding this comment

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

Re: rethinking negative traces, see my edited comment above. I think switching to returning a list is exactly what we need to do to be able to handle "negative" traces well.

@jeffreyrosenbluth
Copy link
Member

Choose a reason for hiding this comment

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

Sorry, I didn't see the last part about negative traces. I'll go ahead and start working on returning a list in the clipTo branch.

@byorgey
Copy link
Member

@byorgey byorgey commented on f4aba13 Jan 2, 2014

Choose a reason for hiding this comment

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

Great! And just to record here what I just said on IRC, the reason we need to return negative t-values at all is so that traces can be translated. A translation can turn negative t-values into positive, so you need to know about all the intersections in order to be able to translate traces properly. However, if we return a list, then this can just be an implementation detail---we can make the main user-facing functions filter out negative t-values.

Please sign in to comment.