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