%%
%%  wings_sel_cmd.erl --
%%
%%     This module implements the commands in the selection menu.
%%
%%  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_sel_cmd.erl,v 1.49 2003/08/16 17:50:35 bjorng Exp $
%%

-module(wings_sel_cmd).

-export([menu/1,command/2]).

%% Utilities.
-export([select_all/1]).

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

menu(St) ->
    [{"Deselect",deselect,"Clear the selection"},
     separator,
     {"More",more,more_help(St)},
     {"Less",less,less_help(St)},
     {"Similar",similar,similar_help(St)},
     separator,
     {"Edge Loop",
      {edge_loop,
       [{"Edge Loop",edge_loop,"Expand edge selection to loop; "
	 "convert face selection to selected border edges"},
	{"Edge Loop to Region",edge_loop_to_region,
	 "Select all faces on one side of an edge loop"},
	{"Edge Ring",edge_ring,"Expand edge selection to ring"},
	separator,
	{"Previous Edge Loop",prev_edge_loop,"Select the next edge loop"},
	{"Next Edge Loop",next_edge_loop,"Select the previous edge loop"},
	separator,
	{"Grow Edge Loop",edge_link_incr,
	 "Grow edge selection by one edge in loop directions"},
	{"Shrink Edge Loop",edge_link_decr,
	 "Shrink edge selection by one in loop direction"},
	{"Grow Edge Ring",edge_ring_incr,
	 "Grow edge selection by one edge in ring direction"},
	{"Shrink Edge Ring",edge_ring_decr,
	 "Shrink edge selection by one edge in ring directions"}]}},
     separator,
     {"Adjacent",{adjacent,[{"Vertices",vertex},
			    {"Edges",edge},
			    {"Faces",face},
			    {"Objects",body}]}},
     {"By",{by,[{"Hard Edges",hard_edges,"Select all hard edges"},
		{"Isolated Vertices",isolated_vertices,"Select all isolated vertices"},
		{"Vertices With",{vertices_with,
				  [{"2 Edges",2},
				   {"3 Edges",3},
				   {"4 Edges",4},
				   {"5 Edges",5}]}},
		{"Faces With",{faces_with,
			       [{"2 Edges",2},
				{"3 Edges",3},
				{"4 Edges",4},
				{"5 or More",5}]}},
		{"Random",{random,[{"10%",10},
				   {"20%",20},
				   {"30%",30},
				   {"40%",40},
				   {"50%",50},
				   {"60%",60},
				   {"70%",70},
				   {"80%",80},
				   {"90%",90}]}},
		{"Short Edges",short_edges,"Select (too) short edges",[option]},
		{"Material Edges",material_edges,
		 "Select all edges between different edges"},
		{"Id...",id,"Select by numeric id"}]}},
     {"Lights",lights,"Select all lights"},
     separator,
     {sel_all_str(St),all},
     separator,
     {"Inverse",inverse},
     separator,
     {"Hide Selected",hide_selected,"Hide all (partly or wholly) selected objects"},
     {"Hide Unselected",hide_unselected,"Hide objects that have no selection"},
     {"Lock Unselected",lock_unselected,"Lock objects that have no selection"},
     separator,
     {"Show All",show_all,"Show all objects that have been hidden"},
     {"Unlock All",unlock_all,"Unlock all locked objects"},
     separator,
     {"Store Selection",store_selection,
      "Store the selection into the selection group named \"StoredSelection\""},
     {"Recall Selection",recall_selection,
      "Recall the selection from the selection group named \"StoredSelection\""},
     separator,
     {"New Group...",new_group,"Create a new selection group"}|groups_menu(St)].

