%%
%%  wings_vertex_cmd.erl --
%%
%%     This module contains most of the commands for vertices.
%%
%%  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_vertex_cmd.erl,v 1.44 2003/11/09 13:50:44 bjorng Exp $
%%

-module(wings_vertex_cmd).
-export([menu/3,command/2,tighten/3,connect/2,bevel_vertex/2]).

-include("wings.hrl").
-import(lists, [member/2,keymember/3,foldl/3,mapfoldl/3,
		reverse/1,last/1,sort/1]).

menu(X, Y, St) ->
    Dir = wings_menu_util:directions(St),
    Menu = [{basic,{"Vertex operations",ignore}},
	    {basic,separator},
	    {"Move",{move,Dir},[],[magnet]},
	    wings_menu_util:rotate(St),
	    wings_menu_util:scale(St),
	    separator,
	    {"Extrude",{extrude,Dir}},
	    separator,
	    wings_menu_util:flatten(),
	    separator,
	    {"Connect",connect,
	     "Create a new edge by connecting selected vertices"},
	    {"Tighten",tighten,
	     "Move selected vertices towards average "
	     "midpoint",[magnet]},
	    {"Bevel",bevel,"Create faces of selected vertices"},
	    {"Dissolve",dissolve,
	     "Delete selected vertices (clearing selection)"},
	    {"Collapse",collapse,
	     "Delete selected vertices (creating a face selection)"},
	    separator,
	    {"Deform",wings_deform:sub_menu(St)},
	    separator,
	    {"Vertex Color",vertex_color,
	     "Apply vertex colors to selected vertices"}],
    wings_menu:popup_menu(X, Y, vertex, Menu).

%% Vertex menu.
command({flatten,Plane}, St) ->
    flatten(Plane, St);
command(connect, St) ->
    {save_state,connect(St)};
command(tighten, St) ->
    tighten(St);
command({tighten,Magnet}, St) ->
    tighten(Magnet, St);
command(bevel, St) ->
    ?SLOW(bevel(St));
command({extrude,Type}, St) ->
    ?SLOW(extrude(Type, St));
command({deform,Deform}, St0) ->
    ?SLOW(wings_deform:command(Deform, St0));
command(auto_smooth, St) ->
    wings_body:auto_smooth(St);
command(dissolve, St) ->
    {save_state,dissolve(St)};
command(collapse, St) ->
    {save_state,wings_collapse:collapse(St)};
command({move,Type}, St) ->
    wings_move:setup(Type, St);
command({rotate,Type}, St) ->
    wings_rotate:setup(Type, St);
command({scale,Type}, St) ->
    wings_scale:setup(Type, St);
command(vertex_color, St) ->
    wings_color:choose(fun(Color) ->
			       set_color(Color, St)
		       end);
command({'ASK',Ask}, St) ->
    wings:ask(Ask, St, fun command/2).
    
%%%
%%% The Flatten command.
%%%

flatten({'ASK',Ask}, St) ->
    wings:ask(Ask, St, fun flatten/2);
flatten({Plane,Center}, St) ->
    flatten(Plane, Center, St);
flatten(Plane, St) ->
    flatten(Plane, average, St).

flatten(Plane0, average, St) ->
    Plane = wings_util:make_vector(Plane0),
    {save_state,
     wings_sel:map(
       fun(Vs, We) ->
	       wings_vertex:flatten(Vs, Plane, We)
       end, St)};
flatten(Plane0, Center, St) ->
    Plane = wings_util:make_vector(Plane0),
    {save_state,
     wings_sel:map(
       fun(Vs, We) ->
	       wings_vertex:flatten(Vs, Plane, Center, We)
       end, St)}.
    
%%%
%%% The Extrude command.
%%%

extrude(Type, St0) ->
    {St,Tvs} = wings_sel:mapfold(
		 fun(Vs, We0, Acc) ->
			 extrude_vertices(Vs, We0, Acc)
		 end, [], St0),
    wings_move:plus_minus(Type, Tvs, St).

extrude_vertices(Vs, We0, Acc) ->
    We = foldl(fun(V, A) ->
		       ex_new_vertices(V, We0, A)
	       end, We0, gb_sets:to_list(Vs)),
    NewVs = gb_sets:to_list(wings_we:new_items(vertex, We0, We)),
    {We,[{Vs,NewVs,gb_sets:empty(),We}|Acc]}.

