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