1%%
2%%  wpc_circularise.erl --
3%%
4%%    Plugin to flatten, equalise, and inflate open or closed edge loops
5%%    making them circular.
6%%
7%%  Copyright (c) 2008-2011 Richard Jones.
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%%
13
14-module(wpc_circularise).
15-export([init/0,menu/2,command/2]).
16-include_lib("src/wings.hrl").
17
18-import(lists, [member/2]).
19
20init() ->
21    true.
22
23%%%% Menu
24menu({Mode}, Menu) when Mode =:= edge; Mode =:= {auv,edge} ->
25    lists:reverse(parse(Menu, [], Mode, false));
26menu(_,Menu) ->
27    Menu.
28
29parse([], NewMenu, _, true) ->
30    NewMenu;
31parse([], NewMenu, Mode, false) ->
32    [circular_arc_menu(Mode), separator|NewMenu];
33parse([A = {_,loop_cut,_}|Rest], NewMenu, Mode, false) ->
34    parse(Rest, [A,circular_arc_menu(Mode)|NewMenu], Mode, true);
35parse([Elem|Rest], NewMenu, Mode, Found) ->
36    parse(Rest, [Elem|NewMenu], Mode, Found).
37
38circular_arc_menu(edge) ->
39    Name = ?__(1,"Circularise"),
40    Help = {?__(2,"Flatten, equalise, and inflate selected edge loops making them circular"),
41       ?__(6,"Specify using secondary selections"),
42       ?__(5,"Choose common plane to which loops will be flattened")},
43    F = fun
44      (1,_Ns) -> {edge,circularise};
45      (2,_Ns) -> {edge,circularise_center};
46      (3,_Ns) -> {edge,{circularise,{'ASK',[plane]}}}
47    end,
48    {Name, {circular, F}, Help, []};
49circular_arc_menu({auv,edge}) ->
50    {?__(1,"Circularise"),circularise,
51     ?__(2,"Flatten, equalise, and inflate selected edge loops making them circular")}.
52
53%%%% Commands
54command({Mode,circularise}, St) when Mode =:= edge; Mode =:= {auv,edge} ->
55    process_circ_cmd(find_plane, St);
56command({edge,{circularise,Plane}}, St) ->
57    process_circ_cmd(Plane, St);
58command({edge, circularise_center}, St) ->
59    process_cc_cmd(none, St);
60command({edge,{circularise_center,Data}}, St) ->
61    process_cc_cmd(Data, St);
62command(_, _) ->
63    next.
64
65process_circ_cmd(Plane, St0) ->
66    MF = fun(Edges, We) ->
67                 case wings_edge_loop:edge_loop_vertices(Edges, We) of
68                     [_|_] ->
69                         true;
70                     none ->
71                         Gs = wings_edge_loop:partition_edges(Edges, We),
72                         VsList = wings_edge_loop:edge_links(Edges, We),
73                         case check_partial_and_full_loops(VsList, We) of
74                             not_mixed when length(Gs) =:= length(VsList) ->
75                                 false;
76                             not_mixed ->
77                                 circ_sel_error_4();
78                             single_edge ->
79                                 circ_sel_error_3();
80                             mixed ->
81                                 circ_sel_error_4()
82                         end
83                 end
84         end,
85    RF = fun erlang:'or'/2,
86    IsCircle = wings_sel:dfold(MF, RF, false, St0),
87    Setup = case IsCircle of
88                true -> fun circle_setup/2;
89                false -> fun arc_setup/2
90            end,
91    case Plane of
92        {'ASK',Ask} ->
93            wings:ask(selection_ask(Ask), St0, Setup);
94        _ ->
95            Setup(Plane, St0)
96    end.
97
98process_cc_cmd(Data, #st{sel=[_]}=St0) ->
99    MF = fun(Edges, #we{id=Id}=We) ->
100                 case wings_edge_loop:edge_loop_vertices(Edges, We) of
101                     [Vs] ->
102                         {Id,gb_sets:to_list(Edges),Vs};
103                     _ ->
104                         none
105                 end
106         end,
107    RF = fun(Res, none) -> Res end,
108    case wings_sel:dfold(MF, RF, none, St0) of
109        none ->
110            process_cc_cmd_1(Data, St0);
111        {_,_,_}=OrigSel ->
112            %% Single closed loop, MMB.
113            F = fun({Center,Plane,RayPos}, St) ->
114                        circle_pick_all_setup(RayPos, Center, Plane, St)
115                end,
116            wings:ask(secondary_sel_ask(OrigSel), St0, F)
117    end;
118process_cc_cmd(Data, St) ->
119    process_cc_cmd_1(Data, St).
120
121process_cc_cmd_1(Data, St0) ->
122    MF = fun(Es, We) ->
123                 Edges = gb_sets:to_list(Es),
124                 Vs = wings_edge_loop:edge_links(Edges, We),
125                 EdgeGroups = wings_edge_loop:partition_edges(Edges, We),
126                 case check_partial_and_full_loops(Vs, We) of
127                     not_mixed when length(EdgeGroups) =:= length(Vs) ->
128                         ok;
129                     not_mixed ->
130                         circ_sel_error();
131                     single_edge ->
132                         circ_sel_error_3();
133                     mixed ->
134                         circ_sel_error()
135                 end
136         end,
137    RF = fun(ok, ok) -> ok end,
138    ok = wings_sel:dfold(MF, RF, ok, St0),
139    case Data of
140      {Plane,Center} ->
141            arc_center_setup(Plane, Center, St0);
142      _ ->
143            wings:ask(selection_ask([plane,arc_center]), St0,
144                      fun({Plane,Center}, St) ->
145                              arc_center_setup(Plane, Center, St)
146                      end)
147    end.
148
149%%%% Asks
150selection_ask(Asks) ->
151    Ask = selection_ask(Asks, []),
152    {Ask,[],[],[vertex,edge,face]}.
153
154selection_ask([],Ask) -> lists:reverse(Ask);
155
156selection_ask([plane|Rest],Ask) ->
157    Desc = ?__(1,"Pick plane"),
158    selection_ask(Rest, [{axis,Desc}|Ask]);
159
160selection_ask([center|Rest], Ask) ->
161    Desc = ?__(2,"Pick center point"),
162    selection_ask(Rest, [{point,Desc}|Ask]);
163
164selection_ask([arc_center|Rest], Ask) ->
165    Desc = ?__(3,"Pick point from which the center will be calculated relative to chosen plane and ends of edge selection"),
166    selection_ask(Rest, [{point,Desc}|Ask]).
167
168secondary_sel_ask(OrigSel) ->
169    Desc1 = ?__(1,"Select a single edge or vertex from the original edge loop marking the stable ray from the center point"),
170    Desc2 = ?__(2,"Pick center point"),
171    Desc3 = ?__(3,"Pick plane"),
172    Fun = fun(check, St) ->
173                  case check_selection(OrigSel, St) of
174                      {_,Point,Msg} ->
175                          {Point,Msg};
176                      Msg ->
177                          {none,Msg}
178                  end;
179             (exit, {_,_,St}) ->
180                  case check_selection(OrigSel, St) of
181                      {Result,_,_} ->
182                          {result,Result};
183                      _ ->
184                          error
185                  end
186           end,
187    {[{point,Desc2},{axis,Desc3},{Fun,Desc1}],[],[],[vertex,edge,face]}.
188
189check_selection({OrigId,Edges,Vs}, #st{selmode=Mode}=St) ->
190    Sel = case Mode of
191              vertex -> Vs;
192              edge -> Edges;
193              face -> []
194          end,
195    MF = fun(Items, #we{id=Id}=We) when Id =:= OrigId ->
196                 case gb_sets:size(Items) of
197                     1 ->
198                         [Item] = gb_sets:to_list(Items),
199                         case member(Item, Sel) of
200                             false ->
201                                 [wrong];
202                             true ->
203                                 [get_point(Mode, Item, We)]
204                         end;
205                     _ ->
206                         [multiple]
207                 end;
208            (_, _) ->
209                 [wrong]
210         end,
211    RF = fun erlang:'++'/2,
212    case wings_sel:dfold(MF, RF, [], St) of
213        [] ->
214            ?__(1,"Nothing selected");
215        [{Result,Point}] ->
216            {Result,Point,?__(2,"Point saved as stable ray location.")};
217        [wrong] ->
218            ?__(3,"Only a vertex or an edge from the original "
219                "edge loop may be selected.");
220        [_|_] ->
221            ?__(4,"Only a single edge or vertex may be selected.")
222    end.
223
224get_point(vertex, V, We) ->
225    {V,wings_vertex:pos(V, We)};
226get_point(edge, E, #we{es=Etab,vp=Vtab}) ->
227    #edge{vs=Va,ve=Vb} = array:get(E, Etab),
228    Pos = e3d_vec:average(wings_vertex:pos(Va, Vtab),
229                          wings_vertex:pos(Vb, Vtab)),
230    {{Va,Vb},Pos}.
231
232% % % % % % % % % % %
233%    Data Setup     %
234% % % % % % % % % % %
235
236%%%% Arc Setup LMB - find plane for each arc
237%%%% Arc Setup RMB - arc to common plane
238arc_setup(Plane, St0) ->
239    Flatten = wings_pref:get_value(circularise_flatten, true),
240    State = {Flatten,normal,none},
241    F = fun(Es, We, TentVec0) ->
242                VsList = wings_edge_loop:edge_links(Es, We),
243                {TentVec,Tv} = arc_setup(State, Plane, VsList, We, TentVec0),
244                {We#we{temp=Tv},TentVec}
245        end,
246    {St,_} = wings_sel:mapfold(F, tent_vec, St0),
247    Flags = [{mode,{arc_modes(),State}},{initial,[0.0,0.0,1.0]}],
248    Units = [angle,skip,percent],
249    DF = fun(_, #we{temp=Tv}) -> Tv end,
250    wings_drag:fold(DF, Units, Flags, St).
251
252arc_setup(State, Plane, VsData, We, TentVec) ->
253    arc_setup(State, Plane, VsData, We, TentVec, []).
254
255arc_setup(State, Plane0, [VsList|Loops], #we{vp=Vtab}=We, TentVec0, Acc0) ->
256    {Vs0,Edges} = arc_vs(VsList, [], []),
257    CwNorm = wings_face:face_normal_cw(Vs0, Vtab),
258    case e3d_vec:is_zero(CwNorm) of
259        true when Plane0 =:= find_plane -> %% LMB
260          SurfaceNorm = check_plane(Vs0, We),
261          {[NewVsList], TempVtab} = tent_arc(Edges, Vs0, SurfaceNorm, We),
262          {Vs,_} = arc_vs(NewVsList, [], []),
263          CwNorm1 = wings_face:face_normal_ccw(Vs, TempVtab),
264          {Vlist,DegVertList} = make_degree_vert_list(Vs, TempVtab, 0, [], []),
265          TentVec = TentVec0,
266          Norm = CwNorm1;
267        true -> %% RMB
268          TentVec = case TentVec0 of
269            tent_vec ->
270                SurfaceNorm = check_plane(Vs0, We),
271                establish_tent_vec(Plane0, Vs0, SurfaceNorm, We);
272            _ -> TentVec0
273          end,
274          {[NewVsList], TempVtab} = tent_arc(Edges, Vs0, TentVec, We),
275          {Vs,_} = arc_vs(NewVsList, [], []),
276          CwNorm1 = wings_face:face_normal_ccw(Vs, TempVtab),
277          {Vlist,DegVertList} = make_degree_vert_list(Vs, TempVtab, 0, [], []),
278          Norm = CwNorm1;
279        false when Plane0 =:= find_plane -> %% LMB
280          Vs = Vs0,
281          {Vlist,DegVertList} = make_degree_vert_list(Vs0, Vtab, 0, [], []),
282          TentVec = TentVec0,
283          Norm = e3d_vec:neg(CwNorm);
284        false -> %% RMB
285          Plane = check_for_user_plane(Plane0, CwNorm),
286          Vs = check_vertex_order(Vs0, Plane, CwNorm),
287          {Vlist,DegVertList} = make_degree_vert_list(Vs, Vtab, 0, [], []),
288          [V0|_] = Vs,
289          V1 = array:get(V0, Vtab),
290          V2 = array:get(lists:last(Vs), Vtab),
291          Vec = e3d_vec:norm_sub(V1, V2),
292          Cr1 = e3d_vec:norm(e3d_vec:cross(Vec, Plane)),
293          Cr2 = e3d_vec:neg(Cr1),
294          TentVec = TentVec0,
295          Norm = if Cr1 > Cr2 -> e3d_vec:neg(Plane);
296                    true -> Plane
297                 end
298    end,
299    NumVs = length(DegVertList) + 1,
300    [StartVs|_] = Vs,
301    EndVs = lists:last(Vs),
302    SPos = array:get(StartVs, Vtab),
303    EPos = array:get(EndVs, Vtab),
304    Hinge = e3d_vec:average(SPos, EPos),
305    Chord = e3d_vec:sub(Hinge, SPos),
306    Cross = e3d_vec:norm(e3d_vec:cross(Norm, Chord)),
307    Opp = e3d_vec:len(Chord),
308    Data = {{CwNorm, Cross, Opp, Norm, SPos, Hinge, NumVs}, DegVertList},
309    Acc = [{Vlist, make_arc_fun(Data, State)}|Acc0],
310    arc_setup(State, Plane0, Loops, We, TentVec, Acc);
311arc_setup(_, _, [], _, TentVec, Acc) ->
312    {TentVec,wings_drag:compose(Acc)}.
313
314%%%% Arc Setup MMB
315arc_center_setup(Plane, Center, St0) ->
316    Flatten = wings_pref:get_value(circularise_flatten, true),
317    State = {Flatten,normal,acute},
318    F = fun(Edges, #we{vp=Vtab}=We) ->
319                ArcVs = wings_edge_loop:edge_links(Edges, We),
320                {Vlist,Data} = arc_center_setup_1(ArcVs, Plane, Center, Vtab),
321                Tv = {Vlist,make_arc_center_fun(Data, State)},
322                We#we{temp=Tv}
323        end,
324    St = wings_sel:map(F, St0),
325    DF = fun(_, #we{temp=Tv}) -> Tv end,
326    Flags = [{mode,{arc_modes(),State}},{initial,[1.0]}],
327    wings_drag:fold(DF, [percent], Flags, St).
328
329arc_center_setup_1(VData, Plane, Center, Vtab) ->
330    lists:foldl(fun(ArcVs, {VlistAcc,DataAcc}) ->
331        {Vs0,_} = arc_vs(ArcVs, [], []),
332        Vs = check_vertex_order(Vs0,Plane,wings_face:face_normal_cw(Vs0,Vtab)),
333        {Vlist,DegVertList} = make_degree_vert_list(Vs,Vtab,0,[],[]),
334        NumVs = length(DegVertList) + 1,
335        [StartV|_] = Vs,
336        EndV = lists:last(Vs),
337        SPos = array:get(StartV, Vtab),
338        EPos = array:get(EndV, Vtab),
339        Hinge = e3d_vec:average(SPos, EPos),
340        Chord = e3d_vec:sub(Hinge, SPos),
341        Cross = e3d_vec:cross(Chord,Plane),
342        CenterPoint = intersect_vec_plane(Hinge,Center,e3d_vec:norm(Cross)),
343        %% get angle
344        Vec1 = e3d_vec:sub(CenterPoint,SPos),
345        Vec2 = e3d_vec:sub(CenterPoint,EPos),
346        Angle = e3d_vec:degrees(Vec1,Vec2),
347        Axis0 = e3d_vec:normal([SPos,EPos,CenterPoint]),
348        Axis = case Axis0 =:= e3d_vec:zero() of
349            true -> Plane;
350            false -> Axis0
351        end,
352        Data = {Angle, CenterPoint, Axis, SPos, DegVertList, NumVs},
353        {Vlist++VlistAcc,[Data|DataAcc]}
354    end, {[],[]}, VData).
355
356%% StartVs and EndVs are in 3rd of First and 2nd of Last
357arc_vs([{E,LastV,V}|[]], VAcc, EAcc) ->
358    {[LastV,V|VAcc], [E|EAcc]};
359arc_vs([{E,_,V}|VsList], VAcc, EAcc) ->
360    arc_vs(VsList, [V|VAcc], [E|EAcc]).
361
362%%%% Index vertices for open edge loop (Arc)
363%% The first and last vertices in the list don't move, so we skip them.
364make_degree_vert_list([_|[]], _, _, Vlist, DegVertList) ->
365    {Vlist,DegVertList};
366make_degree_vert_list([_|Vs], Vtab, 0, Vlist, DegVertList) ->
367    make_degree_vert_list(Vs, Vtab, 1, Vlist, DegVertList);
368make_degree_vert_list([V|Vs], Vtab, Index, Vlist, DegVertList) ->
369    Vpos = array:get(V, Vtab),
370    make_degree_vert_list(Vs, Vtab, Index+1, [V|Vlist], [{V,{Vpos, Index}}|DegVertList]).
371
372check_partial_and_full_loops([Group|Vs], We) when length(Group) > 1 ->
373    {Edges,Bool} = edges_in_group(Group, [], [], [], false, false),
374    case is_list(wings_edge_loop:edge_loop_vertices(Edges, We)) of
375      true -> mixed;
376      false when Bool -> mixed;
377      false -> check_partial_and_full_loops(Vs, We)
378    end;
379check_partial_and_full_loops([], _) -> not_mixed;
380check_partial_and_full_loops(_, _) -> single_edge.
381
382edges_in_group([{Edge,V,V2}|VsList], EAcc, VAcc, V2Acc, false, false) ->
383    Bool = lists:member(V, VAcc),
384    Bool2 = lists:member(V2, V2Acc),
385    edges_in_group(VsList, [Edge|EAcc], [V|VAcc], [V2|V2Acc], Bool, Bool2);
386edges_in_group(_, _, _, _, true, _) ->
387    {[],true};
388edges_in_group(_, _, _, _, _, true) ->
389    {[],true};
390edges_in_group([], Edges, _, _, _, _) -> {Edges,false}.
391
392%%%% Circularise Setup LMB RMB
393circle_setup(Plane, St) ->
394    Flatten = wings_pref:get_value(circularise_flatten, true),
395    DragMode = wings_pref:get_value(circularise_drag, relative),
396    State = {Flatten,none,DragMode},
397    DF = fun(Edges, We) ->
398                 case wings_edge_loop:edge_loop_vertices(Edges, We) of
399                     none ->
400                         circ_sel_error_4();
401                     Groups ->
402                         TotalVs = length(wings_edge:to_vertices(Edges, We)),
403                         SumCheck = [length(SubGroup) || SubGroup <- Groups],
404                         Sum = lists:sum(SumCheck),
405                         case TotalVs  =:=  Sum of
406                             true ->
407                                 circle_setup_1(Groups, We, Plane, State, []);
408                             false ->
409                                 circ_sel_error_1()
410                         end
411                 end
412         end,
413    Flags = [{mode,{circ_mode(),State}},{initial,[1.0,0.0,1.0]}],
414    wings_drag:fold(DF, circularise_units(State), Flags, St).
415
416
417%%%% Circularise Setup MMB
418circle_pick_all_setup(RayV, Center, Axis0, St0) ->
419    Flatten = wings_pref:get_value(circularise_flatten, true),
420    DragMode = wings_pref:get_value(circularise_drag, relative),
421    State = {Flatten,normal,DragMode},
422    Axis = e3d_vec:norm(Axis0),
423    MF = fun(Es, We) ->
424                 circle_pick_all_setup_1(Es, We, State, RayV, Center, Axis)
425         end,
426    St = wings_sel:map(MF, St0),
427    Flags = [{mode,{circ_mode(),State}},{initial,[1.0,0.0,1.0]}],
428    DF = fun(_, #we{temp=Tv}) -> Tv end,
429    wings_drag:fold(DF, circularise_units(State), Flags, St).
430
431circle_pick_all_setup_1(Edges, #we{vp=Vtab}=We, State, RayV, Center, Axis) ->
432    [Vs0] = wings_edge_loop:edge_loop_vertices(Edges, We),
433    Vs = check_vertex_order(Vs0, Axis, wings_face:face_normal_cw(Vs0, Vtab)),
434    Deg = (360.0/length(Vs)),
435    {Pos,Index} = find_stable_point(Vs, RayV, Vtab, 0.0),
436    Ray0 = e3d_vec:sub(intersect_vec_plane(Pos, Center, Axis), Center),
437    Len = e3d_vec:len(Ray0),
438    Ray = e3d_vec:norm(Ray0),
439    VertDegList = degrees_from_static_ray(Vs, Vtab, Deg, Index, 1, []),
440    Data = {Center,Ray,Len,Axis,VertDegList},
441    Tv = {Vs,make_circular_fun(Data, State)},
442    We#we{temp=Tv}.
443
444circle_setup_1([], _, _, _, Acc) ->
445    wings_drag:compose(Acc);
446circle_setup_1([Vs0|Groups], #we{vp=Vtab}=We, Plane, State, Acc0) ->
447    CwNorm = wings_face:face_normal_cw(Vs0, Vtab),
448    Axis = circle_plane(Plane, CwNorm),
449    Vs = check_vertex_order(Vs0, Axis, CwNorm),
450    Center = wings_vertex:center(Vs, We),
451    Deg = 360.0/length(Vs),
452    {Pos,NearestVpos,Index} = get_radius(Vs, Center, Axis, Vtab, 0.0, 0.0, raypos, lastpos, firstpos, 0.0, index),
453    VertDegList = degrees_from_static_ray(Vs, Vtab, Deg, Index, 1.0, []),
454    Ray = e3d_vec:norm_sub(Pos, Center),
455    Data = {Center,Ray,NearestVpos,Axis,VertDegList},
456    Acc = [{Vs,make_circular_fun(Data, State)}|Acc0],
457    circle_setup_1(Groups, We, Plane, State, Acc).
458
459%% Tent arc for open edge loops that have a ccw normal of {0,0,0}
460tent_arc(Edges, [_,V2|_], Norm, #we{vp=Vtab}=We) ->
461    V2pos = array:get(V2, Vtab),
462    TempV2pos = e3d_vec:add(V2pos, Norm),
463    TempVtab = array:set(V2, TempV2pos, Vtab),
464    We1 = We#we{vp=TempVtab},
465    {wings_edge_loop:edge_links(Edges, We1), TempVtab}.
466
467%% Tent vec when user plane is in effect and ccw norm is {0,0,0}
468establish_tent_vec(Plane, [_,V2|_]=Vs, Norm, #we{vp=Vtab}) ->
469    LastV = lists:last(Vs),
470    V2pos = array:get(V2, Vtab),
471    Lpos = array:get(LastV, Vtab),
472    Chord = e3d_vec:norm_sub(V2pos, Lpos),
473    Cr1 = e3d_vec:cross(Plane, Chord),
474    Cr2 = e3d_vec:neg(Cr1),
475    D1 = e3d_vec:dot(Cr1, Norm),
476    D2 = e3d_vec:dot(Cr2, Norm),
477    TentVec = if
478             D1 > D2 -> Cr1;
479             true -> Cr2
480          end,
481    TentVec.
482
483%% Check whether the UserAxis is opposite to the cw normal of the vert list and
484%% if so, reverse the vertex list. This check reduces the probablility of the
485%% user having to use the Reverse Normal option.
486check_vertex_order(Vs, Axis1, Axis2) ->
487    Dot = e3d_vec:dot(Axis1, Axis2),
488    if Dot < 0.0 -> Vs;
489       true -> lists:reverse(Vs)
490    end.
491
492%% Differentiate between Lmb and Rmb Arc commands
493check_plane(Vs, We) ->
494    Normals = normals_for_surrounding_faces(Vs, We, []),
495    e3d_vec:average(Normals).
496
497check_for_user_plane(find_plane, CwNorm) -> CwNorm;
498check_for_user_plane(Plane, _) -> Plane.
499
500normals_for_surrounding_faces([V|Vs], We, Acc) ->
501    Normal = wings_vertex:normal(V, We),
502    normals_for_surrounding_faces(Vs, We, [Normal|Acc]);
503normals_for_surrounding_faces([], _, Acc) -> Acc.
504
505circle_plane(find_plane, CwNorm) ->
506    e3d_vec:neg(CwNorm);
507circle_plane(Plane, _) -> Plane.
508
509%%%% Return the Pos and Index of the stable point chosen by the user
510find_stable_point([Va|_], {Va,Vb}, Vtab, Index) ->
511    VposA = array:get(Va, Vtab),
512    VposB = array:get(Vb, Vtab),
513    Pos = e3d_vec:average(VposA, VposB),
514    {Pos,Index+1.5};
515find_stable_point([Vb|_], {Va,Vb}, Vtab, Index) ->
516    VposA = array:get(Va, Vtab),
517    VposB = array:get(Vb, Vtab),
518    Pos = e3d_vec:average(VposA, VposB),
519    {Pos,Index+1.5};
520find_stable_point([_|Vs], {Va,Vb}, Vtab, Index) ->
521    find_stable_point(Vs, {Va,Vb}, Vtab, Index+1);
522
523find_stable_point([RayV|_], RayV, Vtab, Index) ->
524    Pos = array:get(RayV, Vtab),
525    {Pos,Index+1};
526find_stable_point([_|Vs], RayV, Vtab, Index) ->
527    find_stable_point(Vs, RayV, Vtab, Index+1).
528
529
530%%%% Return the Index and Position of the Vertex or midpoint between adjacent
531%%%% vertices closeest to the Center. Distance calculation is made after the
532%%%% point in question is flattened to the relevant Plane.
533get_radius([], Center, _, _, RayLen0, NearestVert, Pos, LastPos, FirstPos, AtIndex, Index) ->
534    HalfPos = e3d_vec:average(LastPos, FirstPos),
535    HalfDist = len_sqrt(e3d_vec:sub(HalfPos, Center)),
536    case HalfDist < RayLen0 of
537      true -> {HalfPos, math:sqrt(NearestVert), AtIndex+0.5};
538      false -> {Pos, math:sqrt(NearestVert), Index}
539    end;
540
541get_radius([Vert|Vs], Center, Plane, Vtab, 0.0, 0.0, _Pos, _LastPos, _FirstPos, AtIndex, _Index) ->
542    Pos = array:get(Vert, Vtab),
543    RayPos = intersect_vec_plane(Pos, Center, Plane),
544    Dist = len_sqrt(e3d_vec:sub(RayPos, Center)),
545    get_radius(Vs, Center, Plane, Vtab, Dist, Dist, RayPos, Pos, Pos, AtIndex+1.0, AtIndex+1.0);
546
547get_radius([Vert|Vs], Center, Plane, Vtab, RayLen0, NearestVert0, RayPos0, LastPos0, FirstPos, AtIndex0, Index0) ->
548    Pos = array:get(Vert, Vtab),
549    LastPos = intersect_vec_plane(Pos, Center, Plane),
550    HalfPos = e3d_vec:average(LastPos, LastPos0),
551    FullDist = len_sqrt(e3d_vec:sub(LastPos, Center)),
552    HalfDist = len_sqrt(e3d_vec:sub(HalfPos, Center)),
553    AtIndex = AtIndex0+1.0,
554    case FullDist < HalfDist of
555      true ->
556        case ((FullDist < RayLen0) andalso (FullDist > 0.0)) of
557          true ->
558            RayLen = FullDist,
559            NearestVert = FullDist,
560            RayPos = LastPos,
561            Index = AtIndex;
562          false ->
563            RayLen = RayLen0,
564            NearestVert = NearestVert0,
565            RayPos = RayPos0,
566            Index = Index0
567        end;
568      false ->
569        case ((HalfDist < RayLen0) andalso (HalfDist > 0.0)) of
570          true ->
571            RayLen = HalfDist,
572            NearestVert = case FullDist < NearestVert0 of
573              true -> FullDist;
574              false -> NearestVert0
575            end,
576            RayPos = HalfPos,
577            Index = AtIndex0+0.5;
578          false ->
579            RayLen = RayLen0,
580            NearestVert = case FullDist < NearestVert0 of
581              true -> FullDist;
582              false -> NearestVert0
583            end,
584            RayPos = RayPos0,
585            Index = Index0
586        end
587    end,
588    get_radius(Vs, Center, Plane, Vtab, RayLen, NearestVert, RayPos, LastPos, FirstPos, AtIndex, Index).
589
590len_sqrt({X,Y,Z}) ->
591    X*X+Y*Y+Z*Z.
592
593%%%% Return a tuple list [{Vert, Degrees}] of all the vertices
594%%%% in the edge loop in ccw order and the number of degrees it
595%%%% will be rotated around the center point from the stable ray.
596degrees_from_static_ray([], _, _, _, _, DegList) ->
597    DegList;
598degrees_from_static_ray([Vert|Vs], Vtab, Deg, Index, At, DegList) ->
599    Degrees = Deg * (At-Index),
600    Vpos = array:get(Vert, Vtab),
601    degrees_from_static_ray(Vs, Vtab, Deg, Index, At+1.0, [{Vert,{Vpos,Degrees}}|DegList]).
602
603circularise_units({_, _, relative}) ->
604    [diametric_factor,skip,percent];
605circularise_units({_, _, absolute}) ->
606    [absolute_diameter,skip,percent].
607
608%%%% Arc Modes
609arc_modes() ->
610    fun(help, State) -> arc_mode_help(State);
611      ({key,$1},{true,_normal,_angle})       -> {false,_normal,_angle};
612      ({key,$1},{false,_normal,_angle})      -> {true,_normal,_angle};
613      ({key,$2},{_flatten,normal,_angle})  -> {_flatten,reverse,_angle};
614      ({key,$2},{_flatten,reverse,_angle}) -> {_flatten,normal,_angle};
615      ({key,$3},{_flatten,_normal,acute})    -> {_flatten,_normal,obtuse};
616      ({key,$3},{_flatten,_normal,obtuse})   -> {_flatten,_normal,acute};
617      (done,{Flatten,_,_}) -> wings_pref:set_value(circularise_flatten, Flatten);
618      (_,_) -> none
619    end.
620
621%%%% Mode Help
622arc_mode_help({Flatten, Normal, Angle}) ->
623    [flatten_help(Flatten),
624     norm_help(Normal),
625     angle_help(Angle)].
626
627flatten_help(true)  -> ?__(1,"[1] Don't Flatten");
628flatten_help(false) -> ?__(2,"[1] Flatten").
629
630norm_help(normal)   -> ?__(1,"  [2] Reverse Arc Normal");
631norm_help(reverse)  -> ?__(2,"  [2] Original Arc Normal");
632norm_help(none)     -> [].
633
634angle_help(acute)   -> ?__(1,"  [3] Use Obtuse Angle");
635angle_help(obtuse)  -> ?__(2,"  [3] Use Acute Angle");
636angle_help(none)    -> [].
637
638%%%% Circularise Modes
639circ_mode() ->
640    fun
641      (help, State) -> circ_mode_help(State);
642      ({key,$1},{true,_normal,_dragmode})         -> {false,_normal,_dragmode};
643      ({key,$1},{false,_normal,_dragmode})        -> {true,_normal,_dragmode};
644      ({key,$2},{_flatten,normal,_dragmode})      -> {_flatten,reverse,_dragmode};
645      ({key,$2},{_flatten,reverse,_dragmode})     -> {_flatten,normal,_dragmode};
646      ({key,$2},{_flatten,none,_dragmode})     -> {_flatten,none,_dragmode};
647      ({key,$3},{_flatten,_normal,relative}) -> {_flatten,_normal,absolute};
648      ({key,$3},{_flatten,_normal,absolute}) -> {_flatten,_normal,relative};
649      (units,State) -> circularise_units(State);
650      (done,{Flatten,_,DragMode}) ->
651          wings_pref:set_value(circularise_flatten, Flatten),
652          wings_pref:set_value(circularise_drag, DragMode);
653      (_,_) -> none
654    end.
655
656circ_mode_help({Flatten, Normal, DragMode}) ->
657    [flatten_help(Flatten),
658     circ_norm_help(Normal),
659     radius_help(DragMode)].
660
661circ_norm_help(normal)  -> ?__(1,"  [2] Reverse Plane Normal");
662circ_norm_help(reverse) -> ?__(2,"  [2] Original Plane Normal");
663circ_norm_help(none)  -> [].
664
665radius_help(relative) -> ?__(1,"  [3] Use Absolute Diameter");
666radius_help(absolute) -> ?__(2,"  [3] Use Relative Diameter").
667
668%%%% Arc drag data LMB/RMB
669make_arc_fun(Data0, State) ->
670    fun
671      (new_mode_data,{NewState,_}) ->
672        make_arc_fun(Data0, NewState);
673      ([Angle,_,Percent|_], A) ->
674        {Data,VertDistList} = Data0,
675        lists:foldl(fun({V,{Vpos,Index}}, VsAcc) ->
676          [{V, arc(Vpos, Index, Data, State, Percent, Angle)}|VsAcc]
677        end, A, VertDistList)
678    end.
679
680%%%% Arc Center drag data MMB
681make_arc_center_fun(Data, State) ->
682    fun
683      (new_mode_data,{NewState,_}) ->
684        make_arc_center_fun(Data, NewState);
685      ([Percent|_], A) ->
686        lists:foldl(fun(D, Acc) ->
687            {Angle, Center, Plane, Pos, DegVertList, NumVs} = D,
688            lists:foldl(fun({V,{Vpos,Index}}, VsAcc) ->
689                [{V,arc_center(Vpos, Angle, Index, NumVs, Pos, Center, Plane, State, Percent)}|VsAcc]
690             end, Acc, DegVertList)
691         end, A, Data)
692    end.
693
694%%%% Circularise Mode, Diameter, and Percentage changes LMB/RMB
695make_circular_fun(Data, State) ->
696    fun
697      (new_mode_data,{NewState,_}) ->
698          {_,NewNormal,_} = NewState,
699          {_,Normal,_} = State,
700          case Normal  =:=  NewNormal of
701            true ->
702              make_circular_fun(Data, NewState);
703            false ->
704              {Center,Ray,Nearest,Axis0,VertDegList} = Data,
705              Axis = e3d_vec:neg(Axis0),
706              make_circular_fun({Center,Ray,Nearest,Axis,VertDegList}, NewState)
707          end;
708      ([Dia,_,Percent|_], A) ->
709          {Center,Ray,Nearest,Axis,VertDegList} = Data,
710          lists:foldl(fun({V,{Vpos,Degrees}}, VsAcc) ->
711            [{V,make_circular(Center, Ray, Nearest, Axis, Degrees, Vpos, State, Percent, Dia)}|VsAcc]
712          end, A, VertDegList)
713    end.
714
715%%%% Arc Main Functions
716arc(Vpos, _Index, _Data, _State, 0.0, 0.0) -> Vpos;
717arc(Vpos, Index, {CwNorm, _, Opp, Plane0, Pos, Hinge, NumVs},
718        {Flatten,Orientation,_}, Percent, 0.0) ->
719    Segment = (Opp * 2) / NumVs,
720    ChordNorm = e3d_vec:norm(e3d_vec:sub(Hinge, Pos)),
721    Plane = reverse_norm(CwNorm,Plane0,Orientation),
722    Pos1 = e3d_vec:add(Pos, e3d_vec:mul(ChordNorm, Segment * Index)),
723    Pos2 = flatten(Flatten, Pos1, Vpos, Plane),
724    Vec = e3d_vec:sub(Pos2, Vpos),
725    e3d_vec:add(Vpos, e3d_vec:mul(Vec, Percent));
726
727arc(Vpos, Index, {CwNorm, _, _, Plane0, Pos, Hinge, NumVs},
728        {Flatten,Orientation,_}, Percent, 180.0) ->
729    Plane = reverse_norm(CwNorm, Plane0, Orientation),
730    Deg = 180.0 / NumVs,
731    RotationAmount = Deg * Index,
732    Pos1 = rotate(Pos, Plane, Hinge, RotationAmount),
733    Pos2 = flatten(Flatten, Pos1, Vpos, Plane),
734    Norm = e3d_vec:sub(Pos2, Vpos),
735    e3d_vec:add(Vpos, e3d_vec:mul(Norm, Percent));
736
737arc(Vpos, Index, {CwNorm, Cross0, Opp, Plane0, Pos, Hinge, NumVs},
738        {Flatten,Orientation,_}, Percent, Angle) ->
739    Plane = reverse_norm(CwNorm, Plane0, Orientation),
740    Cross = reverse_norm(CwNorm, Cross0, Orientation),
741    {Deg, RotPoint} = angle_and_point(Angle, Opp, Index, NumVs, Hinge, Cross),
742    Pos1 = rotate(Pos, Plane, RotPoint, Deg),
743    Pos2 = flatten(Flatten, Pos1, Vpos, Plane),
744    Norm = e3d_vec:sub(Pos2, Vpos),
745    e3d_vec:add(Vpos, e3d_vec:mul(Norm, Percent)).
746
747    % % % % % % % % % % % % % % % % % %
748    %                                 %
749    %                 .               %
750    %          *             *        %
751    %      *_ _ _ _ _ _ _ _ _ _ _*    %
752    %                 |_| OPP   /     %
753    %                 |        /      %
754    %                 |       /       %
755    %               A |      /        %
756    %               D |     /         %
757    %               J |    /          %
758    %                 |   /           %
759    %                 |  /            %
760    %                 | /             %
761    %                 |/  ANGLE       %
762    %   ROTATION POINT                %
763    %                                 %
764    %                                 %
765    % % % % % % % % % % % % % % % % % %
766
767arc_center(Vpos, _, _, _, _, _, _, _, 0.0) -> Vpos;
768arc_center(Vpos, Angle, Index, NumVs, Pos, Center, Plane, {Flatten,AxisMode,AngleMode}, Percent) ->
769    DegIncrement = acute_obtuse(AngleMode, Angle, NumVs),
770    RotationAmount = rotation_amount(AxisMode, DegIncrement, Index),
771    Pos1 = rotate(Pos, Plane, Center, RotationAmount),
772    Pos2 = flatten(Flatten, Pos1, Vpos, Plane),
773    Norm = e3d_vec:sub(Pos2, Vpos),
774    e3d_vec:add(Vpos, e3d_vec:mul(Norm, Percent)).
775
776acute_obtuse(acute, Angle, NumVs) -> Angle / NumVs;
777acute_obtuse(obtuse, Angle, NumVs) -> - (360 - Angle) / NumVs.
778
779rotation_amount(normal, Deg, Index) -> Deg * Index;
780rotation_amount(reverse, Deg, Index) -> -Deg * Index.
781
782%%%% Closed Loop. Calculate the final position of each vertex (NewPos).
783%%%% Measure the distance between NewPos and the Center (Factor). Move the
784%%%% vertex towards the NewPos by a distance of the drag Dist * Factor.
785make_circular(_Center, _Ray, _Nearest, _Axis, _Deg, Vpos, _State, 0.0, 0.0) -> Vpos;
786make_circular(Center, Ray, Nearest, Plane, Deg, Vpos, {Flatten,_,Mode}, Percent, Dia) ->
787    Pos0 = static_pos(Mode, Center, Ray, Nearest, Dia),
788    Pos1 = rotate(Pos0, Plane, Center, Deg),
789    Pos2 = flatten(Flatten, Pos1, Vpos, Plane),
790    Norm = e3d_vec:sub(Pos2, Vpos),
791    e3d_vec:add(Vpos, e3d_vec:mul(Norm, Percent)).
792
793%%%% Utilities
794angle_and_point(Angle0, Opp, Index, NumVs, Hinge, Cross) ->
795    Angle = 90.0 - (Angle0/2.0),
796    %% Erlang trigonomic inputs have to be converted from Degrees to Radians
797    Radians = (math:pi()/(180.0/Angle)),
798    Adj = math:tan(Radians) * Opp,
799    Deg = (180.0 - (Angle * 2)) / NumVs,
800    RotationAmount = Deg * Index,
801    RotPoint = e3d_vec:add(Hinge, e3d_vec:mul(Cross, Adj)),
802    {RotationAmount, RotPoint}.
803
804static_pos(relative, Center, Ray, Nearest, Dia) ->
805    e3d_vec:add(Center, e3d_vec:mul(Ray, Nearest*Dia));
806static_pos(absolute, Center, Ray, _Nearest, Dia) ->
807    e3d_vec:add(Center, e3d_vec:mul(Ray, Dia/2)).
808
809flatten(true, Pos, _Vpos, _Plane) -> Pos;
810flatten(false, Pos, Vpos, Plane) -> intersect_vec_plane(Pos, Vpos, Plane).
811
812rotate(Vpos, Axis, {Cx,Cy,Cz}, Angle) ->
813    %% return new position as {x,y,z}
814    A0 = e3d_mat:translate(Cx, Cy, Cz),
815    A1 = e3d_mat:mul(A0, e3d_mat:rotate(Angle, Axis)),
816    A2 = e3d_mat:mul(A1, e3d_mat:translate(-Cx, -Cy, -Cz)),
817    e3d_mat:mul_point(A2, Vpos).
818
819intersect_vec_plane(PosA, PosA, _) -> PosA;
820intersect_vec_plane(PosA, PosB, PlaneNorm) ->
821    %% Return point where Vector through PosA intersects with plane at PosB
822    Intersection = e3d_vec:dot(e3d_vec:sub(PosB, PosA), PlaneNorm),
823    e3d_vec:add(PosA, e3d_vec:mul(PlaneNorm, Intersection)).
824
825reverse_norm({0.0,0.0,0.0}, Norm, reverse) -> e3d_vec:neg(Norm);
826reverse_norm(_, Norm, _) -> Norm.
827
828%%%% Selection errors
829-spec circ_sel_error() -> no_return().
830circ_sel_error() ->
831    wings_u:error_msg(?__(3,"Selection must consist of one or more open edge loops\nor a single closed loop")).
832-spec circ_sel_error_1() -> no_return().
833circ_sel_error_1() ->
834    wings_u:error_msg(?__(1,"Selected edge loops may not share vertices")).
835-spec circ_sel_error_3() -> no_return().
836circ_sel_error_3() ->
837    wings_u:error_msg(?__(2,"Selections including single edges cannot be processed")).
838-spec circ_sel_error_4() -> no_return().
839circ_sel_error_4() ->
840    wings_u:error_msg(?__(2,"Selected edge loops must be non-intersecting, and be either all open or all closed.")).
841