ex_new_vertices(V, OrigWe, #we{vp=Vtab}=We0) ->
    Center = wings_vertex:pos(V, We0),
    {We,VsFaces} =
	wings_vertex:fold(
	  fun(Edge, Face, Rec, {W0,Vs}) ->
		  OtherV = wings_vertex:other(V, Rec),
		  R = edge_ratio(OtherV, OrigWe),
		  Pos0 = gb_trees:get(OtherV, Vtab),
		  Dir = e3d_vec:sub(Pos0, Center),
		  Pos = e3d_vec:add(Center, e3d_vec:mul(Dir, R)),
		  {W,NewV} = wings_edge:fast_cut(Edge, Pos, W0),
		  {W,[NewV,Face|Vs]}
	  end, {We0,[]}, V, We0),
    ex_connect(VsFaces, VsFaces, We).

edge_ratio(V, #we{vp=Vtab}) ->
    case gb_trees:is_defined(V, Vtab) of
	false -> 1/3;
	true -> 0.25
    end.

ex_connect([Va,Face|[Vb|_]=T], More, We0) ->
    {We,_} = wings_vertex:force_connect(Va, Vb, Face, We0),
    ex_connect(T, More, We);
ex_connect([Va,Face], [Vb|_], We0) ->
    {We,_} = wings_vertex:force_connect(Va, Vb, Face, We0),
    We.

%%%
%%% The Bevel command.
%%%

bevel(St0) ->
    {St,{Tvs0,FaceSel}} =
	wings_sel:mapfold(
	  fun(VsSet, We, A) ->
		  bevel_1(VsSet, We, A)
	  end, {[],[]}, St0),
    {Min,Tvs} = bevel_normalize(Tvs0),
    wings_drag:setup(Tvs, [{distance,{0.0,Min}}],
		     wings_sel:set(face, FaceSel, St)).

bevel_vertex(V, We0) ->
    {We,_} = bevel_1(gb_sets:singleton(V), We0, {[],[]}),
    We.

bevel_1(VsSet, #we{id=Id}=We0, {Tvs,Fa}) ->
    Vs = gb_sets:to_list(VsSet),
    {We,Tv,Fs0} = bevel_vertices(Vs, VsSet, We0, We0, [], []),
    FaceSel = case Fs0 of
		  [] -> Fa;
		  _ -> [{Id,gb_sets:from_list(Fs0)}|Fa]
	      end,
    {We,{[{Id,Tv}|Tvs],FaceSel}}.

bevel_vertices([V|Vs], VsSet, WeOrig, We0, Acc0, Facc) ->
    Adj = adjacent(V, VsSet, WeOrig),
    Es = wings_vertex:fold(
	   fun(Edge, Face, Rec, Acc) ->
		   [{Edge,Face,Rec}|Acc]
	   end, [], V, We0),
    case length(Es) of
	2 ->					%Winged vertex - ignore.
	    bevel_vertices(Vs, VsSet, WeOrig, We0, Acc0, Facc);
	NumEdges ->
	    {We,Acc,Face} = bevel_vertex_1(V, Es, NumEdges, Adj, We0, Acc0),
	    bevel_vertices(Vs, VsSet, WeOrig, We, Acc, [Face|Facc])
    end;
bevel_vertices([], _, _, We, Acc, Facc) -> {We,Acc,Facc}.

bevel_vertex_1(V, Es, NumEdges, Adj, We0, Vec0) ->
    {InnerFace,We1} = wings_we:new_id(We0),
    {Ids,We2} = wings_we:new_wrap_range(NumEdges, 2, We1),
    #we{es=Etab0,vc=Vct0,vp=Vtab0,fs=Ftab0} = We1,
    {Vct,Vtab} = bevel_vertices_1(V, Ids, NumEdges, Vct0, Vtab0),
    {_,Etab,Vec} = foldl(
		     fun(E, {Ids0,Etab1,Vs0}) ->
			     {Etab,Vec} = bevel(V, E, InnerFace, Ids0,
						Adj, Vtab0, Etab0, Etab1),
			     {wings_we:bump_id(Ids0),Etab,[Vec|Vs0]}
		     end, {Ids,Etab0,Vec0}, Es),
    Mat = bevel_material(Es, We2),
    NewEdge = wings_we:id(1, Ids),
    Ftab = gb_trees:insert(InnerFace, NewEdge, Ftab0),
    We = wings_material:assign(Mat, [InnerFace], We2),
    {We#we{es=Etab,fs=Ftab,vc=Vct,vp=Vtab},Vec,InnerFace}.

bevel_material(Es, We) ->
    bevel_material(Es, We, []).

bevel_material([{_,Face,_}|Es], We, Acc) ->
    Mat = wings_material:get(Face, We),
    bevel_material(Es, We, [{Mat,Face}|Acc]);
bevel_material([], _, A0) ->
    A1 = sofs:relation(A0, [{mat,face}]),
    A2 = sofs:relation_to_family(A1),
    A = sofs:to_external(A2),
    [{_,Mat}|_] = sort([{-length(Fs),M} || {M,Fs} <- A]),
    Mat.

bevel(V, {Edge,Face,Rec0}, InnerFace, Ids, Adj, Vtab, OrigEtab, Etab0) ->
    Vprev = wings_we:id(0, Ids),
    Eprev = wings_we:id(1, Ids),
    Va = wings_we:id(2, Ids),
    Ecurr = wings_we:id(3, Ids),
    Vb = wings_we:id(4, Ids),
    Enext = wings_we:id(5, Ids),
    {Rec,Curr} =
	case Rec0 of
	    #edge{vs=V,ve=Vother,rf=Face,rtpr=ColEdge} ->
		Col = bevel_color(ColEdge, Face, OrigEtab),
		{Rec0#edge{vs=Va,rtpr=Ecurr,ltsu=Eprev},
		 Rec0#edge{vs=Vb,ve=Va,a=Col,lf=InnerFace,
			   rtsu=Edge,ltpr=Eprev,ltsu=Enext}};
	    #edge{vs=V,ve=Vother,rf=Of,rtpr=ColEdge} ->
		Col = bevel_color(ColEdge, Of, OrigEtab),
		{Rec0#edge{vs=Va,rtpr=Ecurr,ltsu=Enext},
		 Rec0#edge{vs=Vprev,ve=Va,a=Col,lf=InnerFace,
			   rtsu=Edge,ltpr=Enext,ltsu=Eprev}};
	    #edge{ve=V,vs=Vother,lf=Face,ltpr=ColEdge} ->
		Col = bevel_color(ColEdge, Face, OrigEtab),
		{Rec0#edge{ve=Va,ltpr=Ecurr,rtsu=Eprev},
		 Rec0#edge{vs=Va,ve=Vb,b=Col,rf=InnerFace,
			   ltsu=Edge,rtpr=Eprev,rtsu=Enext}};
	    #edge{ve=V,vs=Vother,lf=Of,ltpr=ColEdge} ->
		Col = bevel_color(ColEdge, Of, OrigEtab),
		{Rec0#edge{ve=Va,ltpr=Ecurr,rtsu=Enext},
		 Rec0#edge{vs=Va,ve=Vprev,b=Col,rf=InnerFace,
			   ltsu=Edge,rtpr=Enext,rtsu=Eprev}}
	end,
    Etab = gb_trees:update(Edge, Rec, Etab0),
    Vpos = gb_trees:get(V, Vtab),
    Vec = bevel_vec(Adj, Vother, Vpos, Vtab),
    {gb_trees:insert(Ecurr, Curr, Etab),{Vec,Va}}.

