6 | import Text.Molfile.Types
12 | data Bounds : Type where
17 | Rng : (min, max : Double) -> Bounds
23 | getBounds : Bounds -> Maybe (Double,Double)
24 | getBounds Empty = Nothing
25 | getBounds (Rng x y) = Just (x,y)
34 | val : Double -> Bounds
39 | range : (min,max : Double) -> Bounds
40 | range l u = if l <= u then Rng l u else Empty
45 | expand : Bounds -> Bounds -> Bounds
46 | expand (Rng mi1 ma1) (Rng mi2 ma2) = Rng (min mi1 mi2) (max ma1 ma2)
52 | inBounds1D : {default 0.0 margin : Double} -> Double -> Bounds -> Bool
53 | inBounds1D x (Rng min max) = (min - margin) <= x && x <= (max + margin)
54 | inBounds1D x Empty = False
59 | middle : Bounds -> Maybe Double
60 | middle (Rng min max) = Just $
(max + min) / 2
61 | middle Empty = Nothing
65 | width : Bounds -> Double
66 | width (Rng min max) = max - min
72 | halfWidth : Bounds -> Double
73 | halfWidth bs = width bs / 2.0
76 | Semigroup Bounds where (<+>) = expand
79 | Monoid Bounds where neutral = Empty
84 | overlap : {default 0.0 margin : Double} -> Bounds -> Bounds -> Bounds
85 | overlap (Rng mi1 ma1) (Rng mi2 ma2) =
86 | range (max mi1 mi2 - margin) (min ma1 ma2 + margin)
95 | record Bounds2D (t : AffineTransformation) where
101 | Semigroup (Bounds2D t) where
102 | BS x1 y1 <+> BS x2 y2 = BS (x1 <+> x2) (y1 <+> y2)
105 | Monoid (Bounds2D t) where neutral = BS empty empty
109 | width : Bounds2D t -> Double
113 | height : Bounds2D t -> Double
118 | overlap : {default 0.0 margin : Double} -> Bounds2D t -> Bounds2D t -> Bounds2D t
119 | overlap (BS x1 y1) (BS x2 y2) = BS (overlap {margin} x1 x2) (overlap {margin} y1 y2)
124 | inBounds : {default 0.0 margin : Double} -> (p : Point t) -> Bounds2D t -> Bool
125 | inBounds p (BS x y) = inBounds1D {margin} p.x x && inBounds1D {margin} p.y y
129 | corners : Bounds2D t -> Maybe (Point t, Point t)
130 | corners (BS (Rng xmin xmax) (Rng ymin ymax)) = Just (P xmin ymin, P xmax ymax)
131 | corners _ = Nothing
138 | interface Bounded (0 a : Type) where
140 | btrans : AffineTransformation
141 | bounds : a -> Bounds2D btrans
143 | public export %inline
144 | {t : _} -> Bounded (Bounds2D t) where
149 | Foldable f => (b : Bounded a) => Bounded (f a) where
150 | btrans = btrans @{b}
151 | bounds = foldMap bounds
153 | public export %inline
154 | (g : GetPoint a) => Bounded a where
155 | btrans = gtrans @{g}
156 | bounds v = let P x y := point v in BS (val x) (val y)
159 | convertBounds : {s,t : _} -> Bounds2D s -> Bounds2D t
162 | Just (x,y) => bounds (convert {s,t} x) <+> bounds (convert {s,t} y)
169 | center : (b : Bounded a) => a -> Point (btrans @{b})
170 | center ts = case bounds ts of
171 | BS x y => fromMaybe origin [| P (middle x) (middle y) |]
176 | inRectangle : {t : _} -> (p, edge1, edge2 : Point t) -> Bool
177 | inRectangle p e1 e2 = inBounds p (bounds $
the (List _) [e1,e2])
182 | translatePositive : ModPoint a => Bounded a => a -> a
183 | translatePositive v =
184 | case corners $
bounds v of
186 | Just (P x y, _) => translate (V (-x) (-y)) v
195 | distanceToLineSegment : {t : _} -> (p, pl1, pl2 : Point t) -> Double
196 | distanceToLineSegment p pl1 pl2 =
197 | let pp := perpendicularPoint p (translate (pl1 - pl2) p) 1 True
198 | dflt := min (distance p pl1) (distance p pl2)
199 | in case intersect pl1 pl2 p pp of
200 | Just i => if inRectangle i pl1 pl2 then distance p i else dflt
207 | record BVal a where
219 | area : BVal a -> Double
220 | area b = b.width * b.height
232 | mid : Rect -> Rect -> Rect
233 | mid s l = R 0 0 ((s.width + l.width)/2) ((s.height + l.height)/2)
235 | close : Rect -> Rect -> Bool
236 | close s l = abs (s.width-l.width) <= 0.00001 || abs (s.height-l.height) <= 0.00001
240 | bounds (R x y w h) = BS (range x (x+w)) (range y (y+h))
242 | rect : (x,y,w,h : Double) -> List Rect
243 | rect x y w h = if w > 0 && h > 0 then [R x y w h] else []
246 | record PackParams where
263 | dflt = PP 5 2.5 64 1.61803
265 | ini : PackParams -> List (BVal a) -> Rect
267 | let l := cast {to = Double} $
length vs
268 | w := sum (map width vs) + l * p.gapX
269 | h := sum (map height vs) + l * p.gapY
271 | in if p.ratio >= 1 then R 0 0 (d * p.ratio) d else R 0 0 d (d / p.ratio)
276 | fits : BVal a -> Rect -> Bool
277 | fits bv r = bv.width <= r.width && bv.height <= r.height
279 | score : BVal a -> Rect -> Double
280 | score bv r = (r.width - bv.width) * (r.height - bv.height)
282 | split : BVal a -> Rect -> List Rect
283 | split (BV v w h) (R x y rw rh) =
284 | case rw - w >= rh - h of
285 | True => rect (x+w) y (rw-w) rh ++ rect x (y+h) w (rh-h)
286 | False => rect (x+w) y (rw-w) h ++ rect x (y+h) rw (rh-h)
288 | best1 : BVal a -> SnocList Rect -> Rect -> Double -> Rects -> (Pos a, Rects)
289 | best1 x sr b sc [] = (P x.val b.x b.y, sr <>> split x b)
290 | best1 x sr b sc (r :: rs) =
292 | False => best1 x (sr:<r) b sc rs
295 | in if s < sc then best1 x (sr:<b) r s rs else best1 x (sr:<r) b sc rs
297 | best : BVal a -> SnocList Rect -> Rects -> Maybe (Pos a, Rects)
298 | best x sr [] = Nothing
299 | best x sr (r :: rs) =
300 | if fits x r then Just $
best1 x sr r (score x r) rs else best x (sr:<r) rs
302 | guillotine : SnocList (Pos a) -> List (BVal a) -> Rects -> Maybe (List $
Pos a)
303 | guillotine sp [] rs = Just (sp <>> [])
304 | guillotine sp (v :: vs) rs =
305 | case best v [<] rs of
306 | Just (p,rs2) => guillotine (sp:<p) vs rs2
309 | pack : PackParams -> List (BVal a) -> Maybe (List $
Pos a)
310 | pack p vs = go p.iter zrect (ini p vs) . reverse $
sortBy (comparing area) vs
312 | go : Nat -> (s,l : Rect) -> List (BVal a) -> Maybe (List $
Pos a)
313 | go 0 s r vs = guillotine [<] vs [r]
316 | True => guillotine [<] vs [r]
319 | in case guillotine [<] vs [m] of
320 | Just rs => go k s m vs
321 | Nothing => go k m r vs
323 | parameters {default dflt p : PackParams}
324 | {auto bnd : Bounded a}
325 | {auto mp : ModPoint a}
330 | in BV v (width bs + p.gapX) (height bs + p.gapY)
332 | position : Pos a -> a
333 | position (P v x y) = Point.translate (V x y) (translatePositive v)
345 | align : List a -> List a
348 | align vs = maybe vs (map position) $
pack p (map bval vs)