Skip to content

Commit

Permalink
Merge pull request #79 from diagrams/offset
Browse files Browse the repository at this point in the history
Added offsetSegment function
  • Loading branch information
byorgey committed Apr 26, 2013
2 parents a3cf199 + 749a247 commit c8df658
Show file tree
Hide file tree
Showing 8 changed files with 156 additions and 5 deletions.
3 changes: 2 additions & 1 deletion diagrams-lib.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ Library
Diagrams.TwoD.Arc,
Diagrams.TwoD.Segment,
Diagrams.TwoD.Curvature,
Diagrams.TwoD.Offset,
Diagrams.TwoD.Path,
Diagrams.TwoD.Polygons,
Diagrams.TwoD.Shapes,
Expand All @@ -69,7 +70,7 @@ Library
diagrams-core >= 0.6 && < 0.7,
active >= 0.1 && < 0.2,
vector-space >= 0.7.7 && < 0.9,
NumInstances >= 1.2 && < 1.3,
NumInstances >= 1.2 && < 1.4,
colour >= 2.3.2 && < 2.4,
data-default-class < 0.1,
pretty >= 1.0.1.2 && < 1.2,
Expand Down
4 changes: 4 additions & 0 deletions diagrams/cubicOffsetExample.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions diagrams/diagramA.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions diagrams/diagramNeg.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions diagrams/diagramPos.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions diagrams/diagramZero.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 4 additions & 4 deletions src/Diagrams/TwoD/Curvature.hs
Original file line number Diff line number Diff line change
Expand Up @@ -50,19 +50,19 @@ import Diagrams.TwoD.Vector
--
-- Lets consider the following curve:
--
-- <<diagramA.svg#diagram=diagramA&height=200&width=400>>
-- <<diagrams/diagramA.svg#diagram=diagramA&height=200&width=400>>
--
-- The curve starts with positive curvature,
--
-- <<diagramPos.svg#diagram=diagramPos&height=200&width=400>>
-- <<diagrams/diagramPos.svg#diagram=diagramPos&height=200&width=400>>
--
-- approaches zero curvature
--
-- <<diagramZero.svg#diagram=diagramZero&height=200&width=400>>
-- <<diagrams/diagramZero.svg#diagram=diagramZero&height=200&width=400>>
--
-- then has negative curvature
--
-- <<diagramNeg.svg#diagram=diagramNeg&height=200&width=400>>
-- <<diagrams/diagramNeg.svg#diagram=diagramNeg&height=200&width=400>>
--
-- > import Diagrams.TwoD.Curvature
-- > import Data.Monoid.PosInf
Expand Down
130 changes: 130 additions & 0 deletions src/Diagrams/TwoD/Offset.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
-----------------------------------------------------------------------------
-- |
-- Module : Diagrams.TwoD.Offset
-- Copyright : (c) 2012 diagrams-lib team (see LICENSE)
-- License : BSD-style (see LICENSE)
-- Maintainer : diagrams-discuss@googlegroups.com
--
-- Compute offsets to segments in two dimensions.
--
-----------------------------------------------------------------------------
module Diagrams.TwoD.Offset
( offsetSegment
) where

import Data.AffineSpace
import Data.Monoid.PosInf
import Data.VectorSpace

import Diagrams.Core

import Diagrams.Path
import Diagrams.Segment
import Diagrams.TwoD.Curvature
import Diagrams.TwoD.Transform
import Diagrams.TwoD.Types

perp :: R2 -> R2
perp v = rotateBy (-1/4) v

unitPerp :: R2 -> R2
unitPerp = normalized . perp

perpAtParam :: Segment R2 -> Double -> R2
perpAtParam (Linear a) t = unitPerp a
perpAtParam s@(Cubic _ _ _) t = unitPerp a
where
(Cubic a _ _) = snd $ splitAtParam s t

