1%%
2%%  wpc_absolute_scale.erl --
3%%
4%%     Plug-in for scale -> absolute
5%%
6%%  Copyright (c) 2006-2011 Andrzej Giniewicz
7%%
8%%  See the file "license.terms" for information on usage and redistribution
9%%  of this file, and for a DISCLAIMER OF ALL WARRANTIES.
10%%
11%%     $Id$
12%%
13-module(wpc_absolute_scale).
14
15-include_lib("src/wings.hrl").
16
17-export([init/0,menu/2,command/2]).
18
19-define(EPSILON,0.0000005).
20
21%%%
22%%% plugin interface
23%%%
24
25init() -> true.
26
27menu({Mode},Menu) when Mode == vertex; Mode == edge; Mode == face; Mode == body ->
28    parse(Menu, Mode);
29menu(_,Menu) ->
30    Menu.
31
32parse(Menu, Mode) ->
33    lists:reverse(parse(Menu, Mode, [], false)).
34
35parse([], _, NewMenu, true) ->
36    NewMenu;
37parse([], Mode, NewMenu, false) ->
38    [draw(all, Mode), separator|NewMenu];
39parse([{Name, {absolute, Commands}}|Rest], Mode, NewMenu, false) ->
40    parse(Rest, Mode, [{Name, {absolute, Commands++draw(menu, Mode)}}|NewMenu], true);
41parse([separator|Rest], Mode, NewMenu, false) ->
42    parse(Rest, Mode, [separator, draw(all, Mode)|NewMenu], true);
43parse([Elem|Rest], Mode, NewMenu, Found) ->
44    parse(Rest, Mode, [Elem|NewMenu], Found).
45
46draw(all, Mode) ->
47    {?__(1, "Absolute Commands"), {absolute, draw(menu, Mode)}};
48draw(menu, Mode) ->
49    [{?__(2,"Scale"),scale_fun(Mode),
50     {?__(3,"Scale to exact size in absolute coordinates"),
51      ?__(4,"Scale to target size"),
52      ?__(5,"Scale with center picking")},[]}].
53
54scale_fun(Mode) ->
55    fun(1, _Ns) ->
56	    {Mode,{absolute,scale}};
57       (2, _Ns) ->
58	    {Mode,{absolute,ctscale}};
59       (3, _Ns) ->
60	    {Mode,{absolute,cscale}};
61       (_, _) -> ignore
62    end.
63
64command({_,{absolute,Mode}},St) when Mode == scale; Mode == ctscale; Mode == cscale ->
65    case Mode of
66        scale ->
67            scale(St);
68        ctscale ->
69            wings:ask(selection_ask([center,target]), St, fun ctscale/2);
70        cscale ->
71            wings:ask(selection_ask([center]), St, fun cscale/2)
72    end;
73command(_,_) -> next.
74
75scale(St) ->
76    Options = extract([], St),
77    draw_window(Options, St).
78
79ctscale({Center, TA, TB}, St) ->
80    Options = extract([{center,Center},{ta,TA},{tb,TB}], St),
81    draw_window(Options, St).
82
83cscale(Center, St) ->
84    Options = extract([{center,Center}], St),
85    draw_window(Options, St).
86
87%%
88%% extract(State)
89%%
90%% functions that extracts all needed information from state
91%%  it returns {Options,Selection}, where options is:
92%%   {{center,{CX,CY,CZ}},{size,{SX,SY,SZ}},{scalewhole,WholeObject},{oneobject,OneObject}}
93%%  (return values: {{atom,{float,float,float}},{atom,{float,float,float}},{atom,atom(always/never/ask)},{atom,bool}})
94%%
95
96extract(Options, St) ->
97    Center = wings_sel:center_vs(St),
98    WholeObject = check_whole_obj(St),
99    BB = case check_whole_obj(St) of
100             always -> whole_bbox(St);
101             _ -> wings_sel:bounding_box(St)
102         end,
103    Size = bb2size(BB),
104    TA = lookup(ta, Options, {0.0, 0.0, 0.0}),
105    TB = lookup(tb, Options, Size),
106    [{TX1,TY1,TZ1},{TX2,TY2,TZ2}] = TBB = normalizeBB([TA,TB]),
107    NCenter = lookup(center, Options, Center),
108    OneObject = check_single_obj(St),
109    SugSize = {abs(TX1-TX2),abs(TY1-TY2),abs(TZ1-TZ2)},
110    SugCenter = case lookup(ta, Options, false) of
111                    false -> none;
112                    _ -> getSuggestedCenter(BB, TBB)
113                end,
114    {{center,NCenter},{suggestcenter,SugCenter},{size,Size},
115     {suggestsize,SugSize},{scalewhole,WholeObject},{oneobject,OneObject}}.
116
117whole_bbox(St) ->
118    MF = fun(_, We) -> wings_vertex:bounding_box(We) end,
119    RF = fun(W, []) -> W end,
120    wings_sel:dfold(MF, RF, [], St).
121
122lookup(Key, List, Default) ->
123   case lists:keyfind(Key, 1, List) of
124       {_,Value} -> Value;
125       false -> Default
126   end.
127
128normalizeBB([{AX1,AY1,AZ1},{AX2,AY2,AZ2}]) ->
129    [{min(AX1,AX2), min(AY1,AY2), min(AZ1, AZ2)},
130     {max(AX1,AX2), max(AY1,AY2), max(AZ1, AZ2)}].
131
132getSuggestedCenter([{AX1,AY1,AZ1},{AX2,AY2,AZ2}],
133                   [{BX1,BY1,BZ1},{BX2,BY2,BZ2}]) ->
134    {getC(AX1,AX2,BX1,BX2),
135     getC(AY1,AY2,BY1,BY2),
136     getC(AZ1,AZ2,BZ1,BZ2)}.
137
138getC(A1, A2, B1, B2) when A1 =/= A2, B1 =/= B2, ((A1 - A2)-(B1 - B2))=/=0.0 ->
139    (B1*A2-B2*A1)/((A2-A1) - (B2-B1));
140getC(A1, A2, _, _) -> (A1+A2)/2.
141
142bb2size([LL,UR]) ->
143    e3d_vec:sub(UR, LL).
144
145check_whole_obj(#st{selmode=Mode}=St0) ->
146    MF = fun(Items, We) ->
147                 Vs = wings_sel:to_vertices(Mode, Items, We),
148                 case {Vs,wings_we:visible_vs(We)} of
149                     {[_],_} -> always;
150                     {Vs,Vs} -> never;
151                     {_,_} -> ask
152                 end
153         end,
154    RF = fun(When, never) -> When;
155            (_, _) -> ask
156         end,
157    wings_sel:dfold(MF, RF, never, St0).
158
159check_single_obj(#st{sel=[_]}) -> true;
160check_single_obj(_) -> false.
161
162selection_ask(Asks) ->
163    Ask = selection_ask(Asks,[]),
164    {Ask,[],[],[vertex, edge, face, body]}.
165
166selection_ask([],Ask) -> lists:reverse(Ask);
167selection_ask([center|Rest], Ask) ->
168    Desc = ?__(1,"Select scale center"),
169    selection_ask(Rest, [{point,Desc}|Ask]);
170selection_ask([target|Rest], Ask) ->
171    Desc1 = ?__(2,"Select target size - base point"),
172    Desc2 = ?__(3,"Select target size - range point"),
173    selection_ask(Rest, [{point,Desc2},{point,Desc1}|Ask]).
174
175%%
176%% draw_window(Options,Selection,State)
177%%
178%% functions that draws interface and translates entered options for further processing
179%%  and calls do_scale(ProcessedOptions,Selection,State)
180%%
181
182draw_window({{_,{CX,CY,CZ}}, {_, SugCenter}, {_,{SX,SY,SZ}=Size}, {_, {SugX, SugY, SugZ}}, {_,Whole}, {_,Single}}, St) ->
183    Frame1 = [{hframe,
184	       [draw_window1(size, {{SX,SY,SZ},{SugX,SugY,SugZ}}),
185		draw_window1(aspect, {SX,SY,SZ}),
186		draw_window1(center, {CX,CY,CZ})]}],
187    Frame2 = if
188		 Whole == ask -> [draw_window1(whole, Single)];
189		 true -> []
190	     end,
191    Frame3 = if
192		 SugCenter == none -> [];
193		 true -> [draw_window1(sugc, true)]
194	     end,
195    Frame = [{vframe, Frame1 ++ Frame2 ++ Frame3}],
196    Name = draw_window1(name,default),
197    F = fun({dialog_preview,Scale}) ->
198                {preview,St,translate(Scale,SugCenter, Size, St)};
199           (cancel) ->
200                St;
201           (Scale) ->
202                {commit,St,translate(Scale, SugCenter, Size, St)}
203        end,
204    wings_dialog:dialog(Name, {preview,Frame}, F).
205
206draw_window1(name,_) ->
207    ?__(1,"Absolute scale options");
208draw_window1(size, {{X,Y,Z},{SugX,SugY,SugZ}}) ->
209    {vframe,[
210        {hframe,[{label,?__(2,"Set new size")++":"}]},
211        {label_column,[
212            {"X:",{text,SugX,[{key,sx},disable(X==0.0)]}},
213            {"Y:",{text,SugY,[{key,sy},disable(Y==0.0)]}},
214            {"Z:",{text,SugZ,[{key,sz},disable(Z==0.0)]}}
215        ]}
216    ]};
217draw_window1(center,{X,Y,Z}) ->
218    {vframe,[
219        {hframe,[{label,?__(3,"Set scale center")++":"}]},
220        {label_column,[
221             {"X:",{text,X,[{key,cx}]}},
222             {"Y:",{text,Y,[{key,cy}]}},
223             {"Z:",{text,Z,[{key,cz}]}}
224        ]}
225    ]};
226draw_window1(aspect,{X,Y,Z}) ->
227    {vframe,[
228        {hframe,[{label,"   " ++ ?__(6,"Link")++":"}]},
229        {label_column,[
230            {" ",{hframe,[{"",false,[{key,ax},disable(X==0.0)]}],[{border, 3}]}},
231            {" ",{hframe,[{"",false,[{key,ay},disable(Y==0.0)]}],[{border, 3}]}},
232            {" ",{hframe,[{"",false,[{key,az},disable(Z==0.0)]}],[{border, 3}]}}
233        ]}
234    ]};
235draw_window1(whole,true) ->
236    {?__(4,"Scale whole object"),false,[{key,whole}]};
237draw_window1(whole,false) ->
238    {?__(5,"Scale whole objects"),false,[{key,whole}]};
239draw_window1(sugc,_) ->
240    {?__(7,"Fit selection to target"),false,
241     [{key,sugc}, {hook, fun disable/3}]}.
242
243disable(sugc, Bool, Store) ->
244    _ = [wings_dialog:enable(Key, not Bool, Store) || Key <- [cx,cy,cz]].
245
246disable(Bool) ->
247    {hook, fun(Key, _, Store) ->
248		   wings_dialog:enable(Key, not Bool, Store)
249	   end}.
250
251checkChained(Data) ->
252    [{_,Factor}|_]=lists:keysort(1,checkChained(Data, [{0.0,1.0}])),
253    Factor.
254checkChained([], List) -> List;
255checkChained([{SX,OX,true}|Rest], List) ->
256    checkChained(Rest, [{-abs(SX/OX-1.0),SX/OX}|List]);
257checkChained([_|Rest], List) ->
258    checkChained(Rest, List).
259
260translate(Options, SugCenter, {OX,OY,OZ}=Original, St) ->
261    SX = lookup(sx, Options, OX),
262    SY = lookup(sy, Options, OY),
263    SZ = lookup(sz, Options, OZ),
264    CX = lookup(cx, Options, 0.0),
265    CY = lookup(cy, Options, 0.0),
266    CZ = lookup(cz, Options, 0.0),
267    AX = lookup(ax, Options, false),
268    AY = lookup(ay, Options, false),
269    AZ = lookup(az, Options, false),
270    Whole = lookup(whole, Options, true),
271    SugC = lookup(sugc, Options, false),
272    ChainedFactor = checkChained([{SX,OX,AX},{SY,OY,AY},{SZ,OZ,AZ}]),
273    NX = if
274             AX -> OX*ChainedFactor;
275             true -> SX
276         end,
277    NY = if
278             AY -> OY*ChainedFactor;
279             true -> SY
280         end,
281    NZ = if
282             AZ -> OZ*ChainedFactor;
283             true -> SZ
284         end,
285    Center = if
286                 SugC -> SugCenter;
287                 true -> {CX,CY,CZ}
288             end,
289    do_scale([{NX,NY,NZ},Original,Center,{whole,Whole}], St).
290
291%%
292%% do_scale(Options,Selection,State)
293%%
294%% this is main absolute scale command, it returns new state.
295%%
296
297do_scale([{SX,SY,SZ},{OX,OY,OZ},{CX,CY,CZ},{_,Whole}], St) ->
298    SX2 = if
299        OX == 0.0 -> 1.0;
300        Whole andalso (OX =< ?EPSILON) andalso (SX =< ?EPSILON) -> 1.0;
301        true -> SX/OX
302    end,
303    SY2 = if
304        OY == 0.0 -> 1.0;
305        Whole andalso (OY =< ?EPSILON) andalso (SY =< ?EPSILON) -> 1.0;
306        true -> SY/OY
307    end,
308    SZ2 = if
309        OZ == 0.0 -> 1.0;
310        Whole andalso (OZ =< ?EPSILON) andalso SZ =< ?EPSILON -> 1.0;
311        true -> SZ/OZ
312    end,
313    Pre = e3d_mat:translate(CX, CY, CZ),
314    Scale = e3d_mat:scale(SX2, SY2, SZ2),
315    Post = e3d_mat:translate(-CX, -CY, -CZ),
316    Mat = e3d_mat:mul(e3d_mat:mul(Pre, Scale), Post),
317    if
318        Whole ->
319            MF = fun(_, We) ->
320                         wings_we:transform_vs(Mat, We)
321                 end,
322            wings_sel:map(MF, St);
323        true ->
324            do_scale_1(Mat, St)
325    end.
326
327do_scale_1(Mat, #st{selmode=Mode}=St) ->
328    MF = fun(Items, #we{vp=Vtab0}=We) ->
329                 Vs0 = wings_sel:to_vertices(Mode, Items, We),
330                 Vs = gb_sets:from_list(Vs0),
331                 Vtab = execute_scale(Mat, Vs, Vtab0),
332                 We#we{vp=Vtab}
333         end,
334    wings_sel:map(MF, St).
335
336execute_scale(Mat, Vset, Vtab) ->
337    execute_scale(array:sparse_size(Vtab)-1, Mat, Vset, Vtab).
338
339execute_scale(-1, _, _, Vtab) ->
340    Vtab;
341execute_scale(Vertex, Mat, Vset, Vtab0) ->
342    case array:get(Vertex, Vtab0) of
343	undefined ->
344	    execute_scale(Vertex-1, Mat, Vset, Vtab0);
345	Pos0 ->
346            Vtab = case gb_sets:is_element(Vertex, Vset) of
347		       true ->
348                           Pos = e3d_mat:mul_point(Mat, Pos0),
349			   array:set(Vertex, Pos, Vtab0);
350		       false ->
351			   Vtab0
352		   end,
353            execute_scale(Vertex-1, Mat, Vset, Vtab)
354    end.
355