1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
let segment_rectangle (p0 : Vec2.t) (p1 : Vec2.t) ~width : Vec2.t list option =
let along = Vec2.sub p1 p0 in
if Vec2.length along = 0. then None
else
let side = Vec2.scale (width /. 2.) (Vec2.normalize (Vec2.perp along)) in
Some [ Vec2.add p0 side; Vec2.add p1 side; Vec2.sub p1 side; Vec2.sub p0 side ]
let disk (center : Vec2.t) ~width : Vec2.t list =
let r = width /. 2. in
Circle.ellipse_points ~rx:r ~ry:r ~segments:(Circle.segments_for_radius r)
|> List.map (Vec2.add center)
let signed_area (points : Vec2.t list) : float =
match points with
| [] -> 0.
| first :: rest ->
List.combine points (rest @ [ first ])
|> List.fold_left (fun acc (p, q) -> acc +. Vec2.cross p q) 0.
let same_way (points : Vec2.t list) : Vec2.t list =
if signed_area points < 0. then List.rev points else points
let contours (lines : (float * float) list list) ~width : (float * float) list list =
let rec segments = function
| p :: (q :: _ as rest) -> Option.to_list (segment_rectangle p q ~width) @ segments rest
| [ _ ] | [] -> []
in
List.concat (List.map (fun line -> segments line @ List.map (disk ~width) line) lines)
|> List.map same_way
let polylines (fb : Framebuffer.t) (lines : (float * float) list list) ~width ~rgb ~alpha =
Fill.polygons ~rule:Nonzero fb (contours lines ~width) ~rgb ~alpha