%% %% wings_we.erl -- %% %% This module contains functions to build and manipulate %% we records (winged-edged records, the central data structure %% in Wings 3D). -module(wings_we). -export([rebuild/1, is_consistent/1, is_face_consistent/2, new_id/1, new_items_as_ordset/3, validate_mirror/1, visible/1, visible_edges/1]). -include("wings.hrl"). %%% %%% API. %%% validate_mirror(#we{mirror=none}=We) -> We; validate_mirror(#we{fs=Ftab,mirror=Face}=We) -> case gb_trees:is_defined(Face, Ftab) of false -> We#we{mirror=none}; true -> We end. %% rebuild(We) -> We' %% Rebuild any missing 'vc' and 'fs' tables. If there are %% fewer elements in the 'vc' table than in the 'vp' table, %% remove redundant entries in the 'vp' table. Updated id %% bounds. rebuild(#we{vc=undefined,fs=undefined,es=Etab0}=We0) -> Etab = gb_trees:to_list(Etab0), Ftab = rebuild_ftab(Etab), VctList = rebuild_vct(Etab), We = We0#we{vc=gb_trees:from_orddict(VctList),fs=Ftab}, rebuild_1(VctList, We); rebuild(#we{vc=undefined,es=Etab}=We) -> VctList = rebuild_vct(gb_trees:to_list(Etab), []), rebuild_1(VctList, We#we{vc=gb_trees:from_orddict(VctList)}); rebuild(#we{fs=undefined,es=Etab}=We) -> Ftab = rebuild_ftab(gb_trees:to_list(Etab)), rebuild(We#we{fs=Ftab}); rebuild(We) -> update_id_bounds(We). %%% Utilities for allocating IDs. new_id(#we{next_id=Id}=We) -> {Id,We#we{next_id=Id+1}}. %%% Returns sets of newly created items. new_items_as_ordset(vertex, #we{next_id=Wid}, #we{next_id=NewWid,vp=Tab}) -> new_items_as_ordset_1(Tab, Wid, NewWid); new_items_as_ordset(edge, #we{next_id=Wid}, #we{next_id=NewWid,es=Tab}) -> new_items_as_ordset_1(Tab, Wid, NewWid); new_items_as_ordset(face, #we{next_id=Wid}, #we{next_id=NewWid,fs=Tab}) -> new_items_as_ordset_1(Tab, Wid, NewWid). any_hidden(#we{fs=Ftab}) -> not gb_trees:is_empty(Ftab) andalso wings_util:gb_trees_smallest_key(Ftab) < 0. %%% %%% Local functions. %%% rebuild_1(VctList, #we{vc=Vct,vp=Vtab0}=We) -> case {gb_trees:size(Vct),gb_trees:size(Vtab0)} of {Same,Same} -> rebuild(We); {Sz1,Sz2} when Sz1 < Sz2 -> Vtab = vertex_gc_1(VctList, gb_trees:to_list(Vtab0), []), rebuild(We#we{vp=Vtab}) end. rebuild_vct(Es) -> rebuild_vct(Es, []). rebuild_vct([{Edge,#edge{vs=Va,ve=Vb}}|Es], Acc0) -> Acc = rebuild_maybe_add(Va, Vb, Edge, Acc0), rebuild_vct(Es, Acc); rebuild_vct([], VtoE) -> build_incident_tab(VtoE). rebuild_ftab(Es) -> rebuild_ftab_1(Es, []). rebuild_ftab_1([{Edge,#edge{lf=Lf,rf=Rf}}|Es], Acc0) -> Acc = rebuild_maybe_add(Lf, Rf, Edge, Acc0), rebuild_ftab_1(Es, Acc); rebuild_ftab_1([], FtoE) -> gb_trees:from_orddict(build_incident_tab(FtoE)). rebuild_maybe_add(Ka, Kb, E, [_,{Ka,_}|_]=Acc) -> [{Kb,E}|Acc]; rebuild_maybe_add(Ka, Kb, E, [_,{Kb,_}|_]=Acc) -> [{Ka,E}|Acc]; rebuild_maybe_add(Ka, Kb, E, [{Ka,_}|_]=Acc) -> [{Kb,E}|Acc]; rebuild_maybe_add(Ka, Kb, E, [{Kb,_}|_]=Acc) -> [{Ka,E}|Acc]; rebuild_maybe_add(Ka, Kb, E, Acc) -> [{Ka,E},{Kb,E}|Acc]. vertex_gc_1([{V,_}|Vct], [{V,_}=Vtx|Vpos], Acc) -> vertex_gc_1(Vct, Vpos, [Vtx|Acc]); vertex_gc_1([_|_]=Vct, [_|Vpos], Acc) -> vertex_gc_1(Vct, Vpos, Acc); vertex_gc_1([], _, Acc) -> gb_trees:from_orddict(lists:reverse(Acc)). %%% %%% Handling of hidden faces. %%% visible(#we{mirror=none,fs=Ftab}) -> visible_2(gb_trees:keys(Ftab)); visible(#we{mirror=Face,fs=Ftab}) -> visible_2(gb_trees:keys(gb_trees:delete(Face, Ftab))). visible_2([F|Fs]) when F < 0 -> visible_2(Fs); visible_2(Fs) -> Fs. visible_edges(#we{es=Etab,mirror=Face}=We) -> case any_hidden(We) of false -> gb_trees:keys(Etab); true -> visible_es_1(gb_trees:to_list(Etab), Face, []) end. visible_es_1([{E,#edge{lf=Lf,rf=Rf}}|Es], Face, Acc) -> if Lf < 0 -> %% Left face hidden. if Rf < 0; Rf =:= Face -> %% Both faces invisible (in some way). visible_es_1(Es, Face, Acc); true -> %% Right face is visible. visible_es_1(Es, Face, [E|Acc]) end; Lf =:= Face, Rf < 0 -> %% Left face mirror, right face hidden. visible_es_1(Es, Face, Acc); true -> %% At least one face visible. visible_es_1(Es, Face, [E|Acc]) end; visible_es_1([], _, Acc) -> ordsets:from_list(Acc). update_id_bounds(#we{vp=Vtab,es=Etab,fs=Ftab}=We) -> case gb_trees:is_empty(Etab) of true -> We#we{next_id=0}; false -> LastId = lists:max([wings_util:gb_trees_largest_key(Vtab), wings_util:gb_trees_largest_key(Etab), wings_util:gb_trees_largest_key(Ftab)]), We#we{next_id=LastId+1} end. %% build_incident_tab([{Elem,Edge}]) -> [{Elem,Edge}] %% Elem = Face or Vertex %% Build the table of incident edges for either faces or vertices. %% Returns an ordered list where each Elem is unique. build_incident_tab(ElemToEdgeRel) -> T = ets:new(?MODULE, [ordered_set]), ets:insert(T, ElemToEdgeRel), R = ets:tab2list(T), ets:delete(T), R. %%% %%% Calculate normals. %%% new_items_as_ordset_1(Tab, Wid, NewWid) when NewWid-Wid < 32 -> new_items_as_ordset_2(Wid, NewWid, Tab, []); new_items_as_ordset_1(Tab, Wid, _NewWid) -> [Item || Item <- gb_trees:keys(Tab), Item >= Wid]. new_items_as_ordset_2(Wid, NewWid, Tab, Acc) when Wid < NewWid -> case gb_trees:is_defined(Wid, Tab) of true -> new_items_as_ordset_2(Wid+1, NewWid, Tab, [Wid|Acc]); false -> new_items_as_ordset_2(Wid+1, NewWid, Tab, Acc) end; new_items_as_ordset_2(_Wid, _NewWid, _Tab, Acc) -> lists:reverse(Acc). %%% %%% Test the consistency of a #we{}. %%% is_consistent(#we{}=We) -> try validate_vertex_tab(We), validate_faces(We) catch error:_ -> false end. is_face_consistent(Face, #we{fs=Ftab,es=Etab}) -> Edge = gb_trees:get(Face, Ftab), try validate_face(Face, Edge, Etab) catch error:_ -> false end. validate_faces(#we{fs=Ftab,es=Etab}) -> validate_faces_1(gb_trees:to_list(Ftab), Etab). validate_faces_1([{Face,Edge}|Fs], Etab) -> validate_face(Face, Edge, Etab), validate_faces_1(Fs, Etab); validate_faces_1([], _) -> true. validate_face(Face, Edge, Etab) -> Ccw = walk_face_ccw(Edge, Etab, Face, Edge, []), Edge = walk_face_cw(Edge, Etab, Face, Ccw), [V|Vs] = lists:sort(Ccw), validate_face_vertices(Vs, V). validate_face_vertices([V|_], V) -> erlang:error(repeated_vertex); validate_face_vertices([_], _) -> true; validate_face_vertices([V|Vs], _) -> validate_face_vertices(Vs, V). walk_face_ccw(LastEdge, _, _, LastEdge, [_|_]=Acc) -> Acc; walk_face_ccw(Edge, Etab, Face, LastEdge, Acc) -> case gb_trees:get(Edge, Etab) of #edge{ve=V,lf=Face,ltpr=Next} -> walk_face_ccw(Next, Etab, Face, LastEdge, [V|Acc]); #edge{vs=V,rf=Face,rtpr=Next} -> walk_face_ccw(Next, Etab, Face, LastEdge, [V|Acc]) end. walk_face_cw(Edge, _, _, []) -> Edge; walk_face_cw(Edge, Etab, Face, [V|Vs]) -> case gb_trees:get(Edge, Etab) of #edge{vs=V,lf=Face,ltsu=Next} -> walk_face_cw(Next, Etab, Face, Vs); #edge{ve=V,rf=Face,rtsu=Next} -> walk_face_cw(Next, Etab, Face, Vs) end. validate_vertex_tab(#we{es=Etab,vc=Vct}) -> lists:foreach(fun({V,Edge}) -> case gb_trees:get(Edge, Etab) of #edge{vs=V} -> ok; #edge{ve=V} -> ok end end, gb_trees:to_list(Vct)).