Source file skin.ml
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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
type resampler =
[ `Direct of [ `ByLen | `BySeg ]
| `Reindex of [ `ByLen | `BySeg ]
]
type duplicator =
[ `Distance
| `FastDistance
| `Tangent
]
type mapping =
[ resampler
| duplicator
]
let is_direct = function
| `Direct _ -> true
| _ -> false
let is_resampler = function
| `Direct _ | `Reindex _ -> true
| _ -> false
let is_duplicator = function
| `Distance | `FastDistance | `Tangent -> true
| _ -> false
let linear_transition ~fn ~init a b =
let step = 1. /. Float.of_int fn
and lerps =
try List.map2 V3.lerp a b with
| Invalid_argument _ -> invalid_arg "Profiles must have equal length."
in
let f j acc =
let u = Float.of_int j *. step in
List.map (fun lerp -> lerp u) lerps :: acc
in
Util.fold_init fn f init
let slice_profiles ?(looped = false) ~slices = function
| [] | [ _ ] -> invalid_arg "Too few profiles to slice."
| hd :: tl as profs ->
let len = List.length profs in
let get_slices =
Util.getter ~len:(len - if looped then 0 else 1) ~name:"slice" slices
in
let f (init, last, i) next =
let acc = linear_transition ~fn:(get_slices i + 1) ~init last next in
acc, next, i + 1
in
let profiles, last, i = List.fold_left f ([], hd, 0) tl in
if looped
then (
let profiles, _, _ = f (profiles, last, i) hd in
List.rev profiles )
else List.rev (last :: profiles)
let skin
?(style = `MinEdge)
?(endcaps = `Both)
?refine
?(mapping = `Flat (`Direct `ByLen))
~slices
= function
| [] | [ _ ] -> invalid_arg "At least two profiles are required to skin."
| profs ->
let refine = Option.bind refine (fun n -> if n > 1 then Some n else None)
and looped, bot_cap, top_cap =
match endcaps with
| `Both -> false, true, true
| `Loop -> true, false, false
| `Bot -> false, true, false
| `Top -> false, false, true
| `None -> false, false, false
and resample n s = Path3.subdivide ~closed:true ~freq:(`N (n, s))
and profs = Array.of_list profs in
let n_profs = Array.length profs in
let n_transitions = n_profs - if looped then 0 else 1 in
let get_mapping = Util.getter ~len:n_transitions ~name:"mapping" mapping
and n =
let max = Array.fold_left (fun mx l -> Int.max (List.length l) mx) 0 profs in
Util.value_map_opt ~default:max (fun r -> r * max) refine
and all_resamplers =
match mapping with
| `Flat (`Direct _ | `Reindex _) -> true
| `Mix l -> List.for_all is_resampler l
| _ -> false
in
let len_sliced, sliced =
if all_resamplers
then (
let unpack_resampler i =
match get_mapping i with
| `Direct sampling -> true, sampling
| `Reindex sampling -> false, sampling
| _ -> failwith "impossible"
in
let f i (acc, last_p) =
let direct, sampling = unpack_resampler i in
let resampled = resample n sampling profs.(i + 1) in
if direct
then resampled :: acc, resampled
else Path3.reindex_polygon last_p resampled :: acc, resampled
and resampled_hd = resample n (snd @@ unpack_resampler 0) profs.(0) in
let fixed_hd =
if looped
then (
let direct, samp = unpack_resampler (n_profs - 1) in
if not direct
then Path3.reindex_polygon (resample n samp profs.(n_profs - 1)) resampled_hd
else resampled_hd )
else resampled_hd
in
let fixed =
let l, _ = Util.fold_init (n_profs - 1) f ([ resampled_hd ], resampled_hd) in
List.rev @@ if looped then fixed_hd :: l else l
in
1, [ slice_profiles ~looped:false ~slices fixed ] )
else (
let get_slices = Util.getter ~len:n_transitions ~name:"slices" slices in
let up =
let fallback i p =
match get_mapping (Util.index_wrap ~len:n_profs (i - 1)) with
| `Direct sampling | `Reindex sampling -> resample n sampling p
| _ -> resample n `BySeg p
in
let f i p =
if i < n_transitions || looped
then (
match get_mapping i with
| `Direct sampling | `Reindex sampling -> resample n sampling p
| _ -> if i > 0 || looped then fallback i p else resample n `BySeg p )
else fallback i p
in
Array.mapi f profs
and upsample_dups (a, b) = [ resample n `BySeg a; resample n `BySeg b ] in
let f i acc =
let j = (i + 1) mod n_profs in
let pair =
match get_mapping i with
| `Direct _ -> [ up.(i); up.(j) ]
| `Reindex _ -> [ up.(i); Path3.reindex_polygon up.(i) up.(j) ]
| `Distance -> upsample_dups @@ Path3.distance_match profs.(i) profs.(j)
| `FastDistance ->
upsample_dups @@ Path3.aligned_distance_match profs.(i) profs.(j)
| `Tangent -> upsample_dups @@ Path3.tangent_match profs.(i) profs.(j)
in
slice_profiles ~slices:(`Flat (get_slices i)) pair :: acc
in
n_transitions, List.rev @@ Util.fold_init n_transitions f [] )
in
let f (i, acc) rows =
let endcaps =
match bot_cap, top_cap with
| true, true when i = 0 && i = len_sliced - 1 -> `Both
| true, _ when i = 0 -> `Bot
| _, true when i = len_sliced - 1 -> `Top
| _ -> `None
in
i + 1, Mesh0.of_rows ~style ~endcaps rows :: acc
in
Mesh0.join @@ snd @@ List.fold_left f (0, []) sliced
let skin_between ?style ?endcaps ?refine ?mapping:(m = `Direct `ByLen) ~slices:s a b =
skin ?style ?refine ~mapping:(`Flat m) ?endcaps ~slices:(`Flat s) [ a; b ]