%%%-------------------------------------------------------------------
%%% File    : auv_segment.erl
%%% Author  : Dan Gudmundsson <dgud@erix.ericsson.se>
%%% Description : Different segmentation algorithms.
%%%               Segments Model into set of charts containg faces.
%%% Created :  3 Oct 2002 by Dan Gudmundsson <dgud@erix.ericsson.se>
%%%-------------------------------------------------------------------
%%  Copyright (c) 2001-2003 Dan Gudmundsson, Bjorn Gustavsson
%%
%%  See the file "license.terms" for information on usage and redistribution
%%  of this file, and for a DISCLAIMER OF ALL WARRANTIES.
%%     $Id: auv_segment.erl,v 1.50 2003/09/16 09:01:49 dgud Exp $

-module(auv_segment).

-export([create/2, segment_by_material/1, cut_model/3,
	 normalize_charts/3, map_vertex/2,
	 fv_to_uv_map/2, uv_to_charts/3]).

-ifdef(DEBUG).
-export([degrees/0, find_features/3, build_seeds/2]). %% Debugging
-endif.

-include("wings.hrl").
-include("auv.hrl").

-import(lists, [reverse/1,map/2,mapfoldl/3,sort/1,foldl/3,member/2,append/1]).

%% Returns segments=[Charts={Id,[Faces]}] and Bounds=gb_sets([Edges])
create(Mode, We0) ->
    case Mode of 
	feature ->
	    Tot = gb_trees:size(We0#we.es),
	    {_Distances,Charts0,Cuts0,_Feats} = 
		segment_by_feature(We0, 60, Tot div 50),
	    {Charts0, Cuts0};
	autouvmap ->
	    Charts0 = segment_by_direction(We0),
	    {Charts0, gb_sets:empty()}
    end.

%%%%%% Feature detection Algorithm %%%%%%%%%%%%
%% Algorithms based on the paper, now probably totally ruined by me...
%% 'Least Square Conformal Maps for Automatic Texture Generation Atlas'
%% by Bruno Levy, Sylvain Petitjean, Nicolas Ray, Jerome Mailot.
%% Presented on Siggraph 2002.

-define(MAX_STRING_LENGTH, 5).
-define(MIN_SHARPNESS(MAX), (1 - MAX) * (?MAX_STRING_LENGTH -1)).

-record(restr, {sharp, featl}).

%% Debug func %%
-ifdef(DEBUG).
degrees() ->
    Test = fun(D) ->
		   X = (math:cos(D * math:pi() / 180) + 1) / 2, 
		   Y = math:sin(D *  math:pi() / 180) /2,    
		   Dir = math:sqrt(X*X+Y*Y),
		   io:format("~.3w deg -> ~w ~w~n", [D, Dir, 1 - Dir])
	   end,
    lists:foreach(Test, lists:seq(0,360,15)).
-endif.

degrees(Deg) ->
    X = (math:cos(Deg * math:pi() / 180) + 1) / 2, 
    Y = math:sin(Deg *  math:pi() / 180) /2,    
    math:sqrt(X*X+Y*Y).

segment_by_feature(We, SharpEdgeDeg, MinFeatureLen) ->
    {Features,VEG,EWs} = ?TC(find_features(We, SharpEdgeDeg, MinFeatureLen)),
    {LocalMaxs,Extra} = ?TC(build_seeds(Features, We)),
    {Distances,Charts0,Cuts} = ?TC(build_charts(LocalMaxs, Extra, VEG, EWs, We)),    
    Charts = ?TC(solve_map_problem(Charts0, We)),
    {Distances,Charts,Cuts,Features}.


solve_map_problem(Charts0, _We) when length(Charts0) =< 9 ->
    Charts0;

solve_map_problem(Charts0, We) ->
    Charts = auv_util:number(Charts0),
    FamC2F = sofs:family(Charts),
    RelC2F = sofs:family_to_relation(FamC2F),
    FaceChart0 = sofs:to_external(sofs:converse(RelC2F)),
    Face2Chart  = gb_trees:from_orddict(lists:sort(FaceChart0)),
    EdgesInCharts = 
	lists:foldl(fun({Id, Faces}, Acc) -> 
			    [{Id, wings_face:outer_edges(Faces, We)} | Acc]
		    end, [], Charts),
    Neighbours0 = neighbour_charts(EdgesInCharts, Face2Chart, We#we.es, []),
    Map = color_map(Neighbours0, gb_trees:empty(), gb_sets:empty(), 0),
    MapC2Col = sofs:relation(Map),
    T0 = sofs:range(sofs:relative_product({MapC2Col, FamC2F})),
    T1 = sofs:range(sofs:family_union(sofs:relation_to_family(T0))),
    T2 = sofs:to_external(T1),
    ?DBG("Map painting solved from ~p to ~p colors~n", [length(Charts0), length(T2)]), 
    T2.
    

color_map([{_, Id, NBeds}|Rest], Map0, Cols, NextCol) ->
    NBcols = lists:foldl(fun(Neigh, Acc) ->
				 case gb_trees:lookup(Neigh, Map0) of
				     none -> Acc;
				     {value, Col} ->
					 gb_sets:add(Col, Acc)
				 end
			 end, gb_sets:empty(), NBeds),
    Avail = gb_sets:difference(Cols, NBcols),
    case gb_sets:is_empty(Avail) of
	true ->
	    color_map(Rest, gb_trees:insert(Id, NextCol, Map0), 
		      gb_sets:insert(NextCol,Cols), NextCol +1);
	false ->
	    {First,_} = gb_sets:take_smallest(Avail),
	    color_map(Rest, gb_trees:insert(Id, First, Map0), Cols, NextCol)
    end;
color_map([], Map, _, _) ->
    gb_trees:to_list(Map).

neighbour_charts([{Id, Edges}|Rest], Face2Chart, Etab, Acc) ->
    Neighb0 = foldl(fun(Edge, Acc0) ->
			   #edge{lf=LF, rf=RF} =  gb_trees:get(Edge, Etab),
			   case gb_trees:get(RF, Face2Chart) of
			       Id ->
				   [gb_trees:get(LF, Face2Chart)|Acc0];
			       Chart ->
				   [Chart|Acc0]
			   end
		    end, [], Edges),
    Neighb = lists:usort(Neighb0),
    neighbour_charts(Rest, Face2Chart, Etab, [{length(Neighb), Id, Neighb}|Acc]);
neighbour_charts([], _, _, Acc) ->
    reverse(sort(Acc)).

%%%   SharpEdge should be degrees value is: (180 +/- SharpEdge) 
%%%   MinFeatureLen is the number of edges a feature must contain
find_features(We, SharpEdge, MinFeatureLen) when MinFeatureLen > 20 ->
    find_features(We, SharpEdge, 20);
find_features(We, SharpEdge, MinFeatureLen) when MinFeatureLen < 2 ->
    find_features(We, SharpEdge, 2);
find_features(We, SharpEdge, MinFeatureLen) when SharpEdge > 90 ->
    find_features(We, 90, MinFeatureLen);
find_features(We, SharpEdge, MinFeatureLen) when SharpEdge < 2 ->
    find_features(We, 10, MinFeatureLen);    
find_features(We0, SharpEdgeDeg, MinFeatureLen) ->
    Restrs = #restr{sharp = ?MIN_SHARPNESS(degrees(SharpEdgeDeg)), 
		    featl=MinFeatureLen},
    {Sorted, N, _FNormals} = sort_edges_by_weight(We0),
    %% split list normals may diff with +-60 deg and 20% of the edges
    {Best, _Rest} = pick_features(Sorted, 0, degrees(SharpEdgeDeg), N, []),
%%    ?DBG("Best ~p ~p ~n", [Best, _Rest]),
    EVGraph = build_vertex_graph(Sorted, We0, gb_trees:empty()),
    EWs    = gb_trees:from_orddict(lists:sort(Sorted)),

    Features0 = expand_features(Best, EVGraph, EWs, We0, Restrs),
    {Features0, EVGraph, EWs}.

sort_edges_by_weight(We0 = #we{fs = Ftab, es = Etab}) ->
    Faces = gb_trees:keys(Ftab),
    FaceNormals0 = lists:map(fun(Face) ->
				     {Face,wings_face:normal(Face, We0)}
			     end, Faces),
    FaceNormals = gb_trees:from_orddict(FaceNormals0),
    Edges  = gb_trees:keys(Etab),
    WEdges = lists:map(fun(Edge) ->
			       Val = calc_normal_value(Edge, FaceNormals, We0),
			       {Edge, Val}
		       end, Edges),
    {lists:keysort(2,WEdges), length(WEdges), FaceNormals}.

calc_normal_value(Edge, FaceData, We) ->
    #edge{lf = LF, rf = RF} = gb_trees:get(Edge, We#we.es),
    calc_dir_vector(gb_trees:get(LF,FaceData), gb_trees:get(RF,FaceData)).

pick_features([Edge = {_,Value}|Rest], N, Constraint, Max, Acc) 
  when Value < Constraint, N < Max -> 
    pick_features(Rest, N+1, Constraint, Max, [Edge|Acc]);
pick_features(Rest, _,_,_, Acc) ->
    {lists:reverse(Acc), Rest}.

-record(fd, {graph, vals, neigh, feat}).
-define(fdcreate(VerG,Vals), #fd{graph=VerG, vals=Vals, neigh=gb_sets:empty(), feat=[]}).
-define(fdmember(Edge,Fd), gb_sets:is_member(Edge, Fd#fd.neigh)).
-define(fdisneigh(Edge,Fd),  gb_sets:is_member(Edge, Fd#fd.neigh)).
-define(fdgetfeat(Fd), Fd#fd.feat).
-define(fdgetsurrneigh(EdgVer, Fd), gb_trees:get(EdgVer,Fd#fd.graph)).
-define(fdgetvalue(Edge, Fd), gb_trees:get(Edge,Fd#fd.vals)).
-define(fdmarkused(Edge, Fd), Fd#fd{neigh=gb_sets:add(Edge, Fd#fd.neigh)}).
fdaddneighandfeat(Feat, We, FdXX) ->
    FindXX = fun(EdgeXX, AccXX) ->			 
		     #edge{vs=Vs,ve=Ve} = gb_trees:get(EdgeXX, We#we.es),
		     Neigh1 = ?fdgetsurrneigh({EdgeXX,Vs}, FdXX),
		     Neigh2 = ?fdgetsurrneigh({EdgeXX,Ve}, FdXX),
		     (Neigh1 ++ Neigh2) ++ AccXX
	     end,		    
    PossNeighXX = gb_sets:from_list(lists:foldl(FindXX,[], Feat)),
    NewNBXX = gb_sets:union(PossNeighXX, FdXX#fd.neigh),
    FdXX#fd{neigh = NewNBXX, feat = Feat ++ FdXX#fd.feat}.

expand_features(Possible, EVGr, EdgeCost, We, Restrs) ->
    expand_features(Possible, ?fdcreate(EVGr, EdgeCost), We, Restrs).

expand_features([First = {Edge, _}|Rest], Fd = #fd{}, We, Restrs) ->
    case ?fdmember(Edge,Fd) of
	true -> %% Already used ignore
	    expand_features(Rest, Fd, We, Restrs);
	false ->
	    {Feature, Fd1} = expand_feature_curve(First, Fd, We, Restrs),
%%	    ?DBG("Expand ~p ~p ~p ~p~n", [First, Restrs#restr.featl, length(Feature), Feature]),
	    if 
		length(Feature) < Restrs#restr.featl ->
		    expand_features(Rest, Fd, We,Restrs);
		true -> %% Accepted feature
		    %% Add neighbors
		    Fd2 = fdaddneighandfeat(Feature,We,Fd1),
		    expand_features(Rest, Fd2, We, Restrs)
	    end
    end;
expand_features([], Fd, _We, _Restrs) ->
%    ?DBG("Expand done~n"),
    ?fdgetfeat(Fd).

expand_feature_curve({Edge, _Sharpness}, Fd0, We, Rs) ->
    #edge{vs = Vs, ve = Ve} = gb_trees:get(Edge, We#we.es),    
    Dir1 = get_vector(Vs,Ve,We),
    {Edges1,_}   = get_edges(Vs, Edge, Dir1, We, Fd0),
    {Feat0, Fd2}=depth_traverse_tree([Edges1],0,0,Dir1,Fd0,We,Rs,[Edge]), 
    Dir2 = get_vector(Ve,Vs,We),
    {Edges2,_} = get_edges(Ve, Edge, Dir2, We, Fd2),
    depth_traverse_tree([Edges2],0,0,Dir2,Fd2,We,Rs,Feat0).

depth_traverse_tree(Tree=[[{Val,_,_}|_]|_],Sharp,Depth,_Dir1,Fd0,We,Rs,Feat) 
  when Sharp + Val > Rs#restr.sharp ->
    %% Found suiteable edge -> add to feat
    [[{Val0,{Edge,#edge{vs=VaN,ve=VbN}},V0}|_]|Found] = lists:reverse(Tree),
%%    ?DBG("Found suiteable edge ~p ~p ~p ~n", [Edge,Sharp,Depth]),
    Fd1 = ?fdmarkused(Edge, Fd0),
    case Found of 
	[] -> %% Oops first level hit, restart (special case)
%	    ?DBG("Special case ~p ~n", [Tree]),
	    NextV = if V0 == VaN -> VbN; V0 == VbN -> VaN end,
	    Dir2 = get_vector(VaN,VbN,V0,We),
	    {Edges1,_} = get_edges(NextV, Edge, Dir2, We, Fd1),
	    depth_traverse_tree([Edges1], 0, 0, Dir2, Fd1, We, Rs, 
				[Edge|Feat]);
	[[{_,{_,#edge{vs=Va,ve=Vb}},V}|_]|_] ->
	    Dir2 = get_vector(Va,Vb,V,We),
	    depth_traverse_tree(lists:reverse(Found), Sharp - Val0, 
				Depth -1,Dir2,Fd1,We,Rs,[Edge|Feat])
    end;
 
depth_traverse_tree([[]|[[{Value,_,_}|Alt]|Tree]],Sharp,
		    Depth,Dir,Fd0,We,Rs,Feat) ->
    %% Last tested in that branch -> search other branches
%    ?DBG("Last tested in that branch -> search other branches ~p~n", [Depth]),
    depth_traverse_tree([Alt|Tree], Sharp -Value, 
			Depth -1,Dir,Fd0, We,Rs,Feat);
 
depth_traverse_tree([_Miss|[[Root|Alt]|Tree]], Sharp, Depth, 
		    Dir,Fd0, We,Rs,Feat) 
  when Depth >= ?MAX_STRING_LENGTH ->
    %% To deep -> look in other branch
%    ?DBG("To deep -> look in other branch ~n",[]),
    {Value, _, _} = Root,
    depth_traverse_tree([Alt|Tree], Sharp - Value, Depth -1, 
			Dir,Fd0, We,Rs, Feat);
depth_traverse_tree([[]], _Sharp, _Depth, _Dir,Fd0,_We,_Rs,Feat) ->
    %% done -> tree search complete
%    ?DBG("done[[]]~n",[]),
    {Feat, Fd0};
depth_traverse_tree(Tree=[[{Val,Leaf,Vertex}|_]|_],Sharp,Depth,
		    Dir,Fd0,We,Rs,Feat) ->
    %% No success yet -> search deeper
%%    ?DBG("No success yet -> search deeper ~p (~p) ~p ~n",[Sharp+Val, ?MIN_SHARPNESS, Depth]),
    Next = case Leaf of 
	       {Id, #edge{vs = Vertex, ve = NextV}} ->
		   NextV;
	       {Id, #edge{ve = Vertex, vs = NextV}} ->
		   NextV
	   end,
    case get_edges(Next, Id, Dir, We, Fd0) of
	{[],Poss} -> %% Fixing last edge in edgeloop
	    {NewSharp,NewTree, NewFeat} = 
		patch_tree(Poss, Sharp+Val, Tree, Rs,Feat),
	    depth_traverse_tree(NewTree,NewSharp,Depth+1,Dir,Fd0,
				We,Rs,NewFeat);
        {Edges,_} ->
	    depth_traverse_tree([Edges|Tree],Sharp+Val,Depth+1,Dir,Fd0,
				We,Rs,Feat)
    end.

patch_tree([{Val,{Edge,_ER},_Vs}|_],Sharp,Tree,Rs,Feat) ->
    case lists:member(Edge, Feat) of
	true when (Val + Sharp) > Rs#restr.sharp ->
%%%	    ?DBG("Patched OK ~p ~n",[Edge]),
	    NewFeats = lists:map(fun([{_,{Element,_},_}|_]) -> Element end,
				 Tree),
	    {Val + Sharp, [[]], NewFeats ++ Feat};
	_Mem ->
%%%	    ?DBG("patch miss ~p ~p ~p ~p ~n",[Edge, _Mem, Val, Sharp]),
	    {Sharp, [[]|Tree], Feat}
    end;
patch_tree([],Sharp,Tree, _,Feat) ->
    {Sharp, [[]|Tree], Feat}.

get_edges(V, Current, CurrVect, We, Fd) ->
    Surr = ?fdgetsurrneigh({Current,V}, Fd),
    Add = fun(Edge, Acc = {Acc1,Acc2}) ->
		  case ?fdisneigh(Edge, Fd) of
		      true ->
			  ER = gb_trees:get(Edge, We#we.es),
			  Val = ?fdgetvalue(Edge,Fd),
			  {Acc1,[{1-Val,{Edge,ER},V}|Acc2]};
		      false ->			  
			  ER = #edge{vs = Va,ve = Vb} = gb_trees:get(Edge, We#we.es),
			  ThisVect = get_vector(Va,Vb,V,We),
			  Dir = calc_dir_vector(CurrVect, ThisVect),
			  if Dir < 0.707106  -> %% 90deg
				  Acc;
			     true ->
				  Val = ?fdgetvalue(Edge,Fd),
				  {[{1 - Val, {Edge, ER}, V}|Acc1],Acc2}
			  end
		  end
	  end,
    {New,Bup} = lists:foldl(Add, {[],[]}, Surr),
    {lists:reverse(lists:sort(New)), lists:reverse(lists:sort(Bup))}.


calc_dir_vector(Vec1, Vec2) ->
    Vec = e3d_vec:add(Vec1,Vec2),
    e3d_vec:len(e3d_vec:divide(Vec, 2.0)).

get_vector(A,B,A,We) ->
    get_vector(B,A,We);
get_vector(A,B,B,We) ->
    get_vector(A,B,We).

get_vector(A, B, #we{vp=Vs}) ->
    e3d_vec:norm(e3d_vec:sub(gb_trees:get(A, Vs), gb_trees:get(B, Vs))).

find_extremities(#we{vc=Vct,vp=Vs0,es=Es}) ->
    Center = e3d_vec:average(gb_trees:values(Vs0)),
    Vs = gb_trees:to_list(Vs0),
    AllC = lists:map(fun({Id,Pos}) ->
			     Dist = e3d_vec:dist(Pos, Center),
			     {Dist,Id,Pos}
		    end, Vs),
    [{_,V1,V1Pos}|_] = lists:reverse(lists:sort(AllC)),
    AllV1 = lists:map(fun({Id,Pos}) ->
			      Dist = e3d_vec:dist(Pos, V1Pos),
			      {Dist,Id,Pos}
		      end, Vs),
    [{_,V2,_}|_] = lists:reverse(lists:sort(AllV1)),
    E1 = gb_trees:get(V1, Vct),
    E2 = gb_trees:get(V2, Vct),
    F1 = (gb_trees:get(E1,Es))#edge.lf,
    case (gb_trees:get(E2,Es))#edge.lf of
	F1 ->
	    [F1, (gb_trees:get(E2,Es))#edge.rf];
	F2 ->
	    [F1, F2]
    end.

build_extreme([], _, _, Acc0) ->
    ReNumber = fun({_Group, Fs}, {No, Acc}) ->
		       {No+1,[{No, Fs}|Acc]}
	       end,
    {_,Acc} = lists:foldl(ReNumber, {0,[]}, Acc0),
    F = sofs:family(Acc),    
    Rel = sofs:family_to_relation(F),
    lists:reverse(sofs:to_external(Rel));
build_extreme(Fs,Max,FaceG,Acc) ->    
    Find = fun(Face, {FG, New0, Fs0}) ->
           case gb_trees:lookup(Face,FG) of
               {value, New1} ->
               FG1 = gb_trees:delete(Face,FG),
               {FG1, New1 ++ New0, [Face|Fs0]};
               none ->
               {FG, New0, Fs0}
           end
       end,
    {FG1,New, Fs1} = lists:foldl(Find, {FaceG, [], []}, Fs),
    build_extreme(New, Max-1, FG1, [{Max,Fs1}|Acc]).
    
build_seeds(Features0, #we{fs=Ftab}=We) ->
    FaceGraph = build_face_graph(gb_trees:keys(Ftab), We, 
				 gb_trees:empty()),
    case Features0 of
	[] ->
	    Fs = [F1,F2] = find_extremities(We),        
	    StartNo = gb_trees:size(Ftab),
	    Dists = [{Max, _}|_] = build_extreme(Fs,StartNo,FaceGraph,[]),
	    LMaxs = [{Max, F1}, {Max, F2}],
	    DTree = lists:foldl(fun({Dist, Face}, Tree) ->
					gb_trees:insert(Face, Dist, Tree)
				end, gb_trees:empty(), Dists),
	    {LMaxs,{DTree,Max,Dists}};
	_ ->
	    Distances = [{Max, _}|_] = 
		calc_distance(Features0, FaceGraph, We),
	    DTree = lists:foldl(fun({Dist, Face}, Tree) ->
					gb_trees:insert(Face, Dist, Tree)
				end, gb_trees:empty(), Distances),
	    LocalMaxs = find_local_max(Distances, DTree,Features0,
				       FaceGraph, We),
	    {LocalMaxs,{DTree,Max,Distances}}
    end.

build_charts(LocalMaxs, {DistTree,Max,Distances}, VEG, EWs, We) ->
    {Charts0,Bounds} = expand_charts(LocalMaxs, Max + 1, DistTree, VEG,EWs, We),
    Charts1 = sofs:from_external(gb_trees:to_list(Charts0), [{atom,atom}]),
    Charts2 = sofs:converse(Charts1),
    Charts3 = sofs:relation_to_family(Charts2),
    Charts4 = sofs:to_external(sofs:range(Charts3)),
    {Charts,Cuts} = normalize_charts(Charts4, Bounds, We),
    {Distances,Charts,Cuts}.

add_face_edges_to_heap(Face, FaceDist, ChartBds, Heap, EWs, We) ->
    Find = fun(_, Edge, #edge{lf=LF,rf=RF}, Heap0) ->
		   case gb_sets:is_member(Edge, ChartBds) of
		       true ->
			   Fopp = if LF == Face -> RF; RF == Face ->LF end,
			   EdgeW = 1 - gb_trees:get(Edge, EWs),
			   gb_trees:enter({FaceDist,EdgeW,Edge},{Face,Fopp}, Heap0);
		       false ->
			   Heap0
		   end
	   end,
    wings_face:fold(Find, Heap, Face, We).

delete_inner_edges(Face, VEG, We, Boundaries) ->
    wings_face:fold(fun(_, Edge, _, A) ->
			    case is_extremity(Edge, Boundaries, VEG, We) of
				false -> A;
				true -> gb_sets:delete_any(Edge, A)
			    end
		    end, Boundaries, Face, We).
			    
expand_charts(LocalMaxs, Max, Dt, VEG,EWs, We0) ->
    ChartsE = gb_trees:empty(),
    HeapE = gb_trees:empty(),
    ChartBds = gb_sets:from_ordset(gb_trees:keys(We0#we.es)),
    Init = fun({LMax, Face}, {Chart0, Heap0}) ->
		   Chart1 = gb_trees:insert(Face, Face, Chart0),
		   Heap1  = add_face_edges_to_heap(Face,Max-LMax,ChartBds,Heap0,EWs,We0),
		   {Chart1, Heap1}
	   end,
    {ChartI, HeapI} = lists:foldl(Init, {ChartsE, HeapE}, LocalMaxs),
    expand_charts(HeapI, ChartI, ChartBds, Max, Dt, VEG,EWs, We0).
    
expand_charts(Heap0, Charts0, ChartBds0, Max, Dt, VEG,EWs, We) ->
    case gb_trees:is_empty(Heap0) of
	true ->
	    {Charts0,ChartBds0};
	false ->	    
	    {{_Dist,_,Edge},{Face,Fopp},Heap1} = gb_trees:take_smallest(Heap0),
	    ChartNum = gb_trees:get(Face, Charts0),
	    case gb_trees:lookup(Fopp, Charts0) of
		none ->
		    Charts = gb_trees:insert(Fopp, ChartNum, Charts0),
		    ChartBds1 = gb_sets:delete(Edge, ChartBds0),
		    ChartBds = delete_inner_edges(Fopp, VEG, We, ChartBds1),
		    DistFopp = gb_trees:get(Fopp, Dt),
		    Heap = add_face_edges_to_heap(Fopp, Max-DistFopp,
						  ChartBds, Heap1, EWs, We),
		    expand_charts(Heap, Charts, ChartBds, Max, Dt, VEG,EWs, We);
		{value,ChartNum} ->  %% Fopp and Face are in same chart
		    ChartBds = case is_extremity(Edge, ChartBds0, VEG, We) of
				   false -> ChartBds0;
				   true -> gb_sets:delete_any(Edge, ChartBds0)
			       end,
 		    expand_charts(Heap1, Charts0, ChartBds, Max, Dt, VEG,EWs, We);
		{value,OtherChartNum} ->
		    MaxDistChartFace = gb_trees:get(ChartNum, Dt),
		    MaxDistChartFopp = gb_trees:get(OtherChartNum, Dt),
		    DistFace = gb_trees:get(Face, Dt),
		    DistFopp = gb_trees:get(Fopp, Dt),
		    Const = Max/4,
		    if ((MaxDistChartFace - DistFace) < Const) and
		       ((MaxDistChartFopp - DistFopp) < Const) ->
			    {Charts,ChartBds} = 
				merge_charts(ChartNum, OtherChartNum,
					     Charts0, Dt, VEG, ChartBds0, We),
			    expand_charts(Heap1, Charts, ChartBds, Max,
					  Dt, VEG,EWs, We);
		       true ->
			    expand_charts(Heap1, Charts0, ChartBds0, Max, Dt, VEG,EWs, We)
		    end
	    end
    end.

is_extremity(Edge, ChartBds, VEG, #we{es=Etab}) ->
    #edge{vs=Va,ve=Vb} = gb_trees:get(Edge, Etab),
    not (is_connected(gb_trees:get({Edge,Va}, VEG), ChartBds) andalso
	 is_connected(gb_trees:get({Edge,Vb}, VEG), ChartBds)).

is_connected([E|Es], ChartBds) ->
    gb_sets:is_member(E, ChartBds) orelse is_connected(Es, ChartBds);
is_connected([], _) -> false.

merge_charts(Ch1,Ch2, Charts0, Dt, VEG,ChartBds0,We) ->    
    {C1,C2} =  
	case gb_trees:get(Ch1, Dt) > gb_trees:get(Ch2, Dt) of
	    true ->
		{Ch1, Ch2};
	    false->
		{Ch2, Ch1}
	end,
    List = gb_trees:to_list(Charts0),
    {Merged, ChartBds1} = 
	mapfoldl(fun({Face, Chart}, ChB) when Chart == C2 ->	      
			 %% Removed Merged Charts borders from
			 %% BorderEdges.
			 DelCommonEdge = 
			     fun(_V,Edge,#edge{lf=LF,rf=RF},Acc) ->
				     Test = if LF==Face -> RF; true -> LF end,
				     case gb_trees:lookup(Test, Charts0) of
					 {value, C1} ->
					     case is_extremity(Edge, Acc, VEG, We) of
						 true ->
						     gb_sets:delete_any(Edge,Acc);
						 false ->
						     Acc
					     end;
					 _ ->
					     Acc
				     end
			     end,
			 {{Face, C1}, wings_face:fold(DelCommonEdge, ChB, Face, We)};
		    (Else,ChB) -> 
			 {Else,ChB} end, 
		 ChartBds0, List),
    {gb_trees:from_orddict(Merged), ChartBds1}.   

find_local_max(Distances, DTree, Features, FaceGraph, #we{es = Es}) ->
    %% Remove the features from FaceGraph, edges which are a feature
    %%  shouldn't connect to opposite faces
    FG1 = lists:foldl(fun(Edge, Tree0) ->
			      #edge{lf=LF,rf=RF} = gb_trees:get(Edge, Es),
			      LFL = gb_trees:get(LF, Tree0),
			      LFL1 = lists:delete(RF, LFL),
			      Tree1 = gb_trees:update(LF, LFL1, Tree0),
			      RFL = gb_trees:get(RF, Tree1),
			      RFL1 = lists:delete(LF, RFL),
			      gb_trees:update(RF, RFL1, Tree1)
		      end, FaceGraph, Features),    
    find_local_maximum(Distances, DTree, FG1, []).

find_local_maximum([This = {Dist, Face}|Dists], Dtree0, FG, Maxs) ->
    case gb_trees:delete_any(Face, Dtree0) of
	Dtree0 ->
	    find_local_maximum(Dists, Dtree0, FG, Maxs);
	Dtree1 ->
	    Dtree2 = delete_less([{gb_trees:get(Face, FG), Dist}], Dtree1, FG),
	    find_local_maximum(Dists, Dtree2, FG, [This|Maxs])
    end;
find_local_maximum([], _, _FG, Maxs) ->
    lists:reverse(Maxs).

delete_less([{[Face|Rest1], Dist}|Rest2], Dtree0, FG) ->
    case gb_trees:lookup(Face, Dtree0) of
	none ->
	    delete_less([{Rest1, Dist}|Rest2], Dtree0, FG);
	{value, Val} when Val > Dist ->
	    delete_less([{Rest1, Dist}|Rest2], Dtree0, FG);
	{value, Val} ->
	    Dtree1 = gb_trees:delete(Face, Dtree0),
	    New = {gb_trees:get(Face, FG), Val},
	    delete_less([{Rest1,Dist},New|Rest2], Dtree1, FG)
    end;
delete_less([{[],_}|Rest], Dt, Fg) ->
    delete_less(Rest, Dt,Fg);
delete_less([],Dt,_Fg) ->
    Dt.

build_face_graph([Face|Faces], We, Tree0) ->
    Find = fun(_V, _Edge, #edge{lf=LF,rf=RF}, Acc) ->
		   New = if LF == Face -> RF; RF == Face ->LF end,
		   [New|Acc]
	   end,
    Surround = wings_face:fold(Find, [], Face, We),
    build_face_graph(Faces, We, gb_trees:insert(Face, Surround, Tree0));
build_face_graph([],_, Tree) ->
    Tree.

build_vertex_graph([{Edge, _Value}|R], We, Tree0) ->
    #edge{vs = Vs, ve = Ve} = gb_trees:get(Edge,We#we.es),
    Find = fun(Id, _Face, _, Acc) when Id == Edge ->
		   Acc;
	      (Id, _Face, _, Acc) ->
		   [Id|Acc]
	   end,
    Surround0 = wings_vertex:fold(Find, [], Vs, We),
    Tree1 = gb_trees:insert({Edge,Vs}, Surround0, Tree0),
    Surround1 = wings_vertex:fold(Find, [], Ve, We),
    build_vertex_graph(R, We, gb_trees:insert({Edge,Ve}, Surround1, Tree1));
build_vertex_graph([],_, Tree) ->
    Tree.

calc_distance(Features, FG, We = #we{fs = FsOrig}) ->
    Faces = fun(Edge, {Dist0,Next0,Fs0}) ->
		    #edge{lf = LF, rf = RF} = gb_trees:get(Edge, We#we.es),
		    {Dist1,Next1,Fs1} = find_delete(LF,0,Dist0,Next0,Fs0,FG),
		    find_delete(RF,0,Dist1,Next1,Fs1,FG)
	    end,   
    {Dist2,Next2,Fs2} = lists:foldl(Faces, {[],[],FsOrig}, Features),
    calc_distance(Next2, 1, Dist2, [], Fs2,FG).

calc_distance([Head|Rest], Level, Dist0, Next0, Fs0,Fg) ->
    {Dist2, Next2, Fs2} = find_delete(Head, Level, Dist0, Next0, Fs0,Fg),
    calc_distance(Rest, Level, Dist2, Next2, Fs2,Fg);
calc_distance([], _Level, Dist, [], _Fs, _) ->
    lists:reverse(lists:sort(Dist));
calc_distance([], Level, Dist, Next, Fs, Fg) ->
    calc_distance(Next, Level+1, Dist, [], Fs, Fg).

find_delete(Face,Level,Dist0,Next0,Fs0,Fg) ->
    case gb_trees:delete_any(Face, Fs0) of
	Fs0 -> {Dist0,Next0,Fs0};
	Fs1 -> 
	    Next = gb_trees:get(Face,Fg),
	    {[{Level,Face}|Dist0],Next ++ Next0,Fs1}
    end.
%%%%%%%%%%% End Feature detection

%%
%% The original autouv algorithm...
%%

segment_by_direction(We) ->
    Rel = foldl(fun({_,'_hole_'}, A) -> A;
		   ({Face,_}, A) ->
			N = wings_face:normal(Face, We),
			[{seg_dir(N),Face}|A]
		end, [], wings_material:get_all(We)),
    segment_by_cluster(Rel, We).

seg_dir({X,Y,Z}) ->
    Max = lists:max([abs(X),abs(Y),abs(Z)]),
    if
	X =:= Max -> x;
	Y =:= Max -> y;
	Z =:= Max -> z;
	-X =:= Max -> '-x';
	-Y =:= Max -> '-y';
	-Z =:= Max -> '-z'
    end.

%% By Color 
segment_by_material(We) ->
    Rel = foldl(fun({_,'_hole_'}, A) -> A;
		   ({_, ?HOLE}, A) -> A;
		   ({Face,Name}, A) -> [{Name,Face}|A]
		end, [], wings_material:get_all(We)),
    segment_by_cluster(Rel, We).

%% Common segmentation algorithm
segment_by_cluster(Rel0, We) ->
    Rel = sofs:relation(Rel0),
    Clustered = sofs:relation_to_family(Rel),
    Groups0 = sofs:range(Clustered),
    Groups = sofs:to_external(Groups0),
    Neigh = [wings_sel:face_regions(Group, We) || Group <- Groups],
    foldl(fun(List, Acc) ->
		  [gb_sets:to_list(L) || L <- List]++Acc
	  end, [], Neigh).

%%%
%%% Map back to the original vertex.
%%%

map_vertex(V0, Vmap) ->
    case gb_trees:lookup(V0, Vmap) of
	none -> V0;
	{value,V} -> V
    end.

%%%
%%% Cutting along hard edges.
%%%

cut_model(Charts, Cuts, We) ->
    map(fun(Keep) -> cut_one_chart(Keep, Cuts, We) end, Charts).

cut_one_chart(Keep0, Cuts, We0) ->
    Keep = gb_sets:from_list(Keep0),
    OuterEdges = wings_face:outer_edges(Keep, We0),
    Map0 = gb_trees:empty(),
    {We1,Map1} = cut_shared_vertices(Keep, OuterEdges, We0, Map0),
    {We,Map} = cut_edges(Keep0, Cuts, We1, Map1),
    {Keep0,Map,We}.

cut_shared_vertices(Faces, Es, #we{es=Etab}=We0, InvVmap0) ->
    VsEs0 = foldl(fun(E, A) ->
			  #edge{vs=Va,ve=Vb} = gb_trees:get(E, Etab),
			  [{Va,E},{Vb,E}|A]
		  end, [], Es),
    VsEs = sofs:relation(VsEs0),
    F = sofs:relation_to_family(VsEs),
    Shared0 = sofs:specification({external,fun({_,L}) ->
						   length(L) > 2
					   end}, F),
    Shared = sofs:to_external(sofs:domain(Shared0)),
    foldl(fun(V, A) ->
		  do_cut_shared(V, Faces, A)
	  end, {We0,InvVmap0}, Shared).

do_cut_shared(V, Faces, {#we{next_id=Wid}=We0,Ivmap}) ->
    Fs = wings_vertex:fold(
	   fun(_, Face, _, A) ->
		   case gb_sets:is_member(Face, Faces) of
		       false -> A;
		       true -> [Face|A]
		   end
	   end, [], V, We0),
    We = wings_vertex_cmd:bevel_vertex(V, We0),
    do_cut_shared_1(Fs, V, Wid, We, Ivmap).

do_cut_shared_1([F|Fs], V, Wid, We0, Map0) ->
    {We,Map} = wings_face:fold(
		 fun(_, E, #edge{vs=Va}, {W0,M}) when E >= Wid ->
			 W = wings_collapse:collapse_edge(E, Va, W0),
			 {W,add_new_vs(V, [Va], M)};
		    (_, _, _, A) -> A
		 end, {We0,Map0}, F, We0),
    do_cut_shared_1(Fs, V, Wid, We, Map);
do_cut_shared_1([], _, _, We, Map) -> {We,Map}.

cut_edges(Faces, Cuts0, We, Map) ->
    Inner = wings_face:inner_edges(Faces, We),
    Cuts = gb_sets:intersection(gb_sets:from_list(Inner), Cuts0),
    case gb_sets:is_empty(Cuts) of
	false -> cut_edges_1(Faces, Cuts, We, Map);
	true -> {We,Map}
    end.

cut_edges_1(Faces, Cuts, We0, Map0) ->
    Vs = wings_edge:to_vertices(Cuts, We0),
    {We1,Map1} = bevel_cut_vs(Vs, We0, Map0),
    NewEdges = wings_we:new_items(edge, We0, We1),
    {We2,Map,MaybeRem} = cut_new_edges(gb_sets:to_list(NewEdges), We1, Map1, []),
    We3 = connect_edges(gb_sets:to_list(Cuts), We2),
    We = cut_cleanup(Faces, MaybeRem, We3),
    {We,Map}.

bevel_cut_vs([V|Vs], We0, Map0) ->
    We = wings_vertex_cmd:bevel_vertex(V, We0),
    NewVs = wings_we:new_items(vertex, We0, We),
    Map = add_new_vs(V, gb_sets:to_list(NewVs), Map0),
    bevel_cut_vs(Vs, We, Map);
bevel_cut_vs([], We, Map) -> {We,Map}.

cut_new_edges([Edge|Es], #we{es=Etab}=We0, Map0, MaybeRemove0) ->
    #edge{vs=Va,ve=Vb} = gb_trees:get(Edge, Etab),
    Pos = wpa:vertex_pos(Va, We0),
    {We,NewV=NewEdge} = wings_edge:fast_cut(Edge, Pos, We0),
    Map = add_new_vs(Vb, [NewV], add_new_vs(Va, [NewV], Map0)),
    MaybeRemove = [Edge,NewEdge|MaybeRemove0],
    cut_new_edges(Es, We, Map, MaybeRemove);
cut_new_edges([], We, Map, MaybeRemove) -> {We,Map,MaybeRemove}.

connect_edges([E|Es], #we{es=Etab}=We0) ->
    #edge{vs=Va,ve=Vb,lf=Lf,ltpr=Lp,ltsu=Lu,
	  rf=Rf,rtpr=Rp,rtsu=Ru} = gb_trees:get(E, Etab),
    LpV = other_vertex(Vb, Lp, Etab),
    LuV = other_vertex(Va, Lu, Etab),
    RpV = other_vertex(Va, Rp, Etab),
    RuV = other_vertex(Vb, Ru, Etab),
    {We1,_} = wings_vertex:force_connect(LpV, LuV, Lf, We0),
    {We,_} = wings_vertex:force_connect(RpV, RuV, Rf, We1),
    connect_edges(Es, We);
connect_edges([], We) -> We.

other_vertex(V, Edge, Etab) ->
    wings_vertex:other(V, gb_trees:get(Edge, Etab)).

cut_cleanup(Faces, MaybeRemove, #we{es=Etab}=We) ->
    Es = wings_face:fold_faces(fun(_, _, E, _, A) -> [E|A] end, [], Faces, We),
    S1 = sofs:set(Es),
    S2 = sofs:set(MaybeRemove),
    S = sofs:intersection(S1, S2),
    R = sofs:to_external(S),
    foldl(fun(E, W) ->
		  case gb_trees:is_defined(E, Etab) of
		      false -> W;
		      true -> wings_collapse:collapse_edge(E, W)
		  end
	  end, We, R).

add_new_vs(OldV, NewVs, Map) ->
    foldl(fun(NewV, M) -> add_new_vs_1(OldV, NewV, M) end, Map, NewVs).

add_new_vs_1(To, From, Map) ->
    case gb_trees:lookup(To, Map) of
	none -> gb_trees:enter(From, To, Map);
	{value,NewTo} -> add_new_vs_1(NewTo, From, Map)
    end.

%% normalize_charts(Charts, Cuts, We) -> {Charts',Cuts'}
%%  Possibly split charts that are divided into several non-connected
%%  components by edges in Cuts. Remove any edges in Cuts that goes
%%  along the boundary between two charts.
normalize_charts(Charts, Cuts, We) ->
    normalize_charts(Charts, Cuts, We, []).

normalize_charts([C0|Cs], Cuts, We, Acc0) ->
    C = gb_sets:from_list(C0),
    Acc = normalize_chart(C, Cuts, We, Acc0),
    normalize_charts(Cs, Cuts, We, Acc);
normalize_charts([], Cuts0, We, Charts) ->
    AllInner0 = lists:concat([wings_face:inner_edges(C, We) || C <- Charts]),
    AllInner = gb_sets:from_list(AllInner0),
    Cuts = gb_sets:intersection(Cuts0, AllInner),
    {Charts,Cuts}.

normalize_chart(Fs, Cuts, We, Acc) ->
    case gb_sets:is_empty(Fs) of
	true -> Acc;
	false ->
	    {F,_} = gb_sets:take_smallest(Fs),
	    Reachable = reachable_faces(F, Fs, Cuts, We),
	    Rest = gb_sets:difference(Fs, Reachable),
	    normalize_chart(Rest, Cuts, We, [gb_sets:to_list(Reachable)|Acc])
    end.

%% reachable_faces(Face, FaceSet, EdgeSet, We) -> Faces.
%%  Return a set containing all faces in FaceSet reachable from Face,
%%  without crossing any edge in EdgeSet.
reachable_faces(Face, Faces, Edges, We) ->
    reachable_faces_1(gb_sets:singleton(Face), We, {Faces,Edges}, gb_sets:empty()).

reachable_faces_1(Work0, We, Constraints, Acc0) ->
    case gb_sets:is_empty(Work0) of
	true -> Acc0;
	false ->
	    {Face,Work1} = gb_sets:take_smallest(Work0),
	    Acc = gb_sets:insert(Face, Acc0),
	    Work = collect_maybe_add(Work1, Face, Constraints, We, Acc),
	    reachable_faces_1(Work, We, Constraints, Acc)
    end.

collect_maybe_add(Work, Face, {Faces,Edges}, We, Res) ->
    wings_face:fold(
      fun(_, Edge, Rec, A) ->
	      case gb_sets:is_member(Edge, Edges) of
		  true -> A;
		  false ->
		      Of = wings_face:other(Face, Rec),
		      case gb_sets:is_member(Of, Faces) andalso
			  not gb_sets:is_member(Of, Res) of
			  false -> A;
			  true -> gb_sets:add(Of, A)
		      end
	      end
      end, Work, Face, We).

%%%
%%% Given a model having UV coordinates, partition it into charts.
%%%

uv_to_charts(Faces0, Dict, We) ->
    Faces = gb_sets:from_list(Faces0),
    Cuts = gb_sets:from_list(wings_face:inner_edges(Faces0, We)),
    uv_to_charts_1(Faces, We, Dict, Cuts, []).

fv_to_uv_map(Fs, We) ->
    fv_to_uv_map(Fs, We, []).

fv_to_uv_map([F|Fs], We, Acc0) ->
    Acc = [{{F,V},UV} || [V|UV] <- wings_face:vinfo_ccw(F, We)] ++ Acc0,
    fv_to_uv_map(Fs, We, Acc);
fv_to_uv_map([], _, Acc) ->
    gb_trees:from_orddict(sort(Acc)).

uv_to_charts_1(Faces0, We, D, Cuts0, Charts) ->
    case gb_trees:is_empty(Faces0) of
	true ->
	    normalize_charts(Charts, Cuts0, We);
	false ->
	    {Face,Faces1} = gb_sets:take_smallest(Faces0),
	    Ws = gb_sets:singleton(Face),
	    Empty = gb_sets:empty(),
	    {Chart,Cuts,Faces} = collect_chart(Ws, We, D, Empty, Cuts0, Faces1),
	    uv_to_charts_1(Faces, We, D, Cuts, [gb_sets:to_list(Chart)|Charts])
    end.

collect_chart(Ws0, We, D, Charts0, Cuts0, Faces0) ->
    case gb_sets:is_empty(Ws0) of
	true ->
	    {Charts0,Cuts0,Faces0};
	false ->
	    {Face,Ws1} = gb_sets:take_smallest(Ws0),
	    Charts = gb_sets:add(Face, Charts0),
	    Cuts = remove_non_cutting(Face, D, We, Charts, Cuts0),
	    {Ws,Faces} = collect_adj_faces(Face, We, D, Ws1, Faces0),
	    collect_chart(Ws, We, D, Charts, Cuts, Faces)
    end.

collect_adj_faces(Face, We, D, Ws, Faces0) ->
    wings_face:fold(
      fun(_, _, Rec, {W0,F0}=A) ->
	      Of = wings_face:other(Face, Rec),
	      case gb_sets:is_member(Of, F0) andalso not is_cutting_edge(Rec, D) of
		  false -> A;
		  true -> {gb_sets:insert(Of, W0),gb_sets:delete(Of, F0)}
	      end
      end, {Ws,Faces0}, Face, We).

remove_non_cutting(Face, D, We, Charts, Cuts0) ->
    wings_face:fold(
      fun(_, E, Rec, Cuts) ->
	      Of = wings_face:other(Face, Rec),
	      case gb_sets:is_member(Of, Charts) andalso
		  not is_cutting_edge(Rec, D) of
		  false -> Cuts;
		  true -> gb_sets:delete_any(E, Cuts)
	      end
      end, Cuts0, Face, We).

is_cutting_edge(#edge{vs=Va,ve=Vb,lf=Lf,rf=Rf}, D) ->
    gb_trees:get({Lf,Va}, D) =/= gb_trees:get({Rf,Va}, D) orelse
	gb_trees:get({Lf,Vb}, D) =/= gb_trees:get({Rf,Vb}, D).
