/* * Thin Plate Spline demo/example in C++ * * - a simple TPS editor, using the Boost uBlas library for large * matrix operations and OpenGL + GLUT for 2D function visualization * (curved plane) and user interface * * Copyright (C) 2003,2005 by Jarno Elonen * * TPSDemo is Free Software / Open Source with a very permissive * license: * * Permission to use, copy, modify, distribute and sell this software * and its documentation for any purpose is hereby granted without fee, * provided that the above copyright notice appear in all copies and * that both that copyright notice and this permission notice appear * in supporting documentation. The authors make no representations * about the suitability of this software for any purpose. * It is provided "as is" without express or implied warranty. * * TODO: * - implement TPS approximation 3 as suggested in paper * Gianluca Donato and Serge Belongie, 2002: "Approximation * Methods for Thin Plate Spline Mappings and Principal Warps" */ #include #include #include "linalg3d.h" #include "ludecomposition.h" #include #include using namespace boost::numeric::ublas; // ========= BEGIN INTERESTING STUFF ========= #define GRID_W 100 #define GRID_H 100 static float grid[GRID_W][GRID_H]; std::vector< Vec > control_points; int selected_cp = -1; double regularization = 0.0; double bending_energy = 0.0; static double tps_base_func(double r) { if ( r == 0.0 ) return 0.0; else return r*r * log(r); } /* * Calculate Thin Plate Spline (TPS) weights from * control points and build a new height grid by * interpolating with them. */ static void calc_tps() { // You We need at least 3 points to define a plane if ( control_points.size() < 3 ) return; unsigned p = control_points.size(); // Allocate the matrix and vector matrix mtx_l(p+3, p+3); matrix mtx_v(p+3, 1); matrix mtx_orig_k(p, p); // Fill K (p x p, upper left of L) and calculate // mean edge length from control points // // K is symmetrical so we really have to // calculate only about half of the coefficients. double a = 0.0; for ( unsigned i=0; i w( p, 1 ); for ( int i=0; i be = prod( prod >( trans(w), mtx_orig_k ), w ); bending_energy = be(0,0); } // ========= END INTERESTING STUFF ========= // (The rest is essentially just visualization with OpenGL, // altough that can for sure be interesting, too.) static int winW = 800, winH = 600; static int mouseX = -999, mouseY = -999; static bool mouseState[3] = {false}; static int modifiers = 0; Vec cursor_loc; static float camAlpha=30, camBeta=5, camZoom=0; static bool screen_dirty=true; static void clear_grid() { for (int x=0; x 160 ) fov = 160; gluPerspective( fov, (float)winW/(float)winH, 1.0, 500.0 ); gluLookAt( cam_loc.x, cam_loc.y, cam_loc.z, // eye 0, 0, 0, // target cam_up.x, cam_up.y, cam_up.z ); // up // Curve surface glEnable(GL_LIGHTING) ; glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); glBegin( GL_QUADS ); static GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.0 }; static GLfloat mat_shininess[] = { 100.0 }; for ( int x=-GRID_W/2; x= 0 ) { control_points.erase( control_points.begin() + selected_cp ); selected_cp = -1; calc_tps(); } break; case 'c': control_points.clear(); clear_grid(); break; case '+': regularization += 0.025; calc_tps(); break; case '-': regularization -= 0.025; if (regularization < 0) regularization = 0; calc_tps(); break; case '/': camZoom -= 1; break; case '*': camZoom += 1; break; case 'q': exit( 0 ); break; } screen_dirty=true; } // OGL: mouse button callback static void mouse( int button, int state, int, int ) { mouseState[ button ] = (state==GLUT_DOWN); modifiers = glutGetModifiers(); glutSetCursor( GLUT_CURSOR_CROSSHAIR ); if ( button == 1 && state==GLUT_DOWN ) glutSetCursor( GLUT_CURSOR_CYCLE ); if ( button == 0 ) { if ( state==GLUT_UP ) { calc_tps(); screen_dirty=true; } else if ( state==GLUT_DOWN && selected_cp<0 ) keyboard( 'a', 0,0 ); } } // OGL: mouse movement callback static void mouseMotion( int x, int y ) { if ( mouseState[0] && mouseX != -999 ) if ( selected_cp >= 0 ) control_points[selected_cp].y += -(y - mouseY)/3; if ( mouseState[1] && mouseX != -999 ) { camAlpha += -(y - mouseY); camBeta += (x - mouseX); screen_dirty=true; } if ( mouseX != x || mouseY != y ) { mouseX = x; mouseY = y; screen_dirty=true; } } // OGL: keyboard press callback for special characters static void keyboard_special( int key, int, int ) { switch (key) { case GLUT_KEY_UP: camAlpha += 5 ; break; case GLUT_KEY_DOWN: camAlpha += -5 ; break; case GLUT_KEY_RIGHT: camBeta += -5 ; break; case GLUT_KEY_LEFT: camBeta += 5 ; break; } screen_dirty=true; } // OGL: menu selection callback static void menu_select(int mode) { keyboard( (unsigned char)mode, 0,0 ); } static void create_menu(void) { glutCreateMenu(menu_select); glutAddMenuEntry(" d Delete control point",'d'); glutAddMenuEntry(" a Add control point",'a'); glutAddMenuEntry(" c Clear all",'c'); glutAddMenuEntry(" + Relax more",'+'); glutAddMenuEntry(" - Rela less",'-'); glutAddMenuEntry(" / Zoom in",'/'); glutAddMenuEntry(" * Zoom out",'*'); glutAddMenuEntry(" q Exit",'q'); glutAttachMenu(GLUT_RIGHT_BUTTON); } // Print out OpenGL errors, if any static void glCheckErrors() { GLenum errCode = glGetError(); if ( errCode != GL_NO_ERROR ) { const GLubyte *errString = gluErrorString( errCode ); fprintf(stderr, "OpenGL error: %s\n", errString); } } // OGL: idle callback, continuosly keep redrawing static void idlefunc() { glCheckErrors(); if (screen_dirty) glutPostRedisplay(); } // Startup int main( int argc, char *argv[] ) { glutInit( &argc, argv ); glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH ); glutInitWindowSize( winW, winH ); glutInitWindowPosition( 0, 0 ); if ( !glutCreateWindow( "tpldemo" ) ) { printf( "Couldn't open window.\n" ); return 1; } glutDisplayFunc( display ); glutIdleFunc( idlefunc ); glutMouseFunc( mouse ); glutMotionFunc( mouseMotion ); glutPassiveMotionFunc( mouseMotion ); glutKeyboardFunc( keyboard ); glutSpecialFunc( keyboard_special ); glutReshapeFunc( reshape ); glutSetCursor( GLUT_CURSOR_CROSSHAIR ); glEnable(GL_LIGHTING); GLfloat lightAmbient[] = {0.5f, 0.5f, 0.5f, 1.0f} ; glLightModelfv(GL_LIGHT_MODEL_TWO_SIDE, lightAmbient); glEnable(GL_LIGHT0); GLfloat light0Position[] = {0.7f, 0.5f, 0.9f, 0.0f} ; GLfloat light0Ambient[] = {0.0f, 0.5f, 1.0f, 0.8f} ; GLfloat light0Diffuse[] = {0.0f, 0.5f, 1.0f, 0.8f} ; GLfloat light0Specular[] = {0.0f, 0.5f, 1.0f, 0.8f} ; glLightfv(GL_LIGHT0, GL_POSITION,light0Position) ; glLightfv(GL_LIGHT0, GL_AMBIENT,light0Ambient) ; glLightfv(GL_LIGHT0, GL_DIFFUSE,light0Diffuse) ; glLightfv(GL_LIGHT0, GL_SPECULAR,light0Specular) ; glEnable(GL_LIGHT1); GLfloat light1Position[] = {0.5f, 0.7f, 0.2f, 0.0f} ; GLfloat light1Ambient[] = {1.0f, 0.5f, 0.0f, 0.8f} ; GLfloat light1Diffuse[] = {1.0f, 0.5f, 0.0f, 0.8f} ; GLfloat light1Specular[] = {1.0f, 0.5f, 0.0f, 0.8f} ; glLightfv(GL_LIGHT1, GL_POSITION,light1Position) ; glLightfv(GL_LIGHT1, GL_AMBIENT,light1Ambient) ; glLightfv(GL_LIGHT1, GL_DIFFUSE,light1Diffuse) ; glLightfv(GL_LIGHT1, GL_SPECULAR,light1Specular) ; glCheckErrors(); clear_grid(); create_menu() ; glutMainLoop(); return 0; }