/* ================================================================================== TUIOSmoke - a TUIO/OSC client implementation of the popular smoke demo Copyright (C) 2009 Patrick King This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ================================================================================== */ #include "TuioSmoke.h" void TuioSmoke::addTuioObject(TuioObject *tobj) { if (debug) { std::cout << "add obj " << tobj->getSymbolID() << " (" << tobj->getSessionID() << ") "<< tobj->getX() << " " << tobj->getY() << " " << tobj->getAngle() << std::endl; } } void TuioSmoke::updateTuioObject(TuioObject *tobj) { if (debug) { std::cout << "set obj " << tobj->getSymbolID() << " (" << tobj->getSessionID() << ") "<< tobj->getX() << " " << tobj->getY() << " " << tobj->getAngle() << " " << tobj->getMotionSpeed() << " " << tobj->getRotationSpeed() << " " << tobj->getMotionAccel() << " " << tobj->getRotationAccel() << std::endl; } } void TuioSmoke::removeTuioObject(TuioObject *tobj) { if (debug) { std::cout << "del obj " << tobj->getSymbolID() << " (" << tobj->getSessionID() << ")" << std::endl; } } void TuioSmoke::addTuioCursor(TuioCursor *tcur) { if (debug) { std::cout << "add cur " << tcur->getCursorID() << " (" << tcur->getSessionID() << ") " << tcur->getX() << " " << tcur->getY() << std::endl; } // Add this touch to the color/finger maps // Generate a random color for this touch // Add the touch to the finger map TouchMap nc; nc.ID = tcur->getCursorID(); nc.tagID = tcur->getSessionID(); nc.X = tcur->getX(); nc.Y = tcur->getY(); nc.lX = nc.X; nc.lY = nc.Y; nc.lasttouched = SDL_GetTicks(); RGB_PixelFloat cf; const float div = 1.0 / (RAND_MAX); cf.r = (float) rand() * div; cf.b = (float) rand() * div; cf.g = (float) rand() * div; colormap[nc.ID] = cf; fingermap[nc.ID] = nc; } void TuioSmoke::updateTuioCursor(TuioCursor *tcur) { // Update the fluid to match the movement of this finger TouchMap nc = fingermap[tcur->getCursorID()]; nc.X = tcur->getX(); nc.Y = tcur->getY(); // I'm sometimes getting touch data that doesn't have any values // this is causing a 'stream' of smoke to spawn from the 0,0 coordinate // to fix this, we simply won't drag if we don't have coordinates if (! (nc.X || nc.Y)) return; // For some reason some CCV isn't sending proper dX/dY data // *FIXME: when tracker data is available, this is a waste, // this workaround will have to be removed once the tracker // outputs reliable velocity data float timeelapsed = (SDL_GetTicks() - nc.lasttouched); // calculate the velocity vectors float a, b, c; a = nc.X - nc.lX; b = nc.Y - nc.lY; if ( a && b && timeelapsed ) { // avoid dividing 0 or dividing by 0, GL doens't like indeterminite values nc.dX = a / ( timeelapsed / 100 ); nc.dY = b / ( timeelapsed / 100 ); } else { nc.dX = nc.dY = 0 ; } // --------------------------- //nc.dX = tcur->getXSpeed(); //nc.dY = tcur->getYSpeed(); if (debug) { std::cout << "update [" << nc.ID << "] " << " X:"<< nc.X << ", lX:" << nc.lX << ", dX:" << nc.dX << " Y:"<< nc.Y << ", lY:" << nc.lY << ", dY:" << nc.dY << "\n osc dX/dY:" << tcur->getXSpeed() << " / " << tcur->getYSpeed() << std::endl; std::cout << " time elapsed since last move: " << timeelapsed << std::endl; } // Originally smoke only updated position once per move // on fast movements, this left large gaps or stutter in the fluid drag // Here, we figure out the distance between the points and interpolate // drags along that tangental path // bigger gaps get updated more times, this results in much smoother motion // especially at very high framerates // calculate the acceleration float ad, bd, cd; ad = nc.dX - nc.ldX; bd = nc.dY - nc.ldY; if ( nc.X < nc.lX ) a = nc.lX - nc.X; if ( nc.Y < nc.lY ) b = nc.lY - nc.Y; if ( nc.dX < nc.ldX ) ad = nc.ldX - nc.dX; if ( nc.dY < nc.ldY ) bd = nc.ldY - nc.dY; // calculate the tangent between the last point and the new point c = sqrt ( ( a*a ) + ( b*b ) ); // positional tangent cd = sqrt ( ( ad*ad ) + ( bd * bd ) ); // velocity tangent float k = 0; float cx, cy; // intermediary positions float cdx, cdy; // intermediary velocities if (c && ( nc.lX || nc.lY )) { // sometimes the last move gets sent twice, with 0 diff // only drag if there was actual movement while ( k <= 100.0f ) { // some vector math to calculate the intermediary positions and velocities cx = nc.lX - ( (k/100.0f) * ( nc.lX - nc.X )); cy = nc.lY - ( (k/100.0f) * ( nc.lY - nc.Y )); cdx = nc.ldX - ( (k/100.0f) * ( nc.ldX - nc.dX)); cdy = nc.ldY - ( (k/100.0f) * ( nc.ldY - nc.dY)); // don't draw the last position twice if (cx == nc.X && cy == nc.Y ) { break; } fluid->Drag( cx, 1 - cy, cdx, (-1 * cdy) , colormap[nc.ID].r, colormap[nc.ID].g, colormap[nc.ID].b ); // increment by a fraction of the tangent length // - this avoids drawing too often on short moves k += 1 / (c * 2) ; } } nc.lasttouched = SDL_GetTicks(); nc.lX = nc.X; nc.lY = nc.Y; nc.ldX = nc.dX; nc.ldY = nc.dY; fingermap[nc.ID] = nc; } void TuioSmoke::removeTuioCursor(TuioCursor *tcur) { if (debug) { std::cout << "del cur " << tcur->getCursorID() << " (" << tcur->getSessionID() << ")" << std::endl; } // Remove this touch from the color/finger maps colormap.erase( tcur->getCursorID() ); fingermap.erase( tcur->getCursorID() ); } void TuioSmoke::refresh(TuioTime frameTime) { if (debug) { //std::cout << "refresh " << frameTime.getTotalMilliseconds() << std::endl; } } void TuioSmoke::drawFluidField() { // Draw the fluid field int i, j, idx; const int n = fl_meshsize; fftw_real wn = (fftw_real)c_width / (fftw_real)(n - 1); /* Grid element width */ fftw_real hn = (fftw_real)c_height / (fftw_real)(n - 1); /* Grid element height */ float px, py; const fftw_real* r = fluid->getDensityFieldR(); const fftw_real* g = fluid->getDensityFieldG(); const fftw_real* b = fluid->getDensityFieldB(); if (wireframe) { glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); } else { glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } for (j = 0; j < n - 1; j++) { glBegin(GL_TRIANGLE_STRIP); i = 0; px = (fftw_real)i * wn; py = (fftw_real)j * hn; idx = (j * n) + i; glColor3f(r[idx], g[idx], b[idx]); glVertex2f(px, py); for (i = 0; i < n - 1; i++) { px =(fftw_real)i * wn; py =(fftw_real)(j + 1) * hn; idx = ((j + 1) * n) + i; glColor3f(r[idx], g[idx], b[idx]); glVertex2f(px, py); px =(fftw_real)(i + 1) * wn; py =(fftw_real)j * hn; idx = (j * n) + (i + 1); glColor3f(r[idx], g[idx], b[idx]); glVertex2f(px, py); } px =(fftw_real)(n - 1) * wn; py =(fftw_real)(j + 1) * hn; idx = ((j + 1) * n) + (n - 1); glColor3f(r[idx], g[idx], b[idx]); glVertex2f(px, py); glEnd(); } } void TuioSmoke::drawVelocityMap() { int i, j, idx; const int n = fl_meshsize; fftw_real wn = (fftw_real)c_width / (fftw_real)(n - 1); /* Grid element width */ fftw_real hn = (fftw_real)c_height / (fftw_real)(n - 1); /* Grid element height */ const fftw_real* u = fluid->getVelocityFieldX(); const fftw_real* v = fluid->getVelocityFieldY(); glEnable(GL_BLEND); glBlendFunc( GL_ONE_MINUS_DST_COLOR, GL_ONE ); glBegin(GL_LINES); for (i = 0; i < n; i++) { for (j = 0; j < n; j++) { idx = (j * n) + i; glColor3f(1, 0, 0); glVertex2f( (fftw_real)i * wn, (fftw_real)j * hn); glVertex2f(((fftw_real)i * wn) + 1000 * u[idx], ( (fftw_real)j * hn) + 1000 * v[idx]); } } glEnd(); glDisable(GL_BLEND); } void TuioSmoke::drawFingers() { const int SEGMENTS = 12; const float DEG2RAD = 2*3.14159/SEGMENTS; const float RADIUS = 10.0; std::list cursorList = tuioClient->getTuioCursors(); // Lock the cursor list to prevent changes while processing the current list tuioClient->lockCursorList(); int cursorCount = 0; for ( std::list::iterator iter = cursorList.begin(); iter != cursorList.end(); iter++ ) { cursorCount++; TuioCursor *tuioCursor = (*iter); float dataX = tuioCursor->getX(); float dataY = 1 - tuioCursor->getY(); glColor3f( 1.0f, 1.0f, 1.0f ); glBegin(GL_LINE_LOOP); for (int i=0; i < SEGMENTS; i++) { float degInRad = i*DEG2RAD; glVertex2f(dataX*c_width + cos(degInRad)*RADIUS, dataY*c_height + sin(degInRad)*RADIUS); // wasteful to recalculate sin/cos } glEnd(); } tuioClient->unlockCursorList(); } void TuioSmoke::handleSDLEvent() { GLfloat nscale; switch ( event.type ) { case SDL_KEYDOWN: // if (debug) std::cout << "Caught keypress: " << event.key.keysym.sym << std::endl; switch ( event.key.keysym.sym ) { case SDLK_ESCAPE: case SDLK_q: if (debug) std::cout << "Got exit, cleaning up and quitting." << std::endl; exit(0); break; case SDLK_f: if (fullscreen) { if (debug) std::cout << "Starting window - X:" << width << " Y:" << height << " bpp:" << colordepth << std::endl; resizeWindow(width, height, false); } else { if (debug) std::cout << "Starting window - X:" << f_width << " Y:" << f_height << " bpp:" << colordepth << std::endl; resizeWindow(f_width, f_height, true); } fullscreen = !fullscreen; // hide the mouse cursor in fullscreen mode SDL_ShowCursor(!fullscreen); if (debug) std::cout << "Fullscreen: "<< fullscreen << std::endl; break; case SDLK_c: calcFPS = !calcFPS; if (debug) std::cout << "Framerate display: " << calcFPS << std::endl; toggleFPS( calcFPS ); break; case SDLK_w: wireframe = !wireframe; if (debug) std::cout << "Wireframe display: " << wireframe << std::endl; break; case SDLK_d: draw_fingers = !draw_fingers; if (debug) std::cout << "Drawing fingers: " << draw_fingers << std::endl; break; case SDLK_v: draw_velocity_map = !draw_velocity_map; if (debug) std::cout << "Velocity map: " << draw_velocity_map; toggleVelocityMap ( draw_velocity_map ); break; case SDLK_z: //unused break; case SDLK_UP: framerate->setRCoordY( framerate->getY() + 1.0f); if (debug) std::cout << "FPS pos: " << framerate->getX() << " : " << framerate->getY() << std::endl; break; case SDLK_DOWN: framerate->setRCoordY( framerate->getY() - 1.0f); if (debug) std::cout << "FPS pos: " << framerate->getX() << " : " << framerate->getY() << std::endl; break; case SDLK_LEFT: framerate->setRCoordX ( framerate->getX() - 1.0f); if (debug) std::cout << "FPS pos: " << framerate->getX() << " : " << framerate->getY() << std::endl; break; case SDLK_RIGHT: framerate->setRCoordX ( framerate->getX() + 1.0f); if (debug) std::cout << "FPS pos: " << framerate->getX() << " : " << framerate->getY() << std::endl; break; case SDLK_LESS: case SDLK_COMMA: nscale = framerate->getScale() - 0.1; if (nscale >= 0.0f) framerate->setScale( nscale ); break; case SDLK_GREATER: case SDLK_PERIOD: nscale = framerate->getScale() + 0.1; if (nscale <= 10.0f) // don't scale forever framerate->setScale( nscale ); break; default: if (debug) std::cout << "Caught an unused key." << std::endl; break; } break; case SDL_QUIT: if (debug) std::cout << "Got exit, cleaning up and quitting." << std::endl; exit(0); break; case SDL_VIDEORESIZE: int w, h; w = event.resize.w; h = event.resize.h; if (debug) std::cout << "Got a resize event." << std::endl << "New size: " << w << " x " << h << std::endl; resizeWindow(w, h, false); break; default: break; }; } void TuioSmoke::setFrameDelay ( int delayTime ) { // 1k millisec in 1 sec frameDelay = delayTime; } void TuioSmoke::toggleFPS ( bool toggle ) { calcFPS = toggle; if (toggle) { // Reset FPS counters timebase = 0; s_time = 0; fps = 0; frame = 0; } } void TuioSmoke::toggleVelocityMap ( bool toggle ) { draw_velocity_map = toggle; } std::string TuioSmoke::itos ( int i ) { std::stringstream s; s << i; return s.str(); } void TuioSmoke::setFontFile ( std::string font ) { fontfile = font; } void TuioSmoke::setupFluid ( int mesh, float tstep, float visc ) { fluid = new Fluid2D ( mesh, tstep, visc ); fl_meshsize = mesh; fl_timestep = tstep; fl_viscosity = visc; wireframe = false; } void TuioSmoke::resizeWindow ( int w, int h, bool setFullscreen = false ) { int flags = defaultFlags; if ( setFullscreen ) { flags |= fullscreenFlags; } else { flags |= windowedFlags; }; if (!initSDL()) { std::cerr << "Could not initialize SDL parameters, quitting." << std::endl; exit(1); } // update the window dimensions screen = SDL_SetVideoMode( w, h, colordepth, flags ); if ( screen == 0 ) { std::cerr << "Could not initialize window." << std::endl; exit(1); } // reinitialize the OpenGL display if (debug) std::cout << "\t\tSetting OpenGL Parameters" << std::endl; if (!initGL()) { std::cerr << "Could not initialize OpenGL parameters, quitting." << std::endl; exit(1); } // update the viewport GLfloat ratio; if (h == 0) { h = 1; } // avoid div/0 ratio = (GLfloat)w / (GLfloat)h; if (debug) std::cout << "\t\tSetting viewport" << std::endl; glViewport( 0, 0, (GLint)w, (GLint)h ); glMatrixMode(GL_PROJECTION); glLoadIdentity(); if (debug) std::cout << "\t\tSetting Orthographic view" << std::endl; gluOrtho2D( 0, (GLfloat)w, 0, (GLfloat)h ); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); // update the 'current' width/height c_width = w; c_height = h; if (debug) std::cout << "\t\tWindow resize OK" << std::endl; } bool TuioSmoke::initSDL() { if (debug) std::cout << "\tSetting SDL WM options" << std::endl; SDL_WM_SetCaption( windowName, windowName ); if (debug) std::cout << "\tSetting SDL-GL parameters" << std::endl; if (debug) std::cout << "\t\tSetting color space" << std::endl; SDL_GL_SetAttribute( SDL_GL_RED_SIZE, 8 ); SDL_GL_SetAttribute( SDL_GL_GREEN_SIZE, 8 ); SDL_GL_SetAttribute( SDL_GL_BLUE_SIZE, 8 ); if (debug) std::cout << "\t\tSetting depth size" << std::endl; SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE, 16 ); if (debug) std::cout << "\t\tEnabling double-buffering" << std::endl; SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 ); if (debug) std::cout << "\t\tEnabling SDL-GL HW acceleration (if applicable)" << std::endl; SDL_GL_SetAttribute( SDL_GL_ACCELERATED_VISUAL, 1 ); return true; } bool TuioSmoke::initGL() { // Reload the font once we've resized the window if (debug) std::cout << "\tSetting up OpenGL parameters" << std::endl; /* Enable smooth shading */ if (debug) std::cout << "\t\tSetting up shader" << std::endl; glShadeModel( GL_SMOOTH ); /* Set the background black */ if (debug) std::cout << "\t\tSetting viewport background" << std::endl; glClearColor( 0.0f, 0.0f, 0.0f, 0.0f ); /* Depth buffer setup */ if (debug) std::cout << "\t\tSetting viewport depth" << std::endl; glClearDepth( 1.0f ); /* Enables Depth Testing */ if (debug) std::cout << "\t\tEnabling viewport depth testing" << std::endl; glEnable( GL_DEPTH_TEST ); /* The Type Of Depth Test To Do */ if (debug) std::cout << "\t\tSetting viewport depth function" << std::endl; glDepthFunc( GL_ALWAYS ); /* Really Nice Perspective Calculations */ if (debug) std::cout << "\t\tSetting perspective calculation engine" << std::endl; glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST ); framerate->buildFont(); return true; } void TuioSmoke::initSmoke ( std::string setWindowName, int w, int h, bool setFullscreen, int fW = 0, int fH = 0 ) { isInitialized = false; // Setup the screen, fancy graphics port, etc etc if (debug) std::cout << "Setting up smoke screen" << std::endl; // Setup the SDL window if (debug) std::cout << "\tGetting display info" << std::endl; info = SDL_GetVideoInfo(); colordepth = info->vfmt->BitsPerPixel; // Handle fullscreen size // if a size was specified from the cmd line, use it here // otherwise use the size specified by the window manager if ( fW && fH ) { f_width = fW; f_height = fH; } else { f_width = info->current_w; f_height = info->current_h; } // grab the passed values windowName = setWindowName.c_str(); fullscreen = setFullscreen; width = w; height = h; // Setup the FPS calculator variables timebase = 0; s_time = 0; fps = 0; calcFPS = false; fluid_count = 0; fluid_fps = 0; // Set the default frame delay setFrameDelay(10); // Don't draw fingers by default draw_fingers = false; // Don't draw velocity map by default draw_velocity_map = false; // Setup the framerate display if (debug) std::cout << "Setting up framerate display" << std::endl; framerate = new GLText(debug); // set the framerate display coords framerate->setRCoords(5.0f, 5.0f); // initialize the text to an empty string framerate->updateText(); int flags = defaultFlags; int d_width = 0; int d_height = 0; if (fullscreen) { flags |= fullscreenFlags; c_width = f_width; c_height = f_height; d_width = f_width; d_height = f_height; } else { flags |= windowedFlags; c_width = width; c_height = height; d_width = width; d_height = height; } // If we have a hardware surface available, use it if ( info->hw_available) { if (debug) std::cout << "\tDetected hardware surface" << std::endl; flags |= SDL_HWSURFACE; } else { if (debug) std::cout << "\tDetected software surface" << std::endl; flags |= SDL_SWSURFACE; } if ( info->blit_hw ) { if (debug) std::cout << "\tDetected SDL hardware acceleration" << std::endl; flags |= SDL_HWACCEL; } // Initialize the window to the proper size if (fullscreen) { if (debug) std::cout << "Starting window: fullscreen -- " << " W:" << d_width << " H:" << d_height << " bpp:" << colordepth << std::endl; resizeWindow(f_width, f_height); // Disable the cursor in fullscreen mode SDL_ShowCursor(!fullscreen); } else { if (debug) std::cout << "Starting window: windowed -- " << " W:" << d_width << " H:" << d_height << " bpp:" << colordepth << std::endl; resizeWindow(width, height); } isInitialized = true; } void TuioSmoke::s_spinFluid() { if (debug) std::cout << "Starting Display loop" << std::endl; while (1) { //if (debug) std::cout << "Evolving Fluid" << std::endl; fluid->Evolve(); fluid_count++; SDL_Delay( frameDelay ); } } void TuioSmoke::s_spinDisplay() { while (1) { while (SDL_PollEvent(&event)) { handleSDLEvent(); } if ( calcFPS ) { frame++; s_time = SDL_GetTicks(); // Update the framerate text after 1000 ticks have passed if (s_time - timebase > 1000) { fps = (frame * 1000.0) / (s_time-timebase); fluid_fps = ( fluid_count * 1000.0 ) / (s_time-timebase); timebase = s_time; frame=0; fluid_count=0; framerate->updateText("FPS: " + itos ( fps ) + " FluidFPS: " + itos ( fluid_fps ) ); } } // Draw the scene drawWindow(); #ifdef WIN32 // Until I figure out threading with WIN32, we'll have to // process the fluid evolution in the main loop :( fluid->Evolve(); fluid_count++; #endif // Add a slight delay so we don't hog the CPU SDL_Delay( frameDelay ); } } void TuioSmoke::drawWindow( ) { glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); glLoadIdentity(); drawFluidField(); if (draw_fingers) drawFingers(); if (draw_velocity_map) drawVelocityMap(); if (calcFPS) framerate->drawText(); glFlush(); SDL_GL_SwapBuffers(); } TuioSmoke::TuioSmoke(int port, bool setDebug=false) { // Connect our TUIO listener client to the specified UDP port tuioClient = new TuioClient(port); tuioClient->addTuioListener(this); tuioClient->connect(); debug = setDebug; // Make sure we were able to connect if ( !tuioClient->isConnected() ) { std::cout << "Could not initialize TUIO Client\n"; exit(1); } else { if (debug) { std::cout << "TUIO socket connected, waiting for data.\n"; } } }