diff options
Diffstat (limited to 'lib/wx/examples/demo/ex_gl.erl')
-rw-r--r-- | lib/wx/examples/demo/ex_gl.erl | 409 |
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). + |