-- | Compute the offset of a segment. Given a segment compute the offset
-- curve that is a fixed distance from the original curve. For linear
-- segments nothing special happens, the same linear segment is returned
-- with a point that is offset by a perpendicular vector of the given offset
-- length.
--
-- Cubic segments require a search for a subdivision of cubic segments that
-- gives an approximation of the offset within the given epsilon tolerance.
-- We must do this because the offset of a cubic is not a cubic itself (the
-- degree of the curve increases). Cubics do, however, approach constant
-- curvature as we subdivide. In light of this we scale the handles of
-- the offset cubic segment in proportion to the radius of curvature difference
-- between the original subsegment and the offset which will have a radius
-- increased by the offset parameter.
--
-- In the following example the blue lines are the original segments and
-- the alternating green and red lines are the resulting offset trail segments.
--
-- <<diagrams/cubicOffsetExample.svg#diagram=cubicOffsetExample&width=600>>
--
-- Note that when the original curve has a cusp, the offset curve forms a
-- radius around the cusp, and when there is a loop in the original curve,
-- there can be two cusps in the offset curve.
--
offsetSegment :: Double -- ^ Epsilon value that represents the maximum
-- allowed deviation from the true offset. In
-- the current implementation each result segment
-- should be bounded by arcs that are plus or
-- minus epsilon from the radius of curvature of
-- the offset.
-> Double -- ^ Offset from the original segment, positive is
-- on the right of the curve, negative is on the
-- left.
-> Segment R2 -- ^ Original segment
-> (Point R2, Trail R2) -- ^ Resulting offset point and trail.
offsetSegment _ r s@(Linear a) = (origin .+^ va, Trail [s] False)
where va = r *^ unitPerp a

offsetSegment epsilon r s@(Cubic a b c) = (origin .+^ va, t)
where
t = Trail (go (radiusOfCurvature s 0.5)) False
-- Perpendiculars to handles.
va = r *^ unitPerp a
vc = r *^ unitPerp (c - b)
-- Split segments.
ss = (\(a,b) -> [a,b]) $ splitAtParam s 0.5
subdivided = concatMap (trailSegments . snd . offsetSegment epsilon r) ss

-- Offset with handles scaled based on curvature.
offset factor = Cubic (a^*factor) ((b - c)^*factor + c + vc - va) (c + vc - va)

-- We observe a corner. Subdivide right away.
go (Finite 0) = subdivided
-- We have some curvature
go roc
| close = [o]
| otherwise = subdivided
where
-- We want the multiplicative factor that takes us from the original
-- segment's radius of curvature roc, to roc + r.
--
-- r + sr = x * sr
--
o = offset $ case roc of
PosInfty -> 1 -- Do the right thing.
Finite sr -> 1 + r / sr

close = and [epsilon > (magnitude (p o + va - p s - pp s))
| t <- [0.25, 0.5, 0.75]
, let p = (`atParam` t)
, let pp = (signum r *^) . (`perpAtParam` t)
]


-- > import Diagrams.TwoD.Offset
-- >
-- > showExample :: Segment R2 -> Diagram SVG R2
-- > showExample s = pad 1.1 . centerXY $ d # lc blue # lw 0.1 <> d' # lw 0.1
-- > where
-- > d = stroke $ Path [(origin, Trail [s] False)]
-- > d' = mconcat . zipWith lc colors . map stroke . uncurry explodeTrail
-- > $ offsetSegment 0.1 (-1) s
-- >
-- > colors = cycle [green, red]
-- >
-- > cubicOffsetExample :: Diagram SVG R2
-- > cubicOffsetExample = hcat . map showExample $
-- > [ Cubic (10 & 0) ( 5 & 18) (10 & 20)
-- > , Cubic ( 0 & 20) ( 10 & 10) ( 5 & 10)
-- > , Cubic (10 & 20) ( 0 & 10) (10 & 0)
-- > , Cubic (10 & 20) ((-5) & 10) (10 & 0)
-- > ]

0 comments on commit c8df658

Please sign in to comment.