1%%
2%%  wings_edge.erl --
3%%
4%%     This module contains most edge command and edge utility functions.
5%%
6%%  Copyright (c) 2001-2011 Bjorn Gustavsson.
7%%
8%%  See the file "license.terms" for information on usage and redistribution
9%%  of this file, and for a DISCLAIMER OF ALL WARRANTIES.
10%%
11%%     $Id$
12%%
13
14-module(wings_edge_cmd).
15
16%% Commands.
17-export([menu/3,command/2]).
18-export([hardness/2,set_color/2]).
19-export([loop_cut_partition/2]).
20
21-define(NEED_OPENGL, 1).
22-include("wings.hrl").
23
24-import(lists, [foldl/3,mapfoldl/3,reverse/1,sort/1]).
25-import(e3d_vec, [add/2,sub/2,neg/1,norm/1,len/1,
26		  average/2, dot/2,cross/2]).
27
28menu(X, Y, St) ->
29    Dir = wings_menu_util:directions(St),
30    Menu = [{?__(2,"Move"),{move,Dir},[],[magnet]},
31	    wings_menu_util:rotate(St),
32	    wings_menu_util:scale(St),
33	    {?__(3,"Slide"), slide,
34	     ?__(4,"Slide edges along neighbor edges")},
35	    separator,
36	    {?__(5,"Extrude"),{extrude,Dir}},
37	    {?__(22,"Crease"),crease,
38	     ?__(23,"Extrusion commonly used for adding wrinkles to organic models")},
39	    separator,
40	    wings_menu_util:flatten(),
41	    separator,
42	    cut_line(St),
43	    {?__(6,"Connect"),connect(),
44	     	{?__(7,"Create a new edge by connecting midpoints of selected edges"), [],
45		 ?__(25,"Create a new edge and slides it along neighbor edges")},[]},
46	    {?__(8,"Bevel"),bevel,
47	     ?__(9,"Round off selected edges")},
48	    separator,
49	    {?__(10,"Dissolve"), dslv(),
50		{?__(11,"Eliminate selected edges"), [],
51		 ?__(50,"Eliminate selected edges and remove remaining isolated verts")},[]},
52	    {?__(12,"Collapse"),collapse_fun(),
53	     	{?__(13,"Delete edges, replacing them with vertices"), [],
54		 ?__(21,"Delete edges, replacing them with vertices and remove any newly created isolated vertices")},[]},
55	    separator,
56	    {?__(14,"Hardness"),
57	     {hardness,[{?__(15,"Soft"),soft},
58			        {?__(16,"Hard"),hard},
59			        {?__(24,"Invert"),invert}
60			        ]}},
61	    separator,
62	    {?__(17,"Loop Cut"),loop_cut,
63	     ?__(18,"Cut into two objects along edge loop")},
64	    separator,
65	    {?__(19,"Vertex Color"),vertex_color,
66	     ?__(20,"Apply vertex colors to selected edges")}],
67    wings_menu:popup_menu(X, Y, edge, Menu).
68
69connect() ->
70    fun
71	(1, _Ns) -> {edge,connect};
72	(3, _Ns) -> {edge,connect_slide};
73	(_, _) -> ignore
74    end.
75dslv() ->
76    fun
77	(1, _Ns) -> {edge,dissolve};
78	(3, _Ns) -> {edge,clean_dissolve};
79	(_, _) -> ignore
80    end.
81collapse_fun() ->
82    fun
83	(1, _Ns) -> {edge,collapse};
84	(3, _Ns) -> {edge,clean_collapse};
85	(_, _) -> ignore
86    end.
87cut_line(#st{sel=[{_,Es}]}) ->
88    case gb_sets:size(Es) of
89	1 -> cut_fun();
90	_ -> plain_cut_menu()
91    end;
92cut_line(_) -> plain_cut_menu().
93
94plain_cut_menu() ->
95    {cut_command(),{cut,cut_entries()},
96     ?__(2,"Cut into edges of equal length")}.
97
98cut_fun() ->
99    F = fun(help, _Ns) ->
100		{?__(1,"Cut into edges of equal length"),[],
101		 ?__(2,"Cut at arbitrary position")};
102	   (1, _Ns) -> cut_entries();
103	   (2, _) -> ignore;
104	   (3, _) -> {edge,cut_pick}
105	end,
106    {cut_command(),{cut,F}}.
107
108cut_command() ->
109    ?__(1,"Cut").
110
111cut_entries() ->
112    [cut_entry(2),
113     cut_entry(3),
114     cut_entry(4),
115     cut_entry(5),
116     separator,
117     cut_entry(10),
118     separator,
119     cut_ask_entry()].
120
121cut_entry(N) ->
122    Str = integer_to_list(N),
123    {Str,N,?__(1,"Cut into ") ++ Str ++ ?__(2," edges of equal length")}.
124
125cut_ask_entry() ->
126    {?__(2,"Enter Number..."), ask, ?__(1, "Cut into <N> segments")}.
127
128%% Edge commands.
129command(bevel, St) ->
130    ?SLOW(wings_extrude_edge:bevel(St));
131command({extrude,Type}, St) ->
132    ?SLOW(wings_extrude_edge:extrude(Type, St));
133command({flatten,Plane}, St) ->
134    flatten(Plane, St);
135command(slide, St) ->
136    slide(St);
137command(cut_pick, St) ->
138    cut_pick(St);
139command({cut,ask}, St) ->
140    wings_dialog:ask(cut_command(),
141		     [{?__(1,"Segments"), 2, []}],
142		     fun([Ret]) -> cut(Ret, St) end);
143command({cut,Num}, St) ->
144    {save_state,cut(Num, St)};
145command(connect, St) ->
146    {save_state,connect(St)};
147command(connect_slide, St) ->
148    connect_slide(St);
149command(clean_dissolve, St) ->
150    {save_state,clean_dissolve(St)};
151command(dissolve, St) ->
152    {save_state,dissolve(St)};
153command(collapse, St) ->
154    {save_state, wings_collapse:uniform_collapse(St)};
155command(clean_collapse, St0) ->
156    St = wings_collapse:clean_uniform_collapse(St0),
157    {save_state,St};
158command({hardness,Type}, St) ->
159    {save_state,hardness(Type, St)};
160command(loop_cut, St) ->
161    ?SLOW({save_state,loop_cut(St)});
162command(auto_smooth, St) ->
163    wings_body:auto_smooth(St);
164command({move,Type}, St) ->
165    wings_move:setup(Type, St);
166command({rotate,Type}, St) ->
167    wings_rotate:setup(Type, St);
168command({scale,Type}, St) ->
169    wings_scale:setup(Type, St);
170command(crease, St) ->
171    ?SLOW(wings_extrude_edge:crease(St));
172command(vertex_color, St) ->
173    wings_color:choose(fun(Color) ->
174			       set_color(Color, St)
175		       end).
176%%%
177%%% The Connect command.
178%%%
179
180connect(St) ->
181    wings_sel:map_update_sel(fun connect/2, St).
182
183connect(Es0, We0) ->
184    Es1 = gb_sets:to_list(Es0),
185    Es = remove_nonconnectable(Es1, Es0, We0, []),
186    {Vs,We1} = cut_edges(Es, We0),
187    We2 = wings_vertex_cmd:connect(Vs, We1),
188    Sel = wings_we:new_items_as_gbset(edge, We1, We2),
189    We = wings_edge:dissolve_isolated_vs(Vs, We2),
190    {We,Sel}.
191
192connect_slide(St0) ->
193    St = wings_sel:map_update_sel(fun connect/2, St0),
194    slide(St).
195
196cut_edges(Es, We) ->
197    mapfoldl(fun(Edge, W0) ->
198		     {W,V} = wings_edge:cut(Edge, 2, W0),
199		     {V,W}
200	     end, We, Es).
201
202%% Remove from the selection all edges that will obviously not get connected,
203%% to avoid having those edges first cut and later joined again.
204
205remove_nonconnectable([E|Es], Sel, We, Acc) ->
206    Fs = wings_face:from_edges([E], We),
207    NearEs = gb_sets:delete(E, gb_sets:from_ordset(wings_face:to_edges(Fs, We))),
208    case gb_sets:is_disjoint(Sel, NearEs) of
209	false ->
210	    remove_nonconnectable(Es, Sel, We, [E|Acc]);
211	true ->
212	    %% None of edges in the two faces on either side of this
213	    %% edge is selected. Therefore, don't even bother cutting
214	    %% this edge since there is no chance that the new vertex
215	    %% will get connected.
216	    remove_nonconnectable(Es, Sel, We, Acc)
217    end;
218remove_nonconnectable([], _, _, Acc) -> Acc.
219
220%%%
221%%% The Vertex Color command.
222%%%
223
224set_color(Color, St) ->
225    wings_sel:map(fun(Es, We) ->
226			  wings_va:set_edge_color(Es, Color, We)
227		  end, St).
228
229%%%
230%%% The Cut command.
231%%%
232
233cut(N, #st{selmode=edge}=St) when N > 1 ->
234    wings_sel:map_update_sel(
235      fun(Edges, We0) ->
236	      We = cut_edges(Edges, N, We0),
237	      S = wings_we:new_items_as_gbset(vertex, We0, We),
238	      {We,S}
239      end, vertex, St);
240cut(_, St) -> St.
241
242cut_edges(Edges, N, We0) ->
243    gb_sets:fold(fun(Edge, W0) ->
244			 {We,_} = wings_edge:cut(Edge, N, W0),
245			 We
246		 end, We0, Edges).
247
248%%%
249%%% Cut at an arbitrary position.
250%%%
251
252cut_pick(#st{sel=[_]}=St0) ->
253    MF = fun(Es, We) ->
254                 case gb_sets:to_list(Es) of
255                     [E] -> cut_pick_make_tvs(E, We);
256                     _ -> cut_pick_error()
257                 end
258         end,
259    St = wings_sel:map_update_sel(MF, vertex, St0),
260    Units = [{percent,{0.0,1.0}}],
261    Flags = [{initial,[0]}],
262    DF = fun(#we{temp=General}) -> General end,
263    wings_drag:general(DF, Units, Flags, St);
264cut_pick(#st{}) ->
265    cut_pick_error().
266
267-spec cut_pick_error() -> no_return().
268cut_pick_error() ->
269    wings_u:error_msg(?__(1,"Only one edge can be cut at an arbitrary position.")).
270
271cut_pick_make_tvs(Edge, #we{es=Etab,vp=Vtab,next_id=NewV}=We) ->
272    #edge{vs=Va,ve=Vb} = array:get(Edge, Etab),
273    Start = array:get(Va, Vtab),
274    End = array:get(Vb, Vtab),
275    Dir = e3d_vec:sub(End, Start),
276    Char = {7,7,3.0,3.0,7.0,0.0,
277	    <<2#01111100,
278	     2#10000010,
279	     2#10000010,
280	     2#10000010,
281	     2#10000010,
282	     2#10000010,
283	     2#01111100>>},
284    Fun = fun(I, D) -> cut_pick_marker(I, D, Edge, We, Start, Dir, Char) end,
285    Sel = gb_sets:singleton(NewV),
286    {We#we{temp=Fun},Sel}.
287
288cut_pick_marker([I], D, Edge, We0, Start, Dir, Char) ->
289    Pos = e3d_vec:add_prod(Start, Dir, I),
290    {MM,PM,ViewPort} = wings_u:get_matrices(0, original),
291    {Sx,Sy,_} = e3d_transform:project(Pos, MM, PM, ViewPort),
292    Draw = fun(Ds) ->
293		   gl:pushAttrib(?GL_ALL_ATTRIB_BITS),
294		   gl:color3f(1.0, 0.0, 0.0),
295		   gl:shadeModel(?GL_FLAT),
296		   gl:disable(?GL_DEPTH_TEST),
297		   gl:matrixMode(?GL_PROJECTION),
298		   gl:pushMatrix(),
299		   gl:loadIdentity(),
300		   {W,H} = wings_wm:win_size(),
301                   Ortho = e3d_transform:ortho(0.0, float(W), 0.0, float(H), -1.0, 1.0),
302                   gl:loadMatrixd(e3d_transform:matrix(Ortho)),
303		   gl:matrixMode(?GL_MODELVIEW),
304		   gl:pushMatrix(),
305		   gl:loadIdentity(),
306		   gl:rasterPos2f(Sx, Sy),
307		   wings_io:draw_bitmap(Char),
308		   gl:popMatrix(),
309		   gl:matrixMode(?GL_PROJECTION),
310		   gl:popMatrix(),
311		   gl:popAttrib(),
312                   Ds
313	   end,
314    {We,_} = wings_edge:fast_cut(Edge, Pos, We0),
315    D#dlo{hilite={edge, {call_in_this_win,wings_wm:this(),Draw}},src_we=We};
316cut_pick_marker({finish,[I]}, D0, Edge, We, Start, Dir, Char) ->
317    D = cut_pick_marker([I], D0, Edge, We, Start, Dir, Char),
318    D#dlo{vs=none,hilite=none}.
319
320%%%
321%%% Clean Dissolve
322%%%
323
324clean_dissolve(St0) ->
325    St = wings_sel:map(fun(Es, #we{es=Etab}=We) ->
326            E1 = gb_sets:size(Es),
327            E2 = wings_util:array_entries(Etab),
328            case E1 =:= E2 of
329              false ->
330                    IsolatedVs1 = wings_vertex:isolated(We),
331                    We1 = wings_edge:dissolve_edges(Es, We),
332                    IsolatedVs2 = wings_vertex:isolated(We1),
333                    C = IsolatedVs2 -- IsolatedVs1,
334                    wings_edge:dissolve_isolated_vs(C, We1);
335              true ->
336                   #we{}
337            end
338    end, St0),
339    wings_sel:clear(St).
340
341%%%
342%%% The Dissolve command.
343%%%
344
345dissolve(St0) ->
346    St = wings_sel:map(fun(Es, #we{es=Etab}=We) ->
347            E1 = gb_sets:size(Es),
348            E2 = wings_util:array_entries(Etab),
349            case E1 =:= E2 of
350              false ->
351                   wings_edge:dissolve_edges(Es, We);
352              true ->
353                   #we{}
354            end
355    end, St0),
356    wings_sel:clear(St).
357
358%%%
359%%% The Hardness command.
360%%%
361
362hardness(invert, St) ->
363    wings_sel:map(fun(Edges, #we{he=Htab0}=We) ->
364			  WereSoft = gb_sets:subtract(Edges,Htab0),
365			  Outside = gb_sets:subtract(Htab0,Edges),
366			  NowHard = gb_sets:union(WereSoft,Outside),
367			  We#we{he=NowHard}
368		  end, St);
369hardness(soft, St) ->
370    wings_sel:map(fun(Edges, #we{he=Htab0}=We) ->
371			  Htab = gb_sets:difference(Htab0, Edges),
372			  We#we{he=Htab}
373		  end, St);
374hardness(hard, St) ->
375    wings_sel:map(fun(Edges, #we{he=Htab0}=We) ->
376			  Htab = gb_sets:union(Htab0, Edges),
377			  We#we{he=Htab}
378		  end, St).
379
380%%%
381%%% The Slide command.
382%%%
383
384slide(St0) ->
385    Mode = wings_pref:get_value(slide_mode, relative),
386    Stop = wings_pref:get_value(slide_stop, false),
387    State = {Mode,none,Stop},
388    SUp = SDown = SN = SBi = {0.0,0.0,0.0},
389
390    %% FIXME: The use of the process dictionary (wings_slide) will
391    %% not work when each #we{} is stored in its own process.
392    %%
393    %% FIXME: Someone who understands the Up, Dw, N, and Bi parameters
394    %% should rewrite this code in a way that can be parallelized
395    %% (avoid the use of wings_sel:mapfold/3, since it forces
396    %% sequential evaluation in each process).
397
398    {St,{_,_,_,_,MinUp,MinDw}} =
399	wings_sel:mapfold(
400	  fun(EsSet, We, {Up0,Dw0,N0,Bi0,MinUp,MinDw}) ->
401		  LofEs0 = wings_edge_loop:partition_edges(EsSet, We),
402		  LofEs = reverse(sort([{length(Es),Es} || Es <- LofEs0])),
403		  {{Slides,MUp,MDw},Up,Dw,N,Bi} =
404		      slide_setup_edges(LofEs,Up0,Dw0,N0,Bi0,We,
405					{gb_trees:empty(),MinUp,MinDw}),
406		  {We#we{temp=make_slide_tv(Slides, State)},
407                   {Up,Dw,N,Bi,MUp,MDw}}
408	  end, {SUp, SDown, SN, SBi, unknown,unknown}, St0),
409    Units = slide_units(State,MinUp,MinDw),
410    Flags = [{mode,{slide_mode(MinUp,MinDw),State}},{initial,[0]}],
411    DF = fun(_, #we{temp=Tv}) -> Tv end,
412    wings_drag:fold(DF, Units, Flags, St).
413
414slide_mode(MinUp,MinDw) ->
415    fun(help, State)		  ->	slide_help(State);
416       ({key,$1}, {relative,F,S}) ->	{absolute,F,S};
417       ({key,$1}, {absolute,F,S}) ->	{relative,F,S};
418       ({key,$2}, {Mode,none,S})  ->
419	    case get(wings_slide) of
420		undefined ->
421		    {Mode,none,S};
422		Dx when Dx >= 0 ->
423		    {Mode,positive,S};
424		_ ->
425		    {Mode,negative,S}
426	    end;
427       ({key,$2}, {Mode,_,S})	  ->	{Mode,none,S};
428       ({key,$3}, {Mode,F,false}) ->	{Mode,F,true};
429       ({key,$3}, {Mode,F,true})  ->	{Mode,F,false};
430
431       (units, NewState) ->
432	    slide_units(NewState,MinUp,MinDw);
433
434       (done, {NewMode,_,NewStop})->
435	    wings_pref:set_value(slide_mode, NewMode),
436	    wings_pref:set_value(slide_stop, NewStop),
437	    erase(wings_slide);
438
439       (_, _) -> none
440    end.
441
442slide_units({absolute,_,false},_,_) -> [distance];
443slide_units({absolute,_Freeze,true},MinUp,MinDw) ->
444    [{distance, {-MinUp, MinDw}}];
445slide_units({relative,_,false},_,_) -> [percent];
446slide_units({relative,_,true},_,_) -> [{percent,{-1.0,1.0}}].
447
448slide_help({Mode,Freeze,Stop}) ->
449    ["[1] ",slide_help_mode(Mode),
450     "  [2] ",slide_help_freeze(Freeze),
451     "  [3] ",slide_help_stop(Stop)].
452
453slide_help_mode(relative) -> ?__(1,"Absolute");
454slide_help_mode(absolute) -> ?__(2,"Relative").
455
456slide_help_freeze(none)   -> ?STR(slide_help_mode,3,"Freeze direction");
457slide_help_freeze(_)	  -> ?STR(slide_help_mode,4,"Thaw direction").
458
459slide_help_stop(false)	  -> ?STR(slide_help_mode,5,"Stop at other edges");
460slide_help_stop(true)	  -> ?STR(slide_help_mode,6,"Continue past other edges").
461
462make_slide_tv(Slides, State) ->
463    Vs = gb_trees:keys(Slides),
464    {Vs,make_slide_fun(Vs, Slides, State)}.
465
466%% The calculating fun
467slide_fun(Dx0,{Mode,Freeze,_Stop}, Slides) ->
468    {Dx,I} =
469	case Freeze of	 %% 3 = UP, 2 = Down
470	    none when Dx0 >= 0 -> {Dx0, 3};
471	    none ->		  {-Dx0,2};
472	    positive -> 	  {Dx0, 3};
473	    negative -> 	  {-Dx0,2}
474	end,
475    case Mode of
476	relative ->
477	    fun(V,A) ->
478		    Slide = gb_trees:get(V, Slides),
479		    {Dir, Len, Count} = element(I, Slide),
480		    ScaleDir = e3d_vec:mul(e3d_vec:norm(Dir), Dx*(Len/Count)),
481		    [{V,e3d_vec:add(element(1,Slide), ScaleDir)}|A]
482	    end;
483	absolute ->
484	    fun(V,A) ->
485		    Slide = gb_trees:get(V, Slides),
486		    {Dir, _Len, _C} = element(I, Slide),
487		    ScaleDir = e3d_vec:mul(e3d_vec:norm(Dir), Dx),
488		    [{V,e3d_vec:add(element(1,Slide), ScaleDir)}|A]
489	    end
490    end.
491
492make_slide_fun(Vs, Slides, State) ->
493    fun([Dx|_],Acc) ->
494	    put(wings_slide, Dx),
495	    Fun = slide_fun(Dx,State,Slides),
496	    foldl(Fun, Acc, Vs);
497       (new_mode_data, {NewState,_}) ->
498	    make_slide_fun(Vs,Slides,NewState);
499       (_,_) ->
500	    make_slide_fun(Vs,Slides,State)
501    end.
502
503slide_setup_edges([{_Sz,Es0}|LofEs],GUp0,GDw0,GN0,GBi0,We,Acc0) ->
504    Parts = slide_part_loop(Es0,We),
505    {GUp,GDw,GN,GBi,Acc} = slide_add_edges(Parts,GUp0,GDw0,GN0,GBi0,Acc0),
506    slide_setup_edges(LofEs,GUp,GDw,GN,GBi,We,Acc);
507slide_setup_edges([], Up,Dw,N,Bi,_,Slides) ->
508    {Slides,Up,Dw,N,Bi}.
509
510slide_add_edges([{LUp,LDw,LN,Es}|Parts],GUp,GDw,GN,GBi,Acc0) ->
511    %%	  io:format("UDN ~p ~p ~p ~p~n", [len(LUp), len(LDw), len(LN), length(Es)]),
512    Count  = length(Es)/2,
513    Vec1   = norm(LUp), LenUp = len(LUp),
514    Vec2   = norm(LDw), LenDw = len(LDw),
515    Vec3   = norm(LN),	LenN  = len(LN),
516    Bi	   = norm(cross(Vec1,Vec3)),
517    Rotation = dot(Bi, norm(GBi)),
518%    io:format("BIs ~p ~p ~p ~p ~n", [Bi, norm(GBi), Rotation, Count]),
519    case (LenUp/Count > 0.707) and (LenN/Count > 0.707) and
520	(abs(Rotation) > 0.707) of
521	true ->  %% Edge rings
522	    Acc  = slide_dirs(Es,Rotation,Acc0),
523	    slide_add_edges(Parts,GUp,GDw,GN,GBi,Acc);
524	false ->
525	    case (LenUp >= 1) or (LenDw >= 1)
526		or (LenN =< 1) or (Count < 4) of
527		true -> %% Make sure up is up..
528		    DotUp1 = dot(Vec1,norm(GUp)),
529		    %%DotDw1 = dot(Vec1,norm(GDw)),
530		    %%DotUp2 = dot(Vec2,norm(GUp)),
531		    DotDw2 = dot(Vec2,norm(GDw)),
532		    if (DotUp1 >= 0) and (DotDw2 >= 0) ->
533			    Acc  = slide_dirs(Es,1.0,Acc0),
534			    slide_add_edges(Parts,add(Vec1, GUp),add(Vec2,GDw),
535					    GN,add(GBi,Bi), Acc);
536		       (DotUp1 =< 0) and (DotDw2 =< 0) ->
537			    Acc  = slide_dirs(Es,-1.0,Acc0),
538			    slide_add_edges(Parts,add(neg(Vec1),GUp),
539					    add(neg(Vec2),GDw),
540					    GN,add(GBi,neg(Bi)),Acc);
541		       true ->
542			    Acc  = slide_dirs(Es,-1.0,Acc0),
543			    slide_add_edges(Parts,add(Vec2,GUp),
544					    add(Vec1,GDw),GN,add(GBi,Bi),Acc)
545		    end;
546		false ->
547		    %% Probably a loop, make sure it goes in/out and not out/in.
548		    %% BUGBUG this isn't good enough..
549		    Norm0 = Vec3,
550		    Dot = dot(Norm0,norm(GUp)),
551		    Norm = if Dot < 0.0 -> neg(Norm0); true -> Norm0 end,
552		    Acc = slide_dirs(Es,Dot,Acc0),
553		    slide_add_edges(Parts,GUp,add(Norm, GDw),GN,GBi,Acc)
554	    end
555    end;
556slide_add_edges([],GUp,GDw,GN,GBi,Acc) ->
557    {GUp,GDw,GN,GBi,Acc}.
558
559slide_dirs([{V1,V1dir},{V2,V2dir}|Es],Up,{Acc0,MinU0,MinD0}) ->
560    {_,{_,V1M1},{_,V1M2}} = V1dir,
561    {_,{_,V2M1},{_,V2M2}} = V2dir,
562    case Up < 0 of
563	true ->
564	    MinU = lists:min([MinU0,V1M2,V2M2]),
565	    MinD = lists:min([MinD0,V1M1,V2M1]),
566	    Acc1 = add_slide_vertex(V1,swap(V1dir), Acc0),
567	    Acc  = add_slide_vertex(V2,swap(V2dir), Acc1),
568	    slide_dirs(Es,Up,{Acc,MinU,MinD});
569	false ->
570	    MinU = lists:min([MinU0,V1M1,V2M1]),
571	    MinD = lists:min([MinD0,V1M2,V2M2]),
572	    Acc1 = add_slide_vertex(V1,V1dir,Acc0),
573	    Acc  = add_slide_vertex(V2,V2dir,Acc1),
574	    slide_dirs(Es,Up,{Acc,MinU,MinD})
575    end;
576slide_dirs([],_,Acc) -> Acc.
577
578slide_part_loop(Es,We) ->
579    Def   = {0.0,0.0,0.0},
580    Eis   = slide_gather_info(Es,We,[]),
581    Parts = wings_edge_loop:edge_links(Es,We),
582    [slide_dir(P,Eis,Def,Def,Def,[]) || P <- Parts].
583
584slide_gather_info([Edge|Es],We=#we{es=Etab,vp=Vtab},Acc) ->
585    #edge{vs=V1,ve=V2,ltpr=LP,ltsu=LS,lf=LF,rtpr=RP,rtsu=RS,rf=RF} =
586	array:get(Edge, Etab),
587    A1 = other(V1, array:get(RP, Etab)),
588    B1 = other(V1, array:get(LS, Etab)),
589    A2 = other(V2, array:get(RS, Etab)),
590    B2 = other(V2, array:get(LP, Etab)),
591    N1 = wings_face:normal(LF,We), N2 = wings_face:normal(RF,We),
592    N = norm(average(N2,N1)),
593    V1pos = array:get(V1, Vtab), V2pos = array:get(V2, Vtab),
594    A1pos = array:get(A1, Vtab), A2pos = array:get(A2, Vtab),
595    B1pos = array:get(B1, Vtab), B2pos = array:get(B2, Vtab),
596    E1v1  = sub(A1pos,V1pos), E2v1 = sub(B1pos,V1pos),
597    E1v2  = sub(A2pos,V2pos), E2v2 = sub(B2pos,V2pos),
598    NE1v1 = norm(E1v1), NE2v1 = norm(E2v1),
599    NE1v2 = norm(E1v2), NE2v2 = norm(E2v2),
600
601    E1 = norm(average(NE1v1,NE1v2)), E2 = norm(average(NE2v1,NE2v2)),
602    New = {V1, {V1pos,{NE1v1,len(E1v1)},{NE2v1,len(E2v1)}},
603	   V2, {V2pos,{NE1v2,len(E1v2)},{NE2v2,len(E2v2)}},
604	   E1, E2, N},
605    slide_gather_info(Es, We, [{Edge,New}|Acc]);
606slide_gather_info([], _, Acc) ->
607    gb_trees:from_orddict(sort(Acc)).
608
609slide_dir([Edge|R],Es,Up0,Dw0,N0,Acc) ->
610    {V1id,V1E,V2id,V2E,E1,E2,MyN} = find_edge(Edge,Es),
611    Up = add(Up0, E1),
612    Dw = add(Dw0, E2),
613    N  = add(N0, MyN),
614    slide_dir(R,Es,Up,Dw,N,[{V1id,V1E},{V2id,V2E}|Acc]);
615slide_dir([],_,Up,Dw,N,Acc) ->
616    {Up,Dw,N,Acc}.
617
618find_edge({Edge,V1,V2},Es) ->
619    case gb_trees:get(Edge, Es) of
620	{V1,_,V2,_,_,_,_} = R -> R;
621	{V2,V2E,V1,V1E,E2,E1,N} ->
622	    {V1,swap(V1E),V2,swap(V2E),E1,E2,N}
623    end.
624
625other(Vertex, #edge{vs=Vertex,ve=Other}) -> Other;
626other(Vertex, #edge{vs=Other,ve=Vertex}) -> Other.
627
628swap({Vpos,Ndir,Pdir}) ->
629    {Vpos,Pdir,Ndir}.
630
631add_slide_vertex(V,{Vpos,{Ndir,NL},{Pdir,PL}},Acc) ->
632    case gb_trees:lookup(V,Acc) of
633	none ->
634	    gb_trees:insert(V, {Vpos,{Ndir,NL,1},{Pdir,PL,1}},Acc);
635	{value, {_, {Ndir0,NL0,NC0},{Pdir0,PL0,PC0}}} ->
636	    New = {Vpos, {add(Ndir,Ndir0),NL+NL0,NC0+1},
637		   {add(Pdir,Pdir0),PL+PL0,PC0+1}},
638	    gb_trees:update(V, New, Acc)
639    end.
640
641%%%
642%%% The Loop Cut command.
643%%%
644
645loop_cut(St) ->
646    wings_sel:clone(fun loop_cut/2, body, St).
647
648loop_cut(Edges, #we{id=Id,fs=Ftab}=We0) ->
649    case loop_cut_partition(Edges, We0) of
650	[_] ->
651	    wings_u:error_msg(?__(1,"Edge loop doesn't divide object #~p "
652                                  "into two (or more) parts."),
653                              [Id]);
654	Parts0 ->
655	    %% We arbitrarily decide that the largest part of the object
656	    %% will be left unselected and will keep the name of the object.
657
658	    Parts1 = [{gb_sets:size(P),P} || P <- Parts0],
659	    Parts2 = reverse(sort(Parts1)),
660	    [_|Parts] = [gb_sets:to_list(P) || {_,P} <- Parts2],
661
662	    %% Also, this first part will also contain any sub-object
663	    %% that was not reachable from any of the edges. Therefore,
664	    %% we calculate the first part as the complement of the union
665	    %% of all other parts.
666
667	    FirstComplement = ordsets:union(Parts),
668	    First = ordsets:subtract(gb_trees:keys(Ftab), FirstComplement),
669
670	    We = wings_dissolve:complement(First, We0),
671            New = loop_cut_make_copies(Parts, We0),
672            {We,gb_sets:empty(),New}
673    end.
674
675loop_cut_make_copies([P|Parts], We0) ->
676    Sel = gb_sets:singleton(0),
677    We = wings_dissolve:complement(P, We0),
678    [{We,Sel,cut}|loop_cut_make_copies(Parts, We0)];
679loop_cut_make_copies([], _) -> [].
680
681-spec loop_cut_partition(Edges, #we{}) -> [wings_sel:face_set()] when
682      Edges ::  wings_sel:edge_set() | [wings_edge:edge_num()].
683loop_cut_partition(Edges, We) ->
684    AdjFaces = wings_face:from_edges(Edges, We),
685    loop_cut_partition(AdjFaces, Edges, We, []).
686
687loop_cut_partition(Faces0, Edges, We, Acc) ->
688    case gb_sets:is_empty(Faces0) of
689	true -> Acc;
690	false ->
691	    {AFace,Faces1} = gb_sets:take_smallest(Faces0),
692	    Reachable = collect_faces(AFace, Edges, We),
693	    Faces = gb_sets:difference(Faces1, Reachable),
694	    loop_cut_partition(Faces, Edges, We, [Reachable|Acc])
695    end.
696
697collect_faces(Face, Edges, We) ->
698    collect_faces(gb_sets:singleton(Face), We, Edges, gb_sets:empty()).
699
700collect_faces(Work0, We, Edges, Acc0) ->
701    case gb_sets:is_empty(Work0) of
702	true -> Acc0;
703	false ->
704	    {Face,Work1} = gb_sets:take_smallest(Work0),
705	    Acc = gb_sets:insert(Face, Acc0),
706	    Work = collect_maybe_add(Work1, Face, Edges, We, Acc),
707	    collect_faces(Work, We, Edges, Acc)
708    end.
709
710collect_maybe_add(Work, Face, Edges, We, Res) ->
711    wings_face:fold(
712      fun(_, Edge, Rec, A) ->
713	      case gb_sets:is_member(Edge, Edges) of
714		  true -> A;
715		  false ->
716		      Of = wings_face:other(Face, Rec),
717		      case gb_sets:is_member(Of, Res) of
718			  true -> A;
719			  false -> gb_sets:add(Of, A)
720		      end
721	      end
722      end, Work, Face, We).
723
724%%%
725%%% The Flatten command.
726%%%
727
728flatten({'ASK',Ask}, St) ->
729    wings:ask(Ask, St, fun flatten/2);
730flatten({Plane,Center}, St) ->
731    flatten(Plane, Center, St);
732flatten(edge_loop, St) ->
733    {save_state,
734     wings_sel:map(
735       fun(Es, We0) ->
736	       EGroups = wings_edge_loop:partition_edges(Es, We0),
737	       foldl(fun(Edges, #we{vp=Vtab}=We) ->
738	           case wings_edge_loop:edge_loop_vertices(Edges,We) of
739	               [Vs] ->
740	                   Positions = [array:get(V, Vtab) || V <- Vs],
741	                   Plane = e3d_vec:normal(Positions),
742	                   wings_vertex:flatten(Vs, Plane, We);
743	               _ -> We
744	           end
745	       end, We0, EGroups)
746       end, St)};
747flatten(Plane, St) ->
748    flatten(Plane, average, St).
749
750flatten(Plane0, average, St) ->
751    Plane = wings_util:make_vector(Plane0),
752    {save_state,
753     wings_sel:map(
754       fun(Es, We0) ->
755	       EGroups = wings_edge_loop:partition_edges(Es, We0),
756	       foldl(fun(Edges, We) ->
757	           Vs = wings_edge:to_vertices(Edges, We),
758	           wings_vertex:flatten(Vs, Plane, We)
759	       end, We0, EGroups)
760       end, St)};
761flatten(Plane0, Center, St) ->
762    Plane = wings_util:make_vector(Plane0),
763    {save_state,
764     wings_sel:map(
765       fun(Es, We) ->
766	       Vs = wings_edge:to_vertices(Es, We),
767	       wings_vertex:flatten(Vs, Plane, Center, We)
768       end, St)}.
769