%%
%%  wings_body.erl --
%%
%%     This module contains most of the command for entire Wings objects.
%%
%%  Copyright (c) 2001-2005 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_body.erl 388 2008-10-02 04:21:54Z optigon $
%%

-module(wings_body).
-export([menu/3,command/2]).
-export([auto_smooth/1]).

-include("wings.hrl").
-include("sdl_keyboard.hrl").
-import(lists, [map/2,foldl/3,reverse/1,reverse/2,sort/1,seq/2]).

menu(X, Y, St) ->
    Dir = wings_menu_util:directions(St),
    Dup = flip_str(),
    BasicDup = case wings_pref:get_value(advanced_menus) of
        false -> flip_str();
        true -> []
	end,
    FlipStrL = ?__(34,"Flip the object along ~s axis") ++ BasicDup,
    FlipStrM = ?__(35,"Pick point to flip object along the ~s axis"),
    FlipStrR = ?__(36,"Flip object along the global ~s axis") ++ Dup,

    Menu = [{basic,{?__(1,"Object operations"),ignore}},
	    {basic,separator},
	    {?__(2,"Move"),{move,Dir}},
	    wings_menu_util:rotate(St),
	    wings_menu_util:scale(St),
	    separator,
	    {?__(3,"Flip"),{flip,
	       [{wings_s:dir(x),flip_fun(x),
	         {io_lib:format(FlipStrL,[wings_s:dir(x)]),
	          io_lib:format(FlipStrM,[wings_s:dir(x)]),
	          io_lib:format(FlipStrR,[wings_s:dir(x)])},[]},
	        {wings_s:dir(y),flip_fun(y),
	         {io_lib:format(FlipStrL,[wings_s:dir(y)]),
	          io_lib:format(FlipStrM,[wings_s:dir(y)]),
	          io_lib:format(FlipStrR,[wings_s:dir(y)])},[]},
	        {wings_s:dir(z),flip_fun(z),
	         {io_lib:format(FlipStrL,[wings_s:dir(z)]),
	          io_lib:format(FlipStrM,[wings_s:dir(z)]),
	          io_lib:format(FlipStrR,[wings_s:dir(z)])},[]},
	        {?__(37,"Pick"),flip_fun(pick),
	         {?__(38,"Pick axis to flip object along") ++ BasicDup,
	          ?__(39,"Pick axis and point to flip object along"),
	          ?__(40,"Pick global axis to flip object along") ++ Dup},[]}]}},
	    separator,
	    {?__(10,"Invert"),invert,
	     ?__(11,"Flip all normals, turning the object inside out")},
	    separator,
	    {?__(12,"Tighten"),tighten,
	     ?__(13,"Move vertices towards average midpoint")},
	    {?__(14,"Smooth"),smooth,
	     ?__(15,"Subdivide all faces to give the object a smoother apperance")},
	    {?__(16,"Combine"),combine,
	     ?__(17,"Combine multiple objects into a single object")},
	    {?__(18,"Separate"),separate,
	     ?__(19,"Separate a combined objects into its components")},
	    separator,
	    {?__(20,"Weld"),weld,
	     ?__(21,"Merge pair of faces that are nearly coincident"),
	     [option]},
	    separator,
	    {?__(22,"Cleanup"),cleanup,
	     ?__(23,"Remove various defects"),
	     [option]},
	    {?__(24,"Auto-Smooth"),auto_smooth,
	     ?__(25,"Set edges hard or soft depending on the angle between faces"),
	     [option]},
	    separator,
	    {?__(26,"Duplicate"),{duplicate,Dir}},
	    {?__(27,"Delete"),delete,?__(28,"Delete the selected objects")},
	    {?__(29,"Rename..."),rename,
	     ?__(30,"Rename selected objects")},
	    separator,
	    {?__(31,"Show All"),show_all,
	     ?__(32,"Show all faces for this object")}|mode_dependent(St)],
    wings_menu:popup_menu(X, Y, body, Menu).

flip_fun(pick) ->
    fun
	  (1, _Ns) -> {body,{flip,{dup(local),{'ASK',[flip_axis]}}}};
	  (2, _Ns) -> {body,{flip,{dup(point),{'ASK',[flip_axis,flip_point]}}}};
	  (3, _Ns) -> {body,{flip,{dup(global),{'ASK',[flip_axis]}}}}
	end;
flip_fun(Axis) ->
    fun
	  (1, _Ns) -> {body,{flip,{dup(local),Axis}}};
	  (2, _Ns) -> {body,{flip,{dup(point),{Axis,{'ASK',[flip_point]}}}}};
	  (3, _Ns) -> {body,{flip,{dup(global),Axis}}}
	end.
dup(Type) ->
%% Return {dup,Type} if Alt is pressed during Flip command, otherwise Type.
    Mod = sdl_keyboard:getModState(),
    case (Mod band ?KMOD_ALT) =/= 0 of
        true -> {dup,Type};
        false -> Type
    end.