bevel_vec(Adj, Vother, Vpos, Vtab) ->
    Opos = gb_trees:get(Vother, Vtab),
    case member(Vother, Adj) of
	true ->
	    e3d_vec:sub(e3d_vec:average([Opos,Vpos]), Vpos);
	false ->
	    e3d_vec:sub(Opos, Vpos)
    end.

bevel_vertices_1(V, Ids, N, Vct0, Vtab0) ->
    Pos = gb_trees:get(V, Vtab0),
    Vct = gb_trees:delete(V, Vct0),
    Vtab = gb_trees:delete(V, Vtab0),
    bevel_new_vertices(Ids, N, Pos, Vct, Vtab).

bevel_new_vertices(Ids, N, Pos, Vct0, Vtab0) when N > 0 ->
    V = Id = wings_we:id(0, Ids),
    Edge = Id + 1,
    Vct = gb_trees:insert(V, Edge, Vct0),
    Vtab = gb_trees:insert(V, Pos, Vtab0),
    bevel_new_vertices(wings_we:bump_id(Ids), N-1, Pos, Vct, Vtab);
bevel_new_vertices(_, _, _, Vct, Vtab) -> {Vct,Vtab}.

bevel_normalize(Tvs) ->
    bevel_normalize(Tvs, 1.0E207, []).

bevel_normalize([{Id,VecVs0}|Tvs], Min0, Acc) ->
    {VecVs,Min} = bevel_normalize_1(VecVs0, Min0),
    bevel_normalize(Tvs, Min, [{Id,VecVs}|Acc]);
bevel_normalize([], Min, Tvs) -> {Min,Tvs}.

bevel_normalize_1(VecVs, Min0) ->
    mapfoldl(fun({Vec,V}, M0) ->
		     Min = case e3d_vec:len(Vec) of
			       Len when Len < M0 -> Len;
			       _Len -> M0
			   end,
		     {{e3d_vec:norm(Vec),[V]},Min}
	     end, Min0, VecVs).

