%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2009-2011. 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, handle_cast/2, 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}. handle_cast(Msg, State) -> io:format("Got cast ~p~n",[Msg]), {noreply,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).