├── .gitignore ├── .mailmap ├── Makefile ├── README.md ├── c_src └── provisual_fgraph_nif.c ├── doc ├── processes-bigbang.png └── processes-mnesia.png ├── ebin ├── .gitignore └── .keep ├── include └── provisual_fgraph.hrl ├── rebar ├── src ├── provisual.app.src ├── provisual.erl ├── provisual_app.erl ├── provisual_fgraph.erl ├── provisual_model.erl ├── provisual_sphere.erl ├── provisual_sup.erl ├── provisual_ticker.erl └── provisual_tracer.erl └── start.sh /.gitignore: -------------------------------------------------------------------------------- 1 | *.beam 2 | ebin/*.app 3 | c_src/*.o 4 | priv/*.so 5 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | ./rebar compile 3 | 4 | clean: 5 | rm ebin/*.beam 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fgraph # 2 | 3 | Some of these files already are in the reltool application. I originally wrote it just for fun but it ended up to show dependecies in reltool. 4 | 5 | 6 | # Processes v1.0 # 7 | 8 | Processes is a process visualization tool. It can visualize the processeses and its ancestry in a system or the links between the processes. It will also show messages between processes. 9 | 10 | ## Getting Started v1.0 ## 11 | 12 | Clone, Checkout and Make: 13 | 14 | git clone git://github.com/psyeugenic/fgraph.git 15 | git checkout 1.0 16 | make 17 | 18 | Start a new erlang node: 19 | 20 | erl -pa ebin -sname view 21 | 22 | In the erlang shell type: 23 | 24 | processes:start(). 25 | 26 | 27 | A wxErlang window will appear and you can now connect to a remote node by clicking File in the meny and then Connect. Type in the remote node name, e.g.: mynode@hostname 28 | 29 | The result can look like this: 30 | ![processes-mnesia](https://github.com/psyeugenic/fgraph/raw/master/doc/processes-mnesia.png) 31 | ![processes-mnesia](https://github.com/psyeugenic/fgraph/raw/master/doc/processes-bigbang.png) 32 | 33 | 34 | # Provisual v2.0 # 35 | 36 | Yes a name change 37 | 38 | ## Getting Started v2.0 ## 39 | 40 | Clone and Make 41 | 42 | git clone git://github.com/psyeugenic/fgraph.git 43 | git checkout master 44 | make 45 | 46 | Start a new erlang node: 47 | 48 | erl -pa ebin -sname view 49 | 50 | In the erlang shell type: 51 | 52 | provisual:start(). 53 | 54 | -------------------------------------------------------------------------------- /c_src/provisual_fgraph_nif.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012 Björn-Egil Dahlberg 3 | * 4 | * Created: 2012-06-26 5 | * Author: Björn-Egil Dahlberg 6 | * 7 | */ 8 | 9 | #include "erl_nif.h" 10 | #include 11 | 12 | static ERL_NIF_TERM composition3d(ErlNifEnv *env, const ERL_NIF_TERM *v1, const ERL_NIF_TERM *v0) { 13 | double v1x, v1y, v1z, v0x, v0y, v0z; 14 | float dx,dy,dz; 15 | long i; 16 | float x2, y, number, r; 17 | const float threehalfs = 1.5F; 18 | 19 | if ( !enif_get_double(env, v1[0], &v1x) || 20 | !enif_get_double(env, v1[1], &v1y) || 21 | !enif_get_double(env, v1[2], &v1z) || 22 | 23 | !enif_get_double(env, v0[0], &v0x) || 24 | !enif_get_double(env, v0[1], &v0y) || 25 | !enif_get_double(env, v0[2], &v0z)) { 26 | return enif_make_badarg(env); 27 | } 28 | 29 | dx = v1x - v0x; 30 | dy = v1y - v0y; 31 | dz = v1z - v0z; 32 | 33 | number = dx*dx + dy*dy + dz*dz + 0.0005f; 34 | 35 | x2 = number * 0.5F; 36 | y = number; 37 | i = * ( long * ) &y; 38 | i = 0x5f3759df - ( i >> 1 ); 39 | y = * ( float * ) &i; 40 | y = y * ( threehalfs - ( x2 * y * y ) ); 41 | 42 | r = 1/y; 43 | 44 | return enif_make_tuple2(env, enif_make_double(env, r), 45 | enif_make_tuple3(env, 46 | enif_make_double(env, dx/r), 47 | enif_make_double(env, dy/r), 48 | enif_make_double(env, dz/r) 49 | ) 50 | ); 51 | } 52 | 53 | static ERL_NIF_TERM composition2d(ErlNifEnv *env, const ERL_NIF_TERM *v1, const ERL_NIF_TERM *v0) { 54 | double v1x, v1y, v0x, v0y; 55 | float dx,dy; 56 | long i; 57 | float x2, y, number, r; 58 | const float threehalfs = 1.5F; 59 | 60 | if ( !enif_get_double(env, v1[0], &v1x) || 61 | !enif_get_double(env, v1[1], &v1y) || 62 | 63 | !enif_get_double(env, v0[0], &v0x) || 64 | !enif_get_double(env, v0[1], &v0y)) { 65 | return enif_make_badarg(env); 66 | } 67 | 68 | dx = v1x - v0x; 69 | dy = v1y - v0y; 70 | 71 | number = dx*dx + dy*dy + 0.0005f; 72 | 73 | x2 = number * 0.5F; 74 | y = number; 75 | i = * ( long * ) &y; 76 | i = 0x5f3759df - ( i >> 1 ); 77 | y = * ( float * ) &i; 78 | y = y * ( threehalfs - ( x2 * y * y ) ); 79 | 80 | r = 1/y; 81 | 82 | return enif_make_tuple2(env, enif_make_double(env, r), 83 | enif_make_tuple2(env, 84 | enif_make_double(env, dx/r), 85 | enif_make_double(env, dy/r) 86 | ) 87 | ); 88 | 89 | return enif_make_badarg(env); 90 | } 91 | 92 | static ERL_NIF_TERM composition(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { 93 | const ERL_NIF_TERM *v1, *v0; 94 | int a0 = 0, a1 = 0; 95 | 96 | if (enif_get_tuple(env, argv[0], &a1, &v1) && enif_get_tuple(env, argv[1], &a0, &v0)) { 97 | if (a1 == 3 && a1 == 3) return composition3d(env, v1, v0); 98 | else if (a1 == 2 && a1 == 2) return composition2d(env, v1, v0); 99 | } 100 | 101 | return enif_make_badarg(env); 102 | } 103 | 104 | 105 | static ErlNifFunc nif_functions[] = { 106 | {"composition", 2, composition}, 107 | }; 108 | 109 | static int load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) 110 | { 111 | *priv_data = NULL; 112 | return 0; 113 | } 114 | 115 | ERL_NIF_INIT(provisual_fgraph, nif_functions, load, NULL, NULL, NULL) 116 | -------------------------------------------------------------------------------- /doc/processes-bigbang.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psyeugenic/fgraph/c485fefc9de78012b13b35abe59f0d29090ff6d1/doc/processes-bigbang.png -------------------------------------------------------------------------------- /doc/processes-mnesia.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psyeugenic/fgraph/c485fefc9de78012b13b35abe59f0d29090ff6d1/doc/processes-mnesia.png -------------------------------------------------------------------------------- /ebin/.gitignore: -------------------------------------------------------------------------------- 1 | *.beam 2 | -------------------------------------------------------------------------------- /ebin/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psyeugenic/fgraph/c485fefc9de78012b13b35abe59f0d29090ff6d1/ebin/.keep -------------------------------------------------------------------------------- /include/provisual_fgraph.hrl: -------------------------------------------------------------------------------- 1 | -define(fg_th, (0.25)). 2 | -define(fg_damp, (0.75)). 3 | -define(fg_kc, (1000.0)). 4 | -define(fg_stretch, (0.005)). 5 | -define(fg_grav, (9.82)). 6 | -define(fg_sqrt_eps, (0.005)). 7 | 8 | %% Ke = 8.854187817e9 [N x M^2 x C^(-2)] 9 | -define(fg_wind, (0.15)). 10 | 11 | -record(fg_e, { 12 | l = 10.0, 13 | k = 10.0 14 | }). 15 | 16 | 17 | -record(fg_v, { 18 | p = {0.0,0.0}, 19 | v = {0.0,0.0}, 20 | q = 5.0, 21 | m = 1.0, 22 | type = dynamic, 23 | state = running, 24 | resides = undefined, 25 | selected = false, 26 | color, 27 | name 28 | }). 29 | -------------------------------------------------------------------------------- /rebar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psyeugenic/fgraph/c485fefc9de78012b13b35abe59f0d29090ff6d1/rebar -------------------------------------------------------------------------------- /src/provisual.app.src: -------------------------------------------------------------------------------- 1 | {application, provisual, 2 | [ 3 | {description, ""}, 4 | {vsn, "1"}, 5 | {registered, []}, 6 | {applications, [ 7 | kernel, 8 | stdlib 9 | ]}, 10 | {mod, { provisual_app, []}}, 11 | {env, []} 12 | ]}. 13 | -------------------------------------------------------------------------------- /src/provisual.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (C) 2012 Björn-Egil Dahlberg 2 | %% 3 | %% File: provisual.erl 4 | %% Author: Björn-Egil Dahlberg 5 | %% Created: 2012-06-15 6 | 7 | -module(provisual). 8 | -export([ 9 | start/0 10 | ]). 11 | 12 | -export([ 13 | add_node/4, 14 | del_node/2, 15 | add_link/3, 16 | del_link/3, 17 | add_event/3, 18 | clear/1 19 | ]). 20 | 21 | 22 | 23 | -include_lib("wx/include/wx.hrl"). 24 | -include_lib("wx/include/gl.hrl"). 25 | -include_lib("provisual_fgraph.hrl"). 26 | 27 | -define(file_connect, 101). 28 | -define(file_disconnect, 102). 29 | -define(view_links, 103). 30 | -define(view_ancestry, 104). 31 | -define(file_cookie, 105). 32 | 33 | -compile(inline). 34 | 35 | -define(W, 640). 36 | -define(H, 480). 37 | 38 | -define(PAN_SPEED, 25). 39 | -define(SHADERS_MENU, 200). 40 | -define(SHADOW_MENU, 201). 41 | 42 | 43 | -record(camera, { 44 | origin, 45 | distance, % From origo. 46 | azimuth, 47 | elevation, 48 | pan_x, % Panning in X direction. 49 | pan_y, % Panning in Y direction. 50 | fov, % Field of view. 51 | hither, % Near clipping plane. 52 | yon, % Far clipping plane. 53 | xs = 0, % Prev position 54 | ys = 0, 55 | %% Not camera but needed 56 | ww, 57 | wh, 58 | ortho=false 59 | }). 60 | 61 | 62 | -record(s, { 63 | frame, 64 | canvas, 65 | rstate, 66 | font, 67 | time, 68 | camera, 69 | model, 70 | sphere, 71 | edges, 72 | tracer 73 | }). 74 | 75 | -record(rs, {mat_shader = false, shadows = false}). 76 | 77 | -record(time, { 78 | fps = 0, % frames per second 79 | fc = 0, % frame counter 80 | diff = 0, % Time last frame in ms 81 | start =erlang:now(), 82 | fcst =erlang:now() % frame counter start time 83 | }). 84 | 85 | 86 | start() -> spawn(fun() -> init() end). 87 | 88 | init() -> 89 | Wx = wx:new(), 90 | Frame = wxFrame:new(Wx, 1,"Provisual",[{size, {?W,?H}}]), 91 | wxFrame:connect(Frame, close_window), 92 | 93 | %% Menues 94 | MenuBar = wxMenuBar:new(), 95 | 96 | File = wxMenu:new([]), 97 | wxMenu:append(File, ?file_connect, "&Connect"), 98 | wxMenu:append(File, ?file_disconnect, "&Disconnect"), 99 | wxMenu:appendSeparator(File), 100 | wxMenu:append(File, ?file_cookie, "&Set Cookie"), 101 | 102 | View = wxMenu:new([]), 103 | wxMenu:append(View, ?view_links, "&Links"), 104 | wxMenu:append(View, ?view_ancestry, "&Ancestry"), 105 | 106 | wxMenuBar:append(MenuBar, File, "&File"), 107 | wxMenuBar:append(MenuBar, View, "&View"), 108 | wxFrame:setMenuBar(Frame, MenuBar), 109 | 110 | wxMenu:connect(Frame, command_menu_selected, []), 111 | 112 | SB = wxFrame:createStatusBar(Frame,[{number,2}]), 113 | wxStatusBar:setStatusWidths(SB, [-1, 150]), 114 | SBText = "amera", 115 | wxFrame:setStatusText(Frame, SBText), 116 | 117 | Attrs = [{attribList, [?WX_GL_RGBA,?WX_GL_DOUBLEBUFFER,0]}], 118 | Canvas = wxGLCanvas:new(Frame, Attrs), 119 | wxFrame:connect(Canvas, size), 120 | wxFrame:connect(Canvas, motion), 121 | wxFrame:connect(Canvas, left_up), 122 | wxFrame:connect(Canvas, left_down), 123 | wxFrame:connect(Canvas, right_down), 124 | wxFrame:connect(Canvas, mousewheel), 125 | 126 | wxWindow:show(Frame), 127 | %% Set Current must be called after show and before any opengl call. 128 | wxGLCanvas:setCurrent(Canvas), 129 | 130 | gl:viewport(0,0,?W,?H), 131 | 132 | gl:enable(?GL_DEPTH_TEST), 133 | gl:depthFunc(?GL_LEQUAL), 134 | gl:enable(?GL_CULL_FACE), 135 | 136 | gl:shadeModel(?GL_SMOOTH), 137 | gl:clearColor(0.1,0.1,0.2,1.0), 138 | 139 | DefFont = undefined, 140 | 141 | Me = self(), 142 | register(?MODULE, self()), 143 | _Ticker = provisual_ticker:start(Me), 144 | loop(#s{ 145 | frame=Frame, canvas=Canvas, font=DefFont, 146 | rstate = #rs{}, time=#time{}, camera = camera_init(?W,?H), 147 | model = provisual_model:new(), 148 | sphere = load_sphere(5), 149 | edges = fun() -> ok end 150 | }). 151 | 152 | loop(quit) -> ok; 153 | loop(#s{ frame=F, model = M, 154 | sphere = Sphere, 155 | edges = Edges, 156 | canvas = Canvas, 157 | camera = Cam, time=T} = S) -> 158 | %% Setup camera and light 159 | load_matrices(Cam), 160 | 161 | % draw edges (links|ancestry) 162 | gl:lineWidth(1.0), 163 | gl:color3f(1.0,1.0,1.0), 164 | Edges(), 165 | 166 | % draw edges (messages) 167 | gl:color3f(0.0,0.3,1.0), 168 | 169 | lists:foreach(fun({{K1,K2},I}) -> 170 | gl:lineWidth(8.0*(I/100)), 171 | #fg_v{p={X1,Y1,Z1}} = provisual_fgraph:get(K1, provisual_model:vs(M)), 172 | #fg_v{p={X2,Y2,Z2}} = provisual_fgraph:get(K2, provisual_model:vs(M)), 173 | gl:'begin'(?GL_LINES), 174 | draw_lines([ 175 | {X1, Z1, Y1},{X2, Z2, Y2} 176 | ]), 177 | gl:'end'() 178 | end, provisual_model:messages(M)), 179 | 180 | gl:color3f(1.0,0.0,0.4), 181 | provisual_fgraph:foreach(fun 182 | ({_Key, #fg_v{ p ={X, Y, Z}}}) -> 183 | gl:pushMatrix(), 184 | gl:translatef(X,Z,Y), 185 | Sphere(), 186 | gl:popMatrix() 187 | end, provisual_model:vs(M)), 188 | 189 | %% Everything is drawn, show it. 190 | wxGLCanvas:swapBuffers(Canvas), 191 | %% Handle events 192 | Ns = handle_events(S#s{time=fps(F,T)}), 193 | %% Clear all for the next frame 194 | gl:clear(?GL_COLOR_BUFFER_BIT bor ?GL_DEPTH_BUFFER_BIT), 195 | 196 | %% Sync command queue, so we don't choke the driver and get no events 197 | _ = wxWindow:getSize(F), 198 | loop(Ns). 199 | 200 | %%%% message handling 201 | 202 | handle_events(S) -> 203 | receive 204 | Msg -> 205 | try 206 | handle_msg(Msg, S) 207 | catch 208 | C:E -> 209 | io:format("WTF ~p:~p~n~n~p~n", [C,E,erlang:get_stacktrace()]), 210 | erlang:exit(badarg) 211 | end 212 | after 0 -> S 213 | end. 214 | 215 | handle_msg(#wx{ id=?file_cookie, event = #wxCommand{type = command_menu_selected}, obj = Frame}, S) -> 216 | TextDialog = wxTextEntryDialog:new(Frame, "Cookie: ", [{caption, "Set Cookie"}]), 217 | wxDialog:showModal(TextDialog), 218 | try 219 | case wxTextEntryDialog:getValue(TextDialog) of 220 | [] -> ok; 221 | CookieStr -> 222 | Cookie = list_to_atom(CookieStr), 223 | io:format("Set cookie: ~p ~p~n", [CookieStr, Cookie]), 224 | erlang:set_cookie(node(), Cookie) 225 | end 226 | catch 227 | C:E -> 228 | io:format("crap ~p:~p~n", [C,E]) 229 | end, 230 | 231 | wxDialog:destroy(TextDialog), 232 | S; 233 | 234 | handle_msg(#wx{ id=?file_connect, event = #wxCommand{type = command_menu_selected}, obj = Frame}, #s{ tracer = undefined} = S) -> 235 | TextDialog = wxTextEntryDialog:new(Frame, "Node: ", [{caption, "Connect to Node"}]), 236 | wxDialog:showModal(TextDialog), 237 | 238 | Tracer = case wxTextEntryDialog:getValue(TextDialog) of 239 | [] -> undefined; 240 | NodeStr -> 241 | io:format("connect to node: ~p~n", [NodeStr]), 242 | try 243 | Node = list_to_atom(NodeStr), 244 | pong = net_adm:ping(Node), 245 | % provisual_fgraph:set_click(Pid, fun 246 | % (Key) -> 247 | % Pi = rpc:call(Node, erlang, process_info, [Key]), 248 | % [io:format("~p\t~p~n", [K,V]) || {K, V} <- Pi], 249 | % ok 250 | % end), 251 | 252 | provisual_tracer:start(Node, self(), []) 253 | catch 254 | C:E -> 255 | io:format("connect error ~p:~p~n", [C,E]), 256 | undefined 257 | end 258 | end, 259 | wxDialog:destroy(TextDialog), 260 | S#s{ tracer = Tracer }; 261 | 262 | handle_msg(#wx{ id=?file_disconnect, event = #wxCommand{type = command_menu_selected}}, #s{ tracer = Tracer} = S) when is_pid(Tracer) -> 263 | Tracer ! stop_tracer, 264 | S#s{ tracer = undefined }; 265 | 266 | handle_msg(#wx{ event = #wxClose{}}, _S) -> 267 | erlang:halt(); 268 | 269 | handle_msg(#wx{ id = ?view_links, event = #wxCommand{type = command_menu_selected}}, #s{ } = S) -> 270 | S; 271 | 272 | handle_msg(#wx{ id = ?view_ancestry, event = #wxCommand{type = command_menu_selected}}, #s{ } = S) -> 273 | S; 274 | 275 | handle_msg( #wx{event=#wxMouse{ type = right_down}}, #s{ model = M} = S) -> 276 | S#s{ model = provisual_model:toggle_edge(M) }; 277 | 278 | handle_msg( #wx{event=Mouse=#wxMouse{}}, #s{ camera = Cam0}= S) -> 279 | {_, Cam} = cam_event(Mouse,Cam0), 280 | S#s{ camera = Cam}; 281 | 282 | handle_msg(#wx{event=#wxSize{size={W,H}}}, #s{ camera = Cam0}= S) -> 283 | gl:viewport(0,0,W,H), 284 | S#s{camera = Cam0#camera{ww=W,wh=H}}; 285 | 286 | handle_msg({Pid, force_step}, #s{ model = M0 } = S) -> 287 | M1 = provisual_model:step(M0), 288 | Lines = model_to_line_binary(M1), 289 | Pid ! {self(), ok}, 290 | S#s{ model = M1, edges = Lines }; 291 | 292 | handle_msg({graph, Cmd},#s{ model = M} = S) -> 293 | % try to eat as many graph msgs as we can 294 | handle_events(S#s{ model = provisual_model:event(Cmd, M) }); 295 | 296 | handle_msg(#wx{ id=Id, event = #wxCommand{type = command_menu_selected}}, S) -> 297 | io:format("wx Id = ~p~n", [Id]), 298 | S; 299 | handle_msg(Other, S) -> 300 | io:format("Got ~p ~n",[Other]), 301 | S. 302 | 303 | cam_event(#wxMouse{type=motion, leftDown=false},Cam) -> 304 | {[], Cam}; 305 | cam_event(#wxMouse{type=mousewheel, wheelRotation=Rot},Cam=#camera{distance=Dist}) -> 306 | case Rot > 0 of 307 | true -> {[],Cam#camera{distance=Dist+10}}; 308 | false -> {[],Cam#camera{distance=Dist-10}} 309 | end; 310 | cam_event(#wxMouse{type=motion, leftDown=true,shiftDown=true,x=X,y=Y}, 311 | Cam=#camera{pan_x=PanX0,pan_y=PanY0,distance=D,xs=Xs,ys=Ys}) -> 312 | %% PAN 313 | S = D*(1/20)/(101-float(?PAN_SPEED)), 314 | Dx = (X-Xs)*S, 315 | Dy = (Y-Ys)*S, 316 | PanX = PanX0 + Dx, 317 | PanY = PanY0 - Dy, 318 | {[],Cam#camera{pan_x=PanX,pan_y=PanY,xs=X,ys=Y}}; 319 | cam_event(#wxMouse{type=motion, leftDown=true,controlDown=true,x=X, y=Y}, 320 | Cam=#camera{distance=Dist, ys=Ys}) -> 321 | %% ZOOM (Dy) 322 | {[],Cam#camera{distance=Dist+(Y-Ys)/10,xs=X,ys=Y}}; 323 | cam_event(#wxMouse{type=motion, leftDown=true,x=X,y=Y}, 324 | Cam=#camera{azimuth=Az0,elevation=El0,xs=Xs,ys=Ys}) -> 325 | %% Rotate 326 | Az = Az0 + (X-Xs), 327 | El = El0 + (Y-Ys), 328 | {[], Cam#camera{azimuth=Az,elevation=El, xs=X,ys=Y}}; 329 | cam_event(#wxMouse{type=left_down, x=X,y=Y},Cam) -> 330 | {[],Cam#camera{xs=X,ys=Y}}; 331 | cam_event(Ev,Cam) -> 332 | {Ev,Cam}. 333 | 334 | %% gl functions 335 | draw_lines([{X1,Y1,Z1},{X2,Y2,Z2}|Pts]) -> 336 | gl:vertex3f(X1,Y1,Z1), 337 | gl:vertex3f(X2,Y2,Z2), 338 | draw_lines(Pts); 339 | draw_lines([]) -> ok. 340 | 341 | 342 | -define(F32, 32/float-native). 343 | 344 | model_to_line_binary(M) -> 345 | {Size, Data} = model_to_line_binary(provisual_model:vs(M), provisual_model:es(M)), 346 | [Buff] = gl:genBuffers(1), 347 | gl:bindBuffer(?GL_ARRAY_BUFFER,Buff), 348 | gl:bufferData(?GL_ARRAY_BUFFER, size(Data), Data, ?GL_STATIC_DRAW), 349 | fun() -> 350 | %% Draw buffer 351 | gl:bindBuffer(?GL_ARRAY_BUFFER,Buff), 352 | gl:vertexPointer(3, ?GL_FLOAT, 0, 0), 353 | gl:enableClientState(?GL_VERTEX_ARRAY), 354 | gl:drawArrays(?GL_LINES, 0, Size*3), 355 | gl:bindBuffer(?GL_ARRAY_BUFFER, 0), 356 | gl:disableClientState(?GL_VERTEX_ARRAY) 357 | end. 358 | 359 | model_to_line_binary(Vs, Es) -> 360 | provisual_fgraph:foldl(fun 361 | ({{K1,K2},_},{Size, Bin}) -> 362 | #fg_v{p={X1,Y1,Z1}} = provisual_fgraph:get(K1, Vs), 363 | #fg_v{p={X2,Y2,Z2}} = provisual_fgraph:get(K2, Vs), 364 | {Size + 2, <>} 365 | end, {0, <<>>}, Es). 366 | 367 | load_sphere(Scale) -> 368 | {Size, DataChunk, [Ns]} = 369 | provisual_sphere:tris([{subd,4}, {ccw,false}, {binary,true}, {scale,Scale}, {normals,true}]), 370 | StartNormals = size(DataChunk), 371 | Data = <>, 372 | 373 | [Buff] = gl:genBuffers(1), 374 | gl:bindBuffer(?GL_ARRAY_BUFFER,Buff), 375 | gl:bufferData(?GL_ARRAY_BUFFER, size(Data), Data, ?GL_STATIC_DRAW), 376 | 377 | fun() -> 378 | %% Draw buffer 379 | gl:bindBuffer(?GL_ARRAY_BUFFER,Buff), 380 | gl:vertexPointer(3, ?GL_FLOAT, 0, 0), 381 | gl:normalPointer(?GL_FLOAT, 0, StartNormals), 382 | gl:enableClientState(?GL_VERTEX_ARRAY), 383 | gl:enableClientState(?GL_NORMAL_ARRAY), 384 | 385 | gl:drawArrays(?GL_TRIANGLES, 0, Size*3), 386 | 387 | gl:bindBuffer(?GL_ARRAY_BUFFER, 0), 388 | gl:disableClientState(?GL_VERTEX_ARRAY), 389 | gl:disableClientState(?GL_NORMAL_ARRAY) 390 | end. 391 | 392 | fps(Frame, T) -> fps(Frame, T, 500). 393 | fps(Frame, T0 = #time{fcst=FCSt,start=Start,fc=FC},Interval) -> 394 | Now = erlang:now(), 395 | Diff = tdiff(Now,Start), 396 | Time = tdiff(Now,FCSt), 397 | if Time > Interval -> 398 | Fps = round(1000*FC / Time), 399 | wxFrame:setStatusText(Frame, io_lib:format("FPS: ~p",[Fps]), 400 | [{number,1}]), %% Zero numbered suddenly 401 | T0#time{fc=0,fps=Fps,diff=Diff,fcst=Now}; 402 | true -> 403 | T0#time{fc=FC+1,diff=Diff} 404 | end. 405 | 406 | tdiff({A2,B2,C2},{A1,B1,C1}) -> 407 | (A2-A1)*1000000+(B2-B1)*1000 + (C2-C1) / 1000. 408 | 409 | 410 | load_matrices(Cam) -> load_matrices(Cam,false). 411 | load_matrices(Cam,IncludeLights) -> 412 | gl:matrixMode(?GL_PROJECTION), 413 | gl:loadIdentity(), 414 | projection(Cam), 415 | modelview(Cam, IncludeLights). 416 | 417 | projection(#camera{ 418 | distance=D, 419 | fov=Fov, 420 | hither=Hither, 421 | yon=Yon, ww=W,wh=H,ortho=Ortho}) -> 422 | Aspect = W/H, 423 | case Ortho of 424 | false -> 425 | glu:perspective(Fov, Aspect, Hither, Yon); 426 | true -> 427 | Sz = D*math:tan(Fov*math:pi()/180/2), 428 | gl:ortho(-Sz*Aspect, Sz*Aspect, -Sz, Sz, Hither, Yon) 429 | end. 430 | 431 | modelview(#camera{origin=Origin,distance=Dist,azimuth=Az, 432 | elevation=El,pan_x=PanX,pan_y=PanY}, Lights) -> 433 | gl:matrixMode(?GL_MODELVIEW), 434 | gl:loadIdentity(), 435 | if 436 | is_function(Lights) -> Lights(); 437 | true -> ok 438 | end, 439 | gl:translatef(PanX, PanY, -Dist), 440 | gl:rotatef(El, 1, 0, 0), 441 | gl:rotatef(Az, 0, 1, 0), 442 | {OX,OY,OZ} = Origin, 443 | gl:translatef(OX, OY, OZ), 444 | ok. 445 | 446 | 447 | %%%%%%%%%%%% Camera 448 | 449 | camera_init(W,H) -> #camera{ 450 | origin = {0.0, 0.0, 0.0}, 451 | azimuth = -45.0, 452 | elevation = 25.0, 453 | distance = 1350, 454 | pan_x = 0.0, 455 | pan_y = -2.0, 456 | fov = 45.0, 457 | hither = 0.1, 458 | yon = 10000.0, 459 | ww = W, 460 | wh = H 461 | }. 462 | 463 | %%%%%%%%% 464 | 465 | %% graph nodes 466 | 467 | add_node(G, Id, _, Name) -> 468 | G ! {graph, {add_node, Id, Name}}, 469 | ok. 470 | 471 | del_node(G, Id) -> 472 | G ! {graph, {del_node, Id}}, 473 | ok. 474 | 475 | add_link(G, E, Type) -> 476 | G ! {graph, {add_edge, E, Type}}, 477 | ok. 478 | 479 | del_link(G, E, Type) -> 480 | G ! {graph, {del_edge, E, Type}}, 481 | ok. 482 | 483 | clear(G) -> 484 | G ! {graph, clear}, 485 | ok. 486 | 487 | add_event(G, E, Type) -> 488 | G ! {graph, {add_event, E, Type}}, 489 | ok. 490 | -------------------------------------------------------------------------------- /src/provisual_app.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (C) 2012 Björn-Egil Dahlberg 2 | %% 3 | %% File: provisual_app.erl 4 | %% Author: Björn-Egil Dahlberg 5 | %% Created: 2012-06-15 6 | 7 | -module(provisual_app). 8 | 9 | -behaviour(application). 10 | 11 | %% Application callbacks 12 | -export([start/2, stop/1]). 13 | 14 | %% =================================================================== 15 | %% Application callbacks 16 | %% =================================================================== 17 | 18 | start(_StartType, _StartArgs) -> 19 | provisual_sup:start_link(). 20 | 21 | stop(_State) -> 22 | ok. 23 | -------------------------------------------------------------------------------- /src/provisual_fgraph.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% %CopyrightBegin% 3 | %% 4 | %% Copyright Ericsson AB 2009. All Rights Reserved. 5 | %% 6 | %% The contents of this file are subject to the Erlang Public License, 7 | %% Version 1.1, (the "License"); you may not use this file except in 8 | %% compliance with the License. You should have received a copy of the 9 | %% Erlang Public License along with this software. If not, it can be 10 | %% retrieved online at http://www.erlang.org/. 11 | %% 12 | %% Software distributed under the License is distributed on an "AS IS" 13 | %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See 14 | %% the License for the specific language governing rights and limitations 15 | %% under the License. 16 | %% 17 | %% %CopyrightEnd% 18 | 19 | -module(provisual_fgraph). 20 | 21 | -export([ 22 | composition/2, 23 | load_provisual_nif/0, 24 | step/3 25 | ]). 26 | 27 | -export([ 28 | new/0, 29 | 30 | add/3, 31 | set/3, 32 | del/2, 33 | 34 | is_defined/2, 35 | get/2, 36 | size/1, 37 | 38 | foreach/2, 39 | map/2, 40 | foldl/3, 41 | mapfoldl/3 42 | ]). 43 | 44 | -on_load(load_provisual_nif/0). 45 | 46 | -compile(inline). 47 | -compile({inline_size, 128}). 48 | 49 | -include_lib("provisual_fgraph.hrl"). 50 | 51 | 52 | % size(S) -> dict:size(S). 53 | % new() -> dict:new(). 54 | % add(K, V, S) -> dict:store(K, {K,V}, S). 55 | % set(K, V, S) -> dict:store(K, {K,V}, S). 56 | % del(K, S) -> dict:erase(K, S). 57 | % get(K, S) -> case catch dict:fetch(K, S) of 58 | % {'EXIT', _} -> undefined; 59 | % {_, V} -> V 60 | % end. 61 | % is_defined(K, S) -> dict:is_key(K, S). 62 | % 63 | % map(F, S) -> dict:map(fun(_,V) -> F(V) end, S). 64 | % foldl(F, I, S) -> dict:fold(fun(_, V, O) -> F(V,O) end, I, S). 65 | % 66 | % foreach(F, S) -> dict:map(fun(_,V) -> F(V) end, S), S. 67 | % mapfoldl(_F, I, S) -> {I, S}. 68 | 69 | 70 | %% KEY-VALUE STORAGE Process dictionary 71 | new() -> []. 72 | 73 | is_defined(Key, _Fg) -> 74 | case get(Key) of 75 | undefined -> false; 76 | _ -> true 77 | end. 78 | 79 | get(K, _Fg) -> 80 | case get(K) of 81 | {_, V} -> V; 82 | _ -> undefined 83 | end. 84 | 85 | add(Key, Value, Fg) -> 86 | put(Key, {Key, Value}), 87 | [Key|Fg]. 88 | 89 | set(Key, Value, Fg) -> 90 | put(Key, {Key, Value}), 91 | Fg. 92 | 93 | size(Fg) -> length(Fg). 94 | 95 | del(Key, Fg) -> 96 | erase(Key), 97 | lists:delete(Key, Fg). 98 | 99 | foreach(Fun, Fg) -> 100 | lists:foreach(fun 101 | (Key) -> Fun(get(Key)) 102 | end, Fg), 103 | Fg. 104 | 105 | map(Fun, Fg) -> 106 | lists:foreach(fun 107 | (Key) -> put(Key,Fun(get(Key))) 108 | end, Fg), 109 | Fg. 110 | 111 | foldl(Fun, I, Fg) -> 112 | lists:foldl(fun 113 | (Key, Out) -> 114 | Fun(get(Key), Out) 115 | end, I, Fg). 116 | 117 | mapfoldl(Fun, I, Fg) -> 118 | Acc = lists:foldl(fun 119 | (Key, Out) -> 120 | {Value, Acc} = Fun(get(Key), Out), 121 | put(Key, Value), 122 | Acc 123 | end, I, Fg), 124 | {Fg, Acc}. 125 | 126 | step(Vs, Es, Pa) -> 127 | ?MODULE:map(fun 128 | (Node = {_, #fg_v{ type = static }}) -> Node; 129 | ({Key, #fg_v{ p = {_,_,_}, type = dynamic} = Value}) -> 130 | F0 = {0.0,0.0,0.0}, 131 | F1 = coulomb_repulsion(Key, Value, Vs, F0), 132 | F2 = hooke_attraction(Key, Value, Vs, Es, F1), 133 | F3 = point_attraction(Key, Value, Pa, F2), 134 | 135 | {Key, force_step(Key, Value, F3)}; 136 | ({Key, #fg_v{ p = {_,_}, type = dynamic} = Value}) -> 137 | F0 = {0.0,0.0}, 138 | F1 = coulomb_repulsion(Key, Value, Vs, F0), 139 | F2 = hooke_attraction(Key, Value, Vs, Es, F1), 140 | F3 = point_attraction(Key, Value, Pa, F2), 141 | 142 | {Key, force_step(Key, Value, F3) }; 143 | (Node) -> Node 144 | end, Vs). 145 | 146 | force_step(_Key, #fg_v{ p = {Px, Py, Pz}, v = {Vx, Vy, Vz}} = Value, {Fx, Fy, Fz}) -> 147 | Vx1 = (Vx + ?fg_th*Fx)*?fg_damp, 148 | Vy1 = (Vy + ?fg_th*Fy)*?fg_damp, 149 | Vz1 = (Vz + ?fg_th*Fz)*?fg_damp, 150 | 151 | Px1 = Px + ?fg_th*Vx1, 152 | Py1 = Py + ?fg_th*Vy1, 153 | Pz1 = Pz + ?fg_th*Vz1, 154 | Value#fg_v{ p = {Px1, Py1, Pz1}, v = {Vx1, Vy1, Vz1}}; 155 | force_step(_Key, #fg_v{ p = {Px, Py}, v = {Vx, Vy}} = Value, {Fx, Fy}) -> 156 | Vx1 = (Vx + ?fg_th*Fx)*?fg_damp, 157 | Vy1 = (Vy + ?fg_th*Fy)*?fg_damp, 158 | 159 | Px1 = Px + ?fg_th*Vx1, 160 | Py1 = Py + ?fg_th*Vy1, 161 | 162 | Value#fg_v{ p = {Px1, Py1}, v = {Vx1, Vy1}}. 163 | 164 | point_attraction(_, #fg_v{ p = P0 }, Pa, {Fx, Fy, Fz}) when is_float(Fx), is_float(Fy), is_float(Fz) -> 165 | K = 20, 166 | L = 150, 167 | {R, {Cx,Cy,Cz}} = ?MODULE:composition(P0, Pa), 168 | F = -K*?fg_stretch*(R - L), 169 | {Fx + Cx*F, Fy + Cy*F, Fz + Cz*F}; 170 | point_attraction(_, #fg_v{ p = P0 }, Pa, {Fx, Fy}) when is_float(Fx), is_float(Fy) -> 171 | K = 20, 172 | L = 150, 173 | {R, {Cx,Cy}} = ?MODULE:composition(P0, Pa), 174 | F = -K*?fg_stretch*(R - L), 175 | {Fx + Cx*F, Fy + Cy*F}. 176 | 177 | coulomb_repulsion(K0, #fg_v{ p = P0, q = Q0}, Vs, {Fx0, Fy0,Fz0}) when is_float(Fx0), is_float(Fy0), is_float(Fz0) -> 178 | ?MODULE:foldl(fun 179 | ({K1, _}, F) when K1 == K0 -> F; 180 | ({_, #fg_v{ p = P1, q = Q1}}, {Fx, Fy, Fz}) -> 181 | {R, {Cx, Cy,Cz}} = ?MODULE:composition(P0, P1), 182 | F = ?fg_kc*(Q1*Q0)/(R*R+?fg_sqrt_eps), 183 | {Fx + Cx*F, Fy + Cy*F, Fz + Cz*F}; 184 | (_, F) -> F 185 | end, {Fx0, Fy0, Fz0}, Vs); 186 | coulomb_repulsion(K0, #fg_v{ p = P0, q = Q0}, Vs, {Fx0, Fy0}) when is_float(Fx0), is_float(Fy0) -> 187 | ?MODULE:foldl(fun 188 | ({K1, _}, F) when K1 == K0 -> F; 189 | ({_, #fg_v{ p = P1, q = Q1}}, {Fx, Fy}) -> 190 | {R, {Cx, Cy}} = ?MODULE:composition(P0, P1), 191 | F = ?fg_kc*(Q1*Q0)/(R*R+?fg_sqrt_eps), 192 | {Fx + Cx*F, Fy + Cy*F}; 193 | (_, F) -> F 194 | end, {Fx0, Fy0}, Vs). 195 | 196 | hooke_attraction(Key0, #fg_v{ p = P0 }, Vs, Es, {Fx0, Fy0, Fz0}=F0) when is_float(Fx0), is_float(Fy0), is_float(Fz0) -> 197 | ?MODULE:foldl(fun 198 | ({{Key1,Key1}, _}, F) -> F; 199 | ({{Key1,Key2}, #fg_e{ l = L, k = K}}, {Fx, Fy, Fz}) when Key1 =:= Key0-> 200 | try 201 | #fg_v{ p = P1} = ?MODULE:get(Key2, Vs), 202 | {R, {Cx,Cy,Cz}} = ?MODULE:composition(P0, P1), 203 | F = -K*?fg_stretch*(R - L), 204 | {Fx + Cx*F, Fy + Cy*F, Fz + Cz*F} 205 | catch 206 | C:E -> 207 | io:format("{~p,~p} : bad key ~p ~n", [C,E,Key2]), 208 | {Fx, Fy, Fz} 209 | end; 210 | ({{Key2,Key1}, #fg_e{ l = L, k = K}}, {Fx, Fy, Fz}) when Key1 =:= Key0-> 211 | try 212 | #fg_v{ p = P1} = ?MODULE:get(Key2, Vs), 213 | {R, {Cx,Cy,Cz}} = ?MODULE:composition(P0, P1), 214 | F = -K*?fg_stretch*(R - L), 215 | {Fx + Cx*F, Fy + Cy*F, Fz + Cz*F} 216 | catch 217 | C:E -> 218 | io:format("{~p,~p} : bad key ~p ~n", [C,E,Key2]), 219 | {Fx, Fy, Fz} 220 | end; 221 | (_, F) -> F 222 | end, F0, Es); 223 | hooke_attraction(Key0, #fg_v{ p = P0 }, Vs, Es, {Fx0, Fy0}) when is_float(Fx0), is_float(Fy0) -> 224 | ?MODULE:foldl(fun 225 | ({{Key1,Key1}, _}, F) -> F; 226 | ({{Key1,Key2}, #fg_e{ l = L, k = K}}, {Fx, Fy}) when Key1 =:= Key0-> 227 | try 228 | #fg_v{ p = P1} = ?MODULE:get(Key2, Vs), 229 | {R, {Cx,Cy}} = ?MODULE:composition(P0, P1), 230 | F = -K*?fg_stretch*(R - L), 231 | {Fx + Cx*F, Fy + Cy*F} 232 | catch 233 | C:E -> 234 | io:format("{~p,~p} : bad key ~p ~n", [C,E,Key2]), 235 | {Fx, Fy} 236 | end; 237 | ({{Key2,Key1}, #fg_e{ l = L, k = K}}, {Fx, Fy}) when Key1 =:= Key0-> 238 | try 239 | #fg_v{ p = P1} = ?MODULE:get(Key2, Vs), 240 | {R, {Cx,Cy}} = ?MODULE:composition(P0, P1), 241 | F = -K*?fg_stretch*(R - L), 242 | {Fx + Cx*F, Fy + Cy*F} 243 | catch 244 | C:E -> 245 | io:format("{~p,~p} : bad key ~p ~n", [C,E,Key2]), 246 | {Fx, Fy} 247 | end; 248 | (_, F) -> F 249 | end, {Fx0, Fy0}, Es). 250 | 251 | % This decomposition takes a lot of computing power, needs optimizing 252 | composition({Px1, Py1, Pz1}, {Px0, Py0, Pz0}) when is_float(Px1), is_float(Py1), is_float(Pz1), 253 | is_float(Px0), is_float(Py0), is_float(Pz0) -> 254 | Dx = Px1 - Px0, 255 | Dy = Py1 - Py0, 256 | Dz = Pz1 - Pz0, 257 | R = qsqrt(Dx*Dx + Dy*Dy + Dz*Dz + ?fg_sqrt_eps), 258 | %R = math:sqrt(Dx*Dx + Dy*Dy + Dz*Dz + ?fg_sqrt_eps), 259 | {R, {Dx/R, Dy/R, Dz/R}}; 260 | composition({Px1, Py1}, {Px0, Py0}) when is_float(Px1), is_float(Py1), is_float(Px0), is_float(Py0) -> 261 | Dx = Px1 - Px0, 262 | Dy = Py1 - Py0, 263 | R = qsqrt(Dx*Dx + Dy*Dy + ?fg_sqrt_eps), 264 | %R = math:sqrt(Dx*Dx + Dy*Dy + ?fg_sqrt_eps), 265 | {R, {Dx/R, Dy/R}}. 266 | 267 | %% Carmacks Quake3 square root trick (sadly not faster then builtin math:sqrt/1) 268 | qsqrt(X) -> 269 | X2 = X*0.5, 270 | <> = <>, 271 | Trick = 16#5f3759df - ( I bsr 1 ), 272 | <> = <>, 273 | Inv0 = Inv*(1.50 - (X2 * Inv * Inv)), 274 | % Inv1 = Inv0*(1.5 - (X2 * Inv0 * Inv0)), 275 | 1/Inv0. 276 | 277 | load_provisual_nif() -> 278 | case code:priv_dir(provisual) of 279 | {error, bad_name} -> 280 | SoName = filename:join("priv", provisual_drv); 281 | Dir -> 282 | SoName = filename:join(Dir, provisual_drv) 283 | end, 284 | erlang:load_nif(SoName, 0). 285 | 286 | 287 | -------------------------------------------------------------------------------- /src/provisual_model.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (C) 2012 Björn-Egil Dahlberg 2 | %% 3 | %% File: provisual_model.erl 4 | %% Author: Björn-Egil Dahlberg 5 | %% Created: 2012-06-29 6 | 7 | -module(provisual_model). 8 | 9 | -export([ 10 | new/0, 11 | vs/1, 12 | step/1, 13 | es/1, 14 | messages/1, 15 | toggle_edge/1, 16 | event/2 17 | ]). 18 | 19 | -include_lib("provisual_fgraph.hrl"). 20 | 21 | -record(model, { 22 | vs, 23 | ces, 24 | es, 25 | events 26 | }). 27 | 28 | 29 | -record(es, { 30 | links, 31 | ancestors 32 | }). 33 | 34 | new() -> 35 | #model{ 36 | vs = provisual_fgraph:new(), 37 | ces = ancestors, 38 | es = #es{ 39 | links = provisual_fgraph:new(), 40 | ancestors = provisual_fgraph:new() 41 | }, 42 | events = gb_trees:empty() 43 | }. 44 | 45 | step(#model{ events = Es, vs = Vs } = M) -> 46 | M#model{ 47 | vs = provisual_fgraph:step(Vs, es(M), {0.0, 0.0, 0.0}), 48 | events = decrease_events(Es) 49 | }. 50 | 51 | 52 | vs(#model{ vs = Vs}) -> Vs. 53 | 54 | es(#model{ ces = links, es = #es{ links = Es}}) -> Es; 55 | es(#model{ ces = ancestors, es = #es{ ancestors = Es}}) -> Es. 56 | messages(#model{ events = Es}) -> gb_trees:to_list(Es). 57 | 58 | toggle_edge(#model{ ces = C} = M) -> 59 | M#model{ ces = next_edge(C) }. 60 | 61 | next_edge(links) -> ancestors; 62 | next_edge(ancestors) -> links. 63 | 64 | decrease_events(Es) -> gb_trees:from_orddict(decrease_events(gb_trees:to_list(Es), [])). 65 | decrease_events([{_E,0}|Es], Os) -> 66 | decrease_events(Es, Os); 67 | decrease_events([{E,I}|Es], Os) -> 68 | decrease_events(Es, [{E,I-1}|Os]); 69 | decrease_events([], Os) -> Os. 70 | 71 | %% graph events 72 | 73 | -define(fg_q, 20.0). 74 | -define(fg_m, 0.5). 75 | -define(fg_k, 35.0). % attractive force 76 | -define(fg_l, 3.0). % spring length 77 | 78 | p1() -> float(random:uniform(160) - 80). 79 | p3() -> {p1(), p1(), p1()}. 80 | 81 | event(clear, _) -> provisual_model:new(); 82 | event({add_node, Id, Name}, #model{ vs = Vs0} = M) -> 83 | Vs1 = provisual_fgraph:add(Id, #fg_v{ 84 | p = p3(), 85 | v = {0.0,0.0,0.0}, 86 | m = ?fg_m, 87 | q = ?fg_q, 88 | color = undefined, 89 | name = Name}, Vs0), 90 | M#model{vs = Vs1}; 91 | 92 | event({del_node, Id}, #model{ events = Events, vs = Vs, es = #es{ links = Ls, ancestors = As} = Es} = M) -> 93 | M#model{ 94 | vs = provisual_fgraph:del(Id, Vs), 95 | events = lists:foldl(fun 96 | ({{DId,_},_}, O) when DId =:= Id -> O; 97 | ({{_,DId},_}, O) when DId =:= Id -> O; 98 | ({Event,I}, O) -> gb_trees:enter(Event,I,O) 99 | end, gb_trees:empty(), gb_trees:to_list(Events)), 100 | es = Es#es{ 101 | links = provisual_fgraph:foldl(fun 102 | ({{DId,_},_}, O) when DId =:= Id -> O; 103 | ({{_,DId},_}, O) when DId =:= Id -> O; 104 | ({K,V}, O) -> provisual_fgraph:add(K,V,O) 105 | end, provisual_fgraph:new(), Ls), 106 | ancestors = provisual_fgraph:foldl(fun 107 | ({{DId,_},_}, O) when DId =:= Id -> O; 108 | ({{_,DId},_}, O) when DId =:= Id -> O; 109 | ({K,V}, O) -> provisual_fgraph:add(K,V,O) 110 | end, provisual_fgraph:new(), As) 111 | } 112 | }; 113 | 114 | event({add_edge, E, ancestors}, #model{ es = #es{ ancestors = Ls} = Es } = M) -> 115 | M#model{ 116 | es = Es#es{ 117 | ancestors = provisual_fgraph:add(E, #fg_e{ 118 | k = ?fg_k, 119 | l = ?fg_l 120 | }, Ls) 121 | } 122 | }; 123 | 124 | event({add_edge, E, links}, #model{ es = #es{ links = Ls} = Es } = M) -> 125 | M#model{ 126 | es = Es#es{ 127 | links = provisual_fgraph:add(E, #fg_e{ 128 | k = ?fg_k, 129 | l = ?fg_l 130 | }, Ls) 131 | } 132 | }; 133 | event({add_event, E, _T}, #model{ events = Events } = M) -> 134 | M#model{ events = gb_trees:enter(E,100,Events) }; 135 | 136 | event(Other, M) -> 137 | io:format("Unhandled graph event ~p ~n", [Other]), 138 | M. 139 | -------------------------------------------------------------------------------- /src/provisual_sphere.erl: -------------------------------------------------------------------------------- 1 | %%% File : sphere.erl 2 | %%% Author : 3 | %%% Description : Creates a sphere of triangles 4 | %%% Created : 19 Mar 2009 by 5 | 6 | -module(provisual_sphere). 7 | -export([tris/0, tris/1, norm/1, norm/3]). 8 | 9 | -define(XPLUS, {1.0,0.0,0.0}). 10 | -define(XMIN, {-1.0,0.0,0.0}). 11 | -define(YPLUS, {0.0,1.0,0.0}). 12 | -define(YMIN, {0.0,-1.0,0.0}). 13 | -define(ZPLUS, {0.0,0.0,1.0}). 14 | -define(ZMIN, {0.0,0.0,-1.0}). 15 | 16 | -define(octahedron, 17 | [{?ZPLUS, ?XPLUS, ?YPLUS}, 18 | {?XMIN, ?ZPLUS, ?YPLUS}, 19 | {?ZPLUS, ?XMIN, ?YMIN }, 20 | {?ZPLUS, ?YMIN, ?XPLUS}, 21 | {?YPLUS, ?XPLUS, ?ZMIN }, 22 | {?XMIN, ?YPLUS, ?ZMIN }, 23 | {?YMIN, ?XMIN, ?ZMIN }, 24 | {?XPLUS, ?YMIN, ?ZMIN }]). 25 | 26 | -define(F32, 32/float-native). 27 | 28 | %% equal tris() -> tris([]). 29 | tris() -> 30 | tris([]). 31 | 32 | %% func tris(Options) -> {Size::integer(), Tris::term(), [Extra]} 33 | %% Returns the number of triangles and the triangles in a list 34 | %% or in a binary if option binary is true. 35 | %% Extra contents depends on Options. 36 | %% Options: 37 | %% subd Subdivision level default 1 38 | %% binary All output is binary default false 39 | %% ccw Winding order counter clockwise default true 40 | %% scale Scale output default 1, 41 | %% normals Add normals to the extra list default false 42 | %% Extra = [NormalsIfIncluded] 43 | 44 | tris(Opts) when is_list(Opts) -> 45 | Subd = proplists:get_value(subd, Opts, 1), 46 | Binary = proplists:get_value(binary, Opts, false), 47 | CCW = proplists:get_value(ccw, Opts, true), 48 | Scale = proplists:get_value(scale, Opts, 1), 49 | Normal = proplists:get_value(normals, Opts, false), 50 | %% Do the work 51 | Tris = subd_tris(1, Subd, ?octahedron), 52 | case Binary of 53 | true -> 54 | BinTris = list_to_bin(Tris, CCW, Scale), 55 | Ns = if not Normal -> []; 56 | Scale =:= 1 -> [BinTris]; 57 | true -> [list_to_bin(Tris, CCW, 1)] 58 | end, 59 | {size(BinTris) div (9*4), BinTris, Ns}; 60 | false -> 61 | Scaled = convert_list(Tris, CCW, Scale), 62 | Ns = if not Normal -> []; 63 | Scale =:= 1 -> Scaled; 64 | true -> convert_list(Tris, CCW, 1) 65 | end, 66 | {length(Tris), Scaled, Ns} 67 | end. 68 | 69 | subd_tris(Level, MaxLevel, Sphere0) when Level < MaxLevel -> 70 | Sphere = subd_tris(Sphere0, []), 71 | subd_tris(Level+1, MaxLevel, Sphere); 72 | subd_tris(_,_, Sphere) -> Sphere. 73 | 74 | %% 2 create a, b, c in the middle 75 | %% /\ Normalize a, b, c 76 | %% / \ 77 | %% c/____\ b Construct new triangles 78 | %% /\ /\ [0,b,a] 79 | %% / \ / \ [a,b,c] 80 | %% /____\/____\ [a,c,2] 81 | %% 0 a 1 [b,1,c] 82 | %% 83 | 84 | subd_tris([{V0,V1,V2}|Rest], Acc) -> 85 | A = norm(midpoint(V0,V1)), 86 | B = norm(midpoint(V1,V2)), 87 | C = norm(midpoint(V0,V2)), 88 | T1 = {V0,A,C}, 89 | T2 = {A,B,C}, 90 | T3 = {A,V1,B}, 91 | T4 = {C,B,V2}, 92 | subd_tris(Rest, [T1,T2,T3,T4|Acc]); 93 | subd_tris([],Acc) -> 94 | Acc. 95 | 96 | midpoint({X1,Y1,Z1}, {X2,Y2,Z2}) -> 97 | {(X1+X2)*0.5, (Y1+Y2)*0.5, (Z1+Z2)*0.5}. 98 | 99 | norm({X,Y,Z}) -> 100 | norm(X,Y,Z). 101 | norm(X,Y,Z) -> 102 | Mag = X*X+Y*Y+Z*Z, 103 | if 104 | Mag =:= 0.0 -> {X,Y,Z}; 105 | true -> 106 | Div = 1/math:sqrt(Mag), 107 | {X*Div, Y*Div, Z*Div} 108 | end. 109 | 110 | %%%%%%%%%%%%%% Converters %%%%%%%%%%% 111 | list_to_bin(Tris, true, 1) -> 112 | << <<(X1):?F32,(Y1):?F32,(Z1):?F32, 113 | (X2):?F32,(Y2):?F32,(Z2):?F32, 114 | (X3):?F32,(Y3):?F32,(Z3):?F32>> 115 | || {{X1,Y1,Z1},{X2,Y2,Z2},{X3,Y3,Z3}} <- Tris >>; 116 | list_to_bin(Tris, false, 1) -> 117 | << <<(X1):?F32,(Y1):?F32,(Z1):?F32, 118 | (X2):?F32,(Y2):?F32,(Z2):?F32, 119 | (X3):?F32,(Y3):?F32,(Z3):?F32>> 120 | || {{X1,Y1,Z1},{X3,Y3,Z3},{X2,Y2,Z2}} <- Tris >>; 121 | list_to_bin(Tris, true, Size) -> 122 | << <<(X1*Size):?F32,(Y1*Size):?F32,(Z1*Size):?F32, 123 | (X2*Size):?F32,(Y2*Size):?F32,(Z2*Size):?F32, 124 | (X3*Size):?F32,(Y3*Size):?F32,(Z3*Size):?F32>> 125 | || {{X1,Y1,Z1},{X2,Y2,Z2},{X3,Y3,Z3}} <- Tris >>; 126 | list_to_bin(Tris, false, Size) -> 127 | << <<(X1*Size):?F32,(Y1*Size):?F32,(Z1*Size):?F32, 128 | (X2*Size):?F32,(Y2*Size):?F32,(Z2*Size):?F32, 129 | (X3*Size):?F32,(Y3*Size):?F32,(Z3*Size):?F32>> 130 | || {{X1,Y1,Z1},{X3,Y3,Z3},{X2,Y2,Z2}} <- Tris >>. 131 | 132 | convert_list(List, true, 1) -> 133 | List; 134 | convert_list(List, false, 1) -> 135 | [{V1,V2,V3} || {V1,V3,V2} <- List]; 136 | convert_list(List, true, Size) -> 137 | [{{(X1*Size),(Y1*Size),(Z1*Size)}, 138 | {(X2*Size),(Y2*Size),(Z2*Size)}, 139 | {(X3*Size),(Y3*Size),(Z3*Size)}} 140 | || {{X1,Y1,Z1},{X2,Y2,Z2},{X3,Y3,Z3}} <- List]; 141 | convert_list(List, false, Size) -> 142 | [{{(X1*Size),(Y1*Size),(Z1*Size)}, 143 | {(X2*Size),(Y2*Size),(Z2*Size)}, 144 | {(X3*Size),(Y3*Size),(Z3*Size)}} 145 | || {{X1,Y1,Z1},{X3,Y3,Z3},{X2,Y2,Z2}} <- List]. 146 | -------------------------------------------------------------------------------- /src/provisual_sup.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (C) 2012 Björn-Egil Dahlberg 2 | %% 3 | %% File: provisual_sup.erl 4 | %% Author: Björn-Egil Dahlberg 5 | %% Created: 2012-06-15 6 | 7 | -module(provisual_sup). 8 | 9 | -behaviour(supervisor). 10 | 11 | %% API 12 | -export([start_link/0]). 13 | 14 | %% Supervisor callbacks 15 | -export([init/1]). 16 | 17 | %% Helper macro for declaring children of supervisor 18 | -define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}). 19 | 20 | %% =================================================================== 21 | %% API functions 22 | %% =================================================================== 23 | 24 | start_link() -> 25 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 26 | 27 | %% =================================================================== 28 | %% Supervisor callbacks 29 | %% =================================================================== 30 | 31 | init([]) -> 32 | {ok, { {one_for_one, 5, 10}, []} }. 33 | 34 | -------------------------------------------------------------------------------- /src/provisual_ticker.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (C) 2012 Björn-Egil Dahlberg 2 | %% 3 | %% File: provisual_ticker.erl 4 | %% Author: Björn-Egil Dahlberg 5 | %% Created: 2012-06-15 6 | 7 | -module(provisual_ticker). 8 | -export([start/1]). 9 | 10 | start(Pid) -> spawn_link(fun() -> init(Pid) end). 11 | 12 | init(Pid) -> loop(Pid, 50). 13 | loop(Pid, Time) -> 14 | receive after Time -> 15 | Pid ! {self(), force_step}, 16 | T0 = now(), 17 | receive {Pid, ok} -> ok end, % flow control 18 | T1 = now(), 19 | D = timer:now_diff(T1, T0) div 1000, 20 | case 40 - D of 21 | Ms when Ms < 0 -> loop(Pid, 0); 22 | Ms -> loop(Pid, Ms) 23 | end 24 | end. 25 | -------------------------------------------------------------------------------- /src/provisual_tracer.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (C) 2012 Björn-Egil Dahlberg 2 | %% 3 | %% File: provisual_tracer.erl 4 | %% Author: Björn-Egil Dahlberg 5 | %% Created: 2012-06-15 6 | 7 | -module(provisual_tracer). 8 | -export([start/3]). 9 | 10 | -record(tstate,{ 11 | n = 1, 12 | registry, 13 | procs, 14 | node, 15 | client, 16 | server, 17 | graph, 18 | rs}). 19 | 20 | 21 | start(Node, Graph, Rs) -> 22 | spawn_link(fun() -> init(Node, Graph, Rs) end). 23 | 24 | init(Node, Graph, Rs) -> 25 | 26 | Me = self(), 27 | 28 | %% setup remote tracer 29 | FunStr = 30 | "TraceFun = dbg:trace_port(ip, {4799, 16000}), Fun = fun() -> link(Me), " 31 | "Port = TraceFun()," 32 | "erlang:system_flag(multi_scheduling, block)," 33 | "Data = [{P, erlang:process_info(P)} || P <- erlang:processes()]," 34 | "erlang:system_profile(Port, [runnable_procs])," 35 | "erlang:trace(all, true, [procs, send, {tracer, Port}])," 36 | "Me ! {trace_port, self(), ready}," 37 | "erlang:system_flag(multi_scheduling, unblock)," 38 | "Me ! {trace_port, self(), {data, Data}}, receive stop -> stop end end, Fun.", 39 | 40 | {_ ,Tokens, _} = erl_scan:string(FunStr), 41 | {ok, Abs} = erl_parse:parse_exprs(Tokens), 42 | B = erl_eval:add_binding('Me', Me, erl_eval:new_bindings()), 43 | {value, SetupFun, _} = erl_eval:exprs(Abs, B), 44 | RemotePid = rpc:call(Node, erlang, spawn, [SetupFun]), 45 | 46 | % wait for ready then setup trace receiver 47 | receive {trace_port, RemotePid, ready} -> ok end, 48 | 49 | Handler = fun 50 | ({drop, N}, In) -> 51 | io:format("dropped ~p msg's~n", [N]), 52 | In; 53 | (Msg, In) -> 54 | Me ! Msg, 55 | In 56 | end, 57 | 58 | Client = dbg:trace_client(ip, 4799, {Handler, io}), 59 | 60 | %% receive initial data and populate graph 61 | 62 | Pis = receive {trace_port, RemotePid, {data, Data}} -> Data end, 63 | 64 | {Registry, Procs} = apps(Graph, Pis), 65 | 66 | loop(#tstate{ 67 | registry = Registry, n = 1, rs = Rs, 68 | graph = Graph, node = Node, procs = Procs, 69 | client = Client, server = RemotePid 70 | }). 71 | 72 | loop(#tstate{ graph = Graph, n = N} = S) -> 73 | receive 74 | stop_tracer -> 75 | provisual:clear(Graph), 76 | dbg:stop_trace_client(S#tstate.client), 77 | S#tstate.server ! stop, 78 | ok; 79 | 80 | {profile, _Pid, active, _Mfa, _Ts} -> 81 | loop(S#tstate{ n = N + 1}); 82 | 83 | {profile, _Pid, inactive, _Mfa, _Ts} -> 84 | loop(S#tstate{ n = N - 1}); 85 | 86 | {trace, Pid, register, Name} -> 87 | loop(S#tstate{ registry = gb_trees:enter(Name, Pid, S#tstate.registry)}); 88 | {trace, _Pid, unregister, _Name} -> 89 | loop(S); 90 | 91 | % liveness 92 | {trace, Pid, exit, _ } -> 93 | provisual:del_node(Graph, Pid), 94 | loop(S#tstate{ procs = gb_trees:delete(Pid, S#tstate.procs)}); 95 | {trace, Pid, spawn, Pid2, MFAs} -> 96 | provisual:add_node(Graph, Pid2, {250, 10, 10}, mfa2name(MFAs)), 97 | provisual:add_link(Graph, {Pid, Pid2}, ancestors), 98 | loop(S#tstate{ procs = gb_trees:enter(Pid2, ok, S#tstate.procs)}); 99 | 100 | % linkage 101 | {trace, _Pid, getting_unlinked, _Pid2} -> 102 | loop(S); 103 | {trace, _Pid, getting_linked, _Pid2} -> 104 | loop(S); 105 | {trace, Pid, link, Pid2} -> 106 | provisual:add_link(Graph, {Pid, Pid2}, links), 107 | loop(S); 108 | {trace, Pid, unlink, Pid2} -> 109 | provisual:del_link(Graph, {Pid, Pid2}, links), 110 | loop(S); 111 | 112 | % msgs 113 | {trace, Pid, send_to_non_existing_process, _Msg, Pid2} -> 114 | io:format("Warning: ~p send to non existing process ~p~n", [Pid, Pid2]), 115 | loop(S); 116 | {trace, Pid, send, _, ToPid} -> 117 | handle_trace_send(S, Pid, ToPid), 118 | loop(S); 119 | 120 | _Other -> 121 | loop(S) 122 | end. 123 | 124 | handle_trace_send(#tstate{ graph = Graph, node = Node, procs = Procs}, Pid, Pid2) when is_pid(Pid2), node(Pid2) =:= Node-> 125 | case gb_trees:lookup(Pid2, Procs) of 126 | none -> 127 | io:format("What the crap: ~p~n", [Pid2]), 128 | ok; 129 | _ -> 130 | provisual:add_event(Graph, {Pid, Pid2}, message) 131 | end; 132 | handle_trace_send(#tstate{ node = Node } = S, Pid, {Name, Node}) when is_atom(Name) -> 133 | handle_trace_send(S, Pid, Name); 134 | handle_trace_send(#tstate{ registry = Registry} = S, Pid, Name) when is_atom(Name) -> 135 | case gb_trees:lookup(Name, Registry) of 136 | none -> 137 | io:format("Warning: ~p send to non existing atom process ~p~n", [Pid, Name]), 138 | ok; 139 | {value, Pid2} -> 140 | handle_trace_send(S, Pid, Pid2) 141 | end; 142 | handle_trace_send(_, Pid, ToPid) -> 143 | io:format("Warning: ~p send to unhandled process ~p~n", [Pid, ToPid]), 144 | ok. 145 | 146 | 147 | mfa2name({erlang, apply, [M,F,As]}) -> 148 | mfa2name({M,F,As}); 149 | mfa2name({erlang, apply, [Fun, _]}) when is_function(Fun) -> 150 | {module, M} = erlang:fun_info(Fun, module), 151 | {name, N} = erlang:fun_info(Fun, name), 152 | {arity, A} = erlang:fun_info(Fun, arity), 153 | mfa2name({M,N,A}); 154 | 155 | mfa2name({proc_lib, init_p, [_Parent,_Ancestors,Fun]}) when is_function(Fun) -> 156 | {module, M} = erlang:fun_info(Fun, module), 157 | {name, N} = erlang:fun_info(Fun, name), 158 | {arity, A} = erlang:fun_info(Fun, arity), 159 | mfa2name({M,N,A}); 160 | mfa2name({proc_lib, init_p, [_Parent,_Ancestors,M,F,As]}) -> 161 | mfa2name(trans_init(M,F,As)); 162 | mfa2name({M, F, Args}) when is_list(Args) -> 163 | lists:flatten(io_lib:format("~p:~p/~p", [M,F,length(Args)])); 164 | mfa2name({M, F, A}) -> 165 | lists:flatten(io_lib:format("~p:~p/~p", [M,F,A])); 166 | mfa2name(Other) -> 167 | lists:flatten(io_lib:format("~p", [Other])). 168 | 169 | % translate otp application names (as defined in stdlib) 170 | trans_init(gen,init_it,[gen_server,_,_,supervisor,{_,Module,_},_]) -> 171 | {supervisor,Module,1}; 172 | trans_init(gen,init_it,[gen_server,_,_,_,supervisor,{_,Module,_},_]) -> 173 | {supervisor,Module,1}; 174 | trans_init(gen,init_it,[gen_server,_,_,supervisor_bridge,[Module|_],_]) -> 175 | {supervisor_bridge,Module,1}; 176 | trans_init(gen,init_it,[gen_server,_,_,_,supervisor_bridge,[Module|_],_]) -> 177 | {supervisor_bridge,Module,1}; 178 | trans_init(gen,init_it,[gen_server,_,_,Module,_,_]) -> 179 | {Module,init,1}; 180 | trans_init(gen,init_it,[gen_server,_,_,_,Module|_]) -> 181 | {Module,init,1}; 182 | trans_init(gen,init_it,[gen_fsm,_,_,Module,_,_]) -> 183 | {Module,init,1}; 184 | trans_init(gen,init_it,[gen_fsm,_,_,_,Module|_]) -> 185 | {Module,init,1}; 186 | trans_init(gen,init_it,[gen_event|_]) -> 187 | {gen_event,init_it,6}; 188 | trans_init(M, F, A) when is_atom(M), is_atom(F) -> 189 | {M,F,length(A)}. 190 | 191 | 192 | %% generate pid <-> name registry and populate graph 193 | apps(Graph, Pis) -> 194 | Registry = lists:foldl(fun 195 | ({Pid, Pi}, Tree) -> 196 | case proplists:get_value(registered_name, Pi) of 197 | undefined -> Tree; 198 | Name -> gb_trees:enter(Name, Pid, Tree) 199 | end 200 | end, gb_trees:empty(), Pis), 201 | apps(Graph, Pis, [], Registry, gb_trees:empty(), []). 202 | apps(Graph, [], Parents, Registry, Procs, AllLinks) -> 203 | lists:foreach(fun 204 | ({P1, P2}) -> 205 | case gb_trees:is_defined(P1, Procs) of 206 | true -> 207 | provisual:add_link(Graph, {P1, P2}, ancestors), 208 | ok; 209 | _ -> 210 | ok 211 | end 212 | end, Parents), 213 | lists:foreach(fun 214 | ({Pid, Links}) -> 215 | lists:foreach(fun 216 | (Link) when is_pid(Link) -> 217 | case gb_trees:is_defined(Link, Procs) of 218 | true -> 219 | % io:format("link ~p~n", [Link]), 220 | provisual:add_link(Graph, {Pid, Link}, links); 221 | false -> 222 | ok 223 | end; 224 | (_) -> 225 | ok 226 | end, Links) 227 | end, AllLinks), 228 | {Registry, Procs}; 229 | apps(Graph, [{Pid, Pi}|Pis], Parents, Registry, Procs, AllLinks) -> 230 | _Status = proplists:get_value(status, Pi), 231 | InitialCall = proplists:get_value(initial_call, Pi), 232 | RegisteredName = proplists:get_value(registered_name, Pi), 233 | Links = proplists:get_value(links, Pi, []), 234 | 235 | {Name, Relations} = case {InitialCall, RegisteredName} of 236 | {{proc_lib, init_p, 5}, _} -> 237 | Dict = proplists:get_value(dictionary, Pi, []), 238 | Ic = proplists:get_value('$initial_call', Dict, {proc_lib, init_p, 5}), 239 | Pl = case proplists:get_value('$ancestors', Dict) of 240 | [ParentAtom|_] when is_atom(ParentAtom) -> 241 | case gb_trees:lookup(ParentAtom, Registry) of 242 | none -> 243 | io:format("~p not defined~n", [ParentAtom]), 244 | []; 245 | {value, Parent} -> 246 | [{Parent,Pid}] 247 | end; 248 | [Parent|_] -> 249 | [{Parent,Pid}]; 250 | _ -> 251 | [] 252 | end, 253 | {mfa2name(Ic), Pl}; 254 | {Mfa, undefined} -> {mfa2name(Mfa), []}; 255 | {_, _Register} -> {mfa2name(RegisteredName), []} 256 | end, 257 | provisual:add_node(Graph, Pid, {250,10,10}, Name), 258 | apps(Graph, Pis, Relations ++ Parents, Registry, gb_trees:enter(Pid, ok, Procs), [{Pid, Links}|AllLinks]). 259 | 260 | 261 | 262 | 263 | -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | erl -pa ebin -sname provisual -run provisual 4 | --------------------------------------------------------------------------------