%%
%%  wings_rotate.erl --
%%
%%     This module implements the Rotate command.
%%
%%  Copyright (c) 2001-2011 Bjorn Gustavsson
%%
%%  See the file "license.terms" for information on usage and redistribution
%%  of this file, and for a DISCLAIMER OF ALL WARRANTIES.
%%
%%     $Id$
%%

-module(wings_rotate).
-export([setup/2,rotate/5]).
-include("wings.hrl").

-import(lists, [foldl/3]).

setup({'ASK',Ask}, St) ->
    wings:ask(Ask, St, fun setup/2);
setup({Vec,Center,Magnet}, St) ->
    setup_1(Vec, Center, Magnet, St);
setup({Vec,Center}, St) ->
    setup_1(Vec, Center, none, St).

setup_1(Axis, center, Magnet, St)
  when Axis == last_axis; Axis == default_axis ->
    {Point,Vec} = wings_pref:get_value(Axis),
    setup_2(Vec, Point, Magnet, St);
setup_1(Axis, Point, Magnet, St)
  when Axis == last_axis; Axis == default_axis ->
    {_,Vec} = wings_pref:get_value(Axis),
    setup_2(Vec, Point, Magnet, St);
setup_1(Vec, Center, Magnet, St) ->
    setup_2(wings_util:make_vector(Vec), Center, Magnet, St).

