aboutsummaryrefslogtreecommitdiffstats
path: root/lib/wx/examples/demo/ex_gl.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/wx/examples/demo/ex_gl.erl')
-rw-r--r--lib/wx/examples/demo/ex_gl.erl409
1 files changed, 409 insertions, 0 deletions
diff --git a/lib/wx/examples/demo/ex_gl.erl b/lib/wx/examples/demo/ex_gl.erl
new file mode 100644
index 0000000000..53f1eda847
--- /dev/null
+++ b/lib/wx/examples/demo/ex_gl.erl
@@ -0,0 +1,409 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+
+-module(ex_gl).
+
+-behaviour(wx_object).
+
+-export([init/1, code_change/3, handle_info/2, handle_event/2,
+ handle_call/3, terminate/2,
+ start/1]).
+
+-include_lib("wx/include/wx.hrl").
+-include_lib("wx/include/gl.hrl").
+-include_lib("wx/include/glu.hrl").
+
+-record(state,
+ {
+ parent,
+ config,
+ gl,
+ canvas,
+ timer,
+ time
+ }).
+
+-record(gl, {win, data, deg, mat, alpha, text, font, brush, clock, sphere}).
+
+-record(texture, {tid, w, h, minx, miny, maxx, maxy}).
+
+start(Config) ->
+ wx_object:start_link(?MODULE, Config, []).
+
+-define(PAD, fun(Int) -> string:right(integer_to_list(Int), 2, $0) end).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+init(Config) ->
+ wx:batch(fun() -> do_init(Config) end).
+
+do_init(Config) ->
+ Parent = proplists:get_value(parent, Config),
+ Panel = wxPanel:new(Parent, []),
+
+ %% Setup sizer
+ Sizer = wxStaticBoxSizer:new(?wxHORIZONTAL, Panel, [{label, "wxGLCanvas"}]),
+
+ Opts = [{size, {300,300}}, {style, ?wxSUNKEN_BORDER}],
+ GLAttrib = [{attribList, [?WX_GL_RGBA,
+ ?WX_GL_DOUBLEBUFFER,
+ ?WX_GL_MIN_RED,8,
+ ?WX_GL_MIN_GREEN,8,
+ ?WX_GL_MIN_BLUE,8,
+ ?WX_GL_DEPTH_SIZE,24,0]}],
+ Canvas = wxGLCanvas:new(Panel,Opts ++ GLAttrib),
+ wxGLCanvas:connect(Canvas, size),
+
+ wxGLCanvas:setCurrent(Canvas),
+ Image = wxImage:scale(wxImage:new("image.jpg"), 128,128),
+ GL = setup_gl(Canvas,Image),
+ Timer = timer:send_interval(20, self(), update),
+
+ %% Add to sizers
+ wxSizer:add(Sizer, Canvas, [{flag, ?wxEXPAND},{proportion, 1}]),
+ wxWindow:setSizer(Panel,Sizer),
+ wxSizer:layout(Sizer),
+ {Panel, #state{parent = Panel, config = Config,
+ canvas = Canvas,
+ gl = GL, timer = Timer}}.
+
+%% Event handling
+handle_event(#wx{event = #wxSize{size = {W,H}}}, State) ->
+ case W =:= 0 orelse H =:= 0 of
+ true -> skip;
+ _ ->
+ gl:viewport(0,0,W,H),
+ gl:matrixMode(?GL_PROJECTION),
+ gl:loadIdentity(),
+ gl:ortho( -2.0, 2.0, -2.0*H/W, 2.0*H/W, -20.0, 20.0),
+ gl:matrixMode(?GL_MODELVIEW),
+ gl:loadIdentity()
+ end,
+ {noreply, State}.
+
+handle_info(update, State) ->
+ S1 = update_rotation(State),
+ GL = S1#state.gl,
+ S2 = if S1#state.time > State#state.time ->
+ gl:deleteTextures([(GL#gl.clock)#texture.tid]),
+ {Hour,Min,Sec} = S1#state.time,
+ Clock = load_texture_by_string(GL#gl.font, GL#gl.brush, {40,40,40},
+ [?PAD(Hour), $:, ?PAD(Min), $:, ?PAD(Sec)], false),
+ S1#state{gl = GL#gl{clock = Clock}};
+ true ->
+ S1
+ end,
+ wx:batch(fun() -> drawBox(S2#state.gl) end),
+ {noreply, S2};
+handle_info(stop, State) ->
+ timer:cancel(State#state.timer),
+ catch wxGLCanvas:destroy(State#state.canvas),
+ {stop, normal, State}.
+
+handle_call(Msg, _From, State) ->
+ io:format("Got Call ~p~n",[Msg]),
+ {reply,ok,State}.
+
+code_change(_, _, State) ->
+ {stop, not_yet_implemented, State}.
+
+terminate(_Reason, State) ->
+ catch wxGLCanvas:destroy(State#state.canvas),
+ timer:cancel(State#state.timer),
+ timer:sleep(300).
+
+
+
+-define(VS, {{-0.5, -0.5, -0.5}, %1
+ { 0.5, -0.5, -0.5}, %2
+ { 0.5, 0.5, -0.5},
+ {-0.5, 0.5, -0.5}, %4
+ {-0.5, 0.5, 0.5},
+ { 0.5, 0.5, 0.5}, %6
+ { 0.5, -0.5, 0.5},
+ {-0.5, -0.5, 0.5}}).%8
+
+-define(FACES,
+ %% Faces Normal U-axis V-axis
+ [{{1,2,3,4},{0,0,-1},{-1,0,0}, {0,1,0}}, %
+ {{8,1,4,5},{-1,0,0},{0,0,1}, {0,1,0}}, %
+ {{2,7,6,3},{1,0,0}, {0,0,-1}, {0,1,0}}, %
+ {{7,8,5,6},{0,0,1}, {1,0,0}, {0,1,0}}, %
+ {{4,3,6,5},{0,1,0}, {-1,0,0}, {0,0,1}}, %
+ {{1,2,7,8},{0,-1,0},{1,0,0}, {0,0,1}}]).
+
+-define(COLORS,{{ 0.0, 0.0, 0.0},
+ { 1.0, 0.0, 0.0},
+ { 1.0, 1.0, 0.0},
+ { 0.0, 1.0, 0.0},
+ { 0.0, 1.0, 1.0},
+ { 1.0, 1.0, 1.0},
+ { 1.0, 0.0, 1.0},
+ { 0.0, 0.0, 1.0}}).
+
+
+update_rotation(S=#state{gl=GL=#gl{deg=Rot}}) ->
+ {_, Time} = calendar:local_time(),
+ S#state{gl=GL#gl{deg = Rot + 1.0}, time = Time}.
+
+%% Needs to setup opengl after window is shown...
+%% GL context is created when shown first time.
+setup_gl(Win, Image) ->
+ {W,H} = wxWindow:getClientSize(Win),
+ gl:viewport(0,0,W,H),
+ gl:matrixMode(?GL_PROJECTION),
+ gl:loadIdentity(),
+ gl:ortho( -2.0, 2.0, -2.0*H/W, 2.0*H/W, -20.0, 20.0),
+ gl:matrixMode(?GL_MODELVIEW),
+ gl:loadIdentity(),
+ gl:enable(?GL_DEPTH_TEST),
+ gl:depthFunc(?GL_LESS),
+ gl:clearColor(1.0,1.0,1.0,1.0),
+ MatTexture = load_texture_by_image(Image),
+ ImgTexture = load_texture_by_image(
+ wxImage:new("erlang.png")),
+ Font = wxFont:new(32, ?wxFONTFAMILY_DEFAULT, ?wxFONTSTYLE_NORMAL, ?wxFONTWEIGHT_BOLD),
+ Brush = wxBrush:new({0,0,0}),
+ StrTexture = load_texture_by_string(Font, Brush, {40,40,40}, "Text from wxFont", true),
+ {_, {Hour,Min,Sec}} = calendar:local_time(),
+ Clock = load_texture_by_string(Font, Brush, {40, 40, 40},
+ [?PAD(Hour), $:, ?PAD(Min), $:, ?PAD(Sec)], false),
+ Sphere = glu:newQuadric(),
+ gl:enable(?GL_TEXTURE_2D),
+ #gl{win=Win,data={?FACES,?VS,?COLORS},deg=0.0,
+ mat=MatTexture, alpha=ImgTexture, text=StrTexture, font = Font,
+ brush = Brush, clock = Clock, sphere = Sphere}.
+
+drawBox(#gl{win=Win,deg=Deg,data={Fs,Vs,Colors},mat=MatT,alpha=ImgA,
+ text=Text, clock = Clock, sphere=Sphere}) ->
+ gl:matrixMode(?GL_MODELVIEW),
+ gl:loadIdentity(),
+ gl:pushMatrix(),
+ gl:translatef(0,0.5,0),
+ gl:rotatef(Deg, 1.0, 1.0, 1.0),
+ gl:clear(?GL_COLOR_BUFFER_BIT bor ?GL_DEPTH_BUFFER_BIT),
+ gl:bindTexture(?GL_TEXTURE_2D, MatT#texture.tid),
+ gl:disable(?GL_BLEND),
+ gl:texEnvf(?GL_TEXTURE_ENV, ?GL_TEXTURE_ENV_MODE, ?GL_MODULATE),
+ gl:disable(?GL_CULL_FACE),
+ gl:'begin'(?GL_QUADS),
+ wx:foreach(fun(Face) -> drawFace(Face,Vs,Colors) end, Fs),
+ gl:'end'(),
+ gl:popMatrix(),
+
+ gl:texEnvf(?GL_TEXTURE_ENV, ?GL_TEXTURE_ENV_MODE, ?GL_REPLACE),
+
+ enter_2d_mode(Win),
+ {W,H} = wxWindow:getClientSize(Win),
+ Move = abs(90 - (trunc(Deg) rem 180)),
+ draw_texture((W div 2) - 50, (H div 2)-130+Move, Clock),
+ draw_texture((W div 2) - 80, (H div 2)-Move, ImgA),
+ leave_2d_mode(),
+
+ gl:pushMatrix(),
+ gl:enable(?GL_CULL_FACE),
+ gl:enable(?GL_BLEND),
+ gl:blendFunc(?GL_SRC_ALPHA, ?GL_ONE_MINUS_SRC_ALPHA),
+ gl:translatef(0,-0.8,0),
+ gl:bindTexture(?GL_TEXTURE_2D, Text#texture.tid),
+ glu:quadricTexture(Sphere, ?GLU_TRUE),
+ glu:quadricNormals(Sphere, ?GLU_SMOOTH),
+ glu:quadricDrawStyle(Sphere, ?GLU_FILL),
+ glu:quadricOrientation(Sphere, ?GLU_OUTSIDE),
+ %%gl:scalef(2.0, 0.5, 1.0),
+ gl:rotatef(-90, 1.0, 0.0, 0.0),
+ gl:rotatef(-Deg, 0.0, 0.0, 1.0),
+ glu:sphere(Sphere, 0.8, 50,40),
+ gl:popMatrix(),
+
+ wxGLCanvas:swapBuffers(Win).
+
+drawFace({{V1,V2,V3,V4},N,_Ut,_Vt}, Cube, Colors) ->
+ gl:normal3fv(N),
+ gl:color3fv(element(V1, Colors)),
+ gl:texCoord2f(0.0, 0.0), gl:vertex3fv(element(V1, Cube)),
+ gl:color3fv(element(V2, Colors)),
+ gl:texCoord2f(1.0, 0.0), gl:vertex3fv(element(V2, Cube)),
+ gl:color3fv(element(V3, Colors)),
+ gl:texCoord2f(1.0, 1.0), gl:vertex3fv(element(V3, Cube)),
+ gl:color3fv(element(V4, Colors)),
+ gl:texCoord2f(0.0, 1.0), gl:vertex3fv(element(V4, Cube)).
+
+
+draw_texture(X, Y, #texture{tid = TId, w = W, h = H,
+ miny = MinY, minx = MinX,
+ maxx = MaxX, maxy = MaxY}) ->
+ gl:bindTexture(?GL_TEXTURE_2D, TId),
+ gl:'begin'(?GL_TRIANGLE_STRIP),
+ gl:texCoord2f(MinX, MinY), gl:vertex2i(X, Y ),
+ gl:texCoord2f(MaxX, MinY), gl:vertex2i(X+W div 2, Y ),
+ gl:texCoord2f(MinX, MaxY), gl:vertex2i(X, Y+H div 2),
+ gl:texCoord2f(MaxX, MaxY), gl:vertex2i(X+W div 2, Y+H div 2),
+ gl:'end'().
+
+load_texture_by_image(Image) ->
+ ImgW = wxImage:getWidth(Image),
+ ImgH = wxImage:getHeight(Image),
+ W = get_power_of_two_roof(ImgW),
+ H = get_power_of_two_roof(ImgH),
+ Data = get_data_for_use_with_teximage2d(Image),
+ %% Create an OpenGL texture for the image
+ [TId] = gl:genTextures(1),
+ gl:bindTexture(?GL_TEXTURE_2D, TId),
+ gl:texParameteri(?GL_TEXTURE_2D, ?GL_TEXTURE_MAG_FILTER, ?GL_NEAREST),
+ gl:texParameteri(?GL_TEXTURE_2D, ?GL_TEXTURE_MIN_FILTER, ?GL_NEAREST),
+ Format = case wxImage:hasAlpha(Image) of
+ true -> ?GL_RGBA;
+ false -> ?GL_RGB
+ end,
+ gl:texImage2D(?GL_TEXTURE_2D, 0,
+ Format, W, H, 0,
+ Format, ?GL_UNSIGNED_BYTE, Data),
+ #texture{tid = TId, w = ImgW, h = ImgH,
+ minx = 0, miny = 0, maxx = ImgW / W, maxy = ImgH / H}.
+
+
+%% This algorithm (based on http://d0t.dbclan.de/snippets/gltext.html)
+%% prints a string to a bitmap and loads that onto an opengl texture.
+%% Comments for the createTexture function:
+%%
+%% "Creates a texture from the settings saved in TextElement, to be
+%% able to use normal system fonts conviently a wx.MemoryDC is
+%% used to draw on a wx.Bitmap. As wxwidgets device contexts don't
+%% support alpha at all it is necessary to apply a little hack to
+%% preserve antialiasing without sticking to a fixed background
+%% color:
+%%
+%% We draw the bmp in b/w mode so we can use its data as a alpha
+%% channel for a solid color bitmap which after GL_ALPHA_TEST and
+%% GL_BLEND will show a nicely antialiased text on any surface.
+%%
+%% To access the raw pixel data the bmp gets converted to a
+%% wx.Image. Now we just have to merge our foreground color with
+%% the alpha data we just created and push it all into a OpenGL
+%% texture and we are DONE *inhalesdelpy*"
+load_texture_by_string(Font, Brush, Color, String, Flip) ->
+ TmpBmp = wxBitmap:new(200, 200),
+ Tmp = wxMemoryDC:new(TmpBmp),
+ wxMemoryDC:setFont(Tmp, Font),
+ {StrW, StrH} = wxDC:getTextExtent(Tmp, String),
+ wxMemoryDC:destroy(Tmp),
+ wxBitmap:destroy(TmpBmp),
+
+ W = get_power_of_two_roof(StrW),
+ H = get_power_of_two_roof(StrH),
+
+ Bmp = wxBitmap:new(W, H),
+ DC = wxMemoryDC:new(Bmp),
+ wxMemoryDC:setFont(DC, Font),
+ wxMemoryDC:setBackground(DC, Brush),
+ wxMemoryDC:clear(DC),
+ wxMemoryDC:setTextForeground(DC, {255, 255, 255}),
+ wxMemoryDC:drawText(DC, String, {0, 0}),
+
+ Img0 = wxBitmap:convertToImage(Bmp),
+ case Flip of
+ true ->
+ Img = wxImage:mirror(Img0, [{horizontally, false}]),
+ wxImage:destroy(Img0),
+ Img;
+ false ->
+ Img = Img0
+ end,
+
+ Alpha = wxImage:getData(Img),
+ Data = colourize_image(Alpha, Color),
+ wxImage:destroy(Img),
+ wxBitmap:destroy(Bmp),
+ wxMemoryDC:destroy(DC),
+
+ [TId] = gl:genTextures(1),
+ gl:bindTexture(?GL_TEXTURE_2D, TId),
+ gl:texParameteri(?GL_TEXTURE_2D, ?GL_TEXTURE_MAG_FILTER, ?GL_LINEAR),
+ gl:texParameteri(?GL_TEXTURE_2D, ?GL_TEXTURE_MIN_FILTER, ?GL_LINEAR),
+ gl:texEnvf(?GL_TEXTURE_ENV, ?GL_TEXTURE_ENV_MODE, ?GL_REPLACE),
+ %%gl:pixelStorei(?GL_UNPACK_ROW_LENGTH, 0),
+ %%gl:pixelStorei(?GL_UNPACK_ALIGNMENT, 2),
+ gl:texImage2D(?GL_TEXTURE_2D, 0, ?GL_RGBA,
+ W, H, 0, ?GL_RGBA, ?GL_UNSIGNED_BYTE, Data),
+ #texture{tid = TId, w = StrW, h = StrH,
+ minx = 0, miny = 0, maxx = StrW / W, maxy = StrH / H}.
+
+colourize_image(Alpha, {R,G,B}) ->
+ << <<R:8,G:8,B:8,A:8>> || <<A:8,_:8,_:8>> <= Alpha >>.
+
+get_data_for_use_with_teximage2d(Image) ->
+ RGB = wxImage:getData(Image),
+ case wxImage:hasAlpha(Image) of
+ true ->
+ Alpha = wxImage:getAlpha(Image),
+ interleave_rgb_and_alpha(RGB, Alpha);
+ false ->
+ RGB
+ end.
+
+interleave_rgb_and_alpha(RGB, Alpha) ->
+ list_to_binary(
+ lists:zipwith(fun({R, G, B}, A) ->
+ <<R, G, B, A>>
+ end,
+ [{R,G,B} || <<R, G, B>> <= RGB],
+ [A || <<A>> <= Alpha])).
+
+
+enter_2d_mode(Win) ->
+ {W, H} = wxWindow:getClientSize(Win),
+
+ %% Note, there may be other things you need to change,
+ %% depending on how you have your OpenGL state set up.
+ gl:pushAttrib(?GL_ENABLE_BIT),
+ gl:disable(?GL_DEPTH_TEST),
+ gl:disable(?GL_CULL_FACE),
+ gl:enable(?GL_TEXTURE_2D),
+
+ %% This allows alpha blending of 2D textures with the scene
+ gl:enable(?GL_BLEND),
+ gl:blendFunc(?GL_SRC_ALPHA, ?GL_ONE_MINUS_SRC_ALPHA),
+
+ gl:matrixMode(?GL_PROJECTION),
+ gl:pushMatrix(),
+ gl:loadIdentity(),
+
+ %% SDL coordinates will be upside-down in the OpenGL world. We'll
+ %% therefore flip the bottom and top coordinates in the orthogonal
+ %% projection to correct this.
+ %% Note: We could flip the texture/image itself, but this will
+ %% also work for mouse coordinates.
+ gl:ortho(0.0, W, H, 0.0, 0.0, 1.0),
+
+ gl:matrixMode(?GL_MODELVIEW),
+ gl:pushMatrix(),
+ gl:loadIdentity().
+
+leave_2d_mode() ->
+ gl:matrixMode(?GL_MODELVIEW),
+ gl:popMatrix(),
+ gl:matrixMode(?GL_PROJECTION),
+ gl:popMatrix(),
+ gl:popAttrib().
+
+get_power_of_two_roof(X) ->
+ get_power_of_two_roof_2(1, X).
+
+get_power_of_two_roof_2(N, X) when N >= X -> N;
+get_power_of_two_roof_2(N, X) -> get_power_of_two_roof_2(N*2, X).
+