1%%
2%%  wings_extrude_edge.erl --
3%%
4%%     This module contains the Extrude (edge), Bevel (face/edge) and
5%%     Bump commands. (All based on edge extrusion.)
6%%
7%%  Copyright (c) 2001-2011 Bjorn Gustavsson
8%%
9%%  See the file "license.terms" for information on usage and redistribution
10%%  of this file, and for a DISCLAIMER OF ALL WARRANTIES.
11%%
12%%     $Id$
13%%
14
15-module(wings_extrude_edge).
16-export([bump/1,bevel/1,bevel_faces/1,extrude/2,crease/1,extrude_edges/3]).
17
18-include("wings.hrl").
19-import(lists, [foldl/3,reverse/1]).
20
21-define(DEFAULT_EXTRUDE_DIST, 0.2).
22-define(BEVEL_EXTRUDE_DIST_KLUDGE, 0.0001).
23
24-type edge_num() :: wings_edge:edge_num().
25
26%%%
27%%% The Bump command.
28%%%
29
30bump(St0) ->
31    Dist = calc_bump_dist(St0),
32    MF = fun(Fs, We) -> bump(Fs, Dist, We) end,
33    St = wings_sel:map(MF, St0),
34    DF = fun(_, #we{temp=PlusMinus}) -> PlusMinus end,
35    wings_move:plus_minus(normal, DF, St).
36
37calc_bump_dist(St) ->
38    Map = fun calc_bump_dist_1/2,
39    Combine = fun min/2,
40    wings_sel:dfold(Map, Combine, infinity, St) / 2.
41
42calc_bump_dist_1(Faces, We) ->
43    Edges = gb_sets:to_list(wings_edge:from_faces(Faces, We)),
44    min_dist_from_edges(Edges, We).
45
46bump(Faces, Dist, We0) ->
47    Edges = gb_sets:from_list(wings_face:outer_edges(Faces, We0)),
48    {We,_,_,_} = extrude_edges(Edges, Faces, Dist, We0),
49    NewVs = wings_we:new_items_as_ordset(vertex, We0, We),
50    We#we{temp={Faces,NewVs,gb_sets:empty()}}.
51
52%%
53%% The Bevel command (for edges).
54%%
55
56bevel(St0) ->
57    St = wings_sel:map_update_sel(fun bevel_edges/2, face, St0),
58    bevel_drag(St).
59
60bevel_edges(Edges, #we{mirror=MirrorFace}=We0) ->
61    Dist = ?BEVEL_EXTRUDE_DIST_KLUDGE,
62    {We1,OrigVs,_,Forbidden} = extrude_edges(Edges, Dist, We0#we{mirror=none}),
63    We2 = wings_edge:dissolve_edges(Edges, We1),
64    Tv0 = bevel_tv(OrigVs, We2, Forbidden),
65    Tv = scale_tv(Tv0, Dist),
66    We3 = wings_collapse:collapse_vertices(OrigVs, We2),
67    Vtab = bevel_reset_pos(OrigVs, We2, Forbidden, We3#we.vp),
68    We4 = We3#we{vp=Vtab,mirror=MirrorFace},
69    Limit = bevel_limit(Tv, We4),
70    We = We4#we{temp={Limit,Tv}},
71    Sel = case gb_sets:is_empty(Forbidden) of
72	      true ->
73                  wings_we:new_items_as_gbset(face, We0, We);
74	      false ->
75                  gb_sets:empty()
76	  end,
77    {We,Sel}.
78
79%%
80%% The Bevel command (for faces).
81%%
82
83bevel_faces(St0) ->
84    St = wings_sel:map(fun bevel_faces/2, St0),
85    bevel_drag(St).
86
87bevel_faces(Faces, #we{mirror=MirrorFace}=We0) ->
88    Dist = ?BEVEL_EXTRUDE_DIST_KLUDGE,
89    Edges = wings_edge:from_faces(Faces, We0),
90    {We1,OrigVs,_,Forbidden} = extrude_edges(Edges, Dist, We0#we{mirror=none}),
91    case {wings_util:array_entries(We0#we.es),wings_util:array_entries(We1#we.es)} of
92	{Same,Same} ->
93	    wings_u:error_msg(?__(1,"Object is too small to bevel."));
94	{_,_} ->
95	    We2 = wings_edge:dissolve_edges(Edges, We1),
96	    Tv0 = bevel_tv(OrigVs, We2, Forbidden),
97	    Tv = scale_tv(Tv0, Dist),
98	    #we{vp=Vtab0} = We3 = wings_collapse:collapse_vertices(OrigVs, We2),
99	    Vtab = bevel_reset_pos(OrigVs, We2, Forbidden, Vtab0),
100	    We = We3#we{vp=Vtab,mirror=MirrorFace},
101	    Limit = bevel_limit(Tv, We),
102	    We#we{temp={Limit,Tv}}
103    end.
104
105%%
106%% Common bevel utilities.
107%%
108
109bevel_drag(St) ->
110    MF = fun(_, #we{temp={Limit,_}}) -> Limit end,
111    RF = fun min/2,
112    Limit = wings_sel:dfold(MF, RF, infinite, St),
113    DF = fun(_, #we{temp={_,Tv}}=We) ->
114                 wings_drag:translate_fun(Tv, We)
115         end,
116    wings_drag:fold(DF, [{distance,{0.0,Limit}}], [], St).
117
118bevel_tv(Vs, We, Forbidden) ->
119    foldl(fun(V, A) -> bevel_tv_1(V, We, Forbidden, A) end, [], Vs).
120
121bevel_tv_1(V, We, Forbidden, Acc) ->
122    Center = wings_vertex:pos(V, We),
123    wings_vertex:fold(
124      fun(Edge, _, Rec, Tv0) ->
125	      case gb_sets:is_member(Edge, Forbidden) of
126		  true -> Tv0;
127		  false ->
128		      OtherV = wings_vertex:other(V, Rec),
129		      Pos = wings_vertex:pos(OtherV, We),
130		      Vec = e3d_vec:sub(Pos, Center),
131		      [{Vec,[OtherV]}|Tv0]
132	      end
133      end, Acc, V, We).
134
135bevel_reset_pos(Vs, We, Forbidden, Vtab) ->
136    foldl(fun(V, A) -> bevel_reset_pos_1(V, We, Forbidden, A) end, Vtab, Vs).
137
138bevel_reset_pos_1(V, We, Forbidden, Vtab) ->
139    Center = wings_vertex:pos(V, We),
140    wings_vertex:fold(
141      fun(Edge, _, Rec, Vt) ->
142	      case gb_sets:is_member(Edge, Forbidden) of
143		  true -> Vt;
144		  false ->
145		      OtherV = wings_vertex:other(V, Rec),
146		      array:set(OtherV, Center, Vt)
147	      end
148      end, Vtab, V, We).
149
150bevel_limit(Tv, We) ->
151    L0 = foldl(fun({Vec,[V]}, A) ->
152		       bevel_limit_1(V, Vec, We, A)
153	       end, [], Tv),
154    L = wings_util:rel2fam(L0),
155    try
156	bevel_min_limit(L, We, infinite)
157    catch
158	error:badarith ->
159	    extrude_problem()
160    end.
161
162bevel_limit_1(V, Vec, #we{vp=Vtab}=We, Acc) ->
163    Pos = array:get(V, Vtab),
164    Data = {Pos,Vec},
165    wings_vertex:fold(fun(_, _, Rec, A) ->
166			      OtherV = wings_vertex:other(V, Rec),
167			      [{edge_name(V, OtherV),Data}|A]
168		      end, Acc, V, We).
169
170edge_name(Va, Vb) when Va < Vb -> {Va,Vb};
171edge_name(Va, Vb) -> {Vb,Va}.
172
173bevel_min_limit([{_Edge,[{O1,D1},{O2,D2}]}|Tail], We, Min0) ->
174    %% Find intersection between lines.
175    O2MinusO1 = e3d_vec:sub(O2, O1),
176    D1CrossD2 = e3d_vec:cross(D1, D2),
177    LenD1CrossD2 = e3d_vec:len(D1CrossD2),
178    case LenD1CrossD2*LenD1CrossD2 of
179	Z when Z < (0.01*?DEFAULT_EXTRUDE_DIST*?DEFAULT_EXTRUDE_DIST) ->
180	    %% There is no intersection between the lines.
181	    case e3d_vec:len(O2MinusO1) of
182		Odist when Odist < 0.000001 ->
183		    %% As the vertices are already near each other,
184		    %% we will assume that they will be moving apart.
185		    bevel_min_limit(Tail, We, Min0);
186		Odist ->
187		    D1Len = e3d_vec:len(D1),
188		    case e3d_vec:dist(e3d_vec:mul(D1, Odist/D1Len), O2MinusO1) of
189			Dist when Dist < 0.000001 ->
190			    %% The vertices will be moved directly towards each
191			    %% others.
192			    S = Odist / (2*D1Len),
193			    bevel_min_limit(Tail, We, min(Min0, S));
194			_ ->
195			    %% The vertices will not meet each other.
196			    bevel_min_limit(Tail, We, Min0)
197		    end
198	    end;
199	SqrLen ->
200	    %% There is an intersection. Calculate its parameters.
201	    S = e3d_vec:dot(e3d_vec:cross(O2MinusO1, D2), D1CrossD2)/SqrLen,
202	    T = e3d_vec:dot(e3d_vec:cross(O2MinusO1, D1), D1CrossD2)/SqrLen,
203	    if
204		S > 0, T > 0 ->
205		    Min = min(S, min(T, Min0)),
206		    bevel_min_limit(Tail, We, Min);
207		true ->
208		    %% No intersection in the forward direction.
209		    bevel_min_limit(Tail, We, Min0)
210	    end
211    end;
212bevel_min_limit([{{Va,Vb},[{_,D1}]}|Tail], #we{vp=Vtab}=We, Min0) ->
213    VaPos = array:get(Va, Vtab),
214    VbPos = array:get(Vb, Vtab),
215    case e3d_vec:len(D1) of
216	DLen when DLen < 0.000001 ->
217	    bevel_min_limit(Tail, We, 0.0);
218	DLen ->
219	    case e3d_vec:len(e3d_vec:sub(VaPos, VbPos)) / DLen of
220		Min when Min < Min0 ->
221		    bevel_min_limit(Tail, We, Min);
222		_ ->
223		    bevel_min_limit(Tail, We, Min0)
224	    end
225    end;
226bevel_min_limit([], _, Min) -> Min.
227
228-spec extrude_problem() -> no_return().
229extrude_problem() ->
230    M = ?__(1,"Can't extrude/bevel; two or more vertices are "
231	    "probably too near to each other.\n"
232	    "Try the Cleanup command."),
233    wings_u:error_msg(M).
234
235%%
236%% Crease command
237%%
238
239crease(St0) ->
240    Dist = calc_extrude_dist(St0),
241    MF = fun(Edges, We0) ->
242        We1 = extrude_1(Edges, Dist, We0),
243        #we{temp={_,NewVs0,F}} = We1,
244        ValidCaps = find_cap_vs(Edges, We0, []),
245        #we{vp=Vtab}=We = foldl(fun(V, We2) ->
246            EndCap = wings_vertex:fold(fun
247              (Edge, _, #edge{lf=Lf,rf=Rf}=E, EndCapAcc) ->
248                case wings_face:vertices(Lf, We2) of
249                    3 ->
250                      case wings_face:vertices(Rf, We2) of
251                          3 ->
252                              Other = wings_vertex:other(V, E),
253                              case lists:member(Other, ValidCaps) of
254                                  true -> [{d,Edge}|EndCapAcc];
255                                  false -> EndCapAcc
256                              end;
257                          _ -> EndCapAcc
258                      end;
259                    N1 when N1 > 4 ->
260                      case wings_face:vertices(Rf, We2) of
261                          N2 when N2 > 4 ->
262                            Other = wings_vertex:other(V, E),
263                            [{c,Edge,Other}|EndCapAcc];
264                          _ -> EndCapAcc
265                      end;
266                    _ -> EndCapAcc
267                end
268            end, [], V, We2), % vertex fold
269            case lists:sort(EndCap) of
270                [{d,E1},{c,E2,V1}] ->
271                    We3 = wings_edge:dissolve_edge(E1, We2),
272                    Pos = wings_vertex:pos(V1, We3),
273                    #we{vp=Vtab0}=We4 = wings_collapse:collapse_edge(E2, V1, We3),
274                    Vtab = array:set(V1, Pos, Vtab0),
275                    We4#we{vp=Vtab};
276                [{d,E1}|_] ->
277                    #we{vp=Vtab0}=We3 = wings_edge:dissolve_edge(E1, We2),
278                    RVs = wings_vertex:fold(fun(_,_,Erec,RVs0) ->
279                        [wings_vertex:other(V, Erec)|RVs0]
280                    end, [], V, We3),
281                    Center = wings_vertex:center(RVs, We3),
282                    Vtab = array:set(V, Center, Vtab0),
283                    We3#we{vp=Vtab};
284                _ -> We2
285            end
286        end, We1, NewVs0), % list foldl
287        AllVs = orddict:fetch_keys(array:sparse_to_orddict(Vtab)),
288        NewVs =  ordsets:intersection(lists:sort(NewVs0), AllVs),
289        We#we{temp={Edges,NewVs,F}}
290    end,
291    St = wings_sel:map(MF, St0),
292    DF = fun(_, #we{temp=PlusMinus}) -> PlusMinus end,
293    wings_move:plus_minus(normal, DF, St).
294
295find_cap_vs(Edges0, #we{es=Etab}=We, Acc) ->
296    case gb_sets:is_empty(Edges0) of
297      true ->
298          CapVs = find_cap_vs(lists:sort(Acc), []),
299          valid_caps(CapVs, We);
300      false ->
301          {Edge,Edges} = gb_sets:take_smallest(Edges0),
302          #edge{vs=Va,ve=Vb} = array:get(Edge, Etab),
303          find_cap_vs(Edges, We, [Va,Vb|Acc])
304    end.
305
306find_cap_vs([V,V|Vs0], CapVs) ->
307    Vs = rem_v(V, Vs0),
308    find_cap_vs(Vs, CapVs);
309find_cap_vs([V|Vs], CapVs) ->
310    find_cap_vs(Vs, [V|CapVs]);
311find_cap_vs([], CapVs) ->
312    CapVs.
313
314rem_v(V, [V|Vs]) ->
315    rem_v(V, Vs);
316rem_v(_, Vs) ->
317    Vs.
318
319valid_caps(CapVs, We) ->
320    foldl(fun(V, Acc) ->
321        Count = wings_vertex:fold(fun(_, _, _, Cnt) ->
322            Cnt+1
323        end, 0, V, We),
324        case Count rem 2 of
325            0 -> [V|Acc];
326            _ -> Acc
327        end
328    end, [], CapVs).
329
330%%
331%% The Extrude command (for edges).
332%%
333
334extrude(Type, St0) ->
335    Dist = calc_extrude_dist(St0),
336    MF = fun(Edges, We) -> extrude_1(Edges, Dist, We) end,
337    St = wings_sel:map(MF, St0),
338    DF = fun(_, #we{temp=PlusMinus}) -> PlusMinus end,
339    wings_move:plus_minus(Type, DF, St).
340
341calc_extrude_dist(St) ->
342    Map = fun(Edges0, We) ->
343		  Edges = gb_sets:to_list(Edges0),
344		  min_dist_from_edges(Edges, We)
345	  end,
346    Combine = fun min/2,
347    wings_sel:dfold(Map, Combine, 3.0*?DEFAULT_EXTRUDE_DIST, St) / 3.0.
348
349extrude_1(Edges, ExtrudeDist, We0) ->
350    {We1,_,New,Forbidden} = extrude_edges(Edges, ExtrudeDist, We0),
351    Ns = orig_normals(Edges, We1),
352    We = straighten(Ns, New, We1),
353    NewVs = wings_we:new_items_as_ordset(vertex, We0, We),
354    We#we{temp={Edges,NewVs,Forbidden}}.
355
356orig_normals(Es0, #we{es=Etab,vp=Vtab}) ->
357    VsVec0 = gb_sets:fold(
358	       fun(E, A) ->
359		       #edge{vs=Va,ve=Vb} = array:get(E, Etab),
360		       Vec = e3d_vec:norm_sub(array:get(Va, Vtab),
361					      array:get(Vb, Vtab)),
362		       [{Va,{Vec,Vb}},{Vb,{Vec,Va}}|A]
363	       end, [], Es0),
364    VsVec1 = sofs:relation(VsVec0, [{vertex,info}]),
365    VsVec2 = sofs:relation_to_family(VsVec1),
366    VsVec = sofs:to_external(VsVec2),
367    orig_normals_1(VsVec, gb_trees:from_orddict(VsVec), []).
368
369orig_normals_1([{V,[{VecA,_},{VecB,_}]}|T], VsVec, Acc) ->
370    orig_normals_1(T, VsVec, [{V,e3d_vec:cross(VecA, VecB)}|Acc]);
371orig_normals_1([{V,[{VecA,OtherV}]}|T], VsVec, Acc) ->
372    OtherRec = gb_trees:get(OtherV, VsVec),
373    case [Vec || {Vec,Vertex} <- OtherRec, Vertex =/= V] of
374	[VecB] ->
375	    orig_normals_1(T, VsVec, [{V,e3d_vec:cross(VecA, VecB)}|Acc]);
376	_ ->
377	    orig_normals_1(T, VsVec, Acc)
378    end;
379orig_normals_1([_|T], VsVec, Acc) ->
380    orig_normals_1(T, VsVec, Acc);
381orig_normals_1([], _, Acc) -> reverse(Acc).
382
383straighten([{V,N0}|Ns], New, #we{vp=Vtab0}=We0) ->
384    Pos = wings_vertex:pos(V, We0),
385    Vtab = wings_vertex:fold(
386	     fun(_, _, R, Vt0) ->
387		     OtherV = wings_vertex:other(V, R),
388		     case gb_sets:is_member(OtherV, New) of
389			 false -> Vt0;
390			 true ->
391			     OPos0 = array:get(OtherV, Vt0),
392			     Vec = e3d_vec:norm_sub(Pos, OPos0),
393			     case e3d_vec:dot(N0, Vec) of
394				 Dot when abs(Dot) < 0.87 ->
395				     Vt0;
396				 Dot when Dot < 0 ->
397				     N = e3d_vec:neg(N0),
398				     straighten_1(Vec, N, Pos, OtherV, OPos0, Vt0);
399				 _ ->
400				     straighten_1(Vec, N0, Pos, OtherV, OPos0, Vt0)
401			     end
402		     end
403	     end, Vtab0, V, We0),
404    We = We0#we{vp=Vtab},
405    straighten(Ns, New, We);
406straighten([], _, We) -> We.
407
408straighten_1(Vec, N, {Cx,Cy,Cz}, OtherV, OPos0, Vt) ->
409    case catch e3d_mat:rotate_s_to_t(Vec, N) of
410	{'EXIT',_} -> Vt;
411        Rot ->
412	    M0 = e3d_mat:translate(Cx, Cy, Cz),
413	    M1 = e3d_mat:mul(M0, Rot),
414	    M = e3d_mat:mul(M1, e3d_mat:translate(-Cx, -Cy, -Cz)),
415	    OPos = e3d_mat:mul_point(M, OPos0),
416	    array:set(OtherV, OPos, Vt)
417    end.
418
419%%
420%% Common help function for actually extruding edges.
421%%
422extrude_edges(Edges, ExtrudeDist, We) ->
423    NoForbiddenFaces = gb_sets:empty(),
424    extrude_edges(Edges, NoForbiddenFaces, ExtrudeDist, We).
425
426extrude_edges(Edges, ForbiddenFaces, ExtrudeDist,
427	      #we{next_id=Wid,es=Etab,vc=Vct0}=We0) ->
428    G = digraph:new(),
429
430    %% We update the 'vc' table here to handle the case that a
431    %% a vertex's incident edge points to a completely unrelated
432    %% face (i.e. a vertex is shared by two faces that have no
433    %% common edge).
434    Vct = gb_sets:fold(
435	    fun(Edge, A) ->
436		    #edge{vs=Va,ve=Vb} = Rec = array:get(Edge, Etab),
437		    digraph_edge(G, ForbiddenFaces, Rec),
438		    array:set(Vb, Edge, array:set(Va, Edge, A))
439	    end, Vct0, Edges),
440    Vs0 = digraph:vertices(G),
441    Vs = sofs:to_external(sofs:domain(sofs:relation(Vs0))),
442    {We1,Forbidden} =
443	foldl(fun(V, A) ->
444		      new_vertex(V, G, Edges, ForbiddenFaces, ExtrudeDist, A)
445	      end, {We0#we{vc=Vct},[]}, Vs),
446    NewVs = wings_we:new_items_as_gbset(vertex, We0, We1),
447    We = connect(G, ExtrudeDist, Wid, We1),
448    digraph:delete(G),
449    {We,Vs,NewVs,gb_sets:from_list(Forbidden)}.
450
451new_vertex(V, G, Edges, ForbiddenFaces, ExtrudeDist, {We0,F0}=Acc) ->
452    case wings_vertex:fold(fun(E, F, R, A) -> [{E,F,R}|A] end, [], V, We0) of
453	[_,_]=Es ->
454	    case filter_edges(Es, Edges, ForbiddenFaces) of
455		[] -> Acc;
456		[{Edge,_,#edge{lf=Lf,rf=Rf}}] ->
457		    New = {new,V},
458		    digraph_insert(G, New, V, Lf),
459		    digraph_insert(G, V, New, Lf),
460		    digraph_insert(G, V, New, Rf),
461		    digraph_insert(G, New, V, Rf),
462		    {We0,[Edge|F0]}
463	    end;
464	Es0 ->
465	    Es = filter_edges(Es0, Edges, ForbiddenFaces),
466	    Center = wings_vertex:pos(V, We0),
467	    We = foldl(fun({Edge,_,_}, W0) ->
468			       new_vertex_1(V, G, Edge, Center, ExtrudeDist, W0)
469		       end, We0, Es),
470	    {We,F0}
471    end.
472
473new_vertex_1(V, G, Edge, Center, ExtrudeDist, #we{es=Etab}=We0) ->
474    OtherPos = wings_vertex:other_pos(V, array:get(Edge, Etab), We0),
475    Dir = e3d_vec:norm_sub(OtherPos, Center),
476    Pos = e3d_vec:add_prod(Center, Dir, ExtrudeDist),
477    {We,NewV=NewE} = wings_edge:fast_cut(Edge, Pos, We0),
478    Rec = get_edge_rec(V, NewV, Edge, NewE, We),
479    digraph_edge(G, Rec),
480    We.
481
482get_edge_rec(Va, Vb, EdgeA, EdgeB, #we{es=Etab}) ->
483    case array:get(EdgeA, Etab) of
484	#edge{vs=Va,ve=Vb}=Rec -> Rec;
485	#edge{vs=Vb,ve=Va}=Rec -> Rec;
486	_Other -> array:get(EdgeB, Etab)
487    end.
488
489filter_edges(Es, EdgeSet, FaceSet) ->
490    foldl(fun({Edge,Face,_}=E, A) ->
491		  case gb_sets:is_member(Edge, EdgeSet) orelse
492		      gb_sets:is_member(Face, FaceSet) of
493		      true -> A;
494		      false -> [E|A]
495		  end
496	  end, [], Es).
497
498digraph_edge(G, #edge{lf=Lf,rf=Rf,vs=Va,ve=Vb}) ->
499    digraph_insert(G, Va, Vb, Lf),
500    digraph_insert(G, Vb, Va, Rf).
501
502digraph_edge(G, ForbiddenFaces, #edge{lf=Lf,rf=Rf,vs=Va,ve=Vb}) ->
503    case gb_sets:is_member(Lf, ForbiddenFaces) of
504	false -> digraph_insert(G, Va, Vb, Lf);
505	true -> ok
506    end,
507    case gb_sets:is_member(Rf, ForbiddenFaces) of
508	false -> digraph_insert(G, Vb, Va, Rf);
509	true -> ok
510    end.
511
512digraph_insert(G, Va0, Vb0, Face) ->
513    Va = {Va0,Face},
514    Vb = {Vb0,Face},
515    digraph:add_vertex(G, Va),
516    digraph:add_vertex(G, Vb),
517    digraph:add_edge(G, Va, Vb).
518
519connect(G, ExtrudeDist, Wid, We) ->
520    Cs = digraph_utils:components(G),
521    connect(G, Cs, ExtrudeDist, Wid, We, []).
522
523connect(G, [C|Cs], ExtrudeDist, Wid, #we{mirror=Mirror}=We0, Closed) ->
524    case [VF || {V,_}=VF <- C, V >= Wid] of
525	[] ->
526	    case C of
527		[{_,Mirror}|_] ->
528		    connect(G, Cs, ExtrudeDist, Wid, We0, Closed);
529		[{_,Face}|_] ->
530		    connect(G, Cs, ExtrudeDist, Wid, We0, [Face|Closed])
531	    end;
532	[_] ->
533	    extrude_problem();
534	[Va0,Vb0] ->
535	    case digraph_get_path(G, Va0, Vb0) of
536		[{_,Mirror}|_] ->
537		    connect(G, Cs, ExtrudeDist, Wid, We0, Closed);
538		[{Va,Face}|Path0] ->
539		    Path = [V || {V,_} <- Path0],
540		    N = wings_face:normal(Face, We0),
541		    We = connect_inner(Va, Path, N, Face, ExtrudeDist, We0),
542		    connect(G, Cs, ExtrudeDist, Wid, We, Closed)
543	    end
544    end;
545connect(_, [], ExtrudeDist, _, We0, Closed) ->
546    We = wings_extrude_face:faces(Closed, We0),
547    move_vertices(Closed, ExtrudeDist, We).
548
549digraph_get_path(G, Va, Vb) ->
550    case digraph:get_path(G, Va, Vb) of
551	false -> digraph:get_path(G, Vb, Va);
552	Path -> Path
553    end.
554
555connect_inner({new,Va}, [Va,Vb,{new,Vb}], N, Face, ExtrudeDist, We0) ->
556    [{EdgeThrough,_,_}] = wings_vertex:edge_through(Va, Vb, We0),
557    {We1,TempE} = wings_edge:fast_cut(EdgeThrough, default, We0),
558    {We2,Edge} = wings_vertex:force_connect(Vb, Va, Face, We1),
559    #we{vp=Vtab} = We2,
560    APos = array:get(Va, Vtab),
561    BPos = array:get(Vb, Vtab),
562    Vec = e3d_vec:sub(APos, BPos),
563    Pos1 = e3d_vec:add_prod(BPos, e3d_vec:cross(Vec, N), ExtrudeDist),
564    {We3,NewE} = wings_edge:fast_cut(Edge, Pos1, We2),
565    Pos2 = e3d_vec:add_prod(APos, e3d_vec:cross(Vec, N), ExtrudeDist),
566    We4 = wings_edge:dissolve_edge(TempE, We3),
567    {We,_} = wings_edge:fast_cut(NewE, Pos2, We4),
568    wings_we_util:validate(We),
569    We;
570connect_inner({new,V}, [V|[B,C,_|_]=Next], N, Face, ExtrudeDist, We0) ->
571    {We1,Current} = connect_one_inner(V, V, B, C, N, Face, ExtrudeDist, We0),
572    #we{vp=Vtab} = We2 = connect_inner(Current, Next, N, Face, ExtrudeDist, We1),
573    Edge = wings_vertex:fold(
574	     fun(E, _, R, A) ->
575		     case wings_vertex:other(V, R) of
576			 Current -> E;
577			 _ -> A
578		     end
579	     end, none, V, We2),
580    VPos = array:get(V, Vtab),
581    BPos = array:get(B, Vtab),
582    Vec = e3d_vec:sub(VPos, BPos),
583    Pos = e3d_vec:add_prod(VPos, e3d_vec:cross(Vec, N), ExtrudeDist),
584    {We,_} = wings_edge:fast_cut(Edge, Pos, We2),
585    We;
586connect_inner({new,_}, [A|[B,C]], _, Face, _, We0) ->
587    {We1,Edge} = wings_vertex:force_connect(C, A, Face, We0),
588    #we{vp=Vtab} = We1,
589    APos = array:get(A, Vtab),
590    BPos = array:get(B, Vtab),
591    CPos = array:get(C, Vtab),
592    Pos = e3d_vec:add(APos, e3d_vec:sub(CPos, BPos)),
593    {We,_} = wings_edge:fast_cut(Edge, Pos, We1),
594    We;
595connect_inner(C, [B|[A,{new,_}]], N, Face, ExtrudeDist, We0) ->
596    {We1,Edge} = wings_vertex:force_connect(A, C, Face, We0),
597    #we{vp=Vtab} = We1,
598    APos = array:get(A, Vtab),
599    BPos = array:get(B, Vtab),
600    Vec = e3d_vec:sub(BPos, APos),
601    Pos = e3d_vec:add_prod(APos, e3d_vec:cross(Vec, N), ExtrudeDist),
602    {We,_} = wings_edge:fast_cut(Edge, Pos, We1),
603    We;
604connect_inner(Current0, [A|[B,C,_|_]=Next], N, Face, ExtrudeDist, We0) ->
605    {We,Current} = connect_one_inner(Current0, A, B, C, N, Face, ExtrudeDist, We0),
606    connect_inner(Current, Next, N, Face, ExtrudeDist, We);
607connect_inner(Current, [_|[_,_]=Next], N, Face, ExtrudeDist, We) ->
608    connect_inner(Current, Next, N, Face, ExtrudeDist, We);
609connect_inner(Current, [_,Last], _, Face, _, We0) ->
610    {We,_} = wings_vertex:force_connect(Last, Current, Face, We0),
611    We.
612
613connect_one_inner(Current, A, B, C, N, Face, ExtrudeDist, We0) ->
614    {We1,Edge} = wings_vertex:force_connect(B, Current, Face, We0),
615    #we{vp=Vtab} = We1,
616    Pos = new_vertex_pos(A, B, C, N, ExtrudeDist, Vtab),
617    wings_edge:fast_cut(Edge, Pos, We1).
618
619move_vertices([Face|Fs], ExtrudeDist, #we{vp=Vtab0}=We0) ->
620    N = wings_face:normal(Face, We0),
621    Vs = wings_face:vertices_ccw(Face, We0),
622    Vtab = move_vertices(Vs, Vs, N, ExtrudeDist, Vtab0, Vtab0),
623    We = We0#we{vp=Vtab},
624    move_vertices(Fs, ExtrudeDist, We);
625move_vertices([], _, We) -> We.
626
627move_vertices([Va|[Vb,Vc|_]=Vs], First, N, ExtrudeDist, OldVtab, Vtab0) ->
628    Pos = new_vertex_pos(Va, Vb, Vc, N, ExtrudeDist, OldVtab),
629    Vtab = array:set(Vb, wings_util:share(Pos), Vtab0),
630    move_vertices(Vs, First, N, ExtrudeDist, OldVtab, Vtab);
631move_vertices([Va,Vb], [Vc,Vd|_], N, ExtrudeDist, OldVtab, Vtab) ->
632    move_vertices([Va,Vb,Vc,Vd], [], N, ExtrudeDist, OldVtab, Vtab);
633move_vertices([_,_], [], _, _, _, Vtab) -> Vtab.
634
635new_vertex_pos(A, B, C, N, ExtrudeDist, Vtab) ->
636    APos = array:get(A, Vtab),
637    BPos = array:get(B, Vtab),
638    CPos = array:get(C, Vtab),
639    VecA0 = e3d_vec:norm_sub(APos, BPos),
640    VecB0 = e3d_vec:norm_sub(BPos, CPos),
641    VecA = e3d_vec:norm(e3d_vec:cross(VecA0, N)),
642    VecB = e3d_vec:norm(e3d_vec:cross(VecB0, N)),
643    Vec = average(VecA, VecB),
644    e3d_vec:add_prod(BPos, Vec, ExtrudeDist).
645
646average(Na, Nb) ->
647    N = e3d_vec:norm(e3d_vec:add(Na, Nb)),
648    case e3d_vec:dot(N, Na) of
649	Dot when abs(Dot) < 1.0E-6 ->
650	    e3d_vec:add(Na, Nb);
651	Dot ->
652	    e3d_vec:divide(N, Dot)
653    end.
654
655scale_tv(Tv, ExtrudeDist) ->
656    S = 1.0 / ExtrudeDist,
657    scale_tv_1(Tv, S, []).
658
659scale_tv_1([{Vec,Vs}|T], S, Acc) ->
660    scale_tv_1(T, S, [{e3d_vec:mul(Vec, S),Vs}|Acc]);
661scale_tv_1([], _, Acc) -> Acc.
662
663-spec min_dist_from_edges(Edges, #we{}) -> float() when
664      Edges :: nonempty_list(edge_num()).
665
666min_dist_from_edges(Edges0, We) ->
667    Vs = wings_vertex:from_edges(Edges0, We),
668    Edges = wings_edge:from_vs(Vs, We),
669    min_dist_from_edges_1(Edges, We, infinite).
670
671min_dist_from_edges_1([E|Es], #we{es=Etab,vp=Vtab}=We, D0) ->
672    #edge{vs=Va,ve=Vb} = array:get(E, Etab),
673    D = e3d_vec:dist(array:get(Va, Vtab), array:get(Vb, Vtab)),
674    min_dist_from_edges_1(Es, We, min(D0, D));
675min_dist_from_edges_1([], _, D) -> D.
676