bevel_color(ColEdge, Face, Etab0) ->
    case gb_trees:get(ColEdge, Etab0) of
	#edge{lf=Face,a=Col} -> Col;
	#edge{rf=Face,b=Col} -> Col
    end.

adjacent(V, Vs, We) ->
    wings_vertex:fold(
      fun(_, _, Rec, A) ->
	      OtherV = wings_vertex:other(V, Rec),
	      case gb_sets:is_member(OtherV, Vs) of
		  true -> [OtherV|A];
		  false -> A
	      end
      end, [], V, We).
    
%%%
%%% The Connect command.
%%%

connect(St) ->
    wings_sel:map(fun connect/2, St).

connect(Vs0, #we{mirror=MirrorFace}=We) ->
    FaceVs = wings_vertex:per_face(Vs0, We),
    foldl(fun({Face,_}, Acc) when Face =:= MirrorFace -> Acc;
	     ({Face,Vs}, Acc) -> wings_vertex:connect(Face, Vs, Acc)
	  end, We, FaceVs).

%%%
%%% The Tighten command.
%%%

tighten(St) ->
    Tvs = wings_sel:fold(fun tighten/3, [], St),
    wings_drag:setup(Tvs, [percent], St).

tighten(Vs, #we{id=Id}=We, Acc) when is_list(Vs) ->
    Tv = foldl(
	   fun(V, A) ->
		   Vec = tighten_vec(V, We),
		   [{Vec,[V]}|A]
	   end, [], Vs),
    [{Id,Tv}|Acc];
tighten(Vs, We, Acc) -> 
    tighten(gb_sets:to_list(Vs), We, Acc).

tighten_vec(V, #we{vp=Vtab,mirror=MirrorFace}=We) ->
    Cs = wings_vertex:fold(
	   fun(_, Face, _, A) when Face =/= MirrorFace ->
		   FaceVs = wings_face:to_vertices([Face], We),
		   C = wings_vertex:center(FaceVs, We),
		   [C|A];
	      (_, _, _, A) -> A
	   end, [], V, We),
    Center = e3d_vec:average(Cs),
    e3d_vec:sub(Center, gb_trees:get(V, Vtab)).

%%%
%%% The magnetic version of Tighten.
%%%

tighten(Magnet, St) ->
    Tvs = wings_sel:fold(fun(Vs, We, Acc) ->
				 tighten(Vs, We, Magnet, Acc)
			 end, [], St),
    Flags = wings_magnet:flags(Magnet, []),
    wings_drag:setup(Tvs, [percent,falloff], Flags, St).

tighten(Vs, We, Magnet, Acc) when is_list(Vs) ->
    Tv = foldl(
	   fun(V, A) ->
		   Vec = tighten_vec(V, We),
		   [{Vec,[V]}|A]
	   end, [], Vs),
    magnet_move(Tv, Magnet, We, Acc);
tighten(Vs, We, Magnet, Acc) -> 
    tighten(gb_sets:to_list(Vs), We, Magnet, Acc).

magnet_move(Tv, Magnet0, #we{id=Id}=We, Acc) ->
    Vs = lists:append([Vs || {_,Vs} <- Tv]),
    {VsInf,Magnet,Affected} = wings_magnet:setup(Magnet0, Vs, We),
    Vec = magnet_tighten_vec(Affected, We, []),
    [{Id,{Affected,wings_move:magnet_move_fun(Vec, VsInf, Magnet)}}|Acc].

magnet_tighten_vec([V|Vs], We, Acc) ->
    Vec = tighten_vec(V, We),
    magnet_tighten_vec(Vs, We, [{V,Vec}|Acc]);
magnet_tighten_vec([], _, Acc) ->
    gb_trees:from_orddict(sort(Acc)).
    
%%%
%%% The Dissolve command. Like Collapse, but stays in vertex mode
%%% (without any selection).
%%%

dissolve(St0) ->
    St = wings_collapse:collapse(St0),
    St#st{selmode=vertex,sel=[]}.

%%%
%%% Set vertex color.
%%%

set_color(Color, St) ->
    wings_sel:map(fun(Vs, We) ->
			  set_color_1(gb_sets:to_list(Vs), Color,
				      We#we{mode=vertex})
		  end, St).

set_color_1([V|Vs], Color, #we{es=Etab0}=We) ->
    Etab = wings_vertex:fold(
	     fun(Edge, _Face, Rec0, Es) ->
		     Rec = case Rec0 of
			       #edge{vs=V} -> Rec0#edge{a=Color};
			       #edge{ve=V} -> Rec0#edge{b=Color}
			   end,
		     gb_trees:update(Edge, Rec, Es)
	     end, Etab0, V, We),
    set_color_1(Vs, Color, We#we{es=Etab});
set_color_1([], _, We) -> We.
