%% %% wings_dissolve.erl -- %% %% This module implements dissolve of faces. %% -module(wings_dissolve). -export([faces/2, complement/2]). -include("wings.hrl"). %% faces([Face], We) -> We' %% Dissolve the given faces. faces([], We) -> We; faces(Faces, #we{fs=Ftab0}=We) -> case gb_sets:is_empty(Faces) of true -> We; false when is_list(Faces) -> Complement = ordsets:subtract(gb_trees:keys(Ftab0), ordsets:from_list(Faces)), dissolve_1(Faces, Complement, We); false -> Complement = ordsets:subtract(gb_trees:keys(Ftab0), gb_sets:to_list(Faces)), dissolve_1(Faces, Complement, We) end. faces([], _, We) -> We; faces(Faces,Complement,We) -> case gb_sets:is_empty(Faces) of true -> We; false -> dissolve_1(Faces, Complement,We) end. dissolve_1(Faces, Complement, We0) -> We1 = optimistic_dissolve(Faces,Complement,We0#we{vc=undefined}), NewFaces = wings_we:new_items_as_ordset(face, We0, We1), We2 = wings_face:delete_bad_faces(NewFaces, We1), We = wings_we:rebuild(We2), case wings_we:is_consistent(We) of true -> We; false -> io:format("Dissolving would cause an inconsistent object structure.") end. %% complement([Face], We) -> We' %% Dissolve all faces BUT the given faces. Also invalidate the %% mirror face if it existed and was dissolved. complement(Fs0, #we{fs=Ftab0}=We0) when is_list(Fs0) -> Fs = ordsets:subtract(gb_trees:keys(Ftab0), ordsets:from_list(Fs0)), case faces(Fs, Fs0, We0) of #we{mirror=none}=We -> We; #we{mirror=Face,fs=Ftab}=We -> case gb_trees:is_defined(Face, Ftab) of false -> We; true -> We#we{mirror=none} end end; complement(Fs, We) -> complement(gb_sets:to_list(Fs), We). optimistic_dissolve(Faces0, Compl, We0) -> %% Optimistically assume that we have a simple region without %% any holes. case outer_edge_loop(Faces0, We0) of error -> %% Assumption was wrong. We need to partition the selection %% and dissolve each partition in turn. Parts = wings_sel:face_regions(Faces0, We0), complex_dissolve(Parts, We0); [_|_]=Loop -> %% Assumption was correct. simple_dissolve(Faces0, Compl, Loop, We0) end. %% simple_dissolve(Faces, Loop, We0) -> We %% Dissolve a region of faces with no holes and no %% repeated vertices in the outer edge loop. simple_dissolve(Faces0, Compl, Loop, We0) -> Faces = to_gb_set(Faces0), OldFace = gb_sets:smallest(Faces), Mat = wings_facemat:face(OldFace, We0), We1 = fix_materials(Faces, Compl, We0), #we{es=Etab0,fs=Ftab0,he=Htab0} = We1, {Ftab1,Etab1,Htab} = simple_del(Faces, Ftab0, Etab0, Htab0, We1), {NewFace,We2} = wings_we:new_id(We1), Ftab = gb_trees:insert(NewFace, hd(Loop), Ftab1), Last = lists:last(Loop), Etab = update_outer([Last|Loop], Loop, NewFace, Ftab, Etab1), We = We2#we{es=Etab,fs=Ftab,he=Htab}, wings_facemat:assign(Mat, [NewFace], We). fix_materials(Del,Keep,We) -> case gb_sets:size(Del) < length(Keep) of true -> wings_facemat:delete_faces(Del,We); false -> wings_facemat:keep_faces(Keep,We) end. to_gb_set(List) when is_list(List) -> gb_sets:from_list(List); to_gb_set(S) -> S. %% Delete faces and inner edges for a simple region. simple_del(Faces, Ftab0, Etab0, Htab0, We) -> case {gb_trees:size(Ftab0),gb_sets:size(Faces)} of {AllSz,FaceSz} when AllSz < 2*FaceSz -> %% At least half of the faces are selected. %% It is faster to find the edges for the %% unselected faces. UnselFaces = ordsets:subtract(gb_trees:keys(Ftab0), gb_sets:to_list(Faces)), UnselSet = sofs:from_external(UnselFaces, [face]), Ftab1 = sofs:from_external(gb_trees:to_list(Ftab0), [{face,edge}]), Ftab2 = sofs:restriction(Ftab1, UnselSet), Ftab = gb_trees:from_orddict(sofs:to_external(Ftab2)), Keep0 = wings_face:to_edges(UnselFaces, We), Keep = sofs:set(Keep0, [edge]), Etab1 = sofs:from_external(gb_trees:to_list(Etab0), [{edge,info}]), Etab2 = sofs:restriction(Etab1, Keep), Etab = gb_trees:from_orddict(sofs:to_external(Etab2)), Htab = simple_del_hard(Htab0, sofs:to_external(Keep), undefined), {Ftab,Etab,Htab}; {_,_} -> Ftab = lists:foldl(fun(Face, Ft) -> gb_trees:delete(Face, Ft) end, Ftab0, gb_sets:to_list(Faces)), Inner = wings_face:inner_edges(Faces, We), Etab = lists:foldl(fun(Edge, Et) -> gb_trees:delete(Edge, Et) end, Etab0, Inner), Htab = simple_del_hard(Htab0, undefined, Inner), {Ftab,Etab,Htab} end. simple_del_hard(Htab, Keep, Remove) -> case gb_sets:is_empty(Htab) of true -> Htab; false -> simple_del_hard_1(Htab, Keep, Remove) end. simple_del_hard_1(Htab, Keep, undefined) -> gb_sets:intersection(Htab, gb_sets:from_ordset(Keep)); simple_del_hard_1(Htab, undefined, Remove) -> gb_sets:difference(Htab, gb_sets:from_ordset(Remove)). %% complex([Partition], We0) -> We0 %% The general dissolve. complex_dissolve([Faces|T], We0) -> Face = gb_sets:smallest(Faces), Mat = wings_facemat:face(Face, We0), We1 = wings_facemat:delete_faces(Faces, We0), Parts = outer_edge_partition(Faces, We1), We = do_dissolve(Faces, Parts, Mat, We0, We1), complex_dissolve(T, We); complex_dissolve([], We) -> We. do_dissolve(Faces, Ess, Mat, WeOrig, We0) -> We1 = do_dissolve_faces(Faces, We0), Inner = wings_face:inner_edges(Faces, WeOrig), We2 = delete_inner(Inner, We1), #we{he=Htab0} = We = do_dissolve_1(Ess, Mat, We2), Htab = gb_sets:difference(Htab0, gb_sets:from_list(Inner)), We#we{he=Htab}. do_dissolve_1([EdgeList|Ess], Mat, #we{es=Etab0,fs=Ftab0}=We0) -> {Face,We1} = wings_we:new_id(We0), Ftab = gb_trees:insert(Face, hd(EdgeList), Ftab0), Last = lists:last(EdgeList), Etab = update_outer([Last|EdgeList], EdgeList, Face, Ftab, Etab0), We2 = We1#we{es=Etab,fs=Ftab}, We = wings_facemat:assign(Mat, [Face], We2), do_dissolve_1(Ess, Mat, We); do_dissolve_1([], _Mat, We) -> We. do_dissolve_faces(Faces, #we{fs=Ftab0}=We) -> Ftab = lists:foldl(fun(Face, Ft) -> gb_trees:delete(Face, Ft) end, Ftab0, gb_sets:to_list(Faces)), We#we{fs=Ftab}. delete_inner(Inner, #we{es=Etab0}=We) -> Etab = lists:foldl(fun(Edge, Et) -> gb_trees:delete(Edge, Et) end, Etab0, Inner), We#we{es=Etab}. update_outer([Pred|[Edge|Succ]=T], More, Face, Ftab, Etab0) -> #edge{rf=Rf} = R0 = gb_trees:get(Edge, Etab0), Rec = case gb_trees:is_defined(Rf, Ftab) of true -> ?ASSERT(false == gb_trees:is_defined(R0#edge.lf, Ftab)), LS = succ(Succ, More), R0#edge{lf=Face,ltpr=Pred,ltsu=LS}; false -> ?ASSERT(true == gb_trees:is_defined(R0#edge.lf, Ftab)), RS = succ(Succ, More), R0#edge{rf=Face,rtpr=Pred,rtsu=RS} end, Etab = gb_trees:update(Edge, Rec, Etab0), update_outer(T, More, Face, Ftab, Etab); update_outer([_], _More, _Face, _Ftab, Etab) -> Etab. succ([Succ|_], _More) -> Succ; succ([], [Succ|_]) -> Succ. %% outer_edge_loop(FaceSet,WingedEdge) -> [Edge] | error. %% Partition the outer edges of the FaceSet into a single closed loop. %% Return 'error' if the faces in FaceSet does not form a %% simple region without holes. %% %% Equvivalent to %% case outer_edge_partition(FaceSet,WingedEdge) of %% [Loop] -> Loop; %% [_|_] -> error %% end. %% but faster. outer_edge_loop(Faces, We) -> case lists:sort(collect_outer_edges(Faces, We)) of [] -> error; [{Key,Val}|Es0] -> case any_duplicates(Es0, Key) of false -> Es = gb_trees:from_orddict(Es0), N = gb_trees:size(Es), outer_edge_loop_1(Val, Es, Key, N, []); true -> error end end. outer_edge_loop_1({Edge,V}, _, V, 0, Acc) -> %% This edge completes the loop, and we have used all possible edges. [Edge|Acc]; outer_edge_loop_1({_,V}, _, V, _N, _) -> %% Loop is complete, but we haven't used all edges. error; outer_edge_loop_1({_,_}, _, _, 0, _) -> %% We have used all possible edges, but somehow the loop %% is not complete. I can't see how this is possible. erlang:error(internal_error); outer_edge_loop_1({Edge,Vb}, Es, EndV, N, Acc0) -> Acc = [Edge|Acc0], outer_edge_loop_1(gb_trees:get(Vb, Es), Es, EndV, N-1, Acc). any_duplicates([{V,_}|_], V) -> true; any_duplicates([_], _) -> false; any_duplicates([{V,_}|Es], _) -> any_duplicates(Es, V). %% outer_edge_partition(FaceSet, WingedEdge) -> [[Edge]]. %% Partition the outer edges of the FaceSet. Each partion %% of edges form a closed loop with no repeated vertices. %% Outer edges are edges that have one face in FaceSet %% and one outside. %% It is assumed that FaceSet consists of one region returned by %% wings_sel:face_regions/2. outer_edge_partition(Faces, We) -> F0 = collect_outer_edges(Faces, We), F = gb_trees:from_orddict(wings_util:rel2fam(F0)), partition_edges(F, []). collect_outer_edges(Faces, We) when is_list(Faces) -> collect_outer_edges_1(Faces, gb_sets:from_list(Faces), We); collect_outer_edges(Faces, We) -> collect_outer_edges_1(gb_sets:to_list(Faces), Faces, We). collect_outer_edges_1(Fs0, Faces0, #we{fs=Ftab}=We) -> case {gb_trees:size(Ftab),gb_sets:size(Faces0)} of {AllSz,FaceSz} when AllSz < 2*FaceSz -> Fs = ordsets:subtract(gb_trees:keys(Ftab), Fs0), Faces = gb_sets:from_ordset(Fs), Coll = collect_outer_edges_a(Faces), wings_face:fold_faces(Coll, [], Fs, We); {_,_} -> Coll = collect_outer_edges_b(Faces0), wings_face:fold_faces(Coll, [], Fs0, We) end. collect_outer_edges_a(Faces) -> fun(Face, _, Edge, #edge{ve=V,vs=OtherV,lf=Face,rf=Other}, Acc) -> case gb_sets:is_member(Other, Faces) of false -> [{V,{Edge,OtherV}}|Acc]; true -> Acc end; (Face, _, Edge, #edge{ve=OtherV,vs=V,rf=Face,lf=Other}, Acc) -> case gb_sets:is_member(Other, Faces) of false -> [{V,{Edge,OtherV}}|Acc]; true -> Acc end end. collect_outer_edges_b(Faces) -> fun(Face, _, Edge, #edge{vs=V,ve=OtherV,lf=Face,rf=Other}, Acc) -> case gb_sets:is_member(Other, Faces) of false -> [{V,{Edge,OtherV}}|Acc]; true -> Acc end; (Face, _, Edge, #edge{vs=OtherV,ve=V,rf=Face,lf=Other}, Acc) -> case gb_sets:is_member(Other, Faces) of false -> [{V,{Edge,OtherV}}|Acc]; true -> Acc end end. partition_edges(Es0, Acc) -> case gb_trees:is_empty(Es0) of true -> Acc; false -> {Key,Val,Es1} = gb_trees:take_smallest(Es0), {Cycle,Es} = part_collect_cycle(Key, Val, Es1, []), partition_edges(Es, [Cycle|Acc]) end. %% part_collect_cycle(Vertex, VertexInfo, EdgeInfo, Acc0) -> %% none | {[Edge],EdgeInfo} %% Collect the cycle starting with Vertex. %% %% Note: This function can only return 'none' when called %% recursively. part_collect_cycle(_, repeated, _, _) -> %% Repeated vertex - we are not allowed to go this way. %% Can only happen if we were called recursively because %% a fork was encountered. none; part_collect_cycle(_Va, [{Edge,Vb}], Es0, Acc0) -> %% Basic case. Only one way to go. Acc = [Edge|Acc0], case gb_trees:lookup(Vb, Es0) of none -> {Acc,Es0}; {value,Val} -> Es = gb_trees:delete(Vb, Es0), part_collect_cycle(Vb, Val, Es, Acc) end; part_collect_cycle(Va, [Val|More], Es0, []) -> %% No cycle started yet and we have multiple choice of %% edges out from this vertex. It doesn't matter which %% edge we follow, so we'll follow the first one. {Cycle,Es} = part_collect_cycle(Va, [Val], Es0, []), {Cycle,gb_trees:insert(Va, More, Es)}; part_collect_cycle(Va, Edges, Es0, Acc) -> %% We have a partially collected cycle and we have a %% fork (multiple choice of edges). Here we must choose %% an edge that closes the cycle without passing Va %% again (because repeated vertices are not allowed). Es = gb_trees:insert(Va, repeated, Es0), part_fork(Va, Edges, Es, Acc, []). part_fork(Va, [Val|More], Es0, Acc, Tried) -> %% Try to complete the cycle by following this edge. case part_collect_cycle(Va, [Val], Es0, Acc) of none -> %% Failure - try the next edge. part_fork(Va, More, Es0, Acc, [Val|Tried]); {Cycle,Es} -> %% Found a cycle. Update the vertex information %% with all edges remaining. {Cycle,gb_trees:update(Va, lists:reverse(Tried, More), Es)} end; part_fork(_, [], _, _, _) -> %% None of edges were possible. Can only happen if this function %% was called recursively (i.e. if we hit another fork while %% processing a fork). none.