1%%
2%%  wings_render.erl --
3%%
4%%     Render all objects and helpers (such as axes) in the scene.
5%%     Used for the Geometry and AutoUV windows.
6%%
7%%  Copyright (c) 2001-2011 Bjorn Gustavsson
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%%     $Id$
13%%
14
15-module(wings_render).
16-export([init/0,
17	 render/1,
18         %% polygonOffset/1, enable_lighting/1,disable_lighting/0,
19         draw_orig_sel_dl/1
20        ]).
21
22-define(NEED_OPENGL, 1).
23-include("wings.hrl").
24
25-import(lists, [foldl/3]).
26
27init() ->
28    wings_pref:set_default(multisample, true),
29    wings_pref:set_default(smooth_alpha, 0.850013),
30    init_polygon_stipple().
31
32%% render(St)
33%%  Render the entire contents of a Geometry or AutoUV window,
34%%  including groundplane and axes. Use the contents of the display
35%%  lists maintained by wings_dl. All display lists must
36%%  already exist; no display lists are created by this function.
37
38render(#st{selmode=Mode}=St) ->
39    ?CHECK_ERROR(),
40    gl:pushAttrib(?GL_CURRENT_BIT bor ?GL_ENABLE_BIT bor
41		  ?GL_TEXTURE_BIT bor ?GL_POLYGON_BIT bor
42		  ?GL_LINE_BIT bor ?GL_COLOR_BUFFER_BIT),
43    gl:enable(?GL_DEPTH_TEST),
44    gl:enable(?GL_CULL_FACE),
45    case wings_pref:get_value(multisample) of
46	true -> gl:enable(?GL_MULTISAMPLE);
47	_ -> gl:disable(?GL_MULTISAMPLE)
48    end,
49    {PM,MM,SceneLights} = wings_view:load_matrices(true),
50    View = wings_view:current(),
51    mini_axis_icon(View, MM),
52    show_saved_bb(St),
53    show_bb_center(St),
54    user_clipping_planes(on),
55    RS = render_objects(Mode, PM, MM, SceneLights),
56    user_clipping_planes(off),
57    draw_background(MM),
58    ground_and_axes(View, PM,MM, RS),
59    show_camera_image_plane(),
60    gl:popAttrib(),
61    call_post_hook(St),
62    wings_develop:gl_error_check("Rendering scene").
63
64%% draw_orig_sel_dl(Mode) -> ok.
65%%  Set up a display list to draw the original selection. To
66%%  be called from wings_vec when there is a secondary selection.
67
68-spec draw_orig_sel_dl(wings_sel:mode()) -> 'ok'.
69draw_orig_sel_dl(Mode) ->
70    wings_dl:map(fun(#dlo{orig_sel=none,sel=Dlist0}=D, _) ->
71                         Draw = draw_orig_sel_fun(Mode, Dlist0),
72                         Dlist = {call,Draw,Dlist0},
73			 D#dlo{orig_sel=Dlist}
74		 end, []).
75
76call_post_hook(St) ->
77    case wings_wm:lookup_prop(postdraw_hook) of
78        none -> ok;
79        {value,{_Id,Fun}} -> Fun(St)
80    end.
81
82polygonOffset(M) ->
83    case get(polygon_offset) of
84	undefined ->
85	    F = wings_pref:get_value(polygon_offset_f,1.0),
86	    R = wings_pref:get_value(polygon_offset_r,1.0),
87	    put(polygon_offset, {F,R}),
88	    gl:polygonOffset(M*F, M*R);
89        {F,R} ->
90	    gl:polygonOffset(M*F, M*R)
91    end.
92
93%% According Game Programming Gems chap 4.1 (By Eric Lengyel)
94%% http://www.terathon.com/books/code/Listing9.1.txt
95%% http://terathon.com/gdc07_lengyel.pdf
96%% Minimum Eps for 24b depth buffer (skip calc and use min value)
97non_polygon_offset(Offset, TPM) ->
98    ProjMat = e3d_transform:matrix(TPM),
99    Eps = 1.0 - Offset*5.0e-7,
100    E33 = element(11, ProjMat),  %% Modify entry {3,3}
101    gl:loadMatrixd(setelement(11, ProjMat, E33*Eps)).
102
103%%%
104%%% Internal functions follow.
105%%%
106
107init_polygon_stipple() ->
108    P = <<16#DD,16#DD,16#DD,16#DD,16#77,16#77,16#77,16#77,
109	 16#DD,16#DD,16#DD,16#DD,16#77,16#77,16#77,16#77,
110	 16#DD,16#DD,16#DD,16#DD,16#77,16#77,16#77,16#77,
111	 16#DD,16#DD,16#DD,16#DD,16#77,16#77,16#77,16#77,
112	 16#DD,16#DD,16#DD,16#DD,16#77,16#77,16#77,16#77,
113	 16#DD,16#DD,16#DD,16#DD,16#77,16#77,16#77,16#77,
114	 16#DD,16#DD,16#DD,16#DD,16#77,16#77,16#77,16#77,
115	 16#DD,16#DD,16#DD,16#DD,16#77,16#77,16#77,16#77,
116	 16#DD,16#DD,16#DD,16#DD,16#77,16#77,16#77,16#77,
117	 16#DD,16#DD,16#DD,16#DD,16#77,16#77,16#77,16#77,
118	 16#DD,16#DD,16#DD,16#DD,16#77,16#77,16#77,16#77,
119	 16#DD,16#DD,16#DD,16#DD,16#77,16#77,16#77,16#77,
120	 16#DD,16#DD,16#DD,16#DD,16#77,16#77,16#77,16#77,
121	 16#DD,16#DD,16#DD,16#DD,16#77,16#77,16#77,16#77,
122	 16#DD,16#DD,16#DD,16#DD,16#77,16#77,16#77,16#77,
123	 16#DD,16#DD,16#DD,16#DD,16#77,16#77,16#77,16#77>>,
124    gl:polygonStipple(P).
125
126setup_scene_lights(false, _, RS) ->
127    {false, RS};
128setup_scene_lights(true, Lights, RS0) ->
129    {Amb0, SL} = wings_light:global_lights(Lights),
130    case Amb0 of
131        [] ->
132            %% Must render all objects black first otherwise we blend with bg
133            RS1 = wings_shaders:use_prog(ambient_light, RS0),
134            RS  = wings_shaders:set_uloc(light_diffuse, {0.0,0.0,0.0,1.0}, RS1),
135            {SL, RS};
136        [Amb|RestAmb] ->
137            RS = wings_light:setup_light(Amb, RS0),
138            {RestAmb ++ SL, RS}
139    end.
140
141render_objects(Mode, PM, MM, UseSceneLights) ->
142    Dls = wings_dl:display_lists(),
143    {Open,Closed,Lights} = split_objects(Dls, [], [], []),
144    NonLights = Open ++ Closed,
145    RS0 = #{ws_eyepoint => e3d_mat:mul_point(e3d_transform:inv_matrix(MM), {0.0,0.0,0.0}),
146            view_from_world => MM},
147    RS1 = render_lights(Lights, Mode, PM, RS0),
148    {SL, RS2} = setup_scene_lights(UseSceneLights, Lights, RS1),
149    case wings_wm:get_prop(workmode) of
150	false ->
151            RS10 = case UseSceneLights of
152                       true -> render_smooth_objects(Open, Closed, ambient, RS2); %% amb pass
153                       false -> RS2
154                   end,
155            RS21 = render_smooth_objects(Open, Closed, SL, RS10),
156            RS22 = render_wire(NonLights, Mode, true, RS21),
157            render_sel_highlight(NonLights, Mode, true, PM, RS22);
158	true ->
159            RS10 = case UseSceneLights of
160                       true -> render_work_objects(Open, Closed, ambient, RS2); %% amb pass
161                       false -> RS2
162                  end,
163            RS21 = render_work_objects(Open, Closed, SL, RS10),
164            RS22 = render_wire(NonLights, Mode, false, RS21),
165            render_sel_highlight(NonLights, Mode, false, PM, RS22)
166    end.
167
168render_lights([], _Mode, _PM, RS0) ->
169    RS0;
170render_lights(Lights, Mode, PM, RS0) ->
171    RS1 = wings_shaders:use_prog(light_light, RS0),
172    gl:color4ub(255, 255, 255, 255),
173    gl:disable(?GL_CULL_FACE),
174    gl:enable(?GL_POLYGON_OFFSET_FILL),
175    polygonOffset(2.0),
176    RS2 = render_work_objects_0(Lights, false, RS1),
177    gl:color4ub(255, 255, 255, 255),
178    RS21 = wings_shaders:use_prog(0, RS2),
179    polygonOffset(0.0),
180    gl:disable(?GL_POLYGON_OFFSET_FILL),
181    AreaLights = [D || #dlo{src_we=We}=D <- Lights, ?IS_AREA_LIGHT(We)],
182    case AreaLights of
183        [] -> RS21;
184        AreaLights ->
185            RS22 = render_wire(AreaLights, Mode, true, RS21),
186            RS23 = render_sel_highlight(AreaLights, Mode, false, PM, RS22),
187            gl:color4ub(255, 255, 255, 255), %% Reset vertex col from sel_col or edge_col
188            RS23
189    end.
190
191render_work_objects(Open, Closed, SceneLights, RS0) ->
192    gl:polygonMode(?GL_FRONT_AND_BACK, ?GL_FILL),
193    gl:enable(?GL_POLYGON_OFFSET_FILL),
194    polygonOffset(2.0),
195    gl:shadeModel(?GL_SMOOTH),
196    RS1 = enable_lighting(SceneLights, RS0),
197    RS2 = render_work_objects_0(Closed, SceneLights, RS1),
198    wings_pref:get_value(show_backfaces) andalso gl:disable(?GL_CULL_FACE),
199    RS3 = render_work_objects_0(Open, SceneLights, RS2),
200    gl:enable(?GL_CULL_FACE),
201    RS = disable_lighting(RS3),
202    gl:shadeModel(?GL_FLAT),
203    RS.
204
205render_work_objects_0(Dls, [Light|SLs], RS0) ->
206    RS1 = wings_light:setup_light(Light, RS0),
207    RS = render_work_objects_1(Dls, true, RS1),
208    render_work_objects_0(Dls, SLs, RS);
209render_work_objects_0(_, [], RS0) ->
210    RS0;
211render_work_objects_0(Dls, _, RS0) ->
212    render_work_objects_1(Dls, false, RS0).
213
214
215render_work_objects_1([D|Dls], SceneLights, RS0) ->
216    RS = render_object(D, true, false, SceneLights, RS0),
217    render_work_objects_1(Dls, SceneLights, RS);
218render_work_objects_1([], _, RS) -> RS.
219
220render_smooth_objects(Open, Closed, SceneLights, RS0) ->
221    gl:shadeModel(?GL_SMOOTH),
222    gl:polygonMode(?GL_FRONT_AND_BACK, ?GL_FILL),
223    RS1 = enable_lighting(SceneLights, RS0),
224    gl:enable(?GL_POLYGON_OFFSET_FILL),
225    polygonOffset(2.0),
226    gl:enable(?GL_CULL_FACE),
227    RS2 = render_smooth_objects_0(Closed, false, SceneLights, RS1),
228    wings_pref:get_value(show_backfaces) andalso gl:disable(?GL_CULL_FACE),
229    RS3 = render_smooth_objects_0(Open, false, SceneLights, RS2),
230    case maps:get(transparent, RS3, false) of
231        true ->
232            %% Render a alpha test pass for almost opaque fragments
233            gl:enable(?GL_ALPHA_TEST),
234            gl:alphaFunc(?GL_GEQUAL, wings_pref:get_value(smooth_alpha)),
235            gl:enable(?GL_BLEND),
236            gl:blendFunc(?GL_SRC_ALPHA, ?GL_ONE_MINUS_SRC_ALPHA),
237            RS5 = render_smooth_objects_0(Open, true, false, RS3),
238            gl:enable(?GL_CULL_FACE),
239            RS6 = render_smooth_objects_0(Closed, true, false, RS5),
240            gl:disable(?GL_ALPHA_TEST),
241            gl:depthFunc(?GL_LESS),
242
243            gl:depthMask(?GL_FALSE),
244            RS9 = render_smooth_objects_0(Closed, true, SceneLights, RS6),
245            wings_pref:get_value(show_backfaces) andalso gl:disable(?GL_CULL_FACE),
246            RS10 = render_smooth_objects_0(Open, true, SceneLights, RS9),
247            RS = disable_lighting(RS10),
248            gl:enable(?GL_CULL_FACE),
249            gl:disable(?GL_BLEND),
250            gl:depthMask(?GL_TRUE),
251            RS;
252        false ->
253            RS = disable_lighting(RS3),
254            gl:enable(?GL_CULL_FACE),
255            RS
256    end.
257
258render_smooth_objects_0([], _, _, RS0) ->
259    RS0;
260render_smooth_objects_0(Dls, RenderTrans, [Light|SLs], RS0) ->
261    RS1 = wings_light:setup_light(Light, RS0),
262    RS = render_smooth_objects_1(Dls, RenderTrans, true, RS1),
263    render_smooth_objects_0(Dls, RenderTrans, SLs, RS);
264render_smooth_objects_0(_, _, [], RS0) ->
265    RS0;
266render_smooth_objects_0(Dls, RenderTrans, _, RS0) ->
267    render_smooth_objects_1(Dls, RenderTrans, false, RS0).
268
269render_smooth_objects_1([D|Dls], RenderTrans, SceneLights, RS0) ->
270    RS = render_object(D, false, RenderTrans, SceneLights, RS0),
271    render_smooth_objects_1(Dls, RenderTrans, SceneLights, RS);
272render_smooth_objects_1([], _, _, RS) -> RS.
273
274render_object(#dlo{drag={matrix,_,_,DragMat}}=D, Work, RT, SceneLights, RS0) ->
275    gl:pushMatrix(),
276    gl:multMatrixf(DragMat),
277    Prev = wings_shaders:get_state(ws_matrix, RS0),
278    RS1 = wings_shaders:set_uloc(ws_matrix, e3d_mat:compress(e3d_mat:mul(DragMat, Prev)), RS0),
279    RS2 = render_object_1(D, Work, RT, SceneLights, RS1),
280    RS  = wings_shaders:set_uloc(ws_matrix, Prev, RS2),
281    gl:popMatrix(),
282    RS;
283render_object(D, Work, RT, SceneLights, RS) ->
284    render_object_1(D, Work, RT, SceneLights, RS).
285
286render_object_1(#dlo{mirror=none}=D, Work, RenderTrans, SceneLights, RS) ->
287    render_object_2(D, Work, RenderTrans, SceneLights, RS);
288render_object_1(#dlo{mirror=MirrorMat}=DL, Work, RenderTrans, SceneLights, RS0) ->
289    RS1 = render_object_2(DL, Work, RenderTrans, SceneLights, RS0),
290    gl:frontFace(?GL_CW),
291    gl:pushMatrix(),
292    gl:multMatrixf(MirrorMat),
293    Prev = wings_shaders:get_state(ws_matrix, RS0),
294    RS2 = wings_shaders:set_uloc(ws_matrix, e3d_mat:mul(MirrorMat, Prev), RS1),
295    RS3 = render_object_2(DL, Work, RenderTrans, SceneLights, RS2),
296    RS  = wings_shaders:set_uloc(ws_matrix, Prev, RS3),
297    gl:popMatrix(),
298    gl:frontFace(?GL_CCW),
299    RS.
300
301render_object_2(D, true, _, SceneLights, RS) ->
302    render_plain(D, SceneLights, RS);
303render_object_2(D, false, RenderTrans, SceneLights, RS) ->
304    render_smooth(D, RenderTrans, SceneLights, RS).
305
306render_plain(#dlo{work=Faces,src_we=We,proxy=false}, _SceneLights, RS) ->
307    case wire(We) of
308        _ when ?IS_LIGHT(We) -> wings_dl:call(Faces, RS);
309        false -> wings_dl:call(Faces, RS);
310        true -> RS
311    end;
312render_plain(#dlo{proxy_data=PD, drag=Drag}, SceneLights, RS0) ->
313    polygonOffset(3.0),
314    Faces = wings_proxy:flat_dl(PD),
315    Key = case Drag =:= none of
316              true  -> proxy_static_opacity;
317              false -> proxy_moving_opacity
318          end,
319    if SceneLights =:= false ->
320            Blend = wings_gl:is_ext('GL_ARB_imaging') andalso wings_pref:get_value(Key),
321            case Blend of
322                false ->
323                    wings_dl:call(Faces, RS0);
324                1.0 ->
325                    wings_dl:call(Faces, RS0);
326                _ ->
327                    gl:enable(?GL_BLEND),
328		    gl:blendFunc(?GL_CONSTANT_ALPHA, ?GL_ONE_MINUS_CONSTANT_ALPHA),
329		    gl:blendColor(0.0, 0.0, 0.0, Blend),
330                    RS = wings_dl:call(Faces, RS0),
331                    gl:disable(?GL_BLEND),
332                    RS
333            end;
334       true ->
335            wings_dl:call(Faces, RS0)
336    end.
337
338render_smooth(#dlo{work=Work,smooth=Smooth0, proxy=Proxy,proxy_data=PD, transparent=Trans0},
339	      RenderTrans, _SceneLights, RS) ->
340    {Smooth, Trans} = case Proxy of
341                          false -> {Smooth0, Trans0};
342                          true -> wings_proxy:smooth_dl(PD)
343                      end,
344    case {Smooth,RenderTrans} of
345        {none,false}   ->
346            wings_dl:call(Work, RS);
347        {[Op,_],false} ->
348            RS1 = if Trans -> RS#{transparent=>Trans};
349                     true -> RS
350                  end,
351            wings_dl:call(Op, RS1);
352        {[_,Tr],true}  ->
353            wings_dl:call(Tr, RS);
354        {_,_} ->
355            RS
356    end.
357
358enable_lighting(false, #{}=RS0) ->
359    Lighting = wings_pref:get_value(number_of_lights),
360    gl:color4ub(255, 255, 255, 255), %% Needed when vertex colors are not set
361    wings_shaders:use_prog(Lighting, RS0);
362enable_lighting(ambient, RS) ->
363    RS;
364enable_lighting(_, RS) ->
365    gl:color4ub(255, 255, 255, 255),
366    gl:depthFunc(?GL_LEQUAL),
367    gl:enable(?GL_BLEND),
368    gl:blendFunc(?GL_ONE, ?GL_ONE),
369    RS.
370
371disable_lighting(RS0) ->
372    RS = wings_shaders:use_prog(0, RS0),
373    gl:depthFunc(?GL_LESS),
374    gl:disable(?GL_BLEND),
375    gl:depthMask(?GL_TRUE),
376    RS.
377
378render_wire(Dls, SelMode, false, RS0) ->
379    WOs = wings_wm:get_prop(wireframed_objects),
380    Show = wings_pref:get_value(show_edges),
381    case split_wires(Dls, WOs, Show, [], [], []) of
382        {[],[],[]} -> RS0;
383        {Ws,Os,PWs} ->
384            case {SelMode,wings_pref:get_value(edge_color)} of
385		{body,{0.0,0.0,0.0}} ->
386		    gl:color3f(0.3, 0.3, 0.3);
387		{_,EdgeColor} ->
388		    gl:color3fv(EdgeColor)
389	    end,
390            case wings_pref:get_value(aa_edges) of
391		true ->
392		    gl:enable(?GL_LINE_SMOOTH),
393		    gl:enable(?GL_BLEND),
394		    gl:blendFunc(?GL_SRC_ALPHA, ?GL_ONE_MINUS_SRC_ALPHA),
395		    gl:hint(?GL_LINE_SMOOTH_HINT, ?GL_NICEST),
396		    ok;
397		false ->
398		    ok
399	    end,
400	    gl:lineWidth(edge_width(SelMode)),
401	    gl:polygonMode(?GL_FRONT_AND_BACK, ?GL_LINE),
402            gl:depthFunc(?GL_LEQUAL),
403            wings_wm:get_prop(show_wire_backfaces)
404                andalso gl:disable(?GL_CULL_FACE),
405            Cage = fun(D,RS) -> render_wire_object(D, cage, RS) end,
406            RS1 = foldl(Cage, RS0, Ws),
407            gl:disable(?GL_CULL_FACE),
408            RS2 = foldl(Cage, RS1, Os),
409            gl:enable(?GL_CULL_FACE),
410            gl:color3fv(wings_pref:get_value(edge_color)),
411            gl:lineWidth(1.0),
412            RS3 = foldl(Cage, RS2, PWs),
413            gl:enable(?GL_CULL_FACE),
414            gl:depthFunc(?GL_LESS),
415            RS3
416    end;
417render_wire(Dls, _, true, RS0) ->
418    gl:enable(?GL_CULL_FACE),
419    gl:disable(?GL_POLYGON_OFFSET_FILL),
420    gl:depthMask(?GL_TRUE),
421    gl:shadeModel(?GL_FLAT),
422    gl:depthFunc(?GL_LEQUAL),
423    WOs = wings_wm:get_prop(wireframed_objects),
424    {Ws,[],PWs} = split_wires(Dls, WOs, false, [], [], []),
425    gl:color3fv(wings_pref:get_value(edge_color)),
426    gl:lineWidth(1.0),
427    gl:polygonMode(?GL_FRONT_AND_BACK, ?GL_LINE),
428    RS1 = foldl(fun(D,RS) -> render_wire_object(D, cage, RS) end, RS0, Ws),
429    Style = wings_pref:get_value(proxy_shaded_edge_style),
430    foldl(fun(D,RS) -> render_wire_object(D, Style, RS) end, RS1, PWs).
431
432render_wire_object(#dlo{drag={matrix,_,_,Matrix}}=D, PStyle, RS0) ->
433    gl:pushMatrix(),
434    gl:multMatrixf(Matrix),
435    RS1 = render_wire_object_1(D, PStyle, RS0),
436    gl:popMatrix(),
437    RS1;
438render_wire_object(D, PStyle, RS) ->
439    render_wire_object_1(D, PStyle, RS).
440
441render_wire_object_1(#dlo{mirror=none, edges=Edges, proxy=false}, _, RS) ->
442    wings_dl:call(Edges, RS);
443render_wire_object_1(#dlo{mirror=Matrix, edges=Edges, proxy=false}, _, RS0) ->
444    RS1 = wings_dl:call(Edges, RS0),
445    gl:frontFace(?GL_CW),
446    gl:pushMatrix(),
447    gl:multMatrixf(Matrix),
448    RS = wings_dl:call(Edges, RS1),
449    gl:popMatrix(),
450    gl:frontFace(?GL_CCW),
451    RS;
452render_wire_object_1(#dlo{mirror=none}=D, PStyle, RS) ->
453    wings_proxy:draw_smooth_edges(D, PStyle, RS);
454render_wire_object_1(#dlo{mirror=Matrix}=D, PStyle, RS0) ->
455    RS1 = wings_proxy:draw_smooth_edges(D, PStyle, RS0),
456    gl:frontFace(?GL_CW),
457    gl:pushMatrix(),
458    gl:multMatrixf(Matrix),
459    RS = wings_proxy:draw_smooth_edges(D, PStyle, RS1),
460    gl:popMatrix(),
461    gl:frontFace(?GL_CCW),
462    RS.
463
464split_objects([#dlo{src_we=We}=D|Dls], Open, Closed, Lights) when ?IS_ANY_LIGHT(We) ->
465    split_objects(Dls, Open, Closed, [D|Lights]);
466split_objects([#dlo{open=true}=D|Dls], Open, Closed, Lights) ->
467    split_objects(Dls, [D|Open], Closed, Lights);
468split_objects([#dlo{open=false}=D|Dls], Open, Closed, Lights) ->
469    split_objects(Dls, Open, [D|Closed], Lights);
470split_objects([], Open, Closed, Lights) ->
471    {Open, Closed, Lights}.
472
473split_wires([#dlo{src_we=We}|Dls], WOs, Show, Wires, Others, Proxis) when ?IS_LIGHT(We) ->
474    split_wires(Dls, WOs, Show, Wires, Others, Proxis);
475split_wires([#dlo{src_we=#we{id=Id}, proxy=Proxy}=D|Dls], WOs, Show, Wires, Others,Proxis) ->
476    case gb_sets:is_member(Id, WOs) of
477        true when Proxy -> split_wires(Dls, WOs, Show, Wires, Others, [D|Proxis]);
478        true -> split_wires(Dls, WOs, Show, [D|Wires], Others,Proxis);
479        false when Proxy -> split_wires(Dls, WOs, Show, Wires, Others,Proxis);
480        false when Show -> split_wires(Dls, WOs, Show, Wires, [D|Others], Proxis);
481        false -> split_wires(Dls, WOs, Show, Wires, Others,Proxis)
482    end;
483split_wires([], _, _, Ws, Os, Ps) ->
484    {Ws, Os, Ps}.
485
486render_sel_highlight(Dls, SelMode, false, PM, #{}=RS0) ->
487    gl:disable(?GL_POLYGON_OFFSET_LINE),
488    gl:disable(?GL_POLYGON_OFFSET_FILL),
489    gl:matrixMode(?GL_PROJECTION),
490    gl:pushMatrix(),
491    non_polygon_offset(1.0, PM),
492    gl:matrixMode(?GL_MODELVIEW),
493    gl:depthFunc(?GL_LEQUAL),
494    #{} = RS1 = foldl(fun(D, RS) -> render_sel(D, SelMode, false, RS) end, RS0, Dls),
495    #{} = RS2 = foldl(fun(D, RS) -> render_sel(D, SelMode, true, RS) end, RS1, Dls),
496    gl:matrixMode(?GL_PROJECTION),
497    gl:popMatrix(),
498    gl:depthFunc(?GL_LESS),
499    gl:matrixMode(?GL_MODELVIEW),
500    %% arbitrary placement in the grand scheme of things
501    foldl(fun(D, RS) -> wings_plugin:draw(plain,D,SelMode,RS) end, RS2, Dls);
502render_sel_highlight(Dls, SelMode, true, _PM, RS0) ->
503    RS1 = foldl(fun(D, RS) -> render_sel(D, SelMode, true, RS) end, RS0, Dls),
504    gl:depthFunc(?GL_LESS),
505    %% arbitrary placement in the grand scheme of things
506    foldl(fun(D, RS) -> wings_plugin:draw(smooth,D,none,RS) end, RS1, Dls).
507
508render_sel(#dlo{drag={matrix,_,_,Matrix}}=D, Mode, Sel, RS0) ->
509    gl:pushMatrix(),
510    gl:multMatrixf(Matrix),
511    RS = render_sel_1(D, Mode, Sel, RS0),
512    gl:popMatrix(),
513    RS;
514render_sel(D, Mode, Sel, RS) ->
515    render_sel_1(D, Mode, Sel, RS).
516
517render_sel_1(#dlo{mirror=none}=D, Mode, Sel, RS) ->
518    render_sel_2(D, Mode, Sel, RS);
519render_sel_1(#dlo{mirror=Matrix}=D, Mode, Sel, RS0) ->
520    RS1 = render_sel_2(D, Mode, Sel, RS0),
521    gl:frontFace(?GL_CW),
522    gl:pushMatrix(),
523    gl:multMatrixf(Matrix),
524    RS = render_sel_2(D, Mode, Sel, RS1),
525    gl:popMatrix(),
526    gl:frontFace(?GL_CCW),
527    RS.
528
529render_sel_2(#dlo{}=D, SelMode, false, RS0) ->
530    RS1 = draw_hard_edges(D, SelMode, RS0),
531    RS2 = draw_vertices(D, SelMode, RS1),
532    draw_normals(D, RS2);
533render_sel_2(#dlo{sel=none, orig_sel=none, hilite=none}, _SelMode, _, RS0) ->
534    RS0;
535render_sel_2(#dlo{src_we=We}=D, _SelMode, true, RS0) ->
536    RS = case wire(We) of
537             true ->
538                 gl:disable(?GL_CULL_FACE),
539                 #{} = RS1 = draw_orig_sel(D, RS0),
540                 #{} = RS2 = draw_sel(D, RS1),
541                 gl:enable(?GL_CULL_FACE),
542                 RS2;
543             false ->
544                 #{} = RS1 = draw_orig_sel(D, RS0),
545                 draw_sel(D, RS1)
546         end,
547    draw_hilite(D, RS).
548
549wire(#we{id=Id}) ->
550    W = wings_wm:get_prop(wireframed_objects),
551    gb_sets:is_member(Id, W).
552
553draw_sel(#dlo{sel=none}, RS) -> RS;
554draw_sel(#dlo{sel=SelDlist,src_sel={edge,_}}, RS0) ->
555    gl:lineWidth(float(wings_pref:get_value(selected_edge_width))),
556    RS = sel_color(RS0),
557    wings_dl:call(SelDlist, RS);
558draw_sel(#dlo{sel=SelDlist,src_sel={vertex,_}}, RS0) ->
559    gl:pointSize(wings_pref:get_value(selected_vertex_size)),
560    RS = sel_color(RS0),
561    wings_dl:call(SelDlist, RS);
562draw_sel(#dlo{open=Open,sel=SelDlist}, RS0) ->
563    RS1 = sel_color(RS0),
564    gl:polygonMode(?GL_FRONT_AND_BACK, ?GL_FILL),
565    gl:enable(?GL_POLYGON_OFFSET_FILL),
566    polygonOffset(1.0),
567    %Stippled selection style.
568    gl:enable(?GL_POLYGON_STIPPLE),
569    RS = draw_face_sel(Open, SelDlist, RS1),
570    gl:disable(?GL_POLYGON_STIPPLE),
571    gl:disable(?GL_POLYGON_OFFSET_FILL),
572    RS.
573
574draw_face_sel(true, SelDlist, RS0) ->
575    case wings_pref:get_value(show_backfaces) of
576        true ->
577            gl:disable(?GL_CULL_FACE),
578            RS = wings_dl:call(SelDlist, RS0),
579            gl:enable(?GL_CULL_FACE),
580            RS;
581        _ ->
582            wings_dl:call(SelDlist, RS0)
583    end;
584draw_face_sel(false, SelDlist, RS) ->
585    wings_dl:call(SelDlist, RS).
586
587sel_color(RS0) ->
588    gl:color3fv(wings_pref:get_value(selected_color)),
589    RS0.
590
591draw_vertices(#dlo{src_we=#we{perm=P},vs=VsDlist}, vertex, RS0) when ?IS_SELECTABLE(P) ->
592    {R,G,B} = wings_pref:get_value(vertex_color),
593	Size = wings_pref:get_value(vertex_size),
594    gl:pointSize(Size),
595    gl:color3f(R,G,B),
596    wings_dl:call(VsDlist, RS0);
597draw_vertices(_, _, RS) -> RS.
598
599draw_hilite(#dlo{hilite=none}, RS) -> RS;
600draw_hilite(#dlo{hilite={_Mode,DL}}, RS) ->
601    wings_dl:call(DL, RS).
602
603draw_orig_sel_fun(Mode, DlistSel) ->
604    {R,G,B} = wings_pref:get_value(selected_color),
605    SelColor = {R,G,B,0.5},
606    case Mode of
607        vertex ->
608            PointSize = wings_pref:get_value(selected_vertex_size)*2,
609            fun(RS0) ->
610                    gl:pointSize(PointSize),
611                    gl:enable(?GL_BLEND),
612                    gl:blendFunc(?GL_SRC_ALPHA, ?GL_ONE_MINUS_SRC_ALPHA),
613                    gl:color4fv(SelColor),
614                    RS = wings_dl:call(DlistSel, RS0),
615                    gl:disable(?GL_BLEND),
616                    RS
617            end;
618        edge ->
619            LineWidth = wings_pref:get_value(selected_edge_width)*2.0,
620            fun(RS0) ->
621                    gl:lineWidth(LineWidth),
622                    gl:enable(?GL_BLEND),
623                    gl:blendFunc(?GL_SRC_ALPHA, ?GL_ONE_MINUS_SRC_ALPHA),
624                    gl:color4fv(SelColor),
625                    RS = wings_dl:call(DlistSel, RS0),
626                    gl:disable(?GL_BLEND),
627                    RS
628            end;
629        _ ->
630            fun(RS0) ->
631                    gl:enable(?GL_BLEND),
632                    gl:blendFunc(?GL_SRC_ALPHA, ?GL_ONE_MINUS_SRC_ALPHA),
633                    gl:color4fv(SelColor),
634                    gl:enable(?GL_POLYGON_OFFSET_FILL),
635                    gl:polygonMode(?GL_FRONT_AND_BACK, ?GL_FILL),
636                    polygonOffset(1.0),
637                    RS = wings_dl:call(DlistSel, RS0),
638                    gl:disable(?GL_POLYGON_OFFSET_FILL),
639                    RS
640            end
641    end.
642
643draw_orig_sel(#dlo{orig_sel=Dlist}, RS) ->
644    wings_dl:call(Dlist, RS).
645
646draw_hard_edges(#dlo{hard=none}, _, RS) -> RS;
647draw_hard_edges(#dlo{hard=Hard}, SelMode, RS) ->
648    gl:lineWidth(hard_edge_width(SelMode)),
649    gl:color3fv(wings_pref:get_value(hard_edge_color)),
650    wings_dl:call(Hard, RS).
651
652draw_normals(#dlo{normals=none}, RS) -> RS;
653draw_normals(#dlo{normals=Ns}, RS) ->
654    gl:color3f(0.0, 0.0, 1.0),
655    gl:lineWidth(float(wings_pref:get_value(normal_vector_width))),
656    wings_dl:call(Ns, RS).
657
658edge_width(edge) -> float(wings_pref:get_value(edge_width));
659edge_width(_) -> 1.0.
660
661hard_edge_width(edge) -> float(wings_pref:get_value(hard_edge_width));
662hard_edge_width(_) -> max(wings_pref:get_value(hard_edge_width) - 1.0, 1.0).
663
664draw_background(MM) ->
665    case wings_wm:is_geom() of
666        false ->
667            ignore;
668        true ->
669            wings_pref:get_value(show_bg) andalso draw_background_1(MM)
670    end.
671
672draw_background_1(MM) ->
673    gl:disable(?GL_CULL_FACE),
674    gl:polygonMode(?GL_FRONT_AND_BACK, ?GL_FILL),
675    gl:depthFunc(?GL_LEQUAL),
676    gl:matrixMode(?GL_PROJECTION),
677    gl:pushMatrix(),
678    load_perspectiv(),
679    RS0 = #{ws_eyepoint => e3d_mat:mul_point(e3d_transform:inv_matrix(MM), {0.0,0.0,0.0}),
680            view_from_world => MM},
681
682    Update = fun(_) ->
683                     #{size:=NoFs, tris:=Data} = wings_shapes:tri_cube(#{}),
684		     D = fun(RS1) ->
685                                 RS = wings_shaders:set_uloc(bg_blur, wings_pref:get_value(show_bg_blur), RS1),
686				 gl:drawArrays(?GL_TRIANGLES, 0, NoFs*3),
687                                 RS
688			 end,
689		     wings_vbo:new(D, Data)
690	     end,
691    RS = wings_shaders:use_prog(background, RS0),
692    wings_dl:draw(background, ignore, Update, RS),
693
694    %% Reset
695    gl:popMatrix(),
696    gl:matrixMode(?GL_MODELVIEW),
697    gl:depthFunc(?GL_LESS),
698    wings_shaders:use_prog(0, RS),
699    ok.
700
701load_perspectiv() ->
702    {W,H} = wings_wm:win_size(),
703    Aspect = W/H,
704    #view{fov=Fov,hither=Hither,yon=Yon} = wings_view:current(),
705    TP = e3d_transform:perspective(Fov, Aspect, Hither, Yon),
706    gl:loadMatrixd(e3d_transform:matrix(TP)).
707
708ground_and_axes(View, PM,MM, RS0) ->
709    Axes = wings_wm:get_prop(show_axes),
710    RS1 = draw_axes(RS0, Axes, PM, MM, View),
711    groundplane(RS1, View, PM, MM).
712
713draw_axes(RS0, false, _, _, _) ->
714    wings_dl:draw(axes, none, fun(_) -> none end, RS0);
715draw_axes(RS0, _Show, PM, MM, View) ->
716    Yon = axis_size(RS0, View),
717    Key = axis_data([{1,x_color,neg_x_color},
718                     {2,y_color,neg_y_color},
719                     {3,z_color,neg_z_color}],
720                    Yon),
721    Update = fun(Data) ->
722		     D = fun(RS) ->
723                                 gl:lineWidth(2.1),
724				 gl:drawArrays(?GL_LINES, 0, 3*4),
725                                 RS
726			 end,
727		     wings_vbo:new(D, Data, [color,vertex])
728	     end,
729    %% io:format("~p: depth_test ~p ~p~n", [?LINE, gl:isEnabled(?GL_DEPTH_TEST)==1, hd(gl:getIntegerv(?GL_DEPTH_FUNC))]),
730    RS1 = wings_dl:draw(axes, Key, Update, RS0),
731    axis_letters(RS1, PM, MM, Yon).
732
733axis_size(#{ws_eyepoint:=Eye}, #view{yon=Yon, distance=Dist0}) ->
734    case wings_pref:get_value(constrain_axes) of
735        true  -> max((abs(element(2,Eye))+Dist0)*2, 25.0); %% As calculated in shader
736        false -> Yon
737    end.
738
739axis_data([{I,PosKey,NegKey}|T], Yon) ->
740    Pos = wings_pref:get_value(PosKey),
741    Neg = wings_pref:get_value(NegKey),
742    A0 = {0.0,0.0,0.0},
743    A = setelement(I, A0, Yon),
744    B = setelement(I, A0, -Yon),
745    [Pos,A0,Pos,A,Neg,A0,Neg,B|axis_data(T, Yon)];
746axis_data([], _) -> [].
747
748axis_letters(RS, TPM, TMM, Yon0) ->
749    ViewPort = wings_wm:viewport(),
750    gl:matrixMode(?GL_PROJECTION),
751    gl:loadIdentity(),
752    {_,_,W,H} = ViewPort,
753    Ortho = e3d_transform:ortho(0.0, float(W), float(H), 0.0, -1.0, 1.0),
754    gl:loadMatrixd(e3d_transform:matrix(Ortho)),
755    gl:matrixMode(?GL_MODELVIEW),
756    gl:loadIdentity(),
757    gl:polygonMode(?GL_FRONT_AND_BACK, ?GL_FILL),
758    PM = e3d_transform:matrix(TPM),
759    MM = e3d_transform:matrix(TMM),
760    Start = {0.0,0.0,0.0},
761    Origin = proj(Start, MM, PM),
762    Info = {Start,Origin,MM,PM,ViewPort},
763    Yon = Yon0 * 1.2,
764    axis_letter_1(1, Yon, axisx, x_color, Info),
765    axis_letter_1(2, Yon, axisy, y_color, Info),
766    axis_letter_1(3, Yon, axisz, z_color, Info),
767    RS.
768
769axis_letter_1(I, Yon, Char, Color0, {Start,{Ox,Oy,_,Ow},MM,PM,Viewport}) ->
770    Color = wings_pref:get_value(Color0),
771    wings_io:set_color(Color),
772    End = setelement(I, Start, Yon),
773    {Px,Py,_,Pw} = proj(End, MM, PM),
774    NegPw = -Pw,
775    if
776	NegPw < Px, Px < Pw, NegPw < Py, Py < Pw ->
777	    show_letter(Px, Py, Pw, Char, Viewport);
778	true ->
779	    clip(Ox, Oy, Ow, Px, Py, Pw, Char, Viewport)
780    end.
781
782show_camera_image_plane() ->
783    case wings_wm:get_prop(show_cam_imageplane) of
784	false ->
785	    ok;
786	true ->
787	    {WinW,WinH} = wings_wm:win_size(),
788	    CamW = wings_pref:get_value(negative_width),
789	    CamH = wings_pref:get_value(negative_height),
790	    AspRatio = CamW/CamH,
791	    %% it's used only the height as reference because view horizon changes only for height variation
792	    {W,H} = {round(WinH*AspRatio)/2, WinH/2},
793
794	    X1 = (WinW/2.0)-W+5.0,
795	    Y1 = (WinH/2.0)-H+5.0,
796	    X2 = (WinW/2.0)+W-5.0,
797	    Y2 = (WinH/2.0)+H-5.0,
798
799	    Quads = [{X1,Y1,0.0},{X2,Y1,0.0},  			% top
800		     {WinW*1.0,0.0,0.0},{0.0,0.0,0.0},
801		     {X1,Y1,0.0},{0.0,0.0,0.0},			% left
802		     {0.0,WinH*1.0,0.0},{X1,Y2,0.0},
803		     {X1,Y2,0.0},{0.0,WinH*1.0,0.0},		% bottom
804		     {WinW*1.0,WinH*1.0,0.0},{X2*1.0,Y2,0.0},
805		     {X2*1.0,Y2,0.0},{WinW*1.0,WinH*1.0,0.0},	% right
806		     {WinW*1.0,0.0,0.0},{X2,Y1,0.0}],
807	    Poly = << <<X:?F32,Y:?F32,Z:?F32>> || {X,Y,Z} <- Quads >>,
808	    Update = fun draw_camera_image_plane/1,
809	    wings_dl:draw(draw_cam_imageplane, {Poly,{X1,Y1,X2,Y2}}, Update, #{})
810    end.
811
812draw_camera_image_plane({Poly,{X1,Y1,X2,Y2}}) ->
813    NoVs = byte_size(Poly) div 12,
814    Draw =
815        fun(RS) ->
816            wings_io:ortho_setup(),
817            gl:enable(?GL_BLEND),
818            gl:blendFunc(?GL_SRC_ALPHA, ?GL_ONE_MINUS_SRC_ALPHA),
819            gl:color4f(0.0, 0.4, 0.8, 0.5),
820            gl:drawArrays(?GL_QUADS, 0, NoVs),
821            gl:color3f(0.0, 0.4, 0.8),
822            gl:lineWidth(2.0),
823            gl:polygonMode(?GL_FRONT_AND_BACK, ?GL_LINE),
824            gl:rectf(X2, Y1, X1, Y2),
825            gl:flush(),
826            RS
827        end,
828    wings_vbo:new(Draw, Poly).
829
830clip(Ox, Oy, Ow, Px, Py, Pw, Char, Viewport) ->
831    AxisRay = line(Ox, Oy, Px, Py),
832    NegOw = -Ow,
833    Lines = [line(NegOw, NegOw, Ow, NegOw),line(NegOw, Ow, Ow, Ow),
834	     line(NegOw, NegOw, NegOw, Ow),line(Ow, NegOw, Ow, Ow)],
835    case clip_1(AxisRay, Lines, {Ow,Pw}) of
836	none -> ok;
837	{X,Y,W} -> show_letter(X, Y, W, Char, Viewport)
838    end.
839
840clip_1({O1,D1}=Axis, [{O2,D2}|Lines], {Ow,_}=W) ->
841    E = 1.0E-6,
842    case {pdot(D1, D2),pdot(D2, D1)} of
843	{Z1,Z2} when abs(Z1) < E; abs(Z2) < E ->
844	    clip_1(Axis, Lines, W);
845	{Div1,Div2} ->
846	    S = pdot(sub(O2, O1), D2)/Div1,
847	    T = pdot(sub(O1, O2), D1)/Div2,
848	    if
849		S < 0.0; T < 0.0; T > 1.0 ->
850		    clip_1(Axis, Lines, W);
851		true ->
852		    {X,Y} = add_prod(O1, D1, S),
853		    {X,Y,Ow}
854	    end
855    end;
856clip_1(_, [], _W) -> none.
857
858show_letter(X0, Y0, W, Char, {_,_,Vw,Vh}) ->
859    PosX = trunc((0.5*X0/W+0.5)*(Vw-20) + 10),
860    X = max(5, min(Vw-20, PosX)),
861    Y = max(30, min(Vh-20, Vh - trunc((0.5*Y0/W+0.5)*(Vh-16) - 1))),
862    axis_text(X, Y, Char).
863
864axis_text(X, Y, C) ->
865    wings_text:render(X, Y, [C]).
866
867proj({X0,Y0,Z0}, MM, PM) ->
868    e3d_mat:mul(PM, e3d_mat:mul(MM, {X0,Y0,Z0,1.0})).
869
870line(Ox, Oy, Px, Py) -> {{Ox,Oy},{Px-Ox,Py-Oy}}.
871
872pdot({X1,Y1}, {X2,Y2}) when is_float(X1), is_float(Y1) ->
873    Y1*X2-X1*Y2.
874
875sub({X1,Y1}, {X2,Y2}) ->
876    {X1-X2,Y1-Y2}.
877
878add_prod({X1,Y1}, {X2,Y2}, S) when is_float(S) ->
879    {S*X2+X1,S*Y2+Y1}.
880
881groundplane(RS0, #view{along_axis=Along, distance=Dist0}=View, PM, MM) ->
882    Show = wings_wm:get_prop(show_groundplane)
883        orelse (wings_pref:get_value(force_show_along_grid)
884                andalso Along =/= none),
885    Eye = maps:get(ws_eyepoint, RS0),
886    Scale = grid_scale(Eye, Dist0, Along),
887    case Show of
888        false -> RS0;
889        true -> draw_grid(View, PM, MM, Along, Scale, RS0)
890    end.
891
892-spec grid_scale(Eye::e3d_vec:point(), Dist::float(), ViewAlong::atom()) ->
893          {InvScale::float(), InvScale10::float(), Alpha1::float(), Alpha2::float()}.
894grid_scale({_,Y,_}, Dist0, Along) ->
895    Dist = case Along of
896               none -> max(abs(Dist0), abs(Y));
897               _ -> Dist0
898           end,
899    Calc = fun(Len) ->
900                   if Len > 10.0 ->
901                           Exp = trunc(math:log(max(1.0, Len))/math:log(10))-1,
902                           max(trunc(math:pow(10.0,Exp))*1.0, 1.0);
903                      Len > 1.0 -> 0.1;
904                      Len > 0.1 -> 0.01;
905                      true -> 0.001
906                   end
907           end,
908    Res = Calc(Dist),
909    Inc = Calc(Dist*2.0),  %% Start blending when 50% distance covered
910    if
911        Inc > Res ->  %% Blend in new lines
912            Blend = min(0.8,max(0.1, 2*(1.0 - Dist/(Inc*10)))),
913            %% io:format("~.3f ~.3f ~.3f ~.3f~n", [Dist, Inc, Blend, (abs(Y)+Dist0)*2]),
914            {1/Res, 1/Inc, Blend, 1.0};
915        true ->  %% Make every 10th stronger
916            {1/Res, 0.1/Res, 1.0, 1.0}
917    end.
918
919
920draw_grid(#view{origin=Origin, distance=Dist}, PM, MM, Along, Scale, RS0) ->
921    wings_io:ortho_setup(),
922    gl:polygonMode(?GL_FRONT_AND_BACK, ?GL_FILL),
923    gl:enable(?GL_BLEND),
924    gl:enable(?GL_DEPTH_TEST),
925    gl:disable(?GL_CULL_FACE),
926    gl:blendFunc(?GL_SRC_ALPHA, ?GL_ONE_MINUS_SRC_ALPHA),
927    Color = wings_pref:get_value(grid_color),
928    RS1 = wings_shaders:use_prog(grid, RS0),
929    RS2 = wings_shaders:set_uloc(proj, e3d_transform:matrix(PM), RS1),
930    RS3 = wings_shaders:set_uloc(view, e3d_transform:matrix(MM), RS2),
931    RS4 = wings_shaders:set_uloc(ws_origin, Origin, RS3),
932    RS5 = wings_shaders:set_uloc(dist, Dist, RS4),
933    RS6 = wings_shaders:set_uloc(scale, Scale, RS5),
934    RS7 = wings_shaders:set_uloc(color, Color, RS6),
935    RS8 = wings_shaders:set_uloc(along, along_axis(Along), RS7),
936    {_,_,W,H} = wings_wm:viewport(),
937    gl:recti(0, 0, W, H),
938    RS = wings_shaders:use_prog(0, RS8),
939    show_scale(W,H,1/element(1,Scale)),
940    %% Reset state..
941    gl:enable(?GL_DEPTH_TEST),
942    wings_view:load_matrices(true),
943    RS.
944
945along_axis(none) -> 0;
946along_axis(x) -> 1;
947along_axis(y) -> 2;
948along_axis(z) -> 3.
949
950
951show_scale(W, H, Scale) ->
952    ScaleStr = float_to_list(float(Scale),[{decimals, 4}, compact]),
953    StrScale = ?__(1,"Grid Scale: x")++ScaleStr,
954    X = W-wings_text:width(StrScale)-10,
955    wings_io:info(X, H-18, StrScale).
956
957show_saved_bb(St) ->
958    Key = get_saved_bb_key(St),
959    Update = fun update_saved_bb/1,
960    wings_dl:draw(saved_bb, Key, Update, #{}).
961
962get_saved_bb_key(#st{bb=BB}) ->
963    case {wings_pref:get_value(show_bb),BB} of
964	{true,[A,B]} ->
965	    Color = wings_pref:get_value(active_vector_color),
966	    {A,B,Color};
967	{_,_} ->
968	    none
969    end.
970
971update_saved_bb({{X1,Y1,Z1},{X2,Y2,Z2},Color}) ->
972    %% 10 vertices in a line strip.
973    Data = [{X1,Y1,Z1},
974	    {X2,Y1,Z1},
975	    {X2,Y2,Z1},
976	    {X1,Y2,Z1},
977	    {X1,Y1,Z1},
978	    {X1,Y1,Z2},
979	    {X2,Y1,Z2},
980	    {X2,Y2,Z2},
981	    {X1,Y2,Z2},
982	    {X1,Y1,Z2},
983	    %% 6 vertices / 3 lines
984	    {X1,Y2,Z1},
985	    {X1,Y2,Z2},
986	    {X2,Y2,Z1},
987	    {X2,Y2,Z2},
988	    {X2,Y1,Z1},
989	    {X2,Y1,Z2}],
990    D = fun(RS) ->
991		gl:enable(?GL_LINE_STIPPLE),
992		gl:lineStipple(4, 2#1110111011101110),
993		gl:color3fv(Color),
994		gl:drawArrays(?GL_LINE_STRIP, 0, 10),
995		gl:drawArrays(?GL_LINES, 10, 6),
996		gl:disable(?GL_LINE_STIPPLE),
997                RS
998	end,
999    wings_vbo:new(D, Data).
1000
1001show_bb_center(St) ->
1002    Key = get_bb_center_key(St),
1003    Update = fun update_bb_center/1,
1004    wings_dl:draw(saved_bb_center, Key, Update, #{}).
1005
1006get_bb_center_key(#st{bb=BB}) ->
1007    case {wings_pref:get_value(show_bb_center),BB} of
1008	{true,[_,_]} ->
1009	    Center = e3d_vec:average(BB),
1010	    Color = wings_pref:get_value(active_vector_color),
1011	    {Center,Color};
1012	{_,_} ->
1013	    none
1014    end.
1015
1016update_bb_center({{Cx,Cy,Cz}=Center,Color}) ->
1017    Data = [Center,
1018	    {Cx,Cy+0.2,Cz},
1019	    {Cx,Cy-0.2,Cz},
1020	    {Cx+0.2,Cy,Cz},
1021	    {Cx-0.2,Cy,Cz},
1022	    {Cx,Cy,Cz+0.2},
1023	    {Cx,Cy,Cz-0.2}],
1024    D = fun(RS) ->
1025		gl:color3fv(Color),
1026		gl:pointSize(8.0),
1027		gl:drawArrays(?GL_POINTS, 0, 1),
1028		gl:drawArrays(?GL_LINES, 1, 6),
1029                RS
1030	end,
1031    wings_vbo:new(D, Data).
1032
1033mini_axis_icon(View, MM) ->
1034    case mini_axis_icon_key(View) of
1035	none -> ok;
1036	Key -> draw_mini_axis_icon(Key, MM)
1037    end.
1038
1039draw_mini_axis_icon(Key, MM) ->
1040    {W,H} = wings_wm:win_size(),
1041    Ratio = W/H,
1042    Matrix0 = e3d_transform:matrix(MM),
1043    Matrix1 = setelement(15, Matrix0, 0.0),
1044    Matrix2 = setelement(14, Matrix1, -1.0+0.11),
1045    Matrix  = setelement(13, Matrix2, 0.11-Ratio),
1046    gl:pushAttrib(?GL_ALL_ATTRIB_BITS),
1047    gl:matrixMode(?GL_PROJECTION),
1048    gl:pushMatrix(),
1049    gl:loadIdentity(),
1050    Ortho = e3d_transform:ortho(-Ratio, Ratio, -1.0, 1.0, 0.00001, 10000000.0),
1051    gl:loadMatrixd(e3d_transform:matrix(Ortho)),
1052    gl:matrixMode(?GL_MODELVIEW),
1053    gl:pushMatrix(),
1054    gl:loadMatrixd(Matrix),
1055
1056    Update = fun update_mini_axis_icon/1,
1057    wings_dl:draw(mini_axis_icon, Key, Update, #{}),
1058
1059    gl:popMatrix(),
1060    gl:matrixMode(?GL_PROJECTION),
1061    gl:popMatrix(),
1062    gl:matrixMode(?GL_MODELVIEW),
1063    gl:popAttrib().
1064
1065mini_axis_icon_key(#view{along_axis=Along}) ->
1066    case wings_pref:get_value(mini_axis) of
1067	false ->
1068	    none;
1069	true ->
1070	    X = wings_pref:get_value(x_color),
1071	    Y = wings_pref:get_value(y_color),
1072	    Z = wings_pref:get_value(z_color),
1073	    {Along,{X,Y,Z}}
1074    end.
1075
1076update_mini_axis_icon({Along,{X,Y,Z}}) ->
1077    Arrows = mini_axis_arrows(Along, X, Y, Z),
1078    Data = [X,{0.0,0.0,0.0},			%X Axis
1079	    X,{0.1,0.0,0.0},
1080	    Y,{0.0,0.0,0.0},			%Y Axis
1081	    Y,{0.0,0.1,0.0},
1082	    Z,{0.0,0.0,0.0},			%Z Axis
1083	    Z,{0.0,0.0,0.1}|Arrows],
1084    N = case Along of
1085	    none -> 3*2 + 3*4;
1086	    _ -> 3*2 + 2*4
1087	end,
1088    D = fun(RS) ->
1089		gl:drawArrays(?GL_LINES, 0, N),
1090                RS
1091	end,
1092    wings_vbo:new(D, Data, [color,vertex]).
1093
1094mini_axis_arrows(Along, X, Y, Z) ->
1095    PA = 0.08,
1096    PB = 0.01,
1097    case Along of
1098	none ->
1099	    %% X Arrows
1100	    [X,{PA,0.0,-PB},
1101	     X,{0.1,0.0,0.0},
1102	     X,{PA,0.0,PB},
1103	     X,{0.1,0.0,0.0},
1104	     %% Y Arrows
1105	     Y,{-PB,PA,0.0},
1106	     Y,{0.0,0.1,0.0},
1107	     Y,{PB,PA,0.0},
1108	     Y,{0.0,0.1,0.0},
1109	     %% Z Arrows
1110	     Z,{-PB,0.0,PA},
1111	     Z,{0.0,0.0,0.1},
1112	     Z,{PB,0.0,PA},
1113	     Z,{0.0,0.0,0.1}];
1114	x ->
1115	    %% Y Arrows
1116	    [Y,{0.0,PA,-PB},
1117	     Y,{0.0,0.1,0.0},
1118	     Y,{0.0,PA,PB},
1119	     Y,{0.0,0.1,0.0},
1120	     %% Z Arrows
1121	     Z,{0.0,-PB,PA},
1122	     Z,{0.0,0.0,0.1},
1123	     Z,{0.0,PB,PA},
1124	     Z,{0.0,0.0,0.1}];
1125	y ->
1126	    %% X Arrows
1127	    [X,{PA,0.0,-PB},
1128	     X,{0.1,0.0,0.0},
1129	     X,{PA,0.0,PB},
1130	     X,{0.1,0.0,0.0},
1131	     %% Z Arrows
1132	     Z,{-PB,0.0,PA},
1133	     Z,{0.0,0.0,0.1},
1134	     Z,{PB,0.0,PA},
1135	     Z,{0.0,0.0,0.1}];
1136	z ->
1137	    %% X Arrows
1138	    [X,{PA,-PB,0.0},
1139	     X,{0.1,0.0,0.0},
1140	     X,{PA,PB,0.0},
1141	     X,{0.1,0.0,0.0},
1142	     %% Y Arrows
1143	     Y,{-PB,PA,0.0},
1144	     Y,{0.0,0.1,0.0},
1145	     Y,{PB,PA,0.0},
1146	     Y,{0.0,0.1,0.0}]
1147    end.
1148
1149user_clipping_planes(on) ->
1150    case wings_wm:get_prop(clip_plane) of
1151	true ->
1152	    {_Position,Direction = {X,Y,Z}} = wings_pref:get_value(last_axis),
1153	    Expand = fun({X1,Y1,Z1}) -> [X1,Y1,Z1,0.0] end,
1154	    draw_clip_disk(Direction, Expand),
1155	    gl:clipPlane(?GL_CLIP_PLANE0, {X,Y,Z,0.0}),
1156	    gl:enable(?GL_CLIP_PLANE0),
1157	    gl:disable(?GL_CULL_FACE);
1158	false ->
1159	    ok
1160    end;
1161user_clipping_planes(off) ->
1162    gl:disable(?GL_CLIP_PLANE0),
1163    gl:enable(?GL_CULL_FACE).
1164
1165draw_clip_disk(Direction, Expand) ->
1166    NZ = e3d_vec:norm(Direction),
1167    NX = e3d_vec:norm(e3d_vec:cross(NZ,{0.0,0.0,1.0})),
1168    NY = e3d_vec:cross(NX,NZ),
1169    Nx = Expand(NX),
1170    Ny = Expand(NY),
1171    Nz = Expand(NZ),
1172    Nw = [0.0,0.0,0.0,1.0],
1173    M  = list_to_tuple(lists:append([Nx,Ny,Nz,Nw])),
1174    Rad = wings_pref:get_value(clip_plane_size),
1175
1176    gl:color3fv(wings_pref:get_value(clip_plane_color)),
1177    gl:pushMatrix(),
1178    gl:multMatrixd(M),
1179
1180    #{size:=Size, tris:=Tris} = wings_shapes:tri_disc(#{subd=>4, scale=> Rad, binary => true}),
1181    gl:polygonMode(?GL_FRONT_AND_BACK, ?GL_LINE),
1182    wings_vbo:draw(fun(_) -> gl:drawArrays(?GL_TRIANGLES, 0, Size*3) end, Tris),
1183    gl:polygonMode(?GL_FRONT_AND_BACK, ?GL_FILL),
1184    gl:popMatrix().
1185