1%%
2%%	wpc_rotate_unconstrained.erl
3%%
4%%	--	Unconstrained Rotation
5%%		(also known as trackball rotation)
6%%
7%%	Copyright (c) Anna Celarek 2010-2011
8%%
9%%  See the file "license.terms" for information on usage and redistribution
10%%  of this file, and for a DISCLAIMER OF ALL WARRANTIES.
11%%
12
13-module(wpc_rotate_unconstrained).
14-export([init/0, menu/2, command/2]).
15-include_lib("src/wings.hrl").
16-import(lists, [foldl/3]).
17
18init() ->
19    true.
20
21%% adding the function name into rotate menu
22menu({Mode, rotate}, Menu) when Mode =:= vertex; Mode =:= edge; Mode =:= face; Mode =:= body ->
23    [Menu|[separator,orbitrot_menu(Mode)]];
24menu(_, Menu) -> Menu.
25
26%% function name and help (bottom line)
27orbitrot_menu(Mode) ->
28    Help = {?__(2,"Rotate freely around selection "), [],	%% lmb help, [] mmb none,
29            ?__(3,"Pick rotation center")},
30    {?__(1,"Unconstrained"),	% function name visible in menu
31    {rotate_unconstrained,orbitrot_options(Mode, Help)}, magnet_possible(Mode)}.
32
33%% magnet (doesn't work yet
34magnet_possible(body) -> [];
35magnet_possible(_) -> [magnet].
36
37%% lmb, rmb options (have to match with the things after command)
38orbitrot_options(body, Help) ->
39    fun
40      (help, _Ns) -> Help;
41      (1,_Ns) -> {body,{rotate, {unconstrained, lmb}}};
42      (3,_Ns) -> 	%'ASK' is an atom too
43          {body,{rotate, {unconstrained,{rmb,{'ASK',{[center],[],[]}}}}}};
44      (_,_)   -> ignore
45    end;
46orbitrot_options(Mode, Help) ->
47    fun
48      (help, _Ns) -> Help;
49      (1,_Ns) ->
50          {Mode,{rotate, {unconstrained,{lmb,{'ASK',{[],[],[magnet]}}}}}};
51      (3,_Ns) ->    % [magnet]={magnet,Type,Route,Point}
52          {Mode,{rotate, {unconstrained,{rmb,{'ASK',{[center],[],[magnet]}}}}}};
53      (_,_)   -> ignore
54    end.
55
56%%%% COMMANDS
57%% called from menu (see above) or shortkey
58
59% no center, no magnet
60command({_,{rotate, {unconstrained,{lmb, {'ASK',{[],_,_}}}}}},St) ->
61    no_center_setup(none, St);
62% no center, magnet
63command({_,{rotate, {unconstrained,{lmb, {'ASK',Ask}}}}},St) ->
64    wings:ask(selection_ask(Ask), St, fun no_center_setup/2);
65% no center
66command({_,{rotate, {unconstrained,{lmb, Data}}}},St) ->
67    no_center_setup(Data, St);
68% body (has no asks at lmb), no center
69command({_,{rotate, {unconstrained, lmb}}}, St) ->
70    no_center_setup(none, St);
71% chose center (and maybe magnet)
72command({_,{rotate, {unconstrained,{rmb,{'ASK',Ask}}}}},St) ->
73    wings:ask(selection_ask(Ask), St, fun center_setup/2);
74% Data = {Center, (Magnet)}
75command({_,{rotate, {unconstrained,{rmb, Data}}}},St) ->
76    center_setup(Data,St);
77command(_,_) ->
78    next.
79
80%%%% Asks
81selection_ask({Asks,_,_}) ->
82    selection_ask(Asks);
83selection_ask(Asks) ->
84    Ask = selection_ask(Asks,[]),
85    {Ask,[],[],[vertex, edge, face]}.
86selection_ask([],Ask) -> lists:reverse(Ask);
87selection_ask([center|T],Ask) ->
88    Desc = ?__(1,"Pick center of rotation"),
89    selection_ask(T,[{point,Desc}|Ask]);
90selection_ask([magnet|T],Ask) ->
91    selection_ask(T,[magnet|Ask]).
92
93%%%% Setup
94no_center_setup(_, #st{selmode=body}=St) ->
95    Center = no_center,
96    finish_setup(Center, body, St);
97no_center_setup(Mag, St) ->
98    Center = no_center,
99    finish_setup(Center, Mag, St).
100
101center_setup({Center,_Magnet}, #st{selmode=body}=St) ->
102% In case uncon rot + mag is repeated in body mode after being used in v, e, f.
103    finish_setup(Center, body, St);
104center_setup({X,Y,Z}, #st{selmode=body}=St) ->
105    Center = {X,Y,Z},
106    finish_setup(Center, body, St);
107center_setup({X,Y,Z}, St) ->
108    Center = {X,Y,Z},
109    finish_setup(Center, none, St);
110center_setup({Center, Mag}, St) ->
111    finish_setup(Center, Mag, St).
112
113%%%% FOLDING AND DRAGGING
114finish_setup(Center, {}, St) -> finish_setup(Center, none, St);
115finish_setup(Center0, body, St) ->
116    %% body (uses whole matrix instead of single vert coordinates)
117    wings_drag:matrix(
118      fun(We) ->
119              Center = find_center(Center0, body, We),
120              rotate(body, Center)
121      end, [angle,angle], [screen_relative], St);
122finish_setup(Center0, Mag, #st{selmode=Mode}=St) ->
123    Type = magnet_type(Mag),
124    State = {none,Type,{reciprocal,false}},
125    CamX = view_cam(x),
126    CamY = view_cam(y),
127    Units = [angle, angle| magnet_unit(Mag)],
128    wings_drag:fold(
129      fun(Vs0, We) ->
130              Vs = conversion(Mode, Vs0, We),
131              Center = find_center(Center0, Vs, We),
132              finish_setup_1(Vs, We, CamX, CamY, Center, Mag, State)
133      end, Units, flags(State), St).
134
135%%%% vertex list
136finish_setup_1(Vs, We,CamX, CamY, Center, none, State) ->
137    VsPos = wings_util:add_vpos(Vs, We),
138    {Vs,rotate_fun(CamX, CamY, Center, VsPos, none, State)};
139finish_setup_1(Vs, We, CamX, CamY, Center, Mag0, State) ->
140    {VsInf,Magnet,Affected} = wings_magnet:setup(Mag0, Vs, We),
141    {Affected,rotate_fun(CamX, CamY, Center, VsInf, Magnet, State)}.
142
143%%%% Catch view rotations, magnet option changes etc
144rotate_fun(CamX,CamY,Center,VsPos,none,State) ->		% no mag
145    fun
146        (view_changed, NewWe) ->
147            NewCamX = view_cam(x),
148            NewCamY = view_cam(y),
149            NewVsPos = wings_util:update_vpos(VsPos, NewWe),
150            rotate_fun(NewCamX,NewCamY,Center,NewVsPos,none,State);
151
152        ([Dx,Dy|_], A) ->
153            foldl(fun({V,Vpos}, VsAcc) ->
154                [{V,rotate(Vpos,CamX,CamY,Center,State,Dx,Dy)}|VsAcc]
155                end,
156                A, VsPos)
157        end;
158
159rotate_fun(CamX,CamY,Center,VsInf0,{_,R}=Magnet0,State0) ->	% mag
160    fun
161        (new_falloff, Falloff) ->
162            VsInf = wings_magnet:recalc(Falloff, VsInf0, Magnet0),
163            rotate_fun(CamX,CamY,Center,VsInf,Magnet0,State0);
164        (new_mode_data, {State, Falloff}) ->
165            {_,MagType,_} = State,
166            Magnet = {MagType,R},
167            VsInf = wings_magnet:recalc(Falloff, VsInf0, Magnet),
168            rotate_fun(CamX,CamY,Center,VsInf,Magnet,State);
169        (view_changed, NewWe) ->
170            NewCamX = view_cam(x),
171            NewCamY = view_cam(y),
172            NewVsPos = wings_util:update_vpos(VsInf0, NewWe),
173            rotate_fun(NewCamX,NewCamY,Center,NewVsPos,Magnet0,State0);
174        ([Dx,Dy|_], A)  ->
175            foldl(fun({V,Vpos,_,Inf}, Acc) ->
176                [{V,rotate(Vpos,CamX,CamY,Center,State0,Dx*Inf, Dy*Inf)}|Acc]
177                end,
178                A, VsInf0)
179        end.
180
181%%%% ROTATIONS
182%%%% (matrix or vector operations)
183
184rotate(Vpos,_CamX,_CamY,_Center,_, 0.0,0.0) ->
185    Vpos;
186
187rotate(Vpos,CamX,CamY,{Cx,Cy,Cz},_,Dx, Dy) ->		% vertex rotation
188    M0 = e3d_mat:translate(Cx, Cy, Cz),
189    Angle =math:sqrt(Dx*Dx + Dy*Dy),				% mouse radius
190    Vx = e3d_vec:mul(CamX, Dy),						% horizontal
191    Vy = e3d_vec:mul(CamY, Dx),						% vertical
192    Axis = e3d_vec:norm(e3d_vec:sub(Vy, Vx)),		% vector _|_ to mouse direction
193    M1 = e3d_mat:mul(M0, e3d_mat:rotate(Angle, Axis)),
194    M = e3d_mat:mul(M1, e3d_mat:translate(-Cx, -Cy, -Cz)),
195    e3d_mat:mul_point(M, Vpos).
196
197rotate(body, {Cx, Cy, Cz}) ->						% body (matrices)
198    fun(Matrix, [Dx, Dy]) ->
199        CamX = view_cam(x),
200        CamY = view_cam(y),
201        M0 = e3d_mat:mul(Matrix, e3d_mat:translate(Cx, Cy, Cz)),
202        Angle =math:sqrt(Dx*Dx + Dy*Dy),
203        Vx = e3d_vec:mul(CamX, Dy),
204        Vy = e3d_vec:mul(CamY, Dx),
205        Axis = e3d_vec:norm(e3d_vec:sub(Vy, Vx)),
206        M1 = e3d_mat:mul(M0, e3d_mat:rotate(Angle, Axis)),
207        e3d_mat:mul(M1, e3d_mat:translate(-Cx, -Cy, -Cz))
208    end.
209
210%%%% UTILITIES
211
212%%%% Screen coordinate vector
213view_cam(Dir) ->		% Dir: x=right, y=up, z=towards camera
214    #view{azimuth=Az,elevation=El} = wings_view:current(),
215    M0 = e3d_mat:rotate(-Az, {0.0,1.0,0.0}),
216    M = e3d_mat:mul(M0, e3d_mat:rotate(-El, {1.0,0.0,0.0})),
217    e3d_mat:mul_point(M, wings_util:make_vector(Dir)).
218    %% the last vector does {right, up, towards camera} in screen coordinates
219
220%%%% Convert selection to vertex list
221conversion(Mode,Items,We) when not is_list(Items) ->
222    conversion(Mode, gb_sets:to_list(Items), We);
223conversion(vertex,Items,_) ->
224    Items;
225conversion(edge,Items,We) ->
226    wings_edge:to_vertices(Items, We);
227conversion(face,Items,We) ->
228    wings_face:to_vertices(Items,We).
229
230%%%% Selection center
231find_center(no_center, body, We) ->
232    wings_vertex:center(We);
233find_center(no_center, Vs, We) ->
234    wings_vertex:center(Vs, We);
235find_center(Center, _,_) ->
236    Center.
237
238%%%% Flags for the drag function
239flags({_,none,{_,_}}) -> [screen_relative];
240flags(State) ->
241    [{mode,{magnet_modes(),State}}| [screen_relative]].
242
243magnet_type(none) -> none;
244magnet_type({_,Type,_,_}) -> Type.
245
246%%%% magnet options (bottom line)
247magnet_modes() ->
248    fun
249        (help, State) ->
250            {_,Type,{_,_}}=State,
251            wings_magnet:drag_help(Type);
252        ({key, K},{none,_Type,_Recip}) when K =:= $1; K =:= $2; K =:= $3; K =:= $4 ->
253            {none,wings_magnet:hotkey(K),_Recip};
254        (done,{none,Type,_Recip}) ->
255            wings_pref:set_value(magnet_type, Type);
256        (_,_) -> none
257    end.
258
259magnet_unit(none) -> [];
260magnet_unit(_) -> [falloff].
261