sel_all_str(#st{selmode=vertex}) -> "All Vertices";
sel_all_str(#st{selmode=edge}) -> "All Edges";
sel_all_str(#st{selmode=face}) -> "All Faces";
sel_all_str(#st{selmode=body}) -> "All Objects".

groups_menu(#st{ssels=Ssels}=St) -> 
    case gb_trees:is_empty(Ssels) of
        true -> [];
        false ->
	    [{"Delete Group",
	      {delete_group,
	       groups_and_help("Delete group \"", "\"", St)}},
	     separator,
	     {"Add to Group",
	      {add_to_group, 
	       groups_and_help("Add current selection to group \"", "\"", St)}},
	     {"Subtract from Group",
	      {subtract_from_group,
	       groups_and_help("Subtract current selection from group \"", "\"", St)}},
	     separator,
	     {"Select Group",
	      {select_group, 
	       groups_and_help("Select group \"", "\"", St)}},
	     separator,
	     {"Union Group",
	      {union_group, 
	       groups_and_help("Union group \"", "\" with current selection", St)}},
	     {"Subtract Group",
	      {subtract_group, 
	       groups_and_help("Subtract group \"", "\" from current selection", St)}},
	     {"Intersect Group",
	      {intersect_group, 
	       groups_and_help("Intersect group \"", "\" with current selection", St)}}]
    end.
      
groups_and_help(Help0, Help1, #st{ssels=Ssels}) ->
    map(fun({Mode,Name}=Key) ->
		Title = group_title(Name, Mode),
		{Title,fun(help, _) -> {Help0++Name++Help1};
			  (_, Ns) -> wings_menu:build_command(Key, Ns)
		       end,Help0++Name++Help1}
	end,
	gb_trees:keys(Ssels)).

group_title(Name, vertex) -> "vertex: "++Name;
group_title(Name, edge) -> "edge: "++Name;
group_title(Name, face) -> "face: "++Name;
group_title(Name, body) -> "body: "++Name.

more_help(#st{selmode=vertex}) ->
    "Select all vertices adjacent to a selected vertex";
more_help(#st{selmode=edge}) ->
    "Select all edges adjacent to a selected edge";
more_help(#st{selmode=face}) ->
    "Select all faces sharing a vertex with a selected face";
more_help(_) -> "".

less_help(#st{selmode=vertex}) ->
    "Deselect all vertices adjacent to an unselected vertex";
less_help(#st{selmode=edge}) ->
    "Deselect all edges adjacent to an unselected edge";
less_help(#st{selmode=face}) ->
    "Deselect all faces sharing a vertex with an unselected face";
less_help(_) -> "".

similar_help(#st{selmode=vertex}) ->
    "Select vertices similar to the already selected vertices";
similar_help(#st{selmode=edge}) ->
    "Select edges similar to the already selected edges";
similar_help(#st{selmode=face}) ->
    "Select faces similar to the already selected faces";
similar_help(#st{selmode=body}) ->
    "Select objects with the same number of edges, faces, and vertices";
similar_help(_) -> [].
    
command({edge_loop,edge_loop}, #st{selmode=face}=St) ->
    {save_state,
     wings_sel:convert_shape(
       fun(Faces, We) ->
	       gb_sets:from_list(wings_face:outer_edges(Faces, We))
       end, edge, St)};
command({edge_loop,edge_loop}, St) ->
    {save_state,wings_edge_loop:select_loop(St)};
command({edge_loop,edge_link_decr}, St) ->
    {save_state,wings_edge_loop:select_link_decr(St)};
command({edge_loop,edge_link_incr}, St) ->
    {save_state,wings_edge_loop:select_link_incr(St)};
command({edge_loop,edge_ring}, St) ->
    {save_state,wings_edge:select_edge_ring(St)};
command({edge_loop,edge_ring_incr}, St) ->
    {save_state,wings_edge:select_edge_ring_incr(St)};
command({edge_loop,edge_ring_decr}, St) ->
    {save_state,wings_edge:select_edge_ring_decr(St)};
command({edge_loop,next_edge_loop}, St) ->
    {save_state,wings_edge_loop:select_next(St)};
command({edge_loop,prev_edge_loop}, St) ->
    {save_state,wings_edge_loop:select_prev(St)};
command({edge_loop,edge_loop_to_region}, St) ->
    {save_state,wings_edge:select_region(St)};
command(deselect, St) ->
    {save_state,wings_sel:reset(St)};
command(more, St) ->
    select_more(St);
command(less, St) ->
    select_less(St);
command(all, St) ->
    {save_state,select_all(St)};
command(lights, St) ->
    {save_state,select_lights(St)};
command({by,Command}, St) ->
    by_command(Command, St);
command(similar, St) ->
    {save_state,similar(St)};
command({select_group,Id}, St) ->
    {save_state,select_group(Id, St)};
command({union_group, Id}, St) ->
    {save_state,union_group(Id, St)};
command({subtract_group, Id}, St) ->
    {save_state,subtract_group(Id, St)};
command({intersect_group, Id}, St) ->
    {save_state,intersect_group(Id, St)};
command({add_to_group, Id}, St) ->
    {save_state,add_to_group(Id, St)};
command({subtract_from_group, Id}, St) ->
    {save_state,subtract_from_group(Id, St)};
command({new_group_name, Name}, St) ->
    {save_state,new_group_name(Name, St)};
command(new_group, St) ->
    new_group(St);
command({delete_group,Id}, #st{ssels=Ssels}=St) ->
    {save_state,St#st{ssels=gb_trees:delete(Id, Ssels)}};
command(inverse, St) ->
    {save_state,inverse(St)};
command(hide_selected, St) ->
    {save_state,hide_selected(St)};
command(hide_unselected, St) ->
    {save_state,hide_unselected(St)};
command(lock_unselected, St) ->
    {save_state,lock_unselected(St)};
command(show_all, St) ->
    {save_state,wings_shape:show_all(St)};
command(unlock_all, St) ->
    {save_state,wings_shape:unlock_all(St)};
command({adjacent,Type}, St) ->
    set_select_mode(Type, St);
command(store_selection, #st{ssels=Ssels0,selmode=Mode,sel=Sel}=St) ->
    Key = {Mode,"StoredSelection"},
    Ssels = gb_trees:enter(Key, Sel, Ssels0),
    {save_state,St#st{ssels=Ssels}};
command(recall_selection, #st{selmode=Mode,ssels=Ssels}=St0) ->
    Key = {Mode, "StoredSelection"},
    case gb_trees:is_defined(Key, Ssels) of
	false -> St0;
	true ->
	    St = select_group(Key, St0),
	    {save_state,St}
    end;
command(Type, St) ->
    set_select_mode(Type, St).

by_command(hard_edges, St) ->
    Sel = fun(Edge, #we{he=Htab}) ->
		  gb_sets:is_member(Edge, Htab)
	  end,
    {save_state,wings_sel:make(Sel, edge, St)};
by_command(isolated_vertices, St) ->
    {save_state,select_isolated(St)};
by_command({vertices_with,N}, St) ->
    Sel = fun(V, We) ->
		  Cnt = wings_vertex:fold(
			  fun(_, _, _, Cnt) ->
				  Cnt+1
			  end, 0, V, We),
		  Cnt =:= N
	  end, 
    {save_state,wings_sel:make(Sel, vertex, St)};
by_command({faces_with,5}, St) ->
    Sel = fun(Face, We) ->
		  wings_face:vertices(Face, We) >= 5
	    end,
    {save_state,wings_sel:make(Sel, face, St)};
by_command({faces_with,N}, St) ->
    Sel = fun(Face, We) ->
		  N =:= wings_face:vertices(Face, We)
	  end,
    {save_state,wings_sel:make(Sel, face, St)};
by_command({material,_}=Cmd, St) ->
    wings_material:command({select,Cmd}, St);
by_command(material_edges, St) ->
    material_edges(St);
by_command({random,Percent}, St) ->
    {save_state,random(Percent, St)};
by_command({short_edges,Ask}, St) ->
    short_edges(Ask, St);
by_command(id, St) ->
    by_id(St);
by_command({id,Sel}, St) ->
    {save_state,sel_by_id(Sel, St)}.

%%%
%%% Selection commands.
%%%

set_select_mode(Type, St) ->
    {save_state,wings_sel:convert_selection(Type, St)}.

select_all(#st{selmode=body,shapes=Shapes}=St) ->
    Items = gb_sets:singleton(0),
    Sel = [{Id,Items} || #we{id=Id,perm=Perm} <- gb_trees:values(Shapes),
			 ?IS_SELECTABLE(Perm)],
    St#st{sel=Sel};
select_all(#st{selmode=Mode,sel=[],shapes=Shapes}=St) ->
    Sel = [{Id,wings_sel:get_all_items(Mode, We)} ||
	      #we{id=Id,perm=Perm}=We <- gb_trees:values(Shapes),
	      ?IS_SELECTABLE(Perm)],
    St#st{sel=Sel};
select_all(#st{selmode=Mode,sel=Sel0}=St) ->
    Sel = [{Id,wings_sel:get_all_items(Mode, Id, St)} || {Id,_} <- Sel0],
    St#st{sel=Sel}.

select_more(#st{selmode=vertex}=St) ->
    wings_vertex:select_more(St);
select_more(#st{selmode=edge}=St) ->
    wings_edge:select_more(St);
select_more(#st{selmode=face}=St) ->
    wings_face:select_more(St);
select_more(St) -> St.

select_less(#st{selmode=vertex}=St) ->
    wings_vertex:select_less(St);
select_less(#st{selmode=edge}=St) ->
    wings_edge:select_less(St);
select_less(#st{selmode=face}=St) ->
    wings_face:select_less(St);
select_less(St) -> St.

%%%
%%% Select Inverse.
%%%

inverse(#st{selmode=body,sel=Sel0,shapes=Shapes}=St) ->
    Items = gb_sets:singleton(0),
    All = [{Id,Items} || #we{id=Id,perm=Perm} <- gb_trees:values(Shapes),
			 ?IS_SELECTABLE(Perm)],
    Sel = ordsets:subtract(All, Sel0),
    St#st{sel=Sel};
inverse(#st{selmode=Mode}=St) ->
    Sel = wings_sel:fold(
	    fun(Items, #we{id=Id}=We, A) ->
		    Diff = wings_sel:inverse_items(Mode, Items, We),
		    case gb_sets:is_empty(Diff) of
			true -> [{Id,Items}|A];	%Can't inverse.
			false -> [{Id,Diff}|A]
		    end
	    end, [], St),
    St#st{sel=reverse(Sel)}.

%%%
%%% Hide Selected
%%% Hide Unselected
%%% Lock Unselected
%%%

hide_selected(#st{selmode=Mode,shapes=Shs0,sel=Sel}=St) ->
    Shs1 = map(fun(#we{id=Id}=We) ->
		       case keysearch(Id, 1, Sel) of
			   false -> {Id,We};
			   {value,{_,Set}} -> {Id,We#we{perm={Mode,Set}}}
		       end
	       end, gb_trees:values(Shs0)),
    Shs = gb_trees:from_orddict(Shs1),
    St#st{shapes=Shs,sel=[]}.

hide_unselected(St) ->
    update_unsel([], St).

lock_unselected(St) ->
    update_unsel(1, St).

update_unsel(Perm, #st{shapes=Shs0,sel=Sel}=St) ->
    Shs1 = map(fun(#we{id=Id,perm=0}=We) ->
		       case keymember(Id, 1, Sel) of
			   true -> {Id,We};
			   false -> {Id,We#we{perm=Perm}}
		       end;
		  (#we{id=Id}=We) -> {Id,We}
	       end, gb_trees:values(Shs0)),
    Shs = gb_trees:from_orddict(Shs1),
    St#st{shapes=Shs}.
    
%%%
%%% Selection Groups
%%%

union_group(Key, #st{sel=Sel0}=St) ->
    Ssel = coerce_ssel(Key, St),
    Sel = union(Sel0, Ssel),
    St#st{sel=Sel}.

union(Sa, Sb) ->
    combine_sel(fun(Ss) -> gb_sets:union(Ss) end, Sa, Sb).

subtract_group(Key, #st{sel=Sel0}=St) ->		
    Ssel = coerce_ssel(Key, St),
    Sel = subtract(Sel0, Ssel),
    St#st{sel=Sel}.

subtract([{Id1,_}=E1|Es1], [{Id2,_}|_]=Set2) when Id1 < Id2 ->
    [E1|subtract(Es1, Set2)];
subtract([{Id1,_}|_]=Set1, [{Id2,_}|Es2]) when Id1 > Id2 ->
    subtract(Set1, Es2);
subtract([{Id,E1}|Es1], [{Id,E2}|Es2]) ->	%E1 == E2
    E = gb_sets:subtract(E1, E2),
    case gb_sets:is_empty(E) of
	true -> subtract(Es1, Es2);
	false -> [{Id,E}|subtract(Es1, Es2)]
    end;
subtract([], _Es2) -> [];
subtract(Es1, []) -> Es1.

intersect_group(Key, #st{sel=Sel0}=St) ->
    Ssel = coerce_ssel(Key, St),
    Sel = intersection(Sel0, Ssel),
    St#st{sel=Sel}.

intersection(Sa, Sb) ->
    Empty = gb_sets:empty(),
    combine_sel(fun([_]) -> Empty;
		   (Ss) -> gb_sets:intersection(Ss)
		end, Sa, Sb).
			
combine_sel(Combine, Sa, Sb) ->
    combine_sel(Combine, lists:merge(Sa, Sb)).
combine_sel(Combine, [{Id,Sa},{Id,Sb}|T]) ->
    S = Combine([Sa,Sb]),
    case gb_sets:is_empty(S) of
	true -> combine_sel(Combine, T);
	false -> [{Id,S}|combine_sel(Combine, T)]
    end;
combine_sel(Combine, [{Id,S0}|T]) ->
    S = Combine([S0]),
    case gb_sets:is_empty(S) of
	true -> combine_sel(Combine, T);
	false -> [{Id,S}|combine_sel(Combine, T)]
    end;
combine_sel(_Combine, []) -> [].

coerce_ssel({Mode,_}=Key, #st{ssels=Ssels}=St) ->
    Ssel = gb_trees:get(Key, Ssels),
    coerce_ssel(Mode, Ssel, St).
    
coerce_ssel(Mode, Ssel, #st{selmode=Mode}) -> Ssel;
coerce_ssel(Smode, Ssel0, #st{selmode=Mode}=St) ->
    StTemp = St#st{selmode=Smode,sel=wings_sel:valid_sel(Ssel0, Smode, St)},
    #st{sel=Ssel} = wings_sel:convert_selection(Mode, StTemp),
    Ssel.

select_group({Mode,_}=Key, #st{ssels=Ssels}=St) ->
    Ssel = gb_trees:get(Key, Ssels),
    ValidSel = wings_sel:valid_sel(Ssel, Mode, St),
    St#st{selmode=Mode,sel=ValidSel}.
  
add_to_group({Mode,_}=Key, #st{ssels=Ssels}=St) ->
    Ssel0 = gb_trees:get(Key, Ssels),
    Ssel1 = wings_sel:valid_sel(Ssel0, Mode, St),
    #st{sel=Sel} = possibly_convert(Mode, St),
    Ssel = union(Ssel1, Sel),
    save_group(Key, Ssel, St).

subtract_from_group({Mode,_}=Key, #st{ssels=Ssels}=St) ->
    Ssel0 = gb_trees:get(Key, Ssels),
    Ssel1 = wings_sel:valid_sel(Ssel0, Mode, St),
    #st{sel=Sel} = possibly_convert(Mode, St),
    Ssel = subtract(Ssel1, Sel),
    save_group(Key, Ssel, St).
    
possibly_convert(Mode, #st{selmode=Mode}=St) -> St;
possibly_convert(Mode, St) -> wings_sel:convert_selection(Mode, St).
    
save_group(Key, Sel, #st{ssels=Ssels0}=St) ->
    Ssels = gb_trees:update(Key, Sel, Ssels0),
    St#st{ssels=Ssels}.
    
new_group(_) ->
    wings_ask:ask("Create New Group",
		  [{"Group Name", ""}],
		  fun([String]) -> {select,{new_group_name,String}} end).

new_group_name(Name, #st{ssels=Ssels0,selmode=Mode,sel=Sel}=St) ->
    Key = {Mode,Name},
    case gb_trees:is_defined(Key, Ssels0) of
	false -> ok;
	true ->
	    wings_util:error("~s selection group ~p already exists.",
			     [wings_util:cap(atom_to_list(Mode)),
			      Name])
    end,
    Ssels = gb_trees:insert(Key, Sel, Ssels0),
    St#st{ssels=Ssels}.

%%%
%%% Select Similar.
%%%

similar(#st{selmode=vertex}=St) ->
    Seed = wings_sel:fold(fun(Sel0, We, A) ->
				  [make_vertex_template(SelI, We) ||
				      SelI <- gb_sets:to_list(Sel0)] ++ A
			  end, [], St),
    Templates = ordsets:from_list(Seed),
    wings_sel:make(
      fun(V, W) ->
	      match_templates(make_vertex_template(V, W), Templates)
      end, vertex, St);
similar(#st{selmode=edge}=St) ->
    Seed = wings_sel:fold(fun(Sel0, We, A) ->
				  [make_edge_template(SelI, We) ||
				      SelI <- gb_sets:to_list(Sel0)] ++ A
			  end, [], St),
    Templates = ordsets:from_list(Seed),
    wings_sel:make(
      fun(Edge, W) ->
	      match_templates(make_edge_template(Edge, W), Templates)
      end, edge, St);
similar(#st{selmode=face}=St) ->
    Seed = wings_sel:fold(fun(Sel0, We, A) ->
				  [make_face_template(SelI, We) ||
				      SelI <- gb_sets:to_list(Sel0)] ++ A
			  end, [], St),
    Templates = ordsets:from_list(Seed),
    wings_sel:make(
      fun(Face, WeI) ->
	      match_templates(make_face_template(Face, WeI), Templates)
      end, face, St);
similar(#st{selmode=body}=St) ->
    Template0 = wings_sel:fold(fun(_, #we{vp=Vtab,es=Etab,fs=Ftab}, Acc) ->
				       [{gb_trees:size(Vtab),
					 gb_trees:size(Etab),
					 gb_trees:size(Ftab)}|Acc]
			       end, [], St),
    Template = ordsets:from_list(Template0),
    wings_sel:make(fun(_, We) -> match_body(Template, We) end, body, St).

match_body(Template, #we{vp=Vtab,es=Etab,fs=Ftab}) ->
    Sizes = {gb_trees:size(Vtab),gb_trees:size(Etab),gb_trees:size(Ftab)},
    match_body_1(Template, Sizes).

match_body_1([Sizes|_], Sizes) -> true;
match_body_1([_|T], Sizes) -> match_body_1(T, Sizes);
match_body_1([], _) -> false.
    
match_templates(F, [Template|Ts]) ->
    case match_template(F, Template) of
	true -> true;
	false -> match_templates(F, Ts)
    end;
match_templates(_, []) -> false.

match_template({Len,Ad,As}, {Len,Bd,Bs}) ->
    case rel_compare(Ad, Bd, 1.0E-5) of
	true -> rel_compare(As, Bs, 1.0E-5);
	false -> false
    end;
match_template(_, _) -> false.

make_face_template(Face, #we{vp=Vtab}=We) ->
    Vs = wings_face:fold(
	   fun(V, _, _, Acc0) ->
		   [V|Acc0]
	   end, [], Face, We),
    {DotSum,SqSum} = face_dots_and_sqlens(Vs, Vtab),
    {length(Vs),DotSum,SqSum}.

face_dots_and_sqlens(Vs, Vtab) ->
    Vpos = [gb_trees:get(P, Vtab) || P <- Vs],
    face_dots_and_sqlens_1(Vpos).

face_dots_and_sqlens_1([Va,Vb|_]=Vpos) ->
    D = e3d_vec:sub(Va, Vb),
    face_dots_and_sqlens_2(D, Vpos, Vpos, 0, 0).

face_dots_and_sqlens_2(D1, [_|[Vb,Vc|_]=Vs], More, Dot0, Sq0) ->
    D2 = e3d_vec:sub(Vb, Vc),
    Dot = Dot0 + e3d_vec:dot(D1, D2),
    Sq = Sq0 + e3d_vec:dot(D1, D1),
    face_dots_and_sqlens_2(D2, Vs, More, Dot, Sq);
face_dots_and_sqlens_2(D1, Vs, [Va,Vb|_], Dot, Sq) ->
    face_dots_and_sqlens_2(D1, Vs++[Va,Vb], [], Dot, Sq);
face_dots_and_sqlens_2(_D1, _Other, _More, Dot, Sq) -> {Dot,Sq}.

make_edge_template(Edge, #we{vp=Vtab,es=Etab}=We) ->
    #edge{vs=Va,ve=Vb,ltpr=LP,ltsu=LS,rtpr=RP,rtsu=RS} =
	gb_trees:get(Edge, Etab),
    VaPos = gb_trees:get(Va, Vtab),
    VbPos = gb_trees:get(Vb, Vtab),
    Vec = e3d_vec:sub(VaPos, VbPos),
    DotSum = edge_dot(LP, Vb, VbPos, Vec, We) +
	edge_dot(RS, Vb, VbPos, Vec, We) +
	edge_dot(LS, Va, VaPos, Vec, We) +
	edge_dot(RP, Va, VaPos, Vec, We),
    {0,DotSum,e3d_vec:dot(Vec, Vec)}.

edge_dot(Edge, V, Pos, Vec, #we{es=Etab}=We) ->
    Rec = gb_trees:get(Edge, Etab),
    OtherPos = wings_vertex:other_pos(V, Rec, We),
    ThisVec = e3d_vec:sub(Pos, OtherPos),
    abs(e3d_vec:dot(ThisVec, Vec)).

make_vertex_template(V, #we{vp=Vtab}=We) ->
    Center = gb_trees:get(V, Vtab),
    Vecs = wings_vertex:fold(
	     fun(_, _, Rec, Acc0) ->
		     Pos = wings_vertex:other_pos(V, Rec, Vtab),
		     Vec = e3d_vec:sub(Pos, Center),
		     [Vec|Acc0]
	     end, [], V, We),
    {DotSum,SqSum} = vertex_dots_and_sqlens(Vecs, Vecs, 0, 0),
    {length(Vecs),DotSum,SqSum}.

vertex_dots_and_sqlens([VecA|[VecB|_]=T], More, Dot0, Sq0) ->
    Dot = Dot0 + abs(e3d_vec:dot(VecA, VecB)),
    Sq = Sq0 + e3d_vec:dot(VecA, VecA),
    vertex_dots_and_sqlens(T, More, Dot, Sq);
vertex_dots_and_sqlens(Vecs, [VecB|_], Dot, Sq) ->
    vertex_dots_and_sqlens(Vecs++[VecB], [], Dot, Sq);
vertex_dots_and_sqlens(_Other, _More, Dot, Sq) -> {Dot,Sq}.

rel_compare(A, B, Tresh) when abs(A) < Tresh ->
    abs(B) < Tresh;
rel_compare(A, B, Tresh) when abs(A) > abs(B) ->
    abs(A-B)/abs(A) < Tresh;
rel_compare(A, B, Tresh) ->
    abs(A-B)/abs(B) < Tresh.

%%
%% Select Random.
%%

random(Percent, #st{selmode=Mode}=St) ->
    P = Percent / 100,
    wings_sel:make(fun(_, _) -> random:uniform() < P end, Mode, St).

%%
%% Select short edges.
%%

short_edges(Ask, _St) when is_atom(Ask) ->
    Qs = [{label,"Length tolerance"},{text,1.0E-3,[{range,{1.0E-5,10.0}}]}],
    wings_ask:dialog(Ask, "Select Short Edges",
		     [{hframe,Qs}],
		     fun(Res) -> {select,{by,{short_edges,Res}}} end);
short_edges([Tolerance], St0) ->
    St = wings_sel:make(fun(Edge, We) ->
				short_edge(Tolerance, Edge, We)
			end, edge, St0),
    {save_state,St#st{selmode=edge}}.

short_edge(Tolerance, Edge, #we{es=Etab,vp=Vtab}) ->
    #edge{vs=Va,ve=Vb} = gb_trees:get(Edge, Etab),
    VaPos = gb_trees:get(Va, Vtab),
    VbPos = gb_trees:get(Vb, Vtab),
    abs(e3d_vec:dist(VaPos, VbPos)) < Tolerance.

%%
%% Select all edges between materials.
%%

material_edges(St) ->
    wings_sel:make(fun material_edges_fun/2, edge, St).

material_edges_fun(E, #we{es=Etab}=We) ->
    #edge{lf=Lf,rf=Rf} = gb_trees:get(E, Etab),
    wings_material:get(Lf, We) =/= wings_material:get(Rf, We).

%%
%% Select by numerical item id.
%%

by_id(#st{selmode=body}=St) ->
    ask([{"Object Id",1}],
	fun([Id]) ->
		valid_sel("", [{Id,gb_sets:singleton(0)}], St)
	end);
by_id(#st{selmode=vertex}=St) ->
    item_by_id("Vertex Id", St);
by_id(#st{selmode=edge}=St) ->
    item_by_id("Edge Id", St);
by_id(#st{selmode=face}=St) ->
    item_by_id("Face Id", St).

item_by_id(Prompt, #st{sel=[{Id,_}]}) ->
    ask([{Prompt,0}],
	fun([Item]) ->
		{Prompt,[{Id,gb_sets:singleton(Item)}]}
	end);
item_by_id(Prompt, #st{shapes=Shs}) ->
    case gb_trees:to_list(Shs) of
	[] -> wings_util:error("Nothing to select.");
	[{Id,_}] ->
	    ask([{Prompt,0}],
		fun([Item]) ->
			{Prompt,[{Id,gb_sets:singleton(Item)}]}
		end);
	[{Id0,_}|_] ->
	    ask([{"Object Id",Id0},
		 {Prompt,0}],
		fun([Id,Item]) ->
			{Prompt,[{Id,gb_sets:singleton(Item)}]}
		end)
    end.

sel_by_id({Prompt,Sel}, St) ->
    wings_sel:set(valid_sel(Prompt, Sel, St), St).

valid_sel(Prompt, Sel, #st{shapes=Shs,selmode=Mode}=St) ->
    case wings_sel:valid_sel(Sel, Mode, St) of
	[] ->
	    [{Id,Item0}] = Sel,
	    [Item] = gb_sets:to_list(Item0),
	    case gb_trees:is_defined(Id, Shs) of
		false ->
		    wings_util:error("The Object Id "++
				     integer_to_list(Id)++" is invalid.");
		true ->
		    wings_util:error("The "++Prompt++" "++
				     integer_to_list(Item)++" is invalid.")
	    end;
	Sel -> Sel
    end.
    
ask(Qs, Fun) ->
    wings_ask:ask("Select By Id", Qs,
		  fun(Res) ->
			  Sel = Fun(Res),
			  {select,{by,{id,Sel}}}
		  end).

%%%
%%% Select lights.
%%%

select_lights(#st{selmode=Mode,shapes=Shapes}=St) ->
    Sel = select_lights_1(gb_trees:values(Shapes), Mode),
    St#st{selmode=Mode,sel=Sel}.

select_lights_1([#we{perm=Perm}|Shs], Mode) when ?IS_NOT_SELECTABLE(Perm) ->
    select_lights_1(Shs, Mode);
select_lights_1([We|Shs], Mode) when not ?IS_LIGHT(We) ->
    select_lights_1(Shs, Mode);
select_lights_1([#we{id=Id}|Shs], body) ->
    [{Id,gb_sets:singleton(0)}|select_lights_1(Shs, body)];
select_lights_1([#we{id=Id,vp=Vtab,es=Etab,fs=Ftab}|Shs], Mode) ->
    Tab = case Mode of
	      vertex -> Vtab;
	      edge -> Etab;
	      face -> Ftab
	  end,
    Sel = gb_trees:keys(Tab),
    [{Id,gb_sets:from_ordset(Sel)}|select_lights_1(Shs, Mode)];
select_lights_1([], _) -> [].

%%%
%%% Select isolated vertices.
%%%

select_isolated(#st{shapes=Shs}=St) ->
    Sel = foldl(fun(#we{id=Id}=We, A) ->
			Isolated = gb_sets:from_list(wings_vertex:isolated(We)),
			case gb_sets:is_empty(Isolated) of
			    true -> A;
			    false -> [{Id,Isolated}|A]
			end
		end, [], gb_trees:values(Shs)),
    wings_sel:set(vertex, Sel, St).