mode_dependent(St) ->
    SelObj = wings_sel:fold(fun(_, We, A) -> [We|A] end, [], St),
    Area = foldl(fun(#we{has_shape=Shape}=We, A) ->
			 Type = if 
				    ?IS_ANY_LIGHT(We), Shape -> arealight;
				    true -> object
				end,
			 case A of
			     none -> Type;
			     Type -> Type;
			     _ -> mixed
			 end
		 end, none, SelObj),
    ObjMode = foldl(fun(#we{mode=M}, none) -> M;
		       (#we{mode=M}, M) -> M;
		       (_, _) -> mixed
		    end, none, SelObj),
    Tail0 = vertex_color_item(Area),
    Tail1 = mat_col_conv(ObjMode, Area, Tail0),
    Tail2 = arealight_conv(Area, Tail1),
    case mode_conv(ObjMode, Area, Tail2) of
	[] -> [];
	Tail -> [separator|Tail]
    end.

vertex_color_item(object) ->
    [{?__(1,"Vertex Color"),vertex_color,
      ?__(2,"Apply vertex colors to selected objects "
	   "(first changing mode to vertex color mode if needed)")}];
vertex_color_item(_) -> [].
    
mat_col_conv(vertex, object, T) ->
    [{?__(1,"Colors to Materials"),colors_to_materials,
      ?__(2,"Convert vertex colors to materials")}|T];
mat_col_conv(material, object, T) ->
    [{?__(3,"Materials to Colors"),materials_to_colors,
      ?__(4,"Convert materials to vertex colors")}|T];
mat_col_conv(_, _, T) -> T.

arealight_conv(arealight, T) ->
    [{?__(1,"Area Light to Object"),from_arealight,
      ?__(2,"Convert selected area lights to objects")}|T];
arealight_conv(object, T) ->
    [{?__(3,"Object To Area Light"),to_arealight,
      ?__(4,"Convert selected objects to area lights")}|T];
arealight_conv(mixed, T) -> T.

mode_conv(material, object, T) ->
    [{?__(1,"Vertex Color Mode"),vertex_color_mode,
      ?__(2,"Change object mode to vertex color mode "
	   "(materials will be kept, but not shown)")}|T];
mode_conv(vertex, object, T) ->
    [{?__(3,"Material Mode"),material_mode,
      ?__(4,"Change object mode to material mode "
	   "(vertex colors will be kept, but not shown)")}|T];
mode_conv(_, _, T) -> T.

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(invert, St) ->
    {save_state,invert_normals(St)};
command(duplicate, St) ->
    {save_state,duplicate(none, St)};
command({duplicate,Dir}, St) ->
    save_state,duplicate(Dir, St);
command({duplicate_object,Ids}, St) ->
    {save_state,duplicate_object(Ids, St)};
command(delete, St) ->
    {save_state,delete(St)};
command({delete_object,Ids}, St) ->
    {save_state,delete_object(Ids, St)};
command(tighten, St) ->
    tighten(St);
command(smooth, St) ->
    ?SLOW({save_state,smooth(St)});
command(combine, St) ->
    {save_state,combine(St)};
command(separate, St) ->
    {save_state,separate(St)};
command(auto_smooth, St) ->
    auto_smooth(St);
command({auto_smooth,Ask}, St) ->
    auto_smooth(Ask, St);
command({flip,{Type,{Axis,{'ASK',Ask}}}},St) ->
    wings:ask(flip_ask(Ask), St, fun(Result,St0) ->
    {save_state,flip(Type,{Axis,Result}, St0)}
    end);
command({flip,{Type,{'ASK',Ask}}}, St) ->
    wings:ask(flip_ask(Ask), St, fun(Result,St0) ->
    {save_state,flip(Type, Result, St0)}
    end);
command({flip,{Type, Plane}}, St) ->
    {save_state,flip(Type, Plane, St)};
command(cleanup, St) ->
    cleanup(false, St);
command({cleanup,Ask}, St) ->
    cleanup(Ask, St);
command(collapse, St) ->
    {save_state,wings_collapse:collapse(St)};
command(rename, St) ->
    rename(St);
command({rename,Ids}, St) ->
    rename(Ids, St);
command(to_arealight, St) ->
    to_arealight(St);
command({to_arealight,Ids}, St) ->
    to_arealight(Ids, St);
command(from_arealight, St) ->
    from_arealight(St);
command({from_arealight,Ids}, St) ->
    from_arealight(Ids, St);
command(materials_to_colors, St) ->
    {save_state,materials_to_colors(St)};
command(colors_to_materials, St) ->
    {save_state,colors_to_materials(St)};
command(material_mode, St) ->
    {save_state,set_mode(material, St)};
command(vertex_color_mode, St) ->
    {save_state,set_mode(vertex, St)};
command({weld,Ask}, St) ->
    weld(Ask, St);
command(vertex_color, St) ->
    wings_color:choose(fun(Color) ->
			       set_color(Color, St)
		       end);
command(show_all, St) ->
    {save_state,
     wings_sel:map(fun(_, We) ->
			   wings_we:show_faces(We)
		   end, St)}.
%%%
%%% The Cleanup command.
%%%

cleanup(Ask, _) when is_atom(Ask) ->
    Qs = [{?__(1,"Short Edges"),true,[{key,short_edges}]},
	  {hframe,
	   [{label,?__(2,"Length Tolerance")},
	    {text,1.0E-3,[{range,{1.0E-5,10.0}}]}]},
	  {?__(3,"Isolated Vertices"),true,
	   [{key,isolated_vs}]}],
    wings_ask:dialog(Ask,
		     ?__(4,"Cleanup"),
		     [{vframe,Qs}],
		     fun(Res) -> {body,{cleanup,Res}} end);
cleanup(Opts, St0) ->
    St = wings_sel:map(fun(_, We0) ->
			       We1 = cleanup_repeated_vtxs(We0),
			       We2 = cleanup_waists(We1),
			       We = cleanup_2edged_faces(We2),
			       cleanup_1(Opts, We)
		       end, St0),
    {save_state,St}.

cleanup_1([{short_edges,Flag},Tolerance|Opts], We0) ->
    We = case Flag of
	     true -> clean_short_edges(Tolerance, We0);
	     false -> We0
	 end,
    cleanup_1(Opts, We);
cleanup_1([{isolated_vs,true}|Opts], We) ->
    cleanup_1(Opts, clean_isolated_vertices(We));
cleanup_1([_|Opts], We) ->
    cleanup_1(Opts, We);
cleanup_1([], We) -> We.

clean_isolated_vertices(We) ->
    case wings_vertex:isolated(We) of
	[] -> We;
	[_]=Isolated ->
	    io:put_chars(?__(1,"Removed 1 isolated vertex\n")),
	    wings_vertex:dissolve_isolated(Isolated, We);
	Isolated ->
	    io:format(?__(2,"Removed ~p isolated vertices\n"),
		      [length(Isolated)]),
	    wings_vertex:dissolve_isolated(Isolated, We)
    end.
		  
clean_short_edges(Tolerance, #we{es=Etab,vp=Vtab}=We) ->
    Short = foldl(
	      fun({Edge,#edge{vs=Va,ve=Vb}}, A) ->
		      VaPos = gb_trees:get(Va, Vtab),
		      VbPos = gb_trees:get(Vb, Vtab),
		      case abs(e3d_vec:dist(VaPos, VbPos)) of
			  Dist when Dist < Tolerance -> [Edge|A];
			  _Dist -> A
		      end
	      end, [], gb_trees:to_list(Etab)),
    foldl(fun(Edge, #we{es=Et}=W) ->
		  case gb_trees:is_defined(Edge, Et) of
		      true -> wings_collapse:collapse_edge(Edge, W);
		      false -> W
		  end
	  end, We, Short).

%%
%% A vertex may not be used more than once in a single face.
%%
cleanup_repeated_vtxs(#we{fs=Ftab}=We) ->
    cleanup_rep_1(gb_trees:keys(Ftab), We).

cleanup_rep_1([F|Fs], We0) ->
    case repeated_vertex(F, We0) of
	none ->
	    cleanup_rep_1(Fs, We0);
	V ->
	    io:format(?__(1,"Repeated vertex ~p in face ~p\n"), [V,F]),
	    We = cleanup_rep_2(F, V, We0),
	    NewFaces = wings_we:new_items_as_ordset(face, We0, We),
	    cleanup_rep_1(NewFaces++Fs, We)
    end;
cleanup_rep_1([], We) -> We.

cleanup_rep_2(Face, V0, #we{es=Etab,fs=Ftab0}=We0) ->
    Es = wings_vertex:fold(
	   fun(Edge, _, Rec, A) ->
		   case Rec of
		       #edge{ve=V0,lf=Face} -> [Edge|A];
		       #edge{vs=V0,rf=Face} -> [Edge|A];
		       _ -> A
		   end
	   end, [], V0, We0),
    Ves = wings_face:fold(fun(V, E, _, A) ->
				  [{V,E}|A]
			  end, [], Face, hd(Es), We0),
    Ftab = gb_trees:delete(Face, Ftab0),
    Mat = wings_facemat:face(Face, We0),
    We1 = wings_facemat:delete_face(Face, We0),
    cleanup_rep_3(Ves, V0, Face, Mat, Ftab, Etab, We1).

cleanup_rep_3([], _, _, _, Ftab, Etab, We) ->
    We#we{fs=Ftab,es=Etab};
cleanup_rep_3(Ves0, V, Face, Mat, Ftab0, Etab0, We0) ->
    [{_,First}|_] = Ves0,
    {NewFace,We1} = wings_we:new_id(We0),
    We = wings_facemat:assign(Mat, [NewFace], We1),
    Ftab = gb_trees:insert(NewFace, First, Ftab0),
    {Ves,Etab1} = cleanup_rep_4(Ves0, V, Face, NewFace, Etab0),
    [{_,Last}|NextVes] = Ves,
    Etab2 = cleanup_patch_edge(V, NewFace, First, Last, Etab1),
    Etab = cleanup_patch_edge(V, NewFace, Last, First, Etab2),
    cleanup_rep_3(NextVes, V, Face, Mat, Ftab, Etab, We).

cleanup_rep_4([{Vtx,Edge}|T]=Ves, V0, Face, NewFace, Etab0) ->
    Rec = case gb_trees:get(Edge, Etab0) of
	      #edge{lf=Face}=Rec0 -> Rec0#edge{lf=NewFace};
	      #edge{rf=Face}=Rec0 -> Rec0#edge{rf=NewFace}
	  end,
    Etab = gb_trees:update(Edge, Rec, Etab0),
    if
	Vtx =:= V0 -> {Ves,Etab};
	true -> cleanup_rep_4(T, V0, Face, NewFace, Etab)
    end.

repeated_vertex(Face, We) ->
    Vs = wings_face:vertices_ccw(Face, We),
    repeated_vertex_1(sort(Vs)).

repeated_vertex_1([V,V|_]) -> V;
repeated_vertex_1([_|T]) -> repeated_vertex_1(T);
repeated_vertex_1([]) -> none.

cleanup_patch_edge(V, Face, From, To, Etab) ->
    R = case gb_trees:get(From, Etab) of
	    #edge{lf=Face,ve=V}=R0 -> R0#edge{ltpr=To};
	    #edge{lf=Face,vs=V}=R0 -> R0#edge{ltsu=To};
	    #edge{rf=Face,vs=V}=R0 -> R0#edge{rtpr=To};
	    #edge{rf=Face,ve=V}=R0 -> R0#edge{rtsu=To}
	end,
    gb_trees:update(From, R, Etab).

%%
%% A waist is a vertex shared by edges all of which cannot be
%% reached from the incident edge of the vertex.
%%
cleanup_waists(#we{es=Etab,vp=Vtab}=We) ->
    VsEs0 = foldl(fun({E,#edge{vs=Va,ve=Vb}}, A) ->
			  [{Va,E},{Vb,E}|A]
		  end, [], gb_trees:to_list(Etab)),
    VsEs = wings_util:rel2fam(VsEs0),
    cleanup_waists_1(gb_trees:keys(Vtab), VsEs, We).

cleanup_waists_1([V|Vs], [{V,AllEs}|VsEs], #we{es=Etab0,vp=Vtab0,vc=Vct0}=We0) ->
    Es0 = wings_vertex:fold(fun(E, _, _, A) -> [E|A] end, [], V, We0),
    case ordsets:subtract(AllEs, ordsets:from_list(Es0)) of
	[] ->					%Good.
	    cleanup_waists_1(Vs, VsEs, We0);
	[AnEdge|_]=Es ->
	    %% Some edges cannot be reached from the incident edge.
	    %% Repair by duplicating the original vertex.
	    {NewV,We1} = wings_we:new_id(We0),
	    Etab = patch_vtx_refs(Es, V, NewV, Etab0),
	    Vtab = gb_trees:insert(NewV, gb_trees:get(V, Vtab0), Vtab0),
	    Vct = gb_trees:insert(NewV, AnEdge, Vct0),
	    We = We1#we{es=Etab,vp=Vtab,vc=Vct},
	    io:format(?__(1,"Removed waist vertex: ~p\n"), [V]),

	    %% Re-process the newly added vertex. (Some of the
	    %% edges may not be reachable from the incident edge of
	    %% the new vertex.)
	    cleanup_waists_1([NewV|Vs], [{NewV,Es}|VsEs], We)
    end;
cleanup_waists_1([], [], We) -> We.

patch_vtx_refs([E|Es], OldV, NewV, Etab0) ->
    Etab = case gb_trees:get(E, Etab0) of
	       #edge{vs=OldV}=Rec ->
		   gb_trees:update(E, Rec#edge{vs=NewV}, Etab0);
	       #edge{ve=OldV}=Rec ->
		   gb_trees:update(E, Rec#edge{ve=NewV}, Etab0)
	   end,
    patch_vtx_refs(Es, OldV, NewV, Etab);
patch_vtx_refs([], _, _, Etab) -> Etab.

%%
%% Remove faces having only two edges.
%%
cleanup_2edged_faces(#we{fs=Ftab}=We) ->
    delete_2edged_faces_1(gb_trees:keys(Ftab), We).

delete_2edged_faces_1([Face|Faces], We0) ->
    case delete_if_bad(Face, We0) of
	bad_edge ->
	    wings_u:error(?__(1,"Face") ++ integer_to_list(Face) ++
			     ?__(2,"has only one edge"));
	We -> delete_2edged_faces_1(Faces, We)
    end;
delete_2edged_faces_1([], We) -> We.

delete_if_bad(Face, #we{fs=Ftab,es=Etab}=We) ->
    case gb_trees:lookup(Face, Ftab) of
	{value,Edge} ->
	    case gb_trees:get(Edge, Etab) of
		#edge{ltpr=Same,ltsu=Same,rtpr=Same,rtsu=Same} ->
		    bad_edge;
		#edge{ltpr=Same,ltsu=Same} ->
		    wings_edge:dissolve_edge(Edge, We);
		#edge{rtpr=Same,rtsu=Same} ->
		    wings_edge:dissolve_edge(Edge, We);
		_ -> We
	    end;
	none -> We
    end.

%%%
%%% The Invert command.
%%%

invert_normals(St) ->
    wings_sel:map(fun(_, We) -> wings_we:invert_normals(We) end, St).

%%%
%%% The Duplicate command.
%%%

duplicate(Dir, #st{onext=Oid0}=St0) ->
    St1 = wings_sel:fold(fun(_, We, St) ->
				 wings_shape:insert(We, copy, St)
			 end, St0, St0),
    %% Select the duplicate items, not the original items.
    Zero = gb_sets:singleton(0),
    Sel = [{Id,Zero} || Id <- seq(Oid0, St1#st.onext-1)],
    St = wings_sel:set(Sel, St1),
    case Dir of
	none -> St;
	_ -> wings_move:setup(Dir, St)
    end.

%%%
%%% Duplicate called from the Outliner or Object window.
%%%

duplicate_object(Objects, #st{shapes=Shs}=St) ->
    foldl(fun(Id, S) ->
		  We = gb_trees:get(Id, Shs),
		  wings_shape:insert(We, copy, S)
	  end, St, Objects).

%%%
%%% The Delete command.
%%%

delete(#st{shapes=Shapes0}=St) ->
    Shapes = wings_sel:fold(fun(_, #we{id=Id}, Shs) ->
				    gb_trees:delete(Id, Shs)
			    end, Shapes0, St),
    St#st{shapes=Shapes,sel=[]}.

%%%
%%% Delete called from the Outliner or Object window.
%%%

delete_object(Objects, #st{shapes=Shs0}=St) ->
    Shs = foldl(fun(Id, Shs) ->
			gb_trees:delete(Id, Shs)
		end, Shs0, Objects),
    wings_sel:valid_sel(St#st{shapes=Shs}).

%%%
%%% The Flip command
%%%

flip_ask(Asks) ->
    Ask = flip_ask(Asks,[]),
    {Ask,[],[],[vertex,edge,face]}.

flip_ask([],Ask) -> lists:reverse(Ask);
flip_ask([flip_axis|Asks],Result) ->
    Pick = {axis,?__(1,"Select axis to flip object along") ++ flip_str()},
    flip_ask(Asks,[Pick|Result]);
flip_ask([flip_point|Asks],Result) ->
    Pick = {point,?__(2,"Select point along the chosen axis to flip object") ++ flip_str()},
    flip_ask(Asks,[Pick|Result]).

flip_str() -> "  " ++ ?__(1,"[Alt]+Click to Duplicate object").

flip({dup,Type}, Plane, St0) ->
    St = duplicate(none,St0),
    flip_cmd(Type,Plane,St);
flip(Type, Plane, St0) ->
    St = case dup(Type) of
        {dup,_} -> duplicate(none,St0);
        _Otherwise -> St0
    end,
    flip_cmd(Type,Plane,St).

flip_cmd(Type, Plane, St) ->
    wings_sel:map(fun(_, We) -> 
        case Plane of
          {{_,_,_},{_,_,_}} -> flip_body_2(Plane, We);
          {_,_,_} -> flip_body_3(Type, Plane, We);
          _Otherwise -> flip_body_1(Type, Plane, We)
        end
    end, St).

flip_body_1(Type, Plane0, We0) ->
    Plane = flip_scale(Plane0),
    Center = e3d_vec:average(wings_vertex:bounding_box(We0)),
    {Cx,Cy,Cz} = flip_center(Type,Plane0,Center),
    M0 = e3d_mat:translate(Cx, Cy, Cz),
    M1 = e3d_mat:mul(M0, Plane),
    M = e3d_mat:mul(M1, e3d_mat:translate(-Cx, -Cy, -Cz)),
    We = wings_we:transform_vs(M, We0),
    wings_we:invert_normals(We).

flip_body_2({Plane,Center}, #we{vp=Vtab0}=We) ->
    Vtab1 = lists:foldl(fun(Vtx, A) ->
            flip_vs(Vtx, Plane, Center, A)
            end, [], gb_trees:to_list(Vtab0)),
    Vtab = gb_trees:from_orddict(reverse(Vtab1)),
    wings_we:invert_normals(We#we{vp=Vtab}).

flip_body_3(Type, Plane, #we{vp=Vtab0}=We) ->
    Center0 = wings_vertex:center(We),
    Center = flip_center(Type,Plane,Center0),
    Vtab1 = lists:foldl(fun(Vtx, A) ->
            flip_vs(Vtx, Plane, Center, A)
            end, [], gb_trees:to_list(Vtab0)),
    Vtab = gb_trees:from_orddict(reverse(Vtab1)),
    wings_we:invert_normals(We#we{vp=Vtab}).

flip_vs({V,Pos0}, Plane, Center, A) ->
    ToCenter = e3d_vec:sub(Center, Pos0),
    Dot = e3d_vec:dot(ToCenter, Plane),
    Pos = wings_util:share(e3d_vec:add_prod(Pos0, Plane, 2.0*Dot)),
    [{V,Pos}|A].

flip_scale(x) -> e3d_mat:scale(-1.0, 1.0, 1.0);
flip_scale(y) -> e3d_mat:scale(1.0, -1.0, 1.0);
flip_scale(z) -> e3d_mat:scale(1.0, 1.0, -1.0);
flip_scale({Axis,_}) -> flip_scale(Axis).

flip_center(local,_,Center) -> Center;
flip_center(global,x,{_,Cy,Cz}) -> {0.0,Cy,Cz};
flip_center(global,y,{Cx,_,Cz}) -> {Cx,0.0,Cz};
flip_center(global,z,{Cx,Cy,_}) -> {Cx,Cy,0.0};
flip_center(global,{Xa,Ya,Za},{Cx,Cy,Cz}) ->
    Plane = e3d_vec:norm({Xa,Ya,Za}),
    intersect_vec_plane({Cx,Cy,Cz},{0.0,0.0,0.0},Plane,Plane);
flip_center(point,{x,{X,_,_}},{_,Cy,Cz}) -> {X,Cy,Cz};
flip_center(point,{y,{_,Y,_}},{Cx,_,Cz}) -> {Cx,Y,Cz};
flip_center(point,{z,{_,_,Z}},{Cx,Cy,_}) -> {Cx,Cy,Z}.

intersect_vec_plane(PosA,PosB,Vector,Plane) ->
    %% Return point where Vector through PosA intersects with Plane at PosB
    DotProduct = e3d_vec:dot(Vector,Plane),
    case DotProduct of
      0.0 ->
        PosA;
      _Otherwise ->
        Intersection = e3d_vec:dot(e3d_vec:sub(PosB,PosA),Vector)/DotProduct,
        e3d_vec:add(PosA, e3d_vec:mul(Plane, Intersection))
    end.

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

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

tighten(_, #we{vp=Vtab}=We, A) ->
    Vs = gb_trees:keys(Vtab),
    wings_vertex_cmd:tighten(Vs, We, A).
    
%%%
%%% The Smooth command.
%%%

smooth(St) ->
    wings_sel:map(fun(_, We) ->
			  wings_subdiv:smooth(We)
		  end, St).

%%%
%%% The Combine command.
%%%

combine(#st{sel=[]}=St) -> St;
combine(#st{shapes=Shs0,sel=[{Id,_}=S|_]=Sel0}=St) ->
    Shs1 = sofs:from_external(gb_trees:to_list(Shs0), [{id,object}]),
    Sel1 = sofs:from_external(Sel0, [{id,dummy}]),
    Sel2 = sofs:domain(Sel1),
    {Wes0,Shs2} = sofs:partition(1, Shs1, Sel2),
    Wes = sofs:to_external(sofs:range(Wes0)),
    Mode = unify_modes(Wes),
    We0 = wings_we:merge(Wes),
    We = We0#we{id=Id,mode=Mode},
    Shs = gb_trees:from_orddict(sort([{Id,We}|sofs:to_external(Shs2)])),
    St#st{shapes=Shs,sel=[S]}.

unify_modes([#we{mode=Mode}|Wes]) ->
    unify_modes(Wes, Mode).

unify_modes([#we{mode=Mode}|Wes], Mode) ->
    unify_modes(Wes, Mode);
unify_modes([_|_], _) ->
    wings_u:error(?__(1,
			 "Objects with vertex colors cannot be combined " 
			 "with objects with materials."));
unify_modes([], Mode) -> Mode.
		    
%%%
%%% The Separate command.
%%%

separate(St) ->
    wings_sel:fold(
      fun(_, #we{id=Id}=We0, St0) ->
	      case wings_we:separate(We0) of
		  [_] -> St0;
		  [We|Wes] ->
		      St1 = foldl(fun(W, A) ->
					  wings_shape:insert(W, sep, A)
				  end, St0, Wes),
		      wings_shape:replace(Id, We, St1)
	      end
      end, St, St).

%%%
%%% The Auto-Smooth command.
%%%

auto_smooth(St) ->
    do_auto_smooth(60, St).

auto_smooth(Ask, _) when is_atom(Ask) ->
    wings_ask:ask(Ask,?__(1,"Auto Smooth Parameters"),
		  [{?__(2,"Crease Angle"),60,[{range,{0,180}}]}],
		  fun(Res) -> {body,{auto_smooth,Res}} end);
auto_smooth([Angle], St) ->
    {save_state,do_auto_smooth(Angle, St)}.

do_auto_smooth(Angle, St) ->
    Cos = cos_degrees(Angle),
    wings_sel:map(fun(_, We) -> auto_smooth_1(Cos, We) end, St).

auto_smooth_1(Cos, #we{es=Etab,he=Htab0}=We) ->
    Htab = foldl(fun({E,R}, A) ->
			 auto_smooth(E, R, Cos, A, We)
		 end, Htab0, gb_trees:to_list(Etab)),
    We#we{he=Htab}.

auto_smooth(Edge, #edge{lf=Lf,rf=Rf}, Cos, H0, We) ->
    Ln = wings_face:normal(Lf, We),
    Lr = wings_face:normal(Rf, We),
    case e3d_vec:is_zero(Ln) orelse e3d_vec:is_zero(Lr) of
	true -> H0;				%Ignore this edge.
	false ->
	    case e3d_vec:dot(Ln, Lr) of
		P when P < Cos ->
		    wings_edge:hardness(Edge, hard, H0);
		_ ->				%angle =< 60
		    wings_edge:hardness(Edge, soft, H0)
	    end
    end.

cos_degrees(Angle) ->
    math:cos(Angle*math:pi()/180.0).


%%%
%%% Rename selected objects.
%%%

rename(St) ->
    Wes = wings_sel:fold(fun(_, We, A) -> [We|A] end, [], St),
    rename_1(Wes, St).

rename(Objects, #st{shapes=Shs}=St) ->
    Wes = foldl(fun(Id, A) -> [gb_trees:get(Id, Shs)|A] end, [], Objects),
    rename_1(Wes, St).

rename_1(Wes, St) ->
    Qs = rename_qs(Wes),
	wings_ask:dialog(?__(1,"Rename"), Qs,
			 fun(NewNames) ->
				 rename_1(NewNames, Wes, St)
			 end).

rename_1(Names, Wes, #st{shapes=Shs}=St) ->
    rename_2(Names, Wes, Shs, St).

rename_2([N|Ns], [#we{id=Id}=We|Wes], Shs0, St) ->
    Shs = gb_trees:update(Id, We#we{name=N}, Shs0),
    rename_2(Ns, Wes, Shs, St);
rename_2([], [], Shs, St) -> St#st{shapes=Shs}.

rename_qs(Wes) ->
    OldNames = [{label,Name} || #we{name=Name} <- Wes],
    TextFields = [{text,Name,[]} || #we{name=Name} <- Wes],
    [{hframe,
      [{vframe,OldNames},
       {vframe,TextFields}]}].

%%%
%%% Convert selected objects to area lights
%%%
to_arealight(#st{shapes=Shs}=St) ->
    Wes = wings_sel:fold(fun(_, We, A) -> [We|A] end, [], St),
    to_arealight_1(Wes, Shs, St).

to_arealight(Objects, #st{shapes=Shs}=St) ->
    Wes = foldl(fun(Id, A) -> [gb_trees:get(Id, Shs)|A] end, [], Objects),
    to_arealight_1(Wes, Shs, St).

to_arealight_1([], Shs, St) -> 
    St#st{shapes=Shs};
to_arealight_1([We0|Wes], Shs, St) when ?IS_ANY_LIGHT(We0) ->
    to_arealight_1(Wes, Shs, St);
to_arealight_1([#we{id=Id}=We0|Wes], Shs, St) ->
    #we{light=Light,has_shape=HasShape} = 
	wings_light:import([{opengl,[{type,area}]}]),
    We = We0#we{light=Light,has_shape=HasShape},
    to_arealight_1(Wes, gb_trees:update(Id, We, Shs), St).



%%%
%%% Convert selected area lights to objects
%%%
from_arealight(#st{shapes=Shs}=St) ->
    Wes = wings_sel:fold(
	    fun (_, #we{has_shape=HasShape}=We, A) 
		when ?IS_ANY_LIGHT(We), HasShape -> 
		    [We|A];
		(_, _, A) ->
		    A
	    end, [], St),
    from_arealight_1(Wes, Shs, St).

from_arealight(Objects, #st{shapes=Shs}=St) ->
    Wes = foldl(fun(Id, A) -> [gb_trees:get(Id, Shs)|A] end, [], Objects),
    from_arealight_1(Wes, Shs, St).

from_arealight_1([], Shs, St) -> 
    St#st{shapes=Shs};
from_arealight_1([#we{id=Id,has_shape=HasShape}=We|Wes], Shs, St)
    when ?IS_ANY_LIGHT(We), HasShape ->
    from_arealight_1(Wes, gb_trees:update(Id, We#we{light=none}, Shs), St);
from_arealight_1([We|Wes], Shs, St) 
  when ?IS_ANY_LIGHT(We) ->
    to_arealight_1(Wes, Shs, St).



%%%
%%% Set Mode.
%%%

set_mode(Mode, St) ->
    wings_sel:map(fun(_, We) -> We#we{mode=Mode} end, St).

%%%
%%% Convert materials to vertex colors.
%%%

materials_to_colors(St) ->
    wings_sel:map(fun(_, We) ->
			  wings_we:uv_to_color(We, St)
		  end, St).

%%%
%%% Convert vertex colors to materials.
%%%

colors_to_materials(St0) ->
    {St,#st{mat=Mat}} =
	wings_sel:mapfold(fun(_, We, S) ->
				  colors_to_materials_1(We, S)
			  end, St0, St0),
    St#st{mat=Mat}.

colors_to_materials_1(#we{mode=vertex,fs=Ftab}=We0, St) ->
    colors_to_materials_2(gb_trees:keys(Ftab), We0#we{mode=material}, [], St);
colors_to_materials_1(We, St) -> {We,St}.

colors_to_materials_2([F|Fs], We, Acc, St0) ->
    Colors = [C || [_|C] <- wings_face:vinfo_ccw(F, We)],
    case wings_color:average(Colors) of
	none ->
	    colors_to_materials_2(Fs, We, Acc, St0);
	Color ->
	    {Name,St} = color_material(Color, St0),
	    colors_to_materials_2(Fs, We, [{F,Name}|Acc], St)
    end;
colors_to_materials_2([], We, FaceMat, St) ->
    {wings_facemat:assign(FaceMat, We),St}.

color_material({R,G,B}=Color, #st{mat=Mat0}=St0) ->
    Name0 = ?__(1,"color_")++ fmt_int(R) ++ "_" ++ fmt_int(G) ++
	"_" ++ fmt_int(B),
    Name = list_to_atom(Name0),
    case gb_trees:is_defined(Name, Mat0) of
	true -> {Name,St0};
	false ->
	    Mat = [{opengl,[{diffuse,Color}]}],
	    case wings_material:add_materials([{Name,Mat}], St0) of
		{St,[]} -> {Name,St};
		{St,[{Name,New}]} -> {New,St}
	    end
    end.

fmt_int(I) ->
    L = integer_to_list(trunc(256*I)),
    fmt_int(length(L), L).

fmt_int(3, L) -> L;
fmt_int(N, L) -> fmt_int(N+1, [$0|L]).

%%%
%%% The Weld command.
%%%

weld(Ask, _) when is_atom(Ask) ->
    Qs = [{hframe,
	   [{label,?__(1,"Distance Tolerance")},
	    {text,1.0E-3,[{range,{1.0E-5,10.0}}]}]}],
    wings_ask:dialog(Ask, ?__(2,"Weld"), Qs,
		     fun(Res) -> {body,{weld,Res}} end);
weld([Tolerance], St0) ->
    St1 = combine(St0),
    {St2,Sel} = wings_sel:mapfold(fun(_, We, Acc) ->
					  weld_1(Tolerance, We, Acc)
				  end, [], St1),
    St = wings_sel:set(vertex, Sel, St2),
    {save_state,wings_sel:valid_sel(St)}.

weld_1(Tol, #we{id=Id,fs=Fs0}=We0, Acc) ->
    Fs = weld_1_list(gb_trees:keys(Fs0), Tol, We0, []),
    R = sofs:relation(Fs, [{key,face}]),
    F = sofs:relation_to_family(R),
    Part0 = sofs:range(F),
    Part1 = sofs:specification({external,fun([_]) -> false;
					    (_) -> true end}, Part0),
    Part = sofs:to_external(Part1),
    case weld_2(Part, Tol, We0) of
	We0 ->
	    wings_u:error(?__(1,"Found no faces to weld."));
	We ->
	    {We,[{Id,weld_selection(lists:append(Part), We0, We)}|Acc]}
    end.

weld_1_list([F|Fs], Tol, We, Acc) ->
    Vs = wings_face:fold(
	   fun(V, _, _, Acc0) ->
		   [V|Acc0]
	   end, [], F, We),
    {X,Y,Z} = wings_vertex:center(Vs, We),
    Center = {granularize(X, Tol),granularize(Y, Tol),granularize(Z, Tol)},
    weld_1_list(Fs, Tol, We, [{{length(Vs),Center},F}|Acc]);
weld_1_list([], _, _, Acc) -> Acc.

granularize(F, Tol) -> Tol*round(F/Tol).

weld_2([P|Ps], Tol, We0) ->
    We = weld_part(P, Tol, We0),
    weld_2(Ps, Tol, We);
weld_2([], _, We) -> We.

weld_part([F|Fs], Tol, We) ->
    weld_part_1(F, Fs, Tol, We, []);
weld_part([], _, We) -> We.

weld_part_1(Fa, [Fb|Fs], Tol, We0, Acc) ->
    case try_weld(Fa, Fb, Tol, We0) of
	no -> weld_part_1(Fa, Fs, Tol, We0, [Fb|Acc]);	
	We -> weld_part(Fs++Acc, Tol, We)
    end;
weld_part_1(_, [], Tol, We, Acc) ->
    weld_part(Acc, Tol, We).

try_weld(Fa, Fb, Tol, We) ->
    case wings_face:are_neighbors(Fa, Fb, We) of
	true -> no;
	false ->
	    Na = wings_face:normal(Fa, We),
	    Nb = wings_face:normal(Fb, We),
	    case e3d_vec:dot(Na, Nb) of
		Dot when Dot < -0.99 ->
		    try_weld_1(Fa, Fb, Tol, We);
		_Dot -> no
	    end
    end.

try_weld_1(Fa, Fb, Tol, We0) ->
    N = wings_face:vertices(Fa, We0),
    IterA = wings_face:iterator(Fa, We0),
    {Va,_,_,_} = wings_face:next_cw(IterA),
    PosA = wings_vertex:pos(Va, We0),
    IterB0 = weld_synced_iterator(N, Fb, PosA, We0),
    case weld_same_positions(N, IterA, IterB0, Tol, We0) of
	false -> no;
	true ->
	    {Vb,_,_,_} = wings_face:next_ccw(IterB0),
	    We = wings_face_cmd:force_bridge(Fa, Va, Fb, Vb, We0),
	    Es = wings_we:new_items_as_ordset(edge, We0, We),
	    foldl(fun(E, W) ->
			  wings_collapse:collapse_edge(E, W)
		  end, We, Es)
    end.

weld_synced_iterator(N, Face, Pos, We) ->
    Iter = wings_face:iterator(Face, We),
    weld_synced_iterator_1(N, Iter, Pos, We, []).

weld_synced_iterator_1(0, _, _, _, Acc) ->
    [{_,Iter}|_] = sort(Acc),
    Iter;
weld_synced_iterator_1(N, Iter0, Pos, We, Acc) ->
    {V,_,_,Iter} = wings_face:next_ccw(Iter0),
    D = e3d_vec:dist(Pos, wings_vertex:pos(V, We)),
    weld_synced_iterator_1(N-1, Iter, Pos, We, [{D,Iter0}|Acc]).

weld_same_positions(0, _, _, _, _) -> true;
weld_same_positions(N, IterA0, IterB0, Tol, We) ->
    {Va,_,_,IterA} = wings_face:next_cw(IterA0),
    {Vb,_,_,IterB} = wings_face:next_ccw(IterB0),
    PosA = wings_vertex:pos(Va, We),
    PosB = wings_vertex:pos(Vb, We),
    case e3d_vec:dist(PosA, PosB) of
	D when abs(D) < Tol -> weld_same_positions(N-1, IterA, IterB, Tol, We);
	_D -> false
    end.

weld_selection(Fs, OldWe, We) ->
    weld_selection(Fs, OldWe, We, []).

weld_selection([F|Fs], OldWe, #we{fs=Ftab}=We, Acc) ->
    case gb_trees:is_defined(F, Ftab) of
	true -> weld_selection(Fs, OldWe, We, Acc);
	false ->
	    Vs = wings_face:vertices_ccw(F, OldWe),
	    weld_selection(Fs, OldWe, We, Vs++Acc)
    end;
weld_selection([], _, _, Acc) ->
    gb_sets:from_list(Acc).

%%%
%%% Set vertex color for selected objects.
%%%

set_color(Color, St) ->
    wings_sel:map(fun(_, We) ->
			  set_color_1(Color, We)
		  end, St).

set_color_1(Color, #we{es=Etab0}=We) ->
    Etab1 = foldl(fun({E,Rec}, A) ->
			  [{E,Rec#edge{a=Color,b=Color}}|A]
		  end, [], gb_trees:to_list(Etab0)),
    Etab = gb_trees:from_orddict(reverse(Etab1)),
    We#we{es=Etab,mode=vertex}.