setup_2(Vec, center, Magnet, #st{selmode=vertex}=St) ->
    VsPs = wings_sel:fold(
	     fun(Vs, #we{vp=Vtab}, Acc) ->
		     Pos = [array:get(V, Vtab) || V <- gb_sets:to_list(Vs)],
		     [Pos|Acc]
	     end, [], St),
    Center = e3d_vec:average(lists:append(VsPs)),
    setup_2(Vec, Center, Magnet, St);
setup_2(Vec, Center, Magnet, #st{selmode=vertex}=St) ->
    Tvs = wings_sel:fold(
	    fun(Vs, We, Acc) ->
		    rotate(Vec, Center, Magnet, gb_sets:to_list(Vs), We, Acc)
	    end, [], St),
    init_drag(Tvs, Vec, Magnet, St);
setup_2(Vec, Center, Magnet, #st{selmode=edge}=St) ->
    Tvs = wings_sel:fold(
	    fun(Edges, We, Acc) ->
		    edges_to_vertices(Vec, Center, Magnet, Edges, We, Acc)
	    end, [], St),
    init_drag(Tvs, Vec, Magnet, St);
setup_2(Vec, Center, Magnet, #st{selmode=face}=St) ->
    Tvs = wings_sel:fold(
	    fun(Faces, We, Acc) ->
		    faces_to_vertices(Vec, Center, Magnet, Faces, We, Acc)
	    end, [], St),
    init_drag(Tvs, Vec, Magnet, St);
setup_2(Vec, Center, _Magnet, #st{selmode=body}=St) ->
    Tvs = wings_sel:fold(
	    fun(_, #we{id=Id}=We, Acc) ->
		    [{Id,body_rotate(Vec, Center, We)}|Acc]
	    end, [], St),
    init_drag({matrix,Tvs}, Vec, none, St).

init_drag(Tvs, Vec, Magnet, St) ->
    Flags = wings_magnet:flags(Magnet, flags(Vec)),
    wings_drag:setup(Tvs, [angle|magnet_unit(Magnet)], Flags, St).

flags(free) -> [screen_relative];
flags(_) -> [].

magnet_unit(none) -> [];
magnet_unit(_) -> [falloff].

%%
%% Conversion of edge selections to vertices.
%%

edges_to_vertices(Vec, center, none, Edges0, We, Acc) ->
    foldl(fun(Edges, A) ->
		  edges_to_vertices_1(Vec, center, none, Edges, We, A)
	  end, Acc, wings_sel:edge_regions(Edges0, We));
edges_to_vertices(Vec, center, Magnet, Edges0, We, Acc) ->
    case wings_sel:edge_regions(Edges0, We) of
	[Edges] ->
	    edges_to_vertices_1(Vec, center, Magnet, Edges, We, Acc);
	_Other ->
	    wings_u:error_msg(?__(1,
			      "Magnet Rotate on multiple edge regions "
			      "requires an explicit rotate origin."))
    end;
edges_to_vertices(Vec, Center, Magnet, Edges, We, Acc) ->
    edges_to_vertices_1(Vec, Center, Magnet, Edges, We, Acc).

edges_to_vertices_1(normal, Center, Magnet, Es, #we{es=Etab}=We, Acc) ->
    Ns = gb_sets:fold(
	   fun(Edge, A) ->
		   #edge{lf=Lf,rf=Rf} = array:get(Edge, Etab),
		   [wings_face:normal(Lf, We),
		    wings_face:normal(Rf, We)|A]
	   end, [], Es),
    Vec = e3d_vec:norm(e3d_vec:add(Ns)),
    edges_to_vertices_1(Vec, Center, Magnet, Es, We, Acc);
edges_to_vertices_1(Vec, Center, Magnet, Es, We, Acc) ->
    Vs = wings_edge:to_vertices(Es, We),
    rotate(Vec, Center, Magnet, Vs, We, Acc).
 
%%
%% Conversion of face selections to vertices.
%%

faces_to_vertices(Vec, center, none, Faces0, We, Acc) ->
    foldl(fun(Faces, A) ->
		  faces_to_vertices_1(Vec, center, none, Faces, We, A)
	  end, Acc, wings_sel:strict_face_regions(Faces0, We));
faces_to_vertices(Vec, center, Magnet, Faces0, We, Acc) ->
    case wings_sel:strict_face_regions(Faces0, We) of
	[Faces] ->
	    faces_to_vertices_1(Vec, center, Magnet, Faces, We, Acc);
	_Other ->
	    wings_u:error_msg(?__(1,"Magnet Rotate on multiple face regions requires an explicit rotate origin."))
    end;
faces_to_vertices(Vec, Center, Magnet, Faces, We, Acc) ->
    faces_to_vertices_1(Vec, Center, Magnet, Faces, We, Acc).

faces_to_vertices_1(normal, Center, Magnet, Faces, We, Acc) ->
    Ns = gb_sets:fold(
	   fun(Face, N0) ->
		   [wings_face:normal(Face, We)|N0]
	   end, [], Faces),
    Vec = e3d_vec:norm(e3d_vec:add(Ns)),
    Vs = wings_face:to_vertices(Faces, We),
    rotate(Vec, Center, Magnet, Vs, We, Acc);
faces_to_vertices_1(Vec, Center, Magnet, Faces, We, Acc) ->
    Vs = wings_face:to_vertices(Faces, We),
    rotate(Vec, Center, Magnet, Vs, We, Acc).

%%
%% Conversion of body selections (entire objects) to vertices.
%%

body_rotate(Vec, center, We) ->
    body_rotate(Vec, wings_vertex:center(We), Vec);
body_rotate(free, {Cx,Cy,Cz}, _We) ->
    fun(Matrix0, [Angle]) when is_float(Angle) ->
	    Vec = view_vector(),
	    M0 = e3d_mat:mul(Matrix0, e3d_mat:translate(Cx, Cy, Cz)),
	    M = e3d_mat:mul(M0, e3d_mat:rotate(Angle, Vec)),
	    e3d_mat:mul(M, e3d_mat:translate(-Cx, -Cy, -Cz))
    end;
body_rotate(Vec, {Cx,Cy,Cz}, _We) ->
    fun(Matrix0, [Angle]) when is_float(Angle) ->
	    M0 = e3d_mat:mul(Matrix0, e3d_mat:translate(Cx, Cy, Cz)),
	    M = e3d_mat:mul(M0, e3d_mat:rotate(Angle, Vec)),
	    e3d_mat:mul(M, e3d_mat:translate(-Cx, -Cy, -Cz))
    end.

%%%
%%% Setup rotation.
%%%

rotate(Vec, Center, Vs, We, Acc) ->
    rotate(Vec, Center, none, Vs, We, Acc).

rotate(Vec, center, Magnet, Vs, We, Acc) ->
    Center = wings_vertex:center(Vs, We),
    rotate(Vec, Center, Magnet, Vs, We, Acc);
rotate(free, Center, Magnet, Vs, We, Acc) ->
    rotate(view_vector(), Center, Magnet, Vs, We, Acc);
rotate(Vec, Center, Magnet, Vs, #we{id=Id}=We, Acc) ->
    VsPos = wings_util:add_vpos(Vs, We),
    Fun = rotate_fun(Center, VsPos, Vec),
    magnet(Vec, Center, Magnet, Vs, We, {Id,{Vs,Fun}}, Acc).

rotate_fun(Center, VsPos, Axis) ->
    fun(view_changed, NewWe) ->
	    NewAxis = view_vector(),
	    NewVsPos = wings_util:update_vpos(VsPos, NewWe),
	    rotate_fun(Center, NewVsPos, NewAxis);
       ([Dx], A) -> do_rotate(Center, Axis, Dx, VsPos, A)
    end.

do_rotate({Cx,Cy,Cz}, Axis, Angle, VsPos, Acc0) ->
    M0 = e3d_mat:translate(Cx, Cy, Cz),
    M1 = e3d_mat:mul(M0, e3d_mat:rotate(Angle, Axis)),
    M = e3d_mat:mul(M1, e3d_mat:translate(-Cx, -Cy, -Cz)),
    foldl(fun({V,Pos0}, Acc) ->
		  Pos = e3d_mat:mul_point(M, Pos0),
		  [{V,Pos}|Acc]
	  end, Acc0, VsPos).

view_vector() ->
    #view{azimuth=Az,elevation=El} = wings_view:current(),
    M0 = e3d_mat:rotate(-Az, {0.0,1.0,0.0}),
    M = e3d_mat:mul(M0, e3d_mat:rotate(-El, {1.0,0.0,0.0})),
    e3d_mat:mul_point(M, {0.0,0.0,1.0}).

magnet(_Vec, _Center, none, _Vs, _We, Tv, Acc) -> [Tv|Acc];
magnet(Vec, Center, Magnet0, Vs, #we{id=Id}=We, _, Acc) ->
    {VsInf0,Magnet,Affected} = wings_magnet:setup(Magnet0, Vs, We),
    VsInf = pre_transform(Center, VsInf0),
    [{Id,{Affected,magnet_rotate_fun(Vec, Center, VsInf, Magnet)}}|Acc].

pre_transform(Center, VsInf) ->
    wings_magnet:transform(fun(Pos) ->
				   e3d_vec:sub(Pos, Center)
			   end, VsInf).

magnet_rotate_fun(Axis0, Center, VsInf0, {_,R}=Magnet0) ->
    fun(new_falloff, Falloff) ->
	    VsInf = wings_magnet:recalc(Falloff, VsInf0, Magnet0),
	    magnet_rotate_fun(Axis0, Center, VsInf, Magnet0);
       (new_mode_data, {Type,Falloff}) ->
	    Magnet = {Type,R},
	    VsInf = wings_magnet:recalc(Falloff, VsInf0, Magnet),
	    magnet_rotate_fun(Axis0, Center, VsInf, Magnet);
       (view_changed, NewWe) ->
	    Axis = view_vector(),
	    VsInf1 = wings_util:update_vpos(VsInf0, NewWe),
	    VsInf = pre_transform(Center, VsInf1),
	    magnet_rotate_fun(Axis, Center, VsInf, Magnet0);
       ([Dx|_], A) ->
	    magnet_rotate(Axis0, Center, Dx, VsInf0, A)
    end.
    
magnet_rotate(Axis, {Cx,Cy,Cz}, Angle, VsPos, Acc0) ->
    foldl(fun({V,Pos0,_,Inf}, Acc) ->
		  {X,Y,Z} = e3d_mat:mul_point(e3d_mat:rotate(Inf*Angle, Axis), Pos0),
		  Pos = wings_util:share(X+Cx, Y+Cy, Z+Cz),
		  [{V,Pos}|Acc]
	  end, Acc0, VsPos).
