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