1%% 2%% wpc_plane_cut.erl -- 3%% 4%% Plane Cut plugin for Vertex, Face, and Body mode. 5%% Optimized for Standard axis cuts and Object selections. 6%% Multiple plane cuts will time out if the operation is taking too long. 7%% There are Loop Cut options in Body mode. 8%% Slice options in Body and Face mode pressing [Alt]. 9%% 10%% Copyright (c) 2010-2011 Richard Jones. 11%% 12%% See the file "license.terms" for information on usage and redistribution 13%% of this file, and for a DISCLAIMER OF ALL WARRANTIES. 14%% 15 16-module(wpc_plane_cut). 17-export([init/0,menu/2,command/2, plane_cut/3]). 18-include_lib("src/wings.hrl"). 19 20 21-import(lists, [foldl/3,usort/1,reverse/1]). 22 23-define(NON_ZERO, 1.0E-4). 24 25init() -> 26 true. 27 28%%% 29%%% Insert menu heading 30%%% 31 32menu({Mode},Menu) when Mode =:= vertex; Mode =:= face; Mode =:= body -> 33 reverse(parse(Menu, [], Mode, false)); 34menu(_,Menu) -> 35 Menu. 36 37parse([{_,{flatten,_}}=A|Rest], NewMenu, vertex, false) -> 38 parse(Rest, [menu_heading(vertex),A|NewMenu], vertex, true); 39parse([{_,weld,_,_}=A|Rest], NewMenu, body, false) -> 40 parse(Rest, [menu_heading(body),A|NewMenu], body, true); 41parse([{_,bridge,_}=A|Rest], NewMenu, face, false) -> 42 parse(Rest, [menu_heading(face),A|NewMenu], face, true); 43parse([Elem|Rest], NewMenu, Mode, Found) -> 44 parse(Rest, [Elem|NewMenu], Mode, Found); 45parse([], NewMenu, _, true) -> 46 NewMenu; 47parse([], NewMenu, Mode, false) -> 48 [menu_heading(Mode)|NewMenu]. 49 50menu_heading(body) -> 51 [{?__(1,"Plane Cut"),{plane_cut,object_menu(plane_cut)}}, 52 {?__(2,"Slice"),{slice,object_menu(pre_slice)}}]; 53menu_heading(Mode) -> 54 [{?__(1,"Plane Cut"),{plane_cut,plane_cut_menu(Mode)}}]. 55 56object_menu(Cmd) -> 57 fun(help,_) when Cmd =:= pre_slice -> 58 {?__(1,"Slice objects into equal parts along standard axis"), 59 ?__(2,"Pick axis and Loop Cut the result"), 60 ?__(3,"Pick axis")}; 61 (help,_) when Cmd =:= plane_cut -> 62 {?__(4,"Cut around standard axis"), 63 ?__(5,"Pick plane and Loop Cut the result"), 64 ?__(6,"Pick plane")}; 65 (1,_) -> standard_axes(Cmd, body); 66 (2,_) -> {body,{Cmd,{loop_cut,{'ASK',[axis]}}}}; 67 (3,_) -> {body,{Cmd,{'ASK',[axis]}}}; 68 (_,_) -> ignore 69 end. 70 71plane_cut_menu(face) -> 72 fun(help,_) -> 73 {?__(1,"Cut around standard axis"), 74 ?__(2,"Slice selection into equal parts; pick plane"), 75 ?__(3,"Pick plane")}; 76 (1,_) -> standard_axes(plane_cut, face); 77 (2,_) -> {face,{pre_slice,{'ASK',[axis]}}}; 78 (3,_) -> {face,{plane_cut,{'ASK',[axis]}}}; 79 (_,_) -> ignore 80 end; 81plane_cut_menu(vertex) -> 82 fun(help,_) -> 83 {?__(4,"Cut around standard axis at each selected vertex"),[], 84 ?__(3,"Pick plane")}; 85 (1,_) -> standard_axes(plane_cut, vertex); 86 (3,_) -> {vertex,{plane_cut,{'ASK',[axis]}}}; 87 (_,_) -> ignore 88 end. 89 90standard_axes(Cmd, Mode) -> 91 [axis_menu(Cmd, Mode, x), 92 axis_menu(Cmd, Mode, y), 93 axis_menu(Cmd, Mode, z), 94 separator, 95 axis_menu(Cmd, Mode, last_axis), 96 axis_menu(Cmd, Mode, default_axis)]. 97 98axis_menu(Cmd, body, Axis) -> 99 AxisStr = wings_util:cap(wings_s:dir(Axis)), 100 Fun = fun 101 (help,_) when Cmd =:= pre_slice -> 102 Help0 = ?__(1,"Slice into equal parts along the ~s axis"), 103 Help = wings_util:format(Help0, [AxisStr]), 104 {Help, [], Help ++ ?__(2," and Loop Cut the result")}; 105 (help,_) when Cmd =:= plane_cut -> 106 Help0 = ?__(3,"Cut object around the ~s axis"), 107 Help = wings_util:format(Help0, [AxisStr]), 108 {Help, [], Help ++ ?__(2," and Loop Cut the result")}; 109 (1,_) -> {body,{Cmd,Axis}}; 110 (3,_) -> {body,{Cmd,{loop_cut,Axis}}}; 111 (_,_) -> ignore 112 end, 113 {AxisStr,{Cmd,Fun}}; 114axis_menu(_, vertex, Axis) -> 115 AxisStr = wings_util:cap(wings_s:dir(Axis)), 116 Help0 = ?__(4,"Cut objects or faces around the ~s axis at each selected vertex"), 117 Help = wings_util:format(Help0, [AxisStr]), 118 {AxisStr,Axis,Help}; 119axis_menu(_, face, Axis) -> 120 AxisStr = wings_util:cap(wings_s:dir(Axis)), 121 Fun = fun 122 (help,_) -> 123 Help0 = ?__(5,"Cut faces around the ~s axis"), 124 Help = wings_util:format(Help0, [AxisStr]), 125 SHelp0 = ?__(6,"Slice selection into equal parts along the ~s axis"), 126 SHelp = wings_util:format(SHelp0, [AxisStr]), 127 {Help,[],SHelp}; 128 (1,_) -> {face,{plane_cut,Axis}}; 129 (3,_) -> {face,{pre_slice,Axis}}; 130 (_,_) -> ignore 131 end, 132 {AxisStr,{plane_cut,Fun}}. 133 134slices_dialog(Mode,Cmd) -> 135 wings_dialog:dialog(?__(1,"Slice into Equal Parts"), 136 [{vframe,[{hframe,[{slider,{text,2,[{range,{2,100}}]}}]}]}], 137 fun([Parts]) -> 138 {Mode,{slice,{Parts,Cmd}}} 139 end). 140 141%%% 142%%% Commands 143%%% 144 145command({Mode,{pre_slice,Cmd}}, _) -> 146 slices_dialog(Mode,Cmd); 147 148command({body,{plane_cut,{loop_cut,{'ASK',_}}}}, St) -> 149 wings:ask({[axis,point],[]}, St, fun (Res,St0) -> 150 ?SLOW(plane_cut(true, Res, St0)) 151 end); 152command({Mode,{plane_cut,{'ASK',_}}}, St) when Mode =:= face; Mode =:= body -> 153 wings:ask({[axis,point],[]}, St, fun (Res,St0) -> 154 ?SLOW(plane_cut(false, Res, St0)) 155 end); 156command({body,{plane_cut,{loop_cut,Axis}}}, St) -> 157 wings:ask({[point],[]}, St, fun (Point,St0) -> 158 ?SLOW(plane_cut(true, {Axis,Point}, St0)) 159 end); 160command({Mode,{plane_cut,Axis}}, St) when Mode =:= face; Mode =:= body -> 161 wings:ask({[point],[]}, St, fun (Point,St0) -> 162 ?SLOW(plane_cut(false, {Axis,Point}, St0)) 163 end); 164 165command({body,{slice,{N,{loop_cut,{'ASK',Ask}}}}}, St) -> 166 wings:ask({Ask,[]}, St, fun (Axis,St0) -> 167 ?SLOW(plane_cut(true, {Axis,N}, St0)) 168 end); 169command({Mode,{slice,{N,{'ASK',Ask}}}}, St) when Mode =:= face; Mode =:= body -> 170 wings:ask({Ask,[]}, St, fun (Axis,St0) -> 171 ?SLOW(plane_cut(false, {Axis,N}, St0)) 172 end); 173command({body,{slice,{N,{loop_cut,Axis}}}}, St) -> 174 ?SLOW(plane_cut(true, {Axis,N}, St)); 175command({Mode,{slice,{N,Axis}}}, St) when Mode =:= face; Mode =:= body -> 176 ?SLOW(plane_cut(false, {Axis,N}, St)); 177 178command({vertex,{plane_cut,{'ASK',Ask}}}, St) -> 179 wings:ask(secondary_sel_ask(), St, fun (SelSt,St0) -> 180 wings:ask({Ask,[]}, St0, fun (Plane,St1) -> 181 ?SLOW(plane_cut(false, {Plane,SelSt}, St1)) 182 end) 183 end); 184command({vertex,{plane_cut,Axis}}, St) -> 185 wings:ask(secondary_sel_ask(), St, fun (SelSt,St0) -> 186 ?SLOW(plane_cut(false, {Axis,SelSt}, St0)) 187 end); 188 189command(_, _) -> next. 190 191secondary_sel_ask() -> 192 Desc = ?__(2,"Select the objects or faces to cut"), 193 Fun = sel_fun(), 194 {[{Fun,Desc}],[],[],[face,body]}. 195check_selection(#st{sel=[]}) -> {none,?__(1,"Nothing selected")}; 196check_selection(_) -> {none,[]}. 197 198sel_fun() -> 199 fun 200 (check, St) -> 201 check_selection(St); 202 (exit, {_,_,St}) -> 203 case check_selection(St) of 204 {_,[]} -> 205 {result,St}; 206 {_,_} -> error 207 end; 208 (exit,_) -> error 209 end. 210 211%%% 212%%% Process commands 213%%% 214 215plane_cut(Cut, {Axis0,N}, #st{selmode=SelMode}=St0) when is_integer(N) -> 216 T1 = os:timestamp(), 217 Axis = axis_conv(Axis0), 218 Points = slice_points(Axis, N, St0), 219 220 {St1,Sel} = case SelMode of 221 face -> 222 wings_sel:mapfold(fun(Faces, #we{id=Id}=We0, Acc) -> 223 {NewEdges,_,_,We} = foldl(fun 224 (Point,{Es,Fs0,Seen0,We1}) -> 225 T2 = timer:now_diff(os:timestamp(), T1), 226 if T2 > 20000000 -> planecut_error(); 227 true -> 228 {NewEs,We2} = intersects(Fs0, Axis, Point, We1), 229 SeenVs = wings_edge:to_vertices(NewEs,We2), 230 Seen = ordsets:union(SeenVs,Seen0), 231 NewFaces = wings_we:new_items_as_gbset(face, We1, We2), 232 Fs = gb_sets:union(NewFaces,Fs0), 233 {gb_sets:union(NewEs, Es),Fs,Seen,We2} 234 end 235 end, {gb_sets:new(),Faces,[],We0}, Points), 236 {We,[{Id,NewEdges}|Acc]} 237 end,[],St0); 238 body -> 239 wings_sel:mapfold(fun(_, #we{id=Id}=We0, Acc) -> 240 {NewEdges,_,We} = foldl(fun(Point,{Es,Seen0,We1}) -> 241 T2 = timer:now_diff(os:timestamp(), T1), 242 if T2 > 20000000 -> planecut_error(); 243 true -> 244 EdgePos2Cut = intersects(Axis, Point, We1), 245 {NewEs,We2} = cut_intersecting_edges(false, Axis, EdgePos2Cut, We1), 246 SeenVs = wings_edge:to_vertices(NewEs,We2), 247 Seen = ordsets:union(SeenVs,Seen0), 248 {gb_sets:union(NewEs, Es),Seen,We2} 249 end 250 end, {gb_sets:new(),[],We0}, Points), 251 {We,[{Id,NewEdges}|Acc]} 252 end,[],St0) 253 end, 254 St = wings_sel:set(edge, Sel, St1), 255 case Cut of 256 true -> 257 {save_state,loop_cut(Axis, all, wings_sel:valid_sel(St))}; 258 false -> 259 {save_state,wings_sel:valid_sel(St)} 260 end; 261 262plane_cut(_, {Axis0,Point}, #st{selmode=face}=St0) -> 263 Axis = axis_conv(Axis0), 264 {St1,Sel} = wings_sel:mapfold(fun(Faces, #we{id=Id}=We0, Acc) -> 265 {NewEdges,We} = intersects(Faces, Axis, Point, We0), 266 {We,[{Id,NewEdges}|Acc]} 267 end,[],St0), 268 St = wings_sel:set(edge, Sel, St1), 269 {save_state,wings_sel:valid_sel(St)}; 270 271%% Vertex mode 272plane_cut(_, {Axis0,#st{selmode=SelMode}=StA}, 273 #st{selmode=vertex,repeatable=R,drag_args=Da}=StB) -> 274 Axis = axis_conv(Axis0), 275 T1 = os:timestamp(), 276 Points = wings_sel:fold(fun(Vs0,#we{vp=Vtab},Acc0) -> 277 Vs = gb_sets:to_list(Vs0), 278 foldl(fun(V, Acc1) -> 279 [{V,array:get(V, Vtab)}|Acc1] 280 end, Acc0, Vs) 281 end, [], StB), 282 {St1,Sel} = case SelMode of 283 face -> 284 wings_sel:mapfold(fun(Faces, #we{id=Id}=We0, Acc) -> 285 {NewEdges,_,_,We} = foldl(fun 286 ({V,Point},{Es,Fs0,Seen0,We1}=A) -> 287 T2 = timer:now_diff(os:timestamp(), T1), 288 if T2 > 20000000 -> planecut_error(); 289 true -> 290 case ordsets:is_element(V,Seen0) of 291 true -> A; 292 false -> 293 {NewEs,We2} = intersects(Fs0, Axis, Point, We1), 294 SeenVs = wings_edge:to_vertices(NewEs,We2), 295 Seen = ordsets:union(SeenVs,Seen0), 296 NewFaces = wings_we:new_items_as_gbset(face, We1, We2), 297 Fs = gb_sets:union(NewFaces,Fs0), 298 {gb_sets:union(NewEs, Es),Fs,Seen,We2} 299 end 300 end 301 end, {gb_sets:new(),Faces,[],We0}, Points), 302 {We,[{Id,NewEdges}|Acc]} 303 end,[],StA); 304 body -> 305 wings_sel:mapfold(fun(_, #we{id=Id}=We0, Acc) -> 306 {NewEdges,_,We} = foldl(fun({V,Point},{Es,Seen0,We1}=A) -> 307 T2 = timer:now_diff(os:timestamp(), T1), 308 if T2 > 20000000 -> planecut_error(); 309 true -> 310 case ordsets:is_element(V,Seen0) of 311 true -> A; 312 false -> 313 EdgePos2Cut = intersects(Axis, Point, We1), 314 {NewEs,We2} = cut_intersecting_edges(false, Axis, EdgePos2Cut, We1), 315 SeenVs = wings_edge:to_vertices(NewEs,We2), 316 Seen = ordsets:union(SeenVs,Seen0), 317 {gb_sets:union(NewEs, Es),Seen,We2} 318 end 319 end 320 end, {gb_sets:new(),[],We0}, Points), 321 {We,[{Id,NewEdges}|Acc]} 322 end,[],StA) 323 end, 324 St = wings_sel:set(edge, Sel, St1), 325 {save_state,wings_sel:valid_sel(St#st{repeatable=R,ask_args=Axis,drag_args=Da})}; 326 327plane_cut(Cut, {Axis0,Point}, St0) -> 328 Axis = axis_conv(Axis0), 329 {St1,Sel} = wings_sel:mapfold(fun(_, #we{id=Id}=We0, Acc) -> 330 EdgePos2Cut = intersects(Axis, Point, We0), 331 {NewEdges,We} = cut_intersecting_edges(Cut, Axis, EdgePos2Cut, We0), 332 {We,[{Id,NewEdges}|Acc]} 333 end,[],St0), 334 St = wings_sel:set(edge, Sel, St1), 335 case Cut of 336 true -> 337 {save_state,loop_cut(Axis, Point, wings_sel:valid_sel(St))}; 338 false -> 339 {save_state,wings_sel:valid_sel(St)} 340 end. 341 342-spec planecut_error() -> no_return(). 343planecut_error() -> 344 wings_u:error_msg(?__(1,"This is taking too long.\nTry again with a smaller selection.")). 345 346%%% 347%%% Calculate slice points distributed equally along the given axis 348%%% 349 350slice_points(Axis, N, #st{selmode=face}=St) -> 351 Zero = e3d_vec:zero(), 352 [{_,PosA},{_,PosB}] = 353 wings_sel:fold(fun(Faces,#we{vp=Vtab}=We,Acc) -> 354 Vs = wings_face:to_vertices(Faces,We), 355 foldl(fun 356 (V,none) -> 357 Pos = array:get(V, Vtab), 358 D = dist_along_vector(Pos, Zero, Axis), 359 [{D,Pos},{D,Pos}]; 360 (V,[{MinD,_}=Min,{MaxD,_}=Max]) -> 361 Pos = array:get(V, Vtab), 362 D = dist_along_vector(Pos, Zero, Axis), 363 if D < MinD -> [{D,Pos},Max]; 364 D > MaxD -> [Min,{D,Pos}]; 365 true -> [Min,Max] 366 end 367 end, Acc, Vs) 368 end, none, St), 369 Dist = abs(dist_along_vector(PosA, PosB, Axis)) / N, 370 get_point_positions(PosA, Dist, Axis, N-1); 371slice_points(Axis, N, #st{selmode=body}=St) -> 372 Zero = e3d_vec:zero(), 373 [{_,PosA},{_,PosB}] = 374 wings_sel:fold(fun(_,#we{vp=Vtab},Acc) -> 375 array:sparse_foldl(fun 376 (_, Pos, none) -> 377 D = dist_along_vector(Pos, Zero, Axis), 378 [{D,Pos},{D,Pos}]; 379 (_, Pos, [{MinD,_}=Min,{MaxD,_}=Max]) -> 380 D = dist_along_vector(Pos, Zero, Axis), 381 if D < MinD -> [{D,Pos},Max]; 382 D > MaxD -> [Min,{D,Pos}]; 383 true -> [Min,Max] 384 end 385 end, Acc, Vtab) 386 end, none, St), 387 Dist = abs(dist_along_vector(PosA, PosB, Axis)) / N, 388 get_point_positions(PosA, Dist, Axis, N-1). 389 390 391dist_along_vector({Xa,Ya,Za},{Xb,Yb,Zb},{Vx,Vy,Vz}) -> 392%% Return Distance between PosA and PosB along Normalized Vector 393 Vx*(Xa-Xb)+Vy*(Ya-Yb)+Vz*(Za-Zb). 394 395get_point_positions(_, _, _, 0) -> []; 396get_point_positions(Pos0, Dist, Axis, N) -> 397 Pos = e3d_vec:add(Pos0, e3d_vec:mul(Axis, Dist)), 398 [Pos|get_point_positions(Pos, Dist, Axis, N-1)]. 399 400 401%% There are optimizations for Standard Axes (see opposite_sides). 402%% Body mode is faster than face mode for wholly selected objects since face 403%% mode has to cenvert the selection to edges as part of processing the 404%% selection; body mode just folds over the Etab. 405 406%%% 407%%% Body mode 408%%% 409 410intersects({1.0,0.0,0.0}=Plane, {X,_,_}=CutPoint, #we{es=Etab,vp=Vtab}) -> 411 array:sparse_foldl(fun 412 (Edge, #edge{vs=Va,ve=Vb}, EdgesToCut1) -> 413 {Xa,_,_} = PosA = array:get(Va, Vtab), 414 {Xb,_,_} = PosB = array:get(Vb, Vtab), 415 case opposite_sides(Xa, Xb, X) of 416 true -> 417 EdgeVec = e3d_vec:norm_sub(PosA, PosB), 418 case abs(e3d_vec:dot(Plane, EdgeVec)) < ?NON_ZERO of 419 true -> EdgesToCut1; 420 false -> 421 Point = intersect_vec_plane(CutPoint, PosA, Plane, EdgeVec), 422 [{Edge,Point}|EdgesToCut1] 423 end; 424 false -> EdgesToCut1 425 end 426 end, [], Etab); 427intersects({0.0,1.0,0.0}=Plane, {_,Y,_}=CutPoint, #we{es=Etab,vp=Vtab}) -> 428 array:sparse_foldl(fun 429 (Edge, #edge{vs=Va,ve=Vb}, EdgesToCut1) -> 430 {_,Ya,_} = PosA = array:get(Va, Vtab), 431 {_,Yb,_} = PosB = array:get(Vb, Vtab), 432 case opposite_sides(Ya, Yb, Y) of 433 true -> 434 EdgeVec = e3d_vec:norm_sub(PosA, PosB), 435 case abs(e3d_vec:dot(Plane, EdgeVec)) < ?NON_ZERO of 436 true -> EdgesToCut1; 437 false -> 438 Point = intersect_vec_plane(CutPoint, PosA, Plane, EdgeVec), 439 [{Edge,Point}|EdgesToCut1] 440 end; 441 false -> EdgesToCut1 442 end 443 end, [], Etab); 444intersects({0.0,0.0,1.0}=Plane, {_,_,Z}=CutPoint, #we{es=Etab,vp=Vtab}) -> 445 array:sparse_foldl(fun 446 (Edge, #edge{vs=Va,ve=Vb}, EdgesToCut1) -> 447 {_,_,Za} = PosA = array:get(Va, Vtab), 448 {_,_,Zb} = PosB = array:get(Vb, Vtab), 449 case opposite_sides(Za, Zb, Z) of 450 true -> 451 EdgeVec = e3d_vec:norm_sub(PosA, PosB), 452 case abs(e3d_vec:dot(Plane, EdgeVec)) < ?NON_ZERO of 453 true -> EdgesToCut1; 454 false -> 455 Point = intersect_vec_plane(CutPoint, PosA, Plane, EdgeVec), 456 [{Edge,Point}|EdgesToCut1] 457 end; 458 false -> EdgesToCut1 459 end 460 end, [], Etab); 461intersects(Plane, CutPoint, #we{es=Etab,vp=Vtab}) -> 462 SideArray = assign_side(Vtab, Plane, CutPoint), 463 array:sparse_foldl(fun 464 (Edge, #edge{vs=Va,ve=Vb}, EdgesToCut1) -> 465 {SideA,PosA} = array:get(Va, SideArray), 466 {SideB,PosB} = array:get(Vb, SideArray), 467 case opposite_sides(SideA, SideB) of 468 true -> 469 EdgeVec = e3d_vec:norm_sub(PosA, PosB), 470 case abs(e3d_vec:dot(Plane, EdgeVec)) < ?NON_ZERO of 471 true -> EdgesToCut1; 472 false -> 473 Point = intersect_vec_plane(CutPoint, PosA, Plane, EdgeVec), 474 [{Edge,Point}|EdgesToCut1] 475 end; 476 false -> EdgesToCut1 477 end 478 end, [], Etab). 479 480%%% 481%%% Face mode 482%%% 483 484intersects(Faces, {1.0,0.0,0.0}=Plane, {X,_,_}=CutPoint, #we{es=Etab,vp=Vtab}=We0) -> 485 Edges = wings_face:to_edges(Faces, We0), 486 EdgePos2Cut = foldl(fun(Edge, EdgesToCut1) -> 487 #edge{vs=Va,ve=Vb} = array:get(Edge, Etab), 488 {Xa,_,_} = PosA = array:get(Va, Vtab), 489 {Xb,_,_} = PosB = array:get(Vb, Vtab), 490 case opposite_sides(Xa, Xb, X) of 491 true -> 492 EdgeVec = e3d_vec:norm_sub(PosA, PosB), 493 case abs(e3d_vec:dot(Plane, EdgeVec)) < ?NON_ZERO of 494 true -> EdgesToCut1; 495 false -> 496 Point = intersect_vec_plane(CutPoint, PosA, Plane, EdgeVec), 497 [{Edge,Point}|EdgesToCut1] 498 end; 499 false -> EdgesToCut1 500 end 501 end, [], Edges), 502 cut_intersecting_edges(Faces, Plane, EdgePos2Cut, We0); 503intersects(Faces, {0.0,1.0,0.0}=Plane, {_,Y,_}=CutPoint, #we{es=Etab,vp=Vtab}=We0) -> 504 Edges = wings_face:to_edges(Faces, We0), 505 EdgePos2Cut = foldl(fun(Edge, EdgesToCut1) -> 506 #edge{vs=Va,ve=Vb} = array:get(Edge, Etab), 507 {_,Ya,_} = PosA = array:get(Va, Vtab), 508 {_,Yb,_} = PosB = array:get(Vb, Vtab), 509 case opposite_sides(Ya, Yb, Y) of 510 true -> 511 EdgeVec = e3d_vec:norm_sub(PosA, PosB), 512 case abs(e3d_vec:dot(Plane, EdgeVec)) < ?NON_ZERO of 513 true -> EdgesToCut1; 514 false -> 515 Point = intersect_vec_plane(CutPoint, PosA, Plane, EdgeVec), 516 [{Edge,Point}|EdgesToCut1] 517 end; 518 false -> EdgesToCut1 519 end 520 end, [], Edges), 521 cut_intersecting_edges(Faces, Plane, EdgePos2Cut, We0); 522intersects(Faces, {0.0,0.0,1.0}=Plane, {_,_,Z}=CutPoint, #we{es=Etab,vp=Vtab}=We0) -> 523 Edges = wings_face:to_edges(Faces, We0), 524 EdgePos2Cut = foldl(fun(Edge, EdgesToCut1) -> 525 #edge{vs=Va,ve=Vb} = array:get(Edge, Etab), 526 {_,_,Za} = PosA = array:get(Va, Vtab), 527 {_,_,Zb} = PosB = array:get(Vb, Vtab), 528 case opposite_sides(Za, Zb, Z) of 529 true -> 530 EdgeVec = e3d_vec:norm_sub(PosA, PosB), 531 case abs(e3d_vec:dot(Plane, EdgeVec)) < ?NON_ZERO of 532 true -> EdgesToCut1; 533 false -> 534 Point = intersect_vec_plane(CutPoint, PosA, Plane, EdgeVec), 535 [{Edge,Point}|EdgesToCut1] 536 end; 537 false -> EdgesToCut1 538 end 539 end, [], Edges), 540 cut_intersecting_edges(Faces, Plane, EdgePos2Cut, We0); 541intersects(Faces, Plane, CutPoint, #we{es=Etab,vp=Vtab}=We0) -> 542 Edges = wings_face:to_edges(Faces, We0), 543 EdgePos2Cut = foldl(fun(Edge, EdgesToCut1) -> 544 #edge{vs=Va,ve=Vb} = array:get(Edge, Etab), 545 PosA = array:get(Va, Vtab), 546 PosB = array:get(Vb, Vtab), 547 case opposite_sides(PosA, PosB, CutPoint, Plane) of 548 true -> 549 EdgeVec = e3d_vec:norm_sub(PosA, PosB), 550 case abs(e3d_vec:dot(Plane, EdgeVec)) < ?NON_ZERO of 551 true -> EdgesToCut1; 552 false -> 553 Point = intersect_vec_plane(CutPoint, PosA, Plane, EdgeVec), 554 [{Edge,Point}|EdgesToCut1] 555 end; 556 false -> EdgesToCut1 557 end 558 end, [], Edges), 559 cut_intersecting_edges(Faces, Plane, EdgePos2Cut, We0). 560 561%%% 562%%% Cut edges and connect the vertices 563%%% 564 565cut_intersecting_edges(Type, Plane, EdgePos, We0) -> 566 {Vs0,We1} = cut_edges(EdgePos, We0), 567 Vs = ordsets:from_list(Vs0), 568 {GoodVs,We} = connect(Type, Plane, Vs, We1), 569 Edges = vs_to_edges(GoodVs, We, []), 570 {Edges,We}. 571 572connect(true, Plane, Vs0, We) -> 573 FaceVs = wings_vertex:per_face(Vs0, We), 574 foldl(fun({Face,Vs}, {VsA,NewWe}) -> 575 FaceNorm = wings_face:normal(Face, We), 576 case abs(e3d_vec:dot(Plane, FaceNorm)) > 0.999 of 577 true -> {wings_face:to_vertices([Face],We)++VsA,NewWe}; 578 false -> {VsA,wings_vertex:connect(Face, Vs, NewWe)} 579 end 580 end, {Vs0,We}, FaceVs); 581connect(false, Plane, Vs0, #we{mirror=MirrorFace}=We) -> 582 FaceVs = wings_vertex:per_face(Vs0, We), 583 foldl(fun 584 ({Face,_}, Acc) when Face =:= MirrorFace -> Acc; 585 ({Face,Vs}, {VsA,NewWe}) -> 586 FaceNorm = wings_face:normal(Face, We), 587 case abs(e3d_vec:dot(Plane, FaceNorm)) > 0.999 of 588 true -> {wings_face:to_vertices([Face],We)++VsA,NewWe}; 589 false -> {VsA,wings_vertex:connect(Face, Vs, NewWe)} 590 end 591 end, {Vs0,We}, FaceVs); 592connect(Faces, Plane, Vs0, #we{mirror=MirrorFace}=We) -> 593 FaceVs = wings_vertex:per_face(Vs0, We), 594 foldl(fun 595 ({Face,_}, Acc) when Face =:= MirrorFace -> Acc; 596 ({Face,Vs}, {VsA,NewWe}=Acc) -> 597 case gb_sets:is_member(Face, Faces) of 598 true -> 599 FaceNorm = wings_face:normal(Face, We), 600 case abs(e3d_vec:dot(Plane, FaceNorm)) > 0.999 of 601 true -> {wings_face:to_vertices([Face],We)++VsA,NewWe}; 602 false -> {VsA,wings_vertex:connect(Face, Vs, NewWe)} 603 end; 604 false -> Acc 605 end 606 end, {Vs0,We}, FaceVs). 607 608vs_to_edges([Va|Vs], We, Acc0) -> 609%% finds the edges between any two listed vertices and returns a gb_set fo edges 610 Edges = wings_vertex:fold(fun(Edge, _, EdgeRec, Acc) -> 611 Vb = wings_vertex:other(Va, EdgeRec), 612 case ordsets:is_element(Vb,Vs) of 613 true -> [Edge|Acc]; 614 _ -> Acc 615 end 616 end, Acc0, Va, We), 617 vs_to_edges(Vs, We, Edges); 618vs_to_edges([], _, Edges) -> 619 gb_sets:from_list(usort(Edges)). 620 621cut_edges(Es, We0) -> 622%% Fold over the list Es of which each entry is a tuple with the Edge id and the 623%% proposed cut position. If the proposed cut position is VERY close to one of 624%% the edge's vertices, then don't cut that, but return that Vertex id. 625%% If the edge is cut, then return the new vertex id. In a later function, all 626%% of the collected vertex id will be connected with new edges making the final 627%% edge loop. 628 lists:mapfoldl(fun 629 ({Edge,Pos}, #we{es=Etab,vp=Vtab}=We1) -> 630 #edge{vs=Va,ve=Vb} = array:get(Edge, Etab), 631 PosA = array:get(Va, Vtab), 632 case e3d_vec:dist(PosA, Pos) < ?NON_ZERO of 633 true -> {Va,We1}; 634 false -> 635 PosB = array:get(Vb, Vtab), 636 case e3d_vec:dist(PosB, Pos) < ?NON_ZERO of 637 true -> {Vb,We1}; 638 false -> 639 {We,V} = wings_edge:fast_cut(Edge, Pos, We1), 640 {V,We} 641 end 642 end 643 end, We0, Es). 644 645%%% 646%%% Plane Cut utilities 647%%% 648 649intersect_vec_plane(PosA, PosB, Plane, EdgeVec) -> 650%% Return point where Vector through PosA intersects with plane at PosB 651 case e3d_vec:dot(EdgeVec,Plane) of 652 0.0 -> 653 Intersection = e3d_vec:dot(e3d_vec:sub(PosB, PosA), Plane), 654 e3d_vec:add(PosB, e3d_vec:mul(Plane, Intersection)); 655 Dot -> 656 Intersection = e3d_vec:dot(e3d_vec:sub(PosA, PosB), Plane) / Dot, 657 e3d_vec:add(PosB, e3d_vec:mul(EdgeVec, Intersection)) 658 end. 659 660%% Tests whether the 2 vertices of an edge are on opposite sides of the Plane. 661%% The opposite_sides function that is used depends on the Plane and the 662%% selection mode. 663opposite_sides(A, B) when A =:= on_vertex; B =:= on_vertex -> true; 664opposite_sides(Side, Side) -> false; 665opposite_sides(_, _) -> true. 666 667opposite_sides(_, B, B) -> true; 668opposite_sides(A, _, A) -> true; 669opposite_sides(A, B, C) -> 670 SideA = A < C, 671 SideB = B < C, 672 SideA =/= SideB. 673 674opposite_sides(PosA, PosB, CutPoint, Plane) -> 675 VecA = e3d_vec:norm_sub(PosA, CutPoint), 676 VecB = e3d_vec:norm_sub(PosB, CutPoint), 677 Zero = e3d_vec:zero(), 678 case e3d_vec:dot(VecA, Plane) =< 0 of 679 _ when VecA =:= Zero; VecB =:= Zero -> 680 true; 681 true -> 682 e3d_vec:dot(VecB, Plane) >= 0; 683 false -> 684 e3d_vec:dot(VecB, Plane) =< 0 685 end. 686 687assign_side(Vtab, Plane, CutPoint) -> 688%% Create an array using each vertex id as the index and {Dot < 0, Pos} as the 689%% value. The Dot boolean is later used to determine the side of the plane on 690%% which the vertex resides. The position is stored just so is doesn't have to 691%% be looked up again. 692 array:sparse_foldl(fun 693 (V, Pos, Array) -> 694 Vec = e3d_vec:norm_sub(Pos, CutPoint), 695 Dot = e3d_vec:dot(Vec, Plane), 696 case Vec =:= e3d_vec:zero() of 697 true -> 698 array:set(V, {on_vertex ,Pos}, Array); 699 false -> 700 array:set(V, {Dot =< 0 ,Pos}, Array) 701 end 702 end, Vtab, Vtab). 703 704axis_conv(Axis) -> 705%% Converts an atom axis to a tuple axis. 706 case Axis of 707 x -> {1.0,0.0,0.0}; 708 y -> {0.0,1.0,0.0}; 709 z -> {0.0,0.0,1.0}; 710 last_axis -> 711 {_, Dir} = wings_pref:get_value(last_axis), 712 Dir; 713 default_axis -> 714 {_, Dir} = wings_pref:get_value(default_axis), 715 Dir; 716 {X,Y,Z} -> {X,Y,Z}; 717 {LastAxis,_} -> LastAxis 718 end. 719 720%%% 721%%% Loop Cut modified from wings_edge_cmd.erl 722%%% 723 724loop_cut(Axis, Point, St) -> 725 CF = fun(Edges, #we{fs=Ftab}=We0) -> 726 AdjFaces = wings_face:from_edges(Edges, We0), 727 case loop_cut_partition(AdjFaces, Edges, We0, []) of 728 [_] -> 729 {We0,gb_sets:empty()}; 730 [_|Parts0] -> 731 Parts = [gb_sets:to_list(P) || P <- Parts0], 732 FirstComplement = ordsets:union(Parts), 733 First = ordsets:subtract(gb_trees:keys(Ftab), 734 FirstComplement), 735 We = wings_dissolve:complement(First, We0), 736 Sel = select_one_side(Axis, Point, We), 737 New = loop_cut_make_copies(Parts, Axis, Point, We0), 738 {We,Sel,New} 739 end 740 end, 741 wings_sel:clone(CF, body, St). 742 743loop_cut_make_copies([P|Parts], Axis, Point, We0) -> 744 We = wings_dissolve:complement(P, We0), 745 Sel = select_one_side(Axis, Point, We), 746 [{We,Sel,cut}|loop_cut_make_copies(Parts, Axis, Point, We0)]; 747loop_cut_make_copies([], _, _, _) -> []. 748 749loop_cut_partition(Faces0, Edges, We, Acc) -> 750 case gb_sets:is_empty(Faces0) of 751 true -> Acc; 752 false -> 753 {AFace,Faces1} = gb_sets:take_smallest(Faces0), 754 Reachable = wings_edge:reachable_faces([AFace], Edges, We), 755 Faces = gb_sets:difference(Faces1, Reachable), 756 loop_cut_partition(Faces, Edges, We, [Reachable|Acc]) 757 end. 758 759select_one_side(_, all, _) -> 760 gb_sets:singleton(0); 761select_one_side(Plane, Point, #we{fs=Fs}=We) -> 762 {Face,_} = gb_trees:smallest(Fs), 763 Center = wings_face:center(Face, We), 764 Vec = e3d_vec:sub(Point, Center), 765 case e3d_vec:dot(Plane, Vec) < 0.0 of 766 true -> gb_sets:singleton(0); 767 false -> gb_sets:empty() 768 end. 769