%%
%%  wings_we.erl --
%%
%%     This module contains functions to build and manipulate
%%     we records (winged-edged records, the central data structure
%%     in Wings 3D).
%%
%%  Copyright (c) 2001-2003 Bjorn Gustavsson
%%
%%  See the file "license.terms" for information on usage and redistribution
%%  of this file, and for a DISCLAIMER OF ALL WARRANTIES.
%%
%%     $Id: wings_we.erl,v 1.72 2004/02/07 19:35:07 bjorng Exp $
%%

-module(wings_we).
-export([build/2,rebuild/1,vertex_gc/1,
	 new_wrap_range/3,id/2,bump_id/1,
	 new_id/1,new_ids/2,
	 invert_normals/1,
	 merge/1,merge/2,
	 renumber/2,renumber/3,
	 uv_to_color/2,
	 uv_mapped_faces/1,
	 transform_vs/2,
	 separate/1,
	 normals/2,
	 new_items/3,
	 is_consistent/1,is_face_consistent/2]).

-include("wings.hrl").
-include("e3d.hrl").
-import(lists, [map/2,foreach/2,foldl/3,sort/1,keysort/2,
		last/1,reverse/1,duplicate/2,seq/2,filter/2]).

rebuild(#we{vc=undefined,fs=undefined,es=Etab0}=We) ->
    Etab = gb_trees:to_list(Etab0),
    Vct = rebuild_vct(Etab),
    Ftab = rebuild_ftab(Etab),
    rebuild(We#we{vc=Vct,fs=Ftab});
rebuild(#we{vc=undefined,es=Etab}=We) ->
    Vct = rebuild_vct(gb_trees:to_list(Etab)),
    rebuild(We#we{vc=Vct});
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).

rebuild_vct(Es) ->
    gb_trees:from_orddict(do_rebuild_vct(Es, [])).

do_rebuild_vct([{Edge,#edge{vs=Va,ve=Vb}}|Es], Acc) ->
    do_rebuild_vct(Es, [{Va,Edge},{Vb,Edge}|Acc]);
do_rebuild_vct([], VtoE) -> build_incident_tab(VtoE).

rebuild_ftab(Es) ->
    rebuild_ftab_1(Es, []).

rebuild_ftab_1([{Edge,#edge{lf=Lf,rf=Rf}}|Es], Acc) ->
    rebuild_ftab_1(Es, [{Lf,Edge},{Rf,Edge}|Acc]);
rebuild_ftab_1([], FtoE) ->
    gb_trees:from_orddict(build_incident_tab(FtoE)).

%% vertex_gc(We) -> We'
%%  Remove vertices in the 'vc' and 'vp' tables that are no
%%  longer referenced by any edge in the edge table.
vertex_gc(#we{es=Etab,vp=Vtab0}=We) ->
    Es = gb_trees:to_list(Etab),
    Vct0 = do_rebuild_vct(Es, []),
    Vtab = vertex_gc_1(Vct0, gb_trees:to_list(Vtab0), []),
    Vct = gb_trees:from_orddict(Vct0),
    We#we{vc=Vct,vp=Vtab}.

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(reverse(Acc)).

%%%
%%% Build Winged-Edges.
%%%

build(Mode, #e3d_mesh{fs=Fs0,vs=Vs,tx=Tx,he=He}) when is_atom(Mode) ->
    Fs = translate_faces(Fs0, list_to_tuple(Tx), []),
    build(Mode, Fs, Vs, He);
build(Fs, Vs) ->
    build(material, Fs, Vs, []).

translate_faces([#e3d_face{vs=Vs,tx=Tx0,mat=Mat0}|Fs], Txs, Acc) ->
    Mat = translate_mat(Mat0),
    FaceData = case Tx0 of
		   [] -> {Mat,Vs};
		   Tx1 ->
		       Tx = [element(Tx+1, Txs) || Tx <- Tx1],
		       {Mat,Vs,Tx}
	       end,
    translate_faces(Fs, Txs, [FaceData|Acc]);
translate_faces([], _, Acc) -> reverse(Acc).

translate_mat([]) -> default;
translate_mat([Mat]) -> Mat;
translate_mat([_|_]=List) -> List.

build(Type, Fs0, Vs, HardEdges) ->
    {Es0,Fs} = build_and_fix_holes(Fs0, 0),
    Es = number_edges(Es0),
    build_rest(Type, Es, Fs, Vs, HardEdges).

build_and_fix_holes(Fs, N) 
  when N < 10 -> %% Assure that we don't loop forever
    case build_edges(Fs) of
	{Good, []} ->
	    {Good,Fs};
	{_, Bad} ->
	    HF = fill_holes(Bad),
	    [_|_] = HF, %% Assert that we could fix something
	    build_and_fix_holes(HF++Fs, N+1)
    end.

build_rest(Type, Es, Fs, Vs, HardEdges) ->
    Htab = vpairs_to_edges(HardEdges, Es),
    {Vct0,Etab,Ftab0} = build_tables(Es),
    Ftab = build_faces(Ftab0),
    Vct = gb_trees:from_orddict(build_incident_tab(Vct0)),
    Vpos = number_vertices(Vs, 0, []),
    We = update_id_bounds(#we{mode=Type,es=Etab,fs=Ftab,vc=Vct,vp=Vpos,he=Htab}),
    assign_materials(Fs, We).

assign_materials([L|_], We) when is_list(L) -> We;
assign_materials(Fs, We) ->
    MatFace = mat_face(Fs),
    wings_material:assign_materials(MatFace, We).

mat_face(Fs) ->
    mat_face(Fs, 0, []).

mat_face([T|Ts], Face, Acc) ->
    mat_face(Ts, Face+1, [{element(1, T),Face}|Acc]);
mat_face([], _, Acc) -> Acc.

number_vertices([P|Ps], V, Acc) ->
    number_vertices(Ps, V+1, [{V,P}|Acc]);
number_vertices([], _, Acc) ->
    gb_trees:from_orddict(reverse(Acc)).
    
build_edges(Fs) ->
    build_edges(Fs, 0, []).

build_edges([{_Material,Vs,Tx}|Fs], Face, Eacc0) ->
    build_edges_1(Vs, Tx, Fs, Face, Eacc0);
build_edges([{_Material,Vs}|Fs], Face, Eacc0) ->
    build_edges_1(Vs, tx_filler(Vs), Fs, Face, Eacc0);
build_edges([Vs|Fs], Face, Eacc0) ->
    build_edges_1(Vs, tx_filler(Vs), Fs, Face, Eacc0);
build_edges([], _Face, Eacc) ->
    combine_half_edges(wings_util:rel2fam(Eacc)).

build_edges_1(Vs, UVs, Fs, Face, Acc0) ->
    Vuvs = zip(Vs, UVs),
    Pairs = pairs(Vuvs),
    Acc = build_face_edges(Pairs, Face, Acc0),
    build_edges(Fs, Face+1, Acc).

build_face_edges([{Pred,_}|[{E0,{_UVa,UVb}},{Succ,_}|_]=Es], Face, Acc0) ->
    Acc = case E0 of
	      {Vs,Ve}=Name when Vs < Ve ->
		  enter_half_edge(right, Name, Face, Pred, Succ, UVb, Acc0);
	      {Vs,Ve} when Ve < Vs ->
		  Name = {Ve,Vs},
		  enter_half_edge(left, Name, Face, Pred, Succ, UVb, Acc0)
	  end,
    build_face_edges(Es, Face, Acc);
build_face_edges([_,_], _Face, Acc) -> Acc.

enter_half_edge(Side, Name, Face, Pred, Succ, UV,Tab0) ->
    Rec = {Face,UV,edge_name(Pred),edge_name(Succ)},
    [{Name,{Side,Rec}}|Tab0].

pairs(Vs) ->
    pairs(Vs, Vs, []).
    
pairs([{V1,T1}|[{V2,T2}|_]=Vs], First, Acc) ->
    pairs(Vs, First, [{{V2,V1},{T2,T1}}|Acc]);
pairs([{V,T}], [{V1,T1},{V2,T2},{V3,T3}|_], Acc) ->
    [{{V3,V2},{T3,T2}},{{V2,V1},{T2,T1}},{{V1,V},{T1,T}}|Acc].

zip([V|Vs], [UV|UVs]) ->
    [{V,UV}|zip(Vs, UVs)];
zip([], []) -> [].

edge_name({Vs,Ve}=Name) when Vs < Ve -> Name;
edge_name({Vs,Ve}) -> {Ve,Vs}.

tx_filler(Vs) ->
    tx_filler(Vs, none, []).

tx_filler([_|Vs], Col, Acc) ->
    tx_filler(Vs, Col, [Col|Acc]);
tx_filler([], _Col, Acc) -> Acc.

combine_half_edges(HalfEdges) ->
    combine_half_edges(HalfEdges, [], []).
    
combine_half_edges([{Name,[{left,Ldata},{right,Rdata}]}|Hes], Good, Bad) ->
    combine_half_edges(Hes, [{Name,{Ldata,Rdata}}|Good], Bad);
combine_half_edges([{_,[_]}=BadEdge|Hes], Good, Bad) ->
    combine_half_edges(Hes, Good, [BadEdge|Bad]);
combine_half_edges([], Good, Bad) ->
    {reverse(Good),reverse(Bad)}.

number_edges(Es) ->
    number_edges(Es, 1, []).

number_edges([{Name,{_Ldata,_Rdata}=Data}|Es], Edge, Tab0) ->
    Tab = [{Name,{Edge,Data}}|Tab0],
    number_edges(Es, Edge+1, Tab);
number_edges([], _Edge, Tab) -> reverse(Tab).

vpairs_to_edges([], _) -> gb_sets:empty();
vpairs_to_edges(HardNames0, Es) ->
    HardNames = sofs:set([edge_name(He) || He <- HardNames0], [name]),
    SofsEdges = sofs:from_external(Es, [{name,{edge,info}}]),
    SofsHard = sofs:image(SofsEdges, HardNames),
    Htab = sofs:to_external(sofs:domain(SofsHard)),
    gb_sets:from_list(Htab).

build_tables(Edges) ->
    Emap = make_edge_map(Edges),
    build_tables(Edges, Emap, [], [], []).

build_tables([H|T], Emap, Vtab0, Etab0, Ftab0) ->
    {{Vs,Ve},{Edge,{Ldata,Rdata}}} = H,
    {Lf,LUV,Lpred,Lsucc} = Ldata,
    {Rf,RUV,Rpred,Rsucc} = Rdata,
    Erec = #edge{vs=Vs,ve=Ve,lf=Lf,rf=Rf,a=LUV,b=RUV,
		 ltpr=edge_num(Lf, Lpred, Emap),
		 ltsu=edge_num(Lf, Lsucc, Emap),
		 rtpr=edge_num(Rf, Rpred, Emap),
		 rtsu=edge_num(Rf, Rsucc, Emap)},
    Etab = [{Edge,Erec}|Etab0],
    Ftab = [{Lf,Edge},{Rf,Edge}|Ftab0],
    Vtab = [{Vs,Edge},{Ve,Edge}|Vtab0],
    build_tables(T, Emap, Vtab, Etab, Ftab);
build_tables([], _Emap, Vtab, Etab0, Ftab) ->
    Etab = gb_trees:from_orddict(reverse(Etab0)),
    {Vtab,Etab,Ftab}.

make_edge_map(Es) ->
    make_edge_map(Es, []).

make_edge_map([{Name,{Edge,{{Lf,_,_,_},{Rf,_,_,_}}}}|Es], Acc) ->
    make_edge_map(Es, [{{Lf,Name},Edge},{{Rf,Name},Edge}|Acc]);
make_edge_map([], Acc) -> gb_trees:from_orddict(sort(Acc)).

edge_num(Face, Name, Emap) ->
    gb_trees:get({Face,Name}, Emap).

build_faces(Ftab0) ->
    Ftab = wings_util:rel2fam(Ftab0),
    build_faces_1(Ftab, []).

build_faces_1([{Face,[Edge|_]}|Fs], Acc) ->
    build_faces_1(Fs, [{Face,Edge}|Acc]);
build_faces_1([], Acc) -> gb_trees:from_orddict(reverse(Acc)).

fill_holes([]) ->  [];
fill_holes(Es) ->
    G = digraph:new(),
    make_digraph(Es, G),
    C = digraph_utils:cyclic_strong_components(G),
    Holes = make_hole_faces(G, C, []),
    digraph:delete(G),
    Holes.

make_hole_faces(G, [[V|_]|Cs], Acc) ->
    case digraph:get_cycle(G, V) of
	[_|Vs] when length(Vs) >= 3 ->
	    length(Vs) =:= length(ordsets:from_list(Vs)),
	    make_hole_faces(G, Cs, [{'_hole_',Vs}|Acc]);
	_Other ->
	    make_hole_faces(G, Cs, Acc)
    end;
make_hole_faces(_G, [], Acc) -> Acc.
    
make_digraph([{{Va,Vb},[{right,_Data}]}|Es], G) ->
    digraph:add_vertex(G, Va),
    digraph:add_vertex(G, Vb),
    digraph:add_edge(G, Va, Vb),
    make_digraph(Es, G);
make_digraph([{{Vb,Va},[{left,_Data}]}|Es], G) ->
    digraph:add_vertex(G, Va),
    digraph:add_vertex(G, Vb),
    digraph:add_edge(G, Va, Vb),
    make_digraph(Es, G);
make_digraph([], _G) -> ok.

%%% Utilities for allocating IDs.

new_wrap_range(Items, Inc, #we{next_id=Id}=We) ->
    NumIds = Items*Inc,
    {{0,Id,Inc,NumIds},We#we{next_id=Id+NumIds}}.

id(N, {Current,BaseId,_Inc,NumIds}) ->
    BaseId + ((Current+N) rem NumIds).

bump_id({Id,BaseId,Inc,NumIds}) ->
    {Id+Inc,BaseId,Inc,NumIds}.

new_id(#we{next_id=Id}=We) ->
    {Id,We#we{next_id=Id+1}}.

new_ids(N, #we{next_id=Id}=We) ->
    {Id,We#we{next_id=Id+N}}.

%%% Invert all normals.

invert_normals(#we{es=Etab0}=We0) ->
    Etab1 = invert_edges(gb_trees:to_list(Etab0), []),
    Etab = gb_trees:from_orddict(Etab1),
    We = We0#we{es=Etab},
    slide_colors(We).

invert_edges([{Edge,Rec0}|Es], Acc) ->
    #edge{vs=Vs,ve=Ve,ltpr=Ltpr,ltsu=Ltsu,rtpr=Rtpr,rtsu=Rtsu} = Rec0,
    Rec = Rec0#edge{vs=Ve,ve=Vs,ltpr=Ltsu,ltsu=Ltpr,rtpr=Rtsu,rtsu=Rtpr},
    invert_edges(Es, [{Edge,Rec}|Acc]);
invert_edges([], Acc) -> reverse(Acc).

slide_colors(#we{fs=Ftab}=We) ->
    foldl(fun({Face,Edge}, W) ->
		  slide_colors(Face, Edge, W)
	  end, We, gb_trees:to_list(Ftab)).

slide_colors(Face, Edge, #we{es=Etab0}=We) ->
    PrevEdge = case gb_trees:get(Edge, Etab0) of
		   #edge{lf=Face,ltsu=Pe0} -> Pe0;
		   #edge{rf=Face,rtsu=Pe0} -> Pe0
	       end,
    PrevCol = case gb_trees:get(PrevEdge, Etab0) of
		  #edge{lf=Face,a=A} -> A;
		  #edge{rf=Face,b=B} -> B
	      end,
    Etab = slide_colors(Face, Edge, Edge, Etab0, PrevCol, not_done),
    We#we{es=Etab}.

slide_colors(_Face, LastEdge, LastEdge, Etab, _, done) -> Etab;
slide_colors(Face, Edge, LastEdge, Etab0, PrevCol, _) ->
    case gb_trees:get(Edge, Etab0) of
	#edge{a=Col,lf=Face,ltpr=NextEdge}=Rec ->
	    Etab = gb_trees:update(Edge, Rec#edge{a=PrevCol}, Etab0),
	    slide_colors(Face, NextEdge, LastEdge, Etab, Col, done);
	#edge{b=Col,rf=Face,rtpr=NextEdge}=Rec ->
	    Etab = gb_trees:update(Edge, Rec#edge{b=PrevCol}, Etab0),
	    slide_colors(Face, NextEdge, LastEdge, Etab, Col, done)
    end.

%% Merge two winged-edge structures.
merge(We0, We1) ->
    merge([We0,We1]).

%% Merge a list of winged-edge structures.
merge([]) -> [];
merge([We]) -> We;
merge([#we{id=Id,name=Name}|_]=Wes0) ->
    Wes1 = merge_renumber(Wes0),
    {Vpt0,Et0,Ht0,MatTab} = merge_1(Wes1),
    Vpt = gb_trees:from_orddict(Vpt0),
    Et = gb_trees:from_orddict(Et0),
    Ht = gb_sets:from_ordset(Ht0),
    rebuild(#we{id=Id,name=Name,vc=undefined,fs=undefined,
		vp=Vpt,es=Et,he=Ht,mat=MatTab}).

merge_1([We]) -> We;
merge_1(Wes) -> merge_1(Wes, [], [], [], []).

merge_1([#we{vp=Vp0,es=Es,he=He,mat=Mat}=We|Wes], Vpt0, Et0, Ht0, Mt0) ->
    Vpt = [gb_trees:to_list(Vp0)|Vpt0],
    Et = [gb_trees:to_list(Es)|Et0],
    Ht = [gb_sets:to_list(He)|Ht0],
    Mt = [{Mat,We}|Mt0],
    merge_1(Wes, Vpt, Et, Ht, Mt);
merge_1([], Vpt0, Et0, Ht0, Mt0) ->
    Vpt = lists:merge(Vpt0),
    Et = lists:merge(Et0),
    Ht = lists:merge(Ht0),
    Mt = wings_material:merge(Mt0),
    {Vpt,Et,Ht,Mt}.
			       
merge_renumber(Wes0) ->
    [{_,We}|Wes] = merge_bounds(Wes0, []),
    merge_renumber(Wes, [We], []).

merge_renumber([{Low,We}|Wes], [#we{next_id=Next}|_]=Done, NotDone)
  when Low >= Next ->
    merge_renumber(Wes, [We|Done], NotDone);
merge_renumber([{_,We}|Wes], Done, NotDone) ->
    merge_renumber(Wes, Done, [We|NotDone]);
merge_renumber([], [#we{next_id=Next}|_]=Done, NotDone) ->
    merge_renumber_rest(NotDone, Next, Done).

merge_renumber_rest([We0|Wes], Next0, Acc) ->
    #we{next_id=Next} = We = do_renumber(We0, Next0),
    merge_renumber_rest(Wes, Next, [We|Acc]);
merge_renumber_rest([], _, Acc) -> Acc.

merge_bounds([#we{vp=Vtab,fs=Ftab,es=Etab}=We0|Wes], Acc) ->
    First = case gb_trees:is_empty(Etab) of
		true -> 0;
		false ->
		    lists:min([wings_util:gb_trees_smallest_key(Vtab),
			       wings_util:gb_trees_smallest_key(Etab),
			       wings_util:gb_trees_smallest_key(Ftab)])
	    end,
    We = update_id_bounds(We0),
    merge_bounds(Wes, [{First,We}|Acc]);
merge_bounds([], Acc) -> sort(Acc).

%%% Renumber a winged-edge structure.
renumber(We0, Id) ->
    We = do_renumber(We0, Id),
    rebuild(We).

renumber(We0, Id, RootSet0) ->
    {We,RootSet} = do_renumber(We0, Id, RootSet0),
    {rebuild(We),RootSet}.

%% Leaves the vc and fs fields undefined.
do_renumber(We0, Id) ->
    {We,_} = do_renumber(We0, Id, []),
    We.

do_renumber(#we{mode=Mode,vp=Vtab0,es=Etab0,fs=Ftab0,
		mat=MatTab0,he=Htab0,perm=Perm0,mirror=Mirror0}=We0,
	    Id, RootSet0) ->
    Vtab1 = gb_trees:to_list(Vtab0),
    Vmap = make_map(Vtab1, Id),
    Vtab = renumber_vertices(Vtab1, Vmap),

    Fmap = make_map(gb_trees:to_list(Ftab0), Id),
    MatTab = wings_material:renumber(MatTab0, Fmap),

    Etab1 = gb_trees:to_list(Etab0),
    Emap = make_map(Etab1, Id),

    Etab2 = foldl(fun(E, A) ->
			  renum_edge(E, Emap, Vmap, Fmap, A)
		  end, [], Etab1),
    Etab = gb_trees:from_orddict(reverse(Etab2)),


    Htab1 = foldl(fun(E, A) ->
			  renum_hard_edge(E, Emap, A)
		  end, [], gb_sets:to_list(Htab0)),
    Htab = gb_sets:from_list(Htab1),

    Perm = case Perm0 of
	       {SelMode,Elems0} ->
		   Root = [{SelMode,gb_sets:to_list(Elems0),[]}],
		   [{_,Elems,_}] = map_rootset(Root, Emap, Vmap, Fmap),
		   {SelMode,gb_sets:from_list(Elems)};
	       _ -> Perm0
	   end,

    Mirror = if
		 Mirror0 == none -> Mirror0;
		 true -> gb_trees:get(Mirror0, Fmap)
	     end,

    RootSet = map_rootset(RootSet0, Emap, Vmap, Fmap),
    We = We0#we{mode=Mode,vc=undefined,fs=undefined,
		vp=Vtab,es=Etab,mat=MatTab,he=Htab,
		perm=Perm,mirror=Mirror},

    %% In case this function will be used for merging #we records,
    %% it is essential to update the next_id field. Its value can
    %% safely be based the largest key in the edge table only.
    LastId = case gb_trees:size(Etab) of
		 0 -> 0;
		 _ -> wings_util:gb_trees_largest_key(Etab)
	     end,
    {We#we{next_id=LastId+1},RootSet}.

map_rootset([{vertex,Vs,Data}|T], Emap, Vmap, Fmap) when is_list(Vs) ->
    [map_all(vertex, Vs, Data, Vmap)|map_rootset(T, Emap, Vmap, Fmap)];
map_rootset([{edge,Edges,Data}|T], Emap, Vmap, Fmap) when is_list(Edges) ->
    [map_all(edge, Edges, Data, Emap)|map_rootset(T, Emap, Vmap, Fmap)];
map_rootset([{face,Faces,Data}|T], Emap, Vmap, Fmap) when is_list(Faces) ->
    [map_all(face, Faces, Data, Fmap)|map_rootset(T, Emap, Vmap, Fmap)];
map_rootset([{body,_Empty,_Data}=Sel|T], Emap, Vmap, Fmap) ->
    [Sel|map_rootset(T, Emap, Vmap, Fmap)];
map_rootset([{vertex,V}|T], Emap, Vmap, Fmap) ->
    [{vertex,gb_trees:get(V, Vmap)}|map_rootset(T, Emap, Vmap, Fmap)];
map_rootset([{edge,Edge}|T], Emap, Vmap, Fmap) ->
    [{edge,gb_trees:get(Edge, Emap)}|map_rootset(T, Emap, Vmap, Fmap)];
map_rootset([{face,Face}|T], Emap, Vmap, Fmap) ->
    [{face,gb_trees:get(Face, Fmap)}|map_rootset(T, Emap, Vmap, Fmap)];
map_rootset([{body,Empty}|T], Emap, Vmap, Fmap) ->
    [{body,Empty}|map_rootset(T, Emap, Vmap, Fmap)];
map_rootset([], _, _, _) -> [].

map_all(What, Items, Data, Map) ->
    {What,[gb_trees:get(Key, Map) || Key <- Items],Data}.

make_map(Tab, Id0) ->
    make_map(Tab, Id0, []).
make_map([{Old,_}|T], Id, Map) ->
    make_map(T, Id+1, [{Old,Id}|Map]);
make_map([], _, Map) -> gb_trees:from_orddict(reverse(Map)).

renum_edge({Edge0,Rec0}, Emap, Vmap, Fmap, New) ->
    Edge = gb_trees:get(Edge0, Emap),
    #edge{vs=Vs,ve=Ve,lf=Lf,rf=Rf,ltpr=Ltpr,ltsu=Ltsu,
	  rtpr=Rtpr,rtsu=Rtsu} = Rec0,
    Rec = Rec0#edge{vs=gb_trees:get(Vs, Vmap),ve=gb_trees:get(Ve, Vmap),
		    lf=gb_trees:get(Lf, Fmap),rf=gb_trees:get(Rf, Fmap),
		    ltpr=gb_trees:get(Ltpr, Emap),
		    ltsu=gb_trees:get(Ltsu, Emap),
		    rtpr=gb_trees:get(Rtpr, Emap),
		    rtsu=gb_trees:get(Rtsu, Emap)},
    [{Edge,Rec}|New].

renumber_vertices(Vtab, Vmap) ->
    renumber_vertices_1(Vtab, Vmap, []).

renumber_vertices_1([{V0,P}|Vtab], Vmap, VtabAcc) ->
    V = gb_trees:get(V0, Vmap),
    renumber_vertices_1(Vtab, Vmap, [{V,P}|VtabAcc]);
renumber_vertices_1([], _, Vtab) -> gb_trees:from_orddict(sort(Vtab)).
    
renum_hard_edge(Edge0, Emap, New) ->
    Edge = gb_trees:get(Edge0, Emap),
    [Edge|New].

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.

%%%
%%% Separate a combined winged-edge structure.
%%%

separate(We) ->
    separate(We#we{mirror=none,vc=undefined,fs=undefined}, []).

separate(#we{es=Etab0}=We, Acc) ->
    case gb_trees:is_empty(Etab0) of
	true -> Acc;
	false ->
	    {Edge,_,_} = gb_trees:take_smallest(Etab0),
	    Ws = gb_sets:singleton(Edge),
	    {EtabLeft,NewEtab} = separate(Ws, Etab0, gb_trees:empty()),
	    NewWe = copy_dependents(We#we{es=NewEtab}),
	    separate(We#we{es=EtabLeft}, [NewWe|Acc])
    end.

separate(Ws0, Etab0, Acc0) ->
    case gb_sets:is_empty(Ws0) of
	true -> {Etab0,Acc0};
	false ->
	    {Edge,Ws1} = gb_sets:take_smallest(Ws0),
	    case gb_trees:is_defined(Edge, Acc0) of
		true ->
		    separate(Ws1, Etab0, Acc0);
		false ->
		    Rec = gb_trees:get(Edge, Etab0),
		    Etab = gb_trees:delete(Edge, Etab0),
		    Acc = gb_trees:insert(Edge, Rec, Acc0),
		    #edge{ltpr=LP,ltsu=LS,rtpr=RP,rtsu=RS} = Rec,
		    Set = gb_sets:from_list([LP,LS,RP,RS]),
		    Ws = gb_sets:union(Ws1, Set),
		    separate(Ws, Etab, Acc)
	    end
    end.

copy_dependents(We0) ->
    #we{es=Etab,he=Htab0,vc=Vct,vp=Vtab0} = We = rebuild(We0),
    Es = gb_trees:keys(Etab),
    Htab = case gb_sets:is_empty(Htab0) of
	       true ->
		   Htab0;
	       false ->
		   gb_sets:intersection(Htab0, gb_sets:from_ordset(Es))
	   end,
    Vs = sofs:from_external(gb_trees:keys(Vct), [vertex]),
    Vtab1 = sofs:relation(gb_trees:to_list(Vtab0), [{vertex,edge}]),
    Vtab2 = sofs:restriction(Vtab1, Vs),
    Vtab = gb_trees:from_orddict(sofs:to_external(Vtab2)),
    wings_material:cleanup(We#we{he=Htab,vp=Vtab}).

%% build_incident_tab([{Elem,Edge}]) -> GbTree([{Elem,Edge1}])
%%      Elem = Face or Vertex
%%  Build the table of incident edges for either faces or vertices.

build_incident_tab(VtxToEdgeRel) ->
    build_incident_tab(sort(VtxToEdgeRel), []).
    
build_incident_tab([{V,_}|VsEs], [{V,_}|_]=Acc) ->
    build_incident_tab(VsEs, Acc);
build_incident_tab([{V,Edge}|VsEs], Acc) ->
    build_incident_tab(VsEs, [{V,Edge}|Acc]);
build_incident_tab([], Acc) -> reverse(Acc).

%%%
%%% Convert textures to vertex colors.
%%%

uv_to_color(#we{mode=material,es=Etab0}=We, St) ->
    Etab1 = foldl(
	      fun({Edge,#edge{lf=Lf,rf=Rf,a=UVa,b=UVb}=Rec}, A) ->
		      ColA = wings_material:color(Lf, UVa, We, St),
		      ColB = wings_material:color(Rf, UVb, We, St),
		      [{Edge,Rec#edge{a=ColA,b=ColB}}|A]
	      end, [], gb_trees:to_list(Etab0)),
    Etab = gb_trees:from_orddict(reverse(Etab1)),
    We#we{mode=vertex,es=Etab};
uv_to_color(We, _St) -> We.

%% uv_mapped_faces(We) -> [Face]
%%  Return an ordered list of all faces that have UV coordinates.
uv_mapped_faces(#we{fs=Ftab}=We) ->
    uv_mapped_faces_1(gb_trees:to_list(Ftab), We, []).

uv_mapped_faces_1([{F,E}|Fs], We, Acc) ->
    Good = foldl(fun([_|{_,_}], Flag) -> Flag;
		    (_, _) -> false
		 end, true, wings_face:vinfo_ccw(F, E, We)),
    case Good of
	false -> uv_mapped_faces_1(Fs, We, Acc);
	true -> uv_mapped_faces_1(Fs, We, [F|Acc])
    end;
uv_mapped_faces_1([], _, Acc) -> reverse(Acc).

%%%
%%% Transform all vertices according to the matrix.
%%%

transform_vs({1.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,1.0,Tx,Ty,Tz}, We) ->
    Translate = fun({V,{X,Y,Z}}, A) -> [{V,{X+Tx,Y+Ty,Z+Tz}}|A] end,
    transform_vs_1(Translate, We);
transform_vs(Matrix, We) ->
    Transform = fun({V,Pos}, A) ->
			[{V,e3d_mat:mul_point(Matrix, Pos)}|A]
		end,
    transform_vs_1(Transform, We).

transform_vs_1(Transform, #we{vp=Vtab0}=We) ->
    Vtab1 = foldl(Transform, [], gb_trees:to_list(Vtab0)),
    Vtab = gb_trees:from_orddict(reverse(Vtab1)),
    We#we{vp=Vtab}.

%%%
%%% Calculate normals.
%%%

normals(FaceNormals, #we{he=He}=We) ->
    wings_pb:start("calculating soft normals"),
    Res = case FaceNormals of
	      [_,_] ->
		  two_faced(FaceNormals, We);
	      _ ->
		  case gb_sets:is_empty(He) of
		      true -> all_soft(FaceNormals, We);
		      false -> mixed_edges(FaceNormals, We)
		  end
	  end,
    wings_pb:done(Res).

all_soft(FaceNormals, #we{vp=Vtab}=We) ->
    wings_pb:update(0.10, "preparing"),
    VtxNormals = soft_vertex_normals(gb_trees:to_list(Vtab), FaceNormals, We),
    FoldFun = fun(V, VInfo, A) ->
		      Normal = gb_trees:get(V, VtxNormals),
		      [[VInfo|Normal]|A]
	      end,
    wings_pb:update(0.6, "collecting"),
    all_soft_1(FoldFun, FaceNormals, We, []).

all_soft_1(FoldFun, [{Face,_}|FNs], We, Acc) ->
    Vs = wings_face:fold_vinfo(FoldFun, [], Face, We),
    all_soft_1(FoldFun, FNs, We, [{Face,Vs}|Acc]);
all_soft_1(_, [], _, Acc) -> reverse(Acc).

soft_vertex_normals(Vtab, FaceNormals0, We) when is_list(FaceNormals0) ->
    FaceNormals = gb_trees:from_orddict(FaceNormals0),
    soft_vertex_normals(Vtab, FaceNormals, We);
soft_vertex_normals(Vtab, FaceNormals, We) ->
    FoldFun = fun(_, Face, _, A) ->
		      [gb_trees:get(Face, FaceNormals)|A]
	      end,
    Soft = foldl(fun({V,_}, Acc) ->
			 Ns = wings_vertex:fold(FoldFun, [], V, We),
			 N = e3d_vec:norm(e3d_vec:add(Ns)),
			 [{V,N}|Acc]
		 end, [], Vtab),
    gb_trees:from_orddict(reverse(Soft)).

mixed_edges(FaceNormals0, We) ->
    wings_pb:update(0.20, "preparing"),
    G = digraph:new(),
    FaceNormals = gb_trees:from_orddict(FaceNormals0),
    wings_pb:update(0.50, "vertex normals"),
    VtxNormals = vertex_normals(We, G, FaceNormals),
    wings_pb:update(0.99, "vertex normals per face"),
    Ns = foldl(fun({Face,_}, Acc) ->
		       Vs = n_face(Face, G, FaceNormals, VtxNormals, We),
		       [{Face,Vs}|Acc]
	       end, [], FaceNormals0),
    digraph:delete(G),
    reverse(Ns).

n_face(Face, G, FaceNormals, VtxNormals, We) ->
    wings_face:fold_vinfo(
	   fun(V, VInfo, Acc) ->
		   case gb_trees:lookup(V, VtxNormals) of
		       {value,Normal} ->
			   [[VInfo|Normal]|Acc];
		       none ->
			   Normal = hard_vtx_normal(G, V, Face, FaceNormals),
 			   [[VInfo|Normal]|Acc]
		   end
	   end, [], Face, We).

hard_vtx_normal(G, V, Face, FaceNormals) ->
    Reachable = digraph_utils:reachable([{V,Face}], G),
    case [gb_trees:get(AFace, FaceNormals) || {_,AFace} <- Reachable] of
 	[N] -> N;
 	Ns -> e3d_vec:norm(e3d_vec:add(Ns))
    end.

two_faced([{FaceA,Na},{FaceB,Nb}], We) ->
    [{FaceA,two_faced_1(FaceA, Na, We)},
     {FaceB,two_faced_1(FaceB, Nb, We)}].

two_faced_1(Face, Normal, We) ->
    wings_face:fold_vinfo(fun (_, VInfo, Acc) ->
				  [[VInfo|Normal]|Acc]
			  end, [], Face, We).

vertex_normals(#we{vp=Vtab,es=Etab,he=Htab}=We, G, FaceNormals) ->
    He0 = gb_sets:to_list(Htab),
    He = sofs:from_external(He0, [edge]),
    Es0 = gb_trees:to_list(Etab),
    Es1 = sofs:from_external(Es0, [{edge,data}]),
    Es = sofs:image(Es1, He),
    Hvs0 = foldl(fun(#edge{vs=Va,ve=Vb}, A) ->
			 [Va,Vb|A]
		 end, [], sofs:to_external(Es)),
    Hvs = sofs:set(Hvs0, [vertex]),
    Vs = sofs:from_external(gb_trees:to_list(Vtab), [{vertex,data}]),
    Svs = sofs:drestriction(Vs, Hvs),
    SoftVs = sofs:to_external(Svs),
    HardVs = sofs:to_external(Hvs),
    foreach(fun(V) -> update_digraph(G, V, We) end, HardVs),
    soft_vertex_normals(SoftVs, FaceNormals, We).

update_digraph(G, V, #we{he=Htab}=We) ->
    wings_vertex:fold(
      fun(Edge, _, #edge{lf=Lf0,rf=Rf0}, _) ->
	      case gb_sets:is_member(Edge, Htab) of
		  true -> ok;
		  false ->
		      Lf = {V,Lf0},
		      Rf = {V,Rf0},
		      digraph:add_vertex(G, Lf),
		      digraph:add_vertex(G, Rf),
		      digraph:add_edge(G, Lf, Rf),
		      digraph:add_edge(G, Rf, Lf)
	      end
      end, [], V, We).

%%%
%%% Returns sets of newly created items.
%%%

%% new_items(vertex|edge|face, OldWe, NewWe) -> NewItemsGbSet.
%%  Return all items in NewWe that are not in OldWe (as a GbSet).

new_items(vertex, #we{next_id=Wid}, #we{next_id=NewWid,vp=Tab}) ->
    new_items_1(Tab, Wid, NewWid);
new_items(edge, #we{next_id=Wid}, #we{next_id=NewWid,es=Tab}) ->
    new_items_1(Tab, Wid, NewWid);
new_items(face, #we{next_id=Wid}, #we{next_id=NewWid,fs=Tab}) ->
    new_items_1(Tab, Wid, NewWid).

new_items_1(Tab, Wid, NewWid) when NewWid-Wid < 32 ->
    new_items_2(Wid, NewWid, Tab, []);
new_items_1(Tab, Wid, _NewWid) ->
    Items = [Item || Item <- gb_trees:keys(Tab), Item >= Wid],
    gb_sets:from_ordset(Items).

new_items_2(Wid, NewWid, Tab, Acc) when Wid < NewWid ->
    case gb_trees:is_defined(Wid, Tab) of
	true -> new_items_2(Wid+1, NewWid, Tab, [Wid|Acc]);
	false -> new_items_2(Wid+1, NewWid, Tab, Acc)
    end;
new_items_2(_Wid, _NewWid, _Tab, Acc) ->
    gb_sets:from_ordset(reverse(Acc)).

%%%
%%% Test if a winged-edge record is consistent.
%%%

is_consistent(#we{}=We) ->
    case catch validate_we(We) of
	{'EXIT',Reason} ->
	    io:format("~P\n", [Reason,30]),
	    false;
	true -> true
    end.

is_face_consistent(Face, #we{fs=Ftab}=We) ->
    Edge = gb_trees:get(Face, Ftab),
    case catch validate_face(Face, Edge, We) of
	true -> true;
	_ -> false
    end.

validate_we(We) ->
    validate_vertex_tab(We),
    validate_faces(We),
    true.
    
validate_faces(#we{fs=Ftab}=We) ->
    foreach(fun({Face,Edge}) ->
		    validate_face(Face, Edge, We)
	    end, gb_trees:to_list(Ftab)).

validate_face(Face, Edge, We) ->
    Cw = walk_face_cw(Face, Edge, Edge, We, []),
    Ccw = walk_face_ccw(Face, Edge, Edge, We, []),
    case reverse(Ccw) of
	Cw -> validate_face_vertices(Cw);
	_Other -> exit({face_cw_ccw_inconsistency,Face})
    end.

validate_face_vertices(Vs) ->
    validate_face_vertices_1(sort(Vs)).

validate_face_vertices_1([V,V|_]) ->
    exit(repeated_vertex);
validate_face_vertices_1([_|T]) ->
    validate_face_vertices_1(T);
validate_face_vertices_1([]) -> true.

walk_face_cw(_Face, LastEdge, LastEdge, _We, [_|_]=Acc) -> Acc;
walk_face_cw(Face, Edge, LastEdge, We, Acc) ->
    #we{es=Etab} = We,
    case gb_trees:get(Edge, Etab) of
	#edge{vs=V,lf=Face,ltsu=Next} ->
	    walk_face_cw(Face, Next, LastEdge, We, [V|Acc]);
	#edge{ve=V,rf=Face,rtsu=Next} ->
	    walk_face_cw(Face, Next, LastEdge, We, [V|Acc])
    end.

walk_face_ccw(_Face, LastEdge, LastEdge, _We, [_|_]=Acc) -> Acc;
walk_face_ccw(Face, Edge, LastEdge, We, Acc) ->
    #we{es=Etab} = We,
    case gb_trees:get(Edge, Etab) of
	#edge{ve=V,lf=Face,ltpr=Next} ->
	    walk_face_ccw(Face, Next, LastEdge, We, [V|Acc]);
	#edge{vs=V,rf=Face,rtpr=Next} ->
	    walk_face_ccw(Face, Next, LastEdge, We, [V|Acc])
    end.

validate_vertex_tab(#we{es=Etab,vc=Vct}) ->
    foreach(fun({V,Edge}) ->
		    case gb_trees:get(Edge, Etab) of
			#edge{vs=V}=Rec ->
			    validate_edge_rec(Rec);
			#edge{ve=V}=Rec ->
			    validate_edge_rec(Rec)
		    end
	    end, gb_trees:to_list(Vct)).

validate_edge_rec(#edge{ltpr=LP,ltsu=LS,rtpr=RP,rtsu=RS})
  when is_integer(LP+LS+RP+RS) -> ok.
