From 43251deb9f64963dcd199de101dfe5ddea8f2e0d Mon Sep 17 00:00:00 2001 From: Philip Rosedale Date: Mon, 15 Oct 2012 00:48:25 -0700 Subject: [PATCH] Added adjustable test latency buffer, stroked characters, code cleanup. --- SerialInterface.cpp | 201 +-------- SerialInterface.h | 51 +-- hand.cpp | 13 +- .../UserInterfaceState.xcuserstate | Bin 96342 -> 97315 bytes .../xcdebugger/Breakpoints.xcbkptlist | 17 +- main.cpp | 393 +++++++++--------- network.cpp | 51 ++- network.h | 7 +- particle.cpp | 2 +- particle.h | 3 +- util.cpp | 37 +- util.h | 3 + 12 files changed, 318 insertions(+), 460 deletions(-) diff --git a/SerialInterface.cpp b/SerialInterface.cpp index 93e58e73df..427c184812 100644 --- a/SerialInterface.cpp +++ b/SerialInterface.cpp @@ -12,7 +12,7 @@ #include #include -char SERIAL_PORT_NAME[] = "/dev/tty.usbmodem411"; + int serial_fd; const int MAX_BUFFER = 100; @@ -72,12 +72,15 @@ int read_sensors(int first_measurement, float * avg_adc_channels, int * adc_chan // At end - Extract value from string to variables if (serial_buffer[0] != 'p') { - sscanf(serial_buffer, "%d %d %d %d", + sscanf(serial_buffer, "%d %d %d %d %d %d %d", &adc_channels[0], &adc_channels[1], &adc_channels[2], - &adc_channels[3]); - for (int i = 0; i < 4; i++) + &adc_channels[3], + &adc_channels[4], + &adc_channels[5], + &adc_channels[6]); + for (int i = 0; i < NUM_CHANNELS; i++) { if (!first_measurement) avg_adc_channels[i] = (1.f - AVG_RATE)*avg_adc_channels[i] + @@ -92,200 +95,10 @@ int read_sensors(int first_measurement, float * avg_adc_channels, int * adc_chan while(serial_buffer_pos++ < MAX_BUFFER) serial_buffer[serial_buffer_pos] = ' '; serial_buffer_pos = 0; } - /* - if (bufchar[0] == 'p') - { - gettimeofday(&end_ping, NULL); - ping = diffclock(begin_ping,end_ping); - display_ping = 1; - } - */ } return samples_read; } -bool SerialInterface::enable () { - if (!_enabled) { - // Try to re-initialize the interface. - // May fail (if open() fails), in which case an error code is emitted - // (which we can safely ignore), and _enabled is reset to false - initInterface(); - } - return _enabled; -} -void SerialInterface::disable () { - _enabled = false; - closeInterface(); -} - - -void SerialInterface::closeInterface () { - close(_serial_fd); - _enabled = false; -} - -int SerialInterface::initInterface () { - if (_enabled) { - _serial_fd = open("/dev/tty.usbmodem411", O_RDWR | O_NOCTTY | O_NDELAY); // List usbSerial devices using Terminal ls /dev/tty.* - - if (_serial_fd == -1) { - std::cerr << "Unable to open serial port (" << _serial_fd << ")\n"; - _enabled = false; - return 1; - } else { - //init_port(&_serial_fd, 115200); - } - } - return 0; -} - -// Reads data from a serial interface and updates gyro and accelerometer state -// TODO: implement accelerometer -void SerialInterface::readSensors (float deltaTime) { - // Note: changed to use binary format instead of plaintext - // (Changed in balance_maple.pde as well (toggleable)) - - - /* - int lines_read = 0; - const float AVG_RATE = 0.00001; - if (_enabled) - { - char bufchar[1]; - while (read(_serial_fd, bufchar, 1) > 0) - { - serial_buffer[serial_buffer_pos] = bufchar[0]; - serial_buffer_pos++; - // Have we reached end of a line of input? - if ((bufchar[0] == '\n') || (serial_buffer_pos >= MAX_BUFFER)) - { - lines_read++; - // At end - Extract value from string to variables - if (serial_buffer[0] != 'p') - { - samplecount++; - sscanf(serial_buffer, "%d %d %d %d", &adc_channels[0], - &adc_channels[1], - &adc_channels[2], - &adc_channels[3]); - for (int i = 0; i < 4; i++) - { - if (!first_measurement) - avg_adc_channels[i] = (1.f - AVG_RATE)*avg_adc_channels[i] + - AVG_RATE*(float)adc_channels[i]; - else - { - avg_adc_channels[i] = (float)adc_channels[i]; - } - } - } - // Clear rest of string for printing onscreen - while(serial_buffer_pos++ < MAX_BUFFER) serial_buffer[serial_buffer_pos] = ' '; - serial_buffer_pos = 0; - } - if (bufchar[0] == 'p') - { - gettimeofday(&end_ping, NULL); - ping = diffclock(begin_ping,end_ping); - display_ping = 1; - } - } - } - return lines_read; - */ - - - /* - if (_enabled) { - int _channels[CHANNEL_COUNT]; - _channels[0] = _channels[1] = _channels[2] = _channels[3] = 0; - - for (char c; read(_serial_fd, &c, 1) > 0; ) { // Read bytes from serial port - if (_readPacket) { - // load byte into buffer - _buffer[_bufferPos++] = c; - if (_bufferPos > CHANNEL_COUNT * sizeof(int)) { - // if buffer is full: load into channels - for (int i = 0; i < CHANNEL_COUNT; ++i) { - _channels[i] += *((int*)(_buffer + i * sizeof(int))); - } - //memcpy(_channels, _buffer, CHANNEL_COUNT * sizeof(int)); - // And check for next opcode - _readPacket = false; - } - } else { - // read opcode - switch (c) { - case 'd': - _readPacket = true; - _bufferPos = 0; - break; - case 'p': - // TODO: implement pings - break; - } - } - } - - } - */ -} - - - -// Old read_sensors() -/* -// Collect sensor data from serial port -void read_sensors(void) -{ - - if (serial_on) - { - char bufchar[1]; - while (read(serial_fd, bufchar, 1) > 0) - { - serial_buffer[serial__bufferpos] = bufchar[0]; - serial__bufferpos++; - // Have we reached end of a line of input? - if ((bufchar[0] == '\n') || (serial__bufferpos >= MAX_BUFFER)) - { - // At end - Extract value from string to variables - if (serial_buffer[0] != 'p') - { - samplecount++; - sscanf(serial_buffer, "%d %d %d %d", &adc_channels[0], - &adc_channels[1], - &adc_channels[2], - &adc_channels[3]); - for (int i = 0; i < 4; i++) - { - if (!first_measurement) - avg_adc_channels[i] = (1.f - AVG_RATE)*avg_adc_channels[i] + - AVG_RATE*(float)adc_channels[i]; - else - { - avg_adc_channels[i] = (float)adc_channels[i]; - } - } - first_measurement = 0; - } - // Clear rest of string for printing onscreen - while(serial__bufferpos++ < MAX_BUFFER) serial_buffer[serial__bufferpos] = ' '; - serial__bufferpos = 0; - } - if (bufchar[0] == 'p') - { - gettimeofday(&end_ping, NULL); - ping = diffclock(begin_ping,end_ping); - display_ping = 1; - } - } - } -} - - - -*/ \ No newline at end of file diff --git a/SerialInterface.h b/SerialInterface.h index 1276022f89..87e51d9cb5 100644 --- a/SerialInterface.h +++ b/SerialInterface.h @@ -9,54 +9,7 @@ int init_port (int baud); int read_sensors(int first_measurement, float * avg_adc_channels, int * adc_channels); -const int CHANNEL_COUNT = 4; - -class SerialInterface { - int _serial_fd; // internal device id - bool _enabled; // Enables/disables serial i/o - // Disabled by default if open() fails - - // Internal persistant state for readSensors() - char _buffer [CHANNEL_COUNT * sizeof(int)]; - int _bufferPos; // = 0; - bool _readPacket; - - // Try to open the serial port. On failure, disable the interface internally, write - // error message to cerr, and return non-zero error code (which can be ignored). - // Called by constructor and enable(). - int initInterface(); - - // Close the serial port. - // Called by deconstructor and disable(). - void closeInterface(); - -public: - SerialInterface() - : _enabled(true), - _bufferPos(0), - _readPacket(false) - { - initInterface(); - } - ~SerialInterface() { - closeInterface(); - } - - // Try to reinitialize the interface. - // If already enabled, do nothing. - // If reinitialization fails return false; otherwise return true. - bool enable(); - - // Disable the interface. - void disable(); - - bool isEnabled () { return _enabled; } - - // Updates gyro using serial input. - // Reads data from serial port into _buffer and accumulates that into _channels. - // Uses delta time and _channel input to update gyro yaw, pitch, and roll - void readSensors (float deltaTime); -}; - +#define NUM_CHANNELS 7 +#define SERIAL_PORT_NAME "/dev/tty.usbmodem411" #endif diff --git a/hand.cpp b/hand.cpp index afb1d71256..7e9a929b7e 100644 --- a/hand.cpp +++ b/hand.cpp @@ -8,6 +8,10 @@ #include "hand.h" +const float DEFAULT_X = 0.0; +const float DEFAULT_Y = 0.0; +const float DEFAULT_Z = -7.0; + Hand::Hand() { reset(); @@ -33,9 +37,9 @@ void Hand::render() void Hand::reset() { - position.x = 0.0; - position.y = 0.0; - position.z = -7.0; + position.x = DEFAULT_X; + position.y = DEFAULT_Y; + position.z = DEFAULT_Z; velocity.x = velocity.y = velocity.z = 0; } @@ -52,8 +56,5 @@ void Hand::simulate(float deltaTime) velocity.z += (randFloat() - 0.5)*noise; } - //position.x += (randFloat() - 0.5)/20.0; - //position.y += (randFloat() - 0.5)/20.0; - //position.z += (randFloat() - 0.5)/20.0; } \ No newline at end of file diff --git a/interface.xcodeproj/project.xcworkspace/xcuserdata/philip.xcuserdatad/UserInterfaceState.xcuserstate b/interface.xcodeproj/project.xcworkspace/xcuserdata/philip.xcuserdatad/UserInterfaceState.xcuserstate index 0482ce5eb2a4acfad933912162ac9391e66fcddc..629b5a34d2792b80efecfbb525358cdaba69c600 100644 GIT binary patch delta 31984 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 sih&=AfuD+j%f-M|V&DOBQizj!iW9G - - diff --git a/main.cpp b/main.cpp index e632210489..ba6bf38a16 100644 --- a/main.cpp +++ b/main.cpp @@ -49,40 +49,43 @@ using namespace std; // Junk for talking to the Serial Port -int serial_on = 0; // Are we using serial port for I/O? +int serial_on = 0; // Is serial connection on/off? System will try -timeval begin_ping, end_ping; -timeval timer_start, timer_end; -timeval last_frame; - -double elapsedTime; - -// Socket operation stuff +// Network Socket Stuff +// For testing, add milliseconds of delay for received UDP packets int UDP_socket; +int delay = 300; char* incoming_packet; +timeval ping_start; +int ping_count = 0; +float ping_msecs = 0.0; +int packetcount = 0; +int packets_per_second = 0; +int bytes_per_second = 0; +int bytescount = 0; // Getting a target location from other machine (or loopback) to display int target_x, target_y; int target_display = 0; -int bytes_in = 0; - unsigned char last_key = 0; double ping = 0; -//clock_t begin_ping, end_ping; -#define WIDTH 1280 // Width,Height of simulation area in cells -#define HEIGHT 800 + +//#define WIDTH 1200 // Width,Height of simulation area in cells +//#define HEIGHT 800 +int WIDTH = 1200; +int HEIGHT = 800; + #define BOTTOM_MARGIN 0 #define RIGHT_MARGIN 0 -#define TEXT_HEIGHT 14 Head myHead; // The rendered head of oneself or others Hand myHand; // My hand (used to manipulate things in world) -// Test data for creating fields that affect particles +// FIELD INFORMATION // If the simulation 'world' is a box with 10M boundaries, the offset to a field cell is given by: // element = [x/10 + (y/10)*10 + (z*/10)*100] // @@ -154,10 +157,13 @@ float mag_imbalance = 0.f; // 1 Head Gyro Yaw // 2 Head Accelerometer X // 3 Head Accelerometer Z -// +// 4 Hand Accelerometer X +// 5 Hand Accelerometer Y +// 6 Hand Accelerometer Z +// -int adc_channels[4]; -float avg_adc_channels[4]; +int adc_channels[NUM_CHANNELS]; +float avg_adc_channels[NUM_CHANNELS]; int first_measurement = 1; int samplecount = 0; @@ -165,46 +171,27 @@ int samplecount = 0; int framecount = 0; float FPS = 120.f; +timeval timer_start, timer_end; +timeval last_frame; +double elapsedTime; -void output(int x, int y, char *string) -{ - // Writes a text string to the screen as a bitmap at location x,y - int len, i; - glRasterPos2f(x, y); - len = (int) strlen(string); - for (i = 0; i < len; i++) - { - glutBitmapCharacter(GLUT_BITMAP_HELVETICA_12, string[i]); - } -} float randFloat () { return (rand()%10000)/10000.f; } -double diffclock(timeval clock1,timeval clock2) -{ - double diffms = (clock2.tv_sec - clock1.tv_sec) * 1000.0; - diffms += (clock2.tv_usec - clock1.tv_usec) / 1000.0; // us to ms - - return diffms; -} // Every second, check the frame rates and other stuff void Timer(int extra) { - char title[100]; gettimeofday(&timer_end, NULL); FPS = (float)framecount / ((float)diffclock(timer_start,timer_end) / 1000.f); - - // Calculate exact FPS - - sprintf(title, "FPS = %4.4f, IO/sec = %d, IOpng = %4.4f, bytes/sec = %d", - FPS, samplecount, ping, bytes_in); - glutSetWindowTitle(title); - framecount = 0; + packets_per_second = (float)packetcount / ((float)diffclock(timer_start,timer_end) / 1000.f); + bytes_per_second = (float)bytescount / ((float)diffclock(timer_start,timer_end) / 1000.f); + framecount = 0; samplecount = 0; - bytes_in = 0; + packetcount = 0; + bytescount = 0; glutTimerFunc(1000,Timer,0); gettimeofday(&timer_start, NULL); @@ -213,12 +200,14 @@ void Timer(int extra) void display_stats(void) { // bitmap chars are about 10 pels high - glColor3f(1.0f, 1.0f, 1.0f); char legend[] = "/ - toggle this display, Q - exit, N - toggle noise, M - toggle map, T - test audio"; - output(10,15,legend); - char mouse[50]; - sprintf(mouse, "mouse_x = %i, mouse_y = %i, pressed = %i, key = %i", mouse_x, mouse_y, mouse_pressed, last_key); - output(10,35,mouse); + drawtext(10, 15, 0.10, 0, 1.0, 0, legend); + + char stats[200]; + sprintf(stats, "FPS = %3.0f, Ping = %4.1f Packets/Sec = %d, Bytes/sec = %d", + FPS, ping_msecs, packets_per_second, bytes_per_second); + drawtext(10, 30, 0.10, 0, 1.0, 0, stats); + char adc[200]; sprintf(adc, "pitch_rate = %i, yaw_rate = %i, accel_lat = %i, accel_fwd = %i, loc[0] = %3.1f loc[1] = %3.1f, loc[2] = %3.1f", (int)(adc_channels[0] - avg_adc_channels[0]), @@ -227,8 +216,7 @@ void display_stats(void) (int)(adc_channels[3] - avg_adc_channels[3]), location[0], location[1], location[2] ); - output(10,50,adc); - + drawtext(10, 50, 0.10, 0, 1.0, 0, adc); } @@ -246,20 +234,24 @@ void initDisplay(void) void init(void) { + int i, j; + Audio::init(); printf( "Audio started.\n" ); + // Clear serial channels + for (i = i; i < NUM_CHANNELS; i++) + { + adc_channels[i] = 0; + avg_adc_channels[i] = 0.0; + } - avg_adc_channels[0] = avg_adc_channels[1] = avg_adc_channels[2] = avg_adc_channels[3] = 0.f; - head_mouse_x = WIDTH/2; head_mouse_y = HEIGHT/2; - int i, j; - // Initialize Field values field_init(); - printf( "Field Initilialized.\n" ); + printf( "Field Initialized.\n" ); if (noise_on) { @@ -267,16 +259,7 @@ void init(void) myHead.setNoise(noise); } - /* - const float FIELD_SCALE = 0.00005; - for (i = 0; i < FIELD_ELEMENTS; i++) - { - field[i].x = 0.001; //(randFloat() - 0.5)*FIELD_SCALE; - field[i].y = 0.001; //(randFloat() - 0.5)*FIELD_SCALE; - field[i].z = 0.001; //(randFloat() - 0.5)*FIELD_SCALE; - }*/ - - + // Init particles float tri_scale, r; const float VEL_SCALE = 0.00; for (i = 0; i < NUM_TRIS; i++) @@ -450,6 +433,9 @@ void update_tris() void reset_sensors() { + // + // Reset serial I/O sensors + // render_yaw = start_yaw; yaw = render_yaw_rate = 0; pitch = render_pitch = render_pitch_rate = 0; @@ -494,6 +480,16 @@ void update_pos(float frametime) head_mouse_y = max(head_mouse_y, 0); head_mouse_y = min(head_mouse_y, HEIGHT); + // Update hand/manipulator location for measured forces from serial channel + const float MIN_HAND_ACCEL = 30.0; + glm::vec3 hand_accel(avg_adc_channels[4] - adc_channels[4], + avg_adc_channels[5] - adc_channels[5], + avg_adc_channels[6] - adc_channels[6]); + + if (glm::length(hand_accel) > MIN_HAND_ACCEL) + { + myHand.addVel(hand_accel*frametime); + } // Update render direction (pitch/yaw) based on measured gyro rates const int MIN_YAW_RATE = 300; @@ -576,81 +572,84 @@ void display(void) glEnable (GL_DEPTH_TEST); glEnable(GL_LIGHTING); + glEnable(GL_LINE_SMOOTH); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - - glEnable(GL_COLOR_MATERIAL); - glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE); - - GLfloat light_position0[] = { 1.0, 1.0, 0.0, 0.0 }; - glLightfv(GL_LIGHT0, GL_POSITION, light_position0); - GLfloat ambient_color[] = { 0.125, 0.305, 0.5 }; - glLightfv(GL_LIGHT0, GL_AMBIENT, ambient_color); - GLfloat diffuse_color[] = { 0.5, 0.42, 0.33 }; - glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse_color); - GLfloat specular_color[] = { 1.0, 1.0, 1.0, 1.0}; - glLightfv(GL_LIGHT0, GL_SPECULAR, specular_color); - - glMaterialfv(GL_FRONT, GL_SPECULAR, specular_color); - glMateriali(GL_FRONT, GL_SHININESS, 96); - - // Rotate, translate to camera location - glRotatef(render_pitch, 1, 0, 0); - glRotatef(render_yaw, 0, 1, 0); - glTranslatef(location[0], location[1], location[2]); - - glEnable(GL_DEPTH_TEST); - - // Draw a few 'planets' to find and explore glPushMatrix(); - glTranslatef(1.f, 1.f, 1.f); - glColor3f(1, 0, 0); - glutSolidSphere(0.6336, 20, 20); - glTranslatef(5, 5, 5); - glColor3f(1, 1, 0); - glutSolidSphere(0.4, 20, 20); - glTranslatef(-2.5, -2.5, 2.5); - glColor3f(1, 0, 1); - glutSolidSphere(0.3, 20, 20); - glPopMatrix(); - - // Draw Triangles - - glBegin(GL_TRIANGLES); - for (i = 0; i < NUM_TRIS; i++) - { - glColor3f(tris.colors[i*3], - tris.colors[i*3+1], - tris.colors[i*3+2]); - for (j = 0; j < 3; j++) - { - glVertex3f(tris.vertices[i*9 + j*3], - tris.vertices[i*9 + j*3 + 1], - tris.vertices[i*9 + j*3 + 2]); - } - glNormal3f(tris.normals[i*3], - tris.normals[i*3 + 1], - tris.normals[i*3 + 2]); - } - glEnd(); - - // Show field vectors - if (display_field) field_render(); - - render_world_box(); + glLoadIdentity(); + glEnable(GL_COLOR_MATERIAL); + glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE); - - // Display floating head in front of viewer - if (display_head) - { - myHead.render(); - } - myHand.render(); - - + GLfloat light_position0[] = { 1.0, 1.0, 0.0, 0.0 }; + glLightfv(GL_LIGHT0, GL_POSITION, light_position0); + GLfloat ambient_color[] = { 0.125, 0.305, 0.5 }; + glLightfv(GL_LIGHT0, GL_AMBIENT, ambient_color); + GLfloat diffuse_color[] = { 0.5, 0.42, 0.33 }; + glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse_color); + GLfloat specular_color[] = { 1.0, 1.0, 1.0, 1.0}; + glLightfv(GL_LIGHT0, GL_SPECULAR, specular_color); + + glMaterialfv(GL_FRONT, GL_SPECULAR, specular_color); + glMateriali(GL_FRONT, GL_SHININESS, 96); + + // Rotate, translate to camera location + glRotatef(render_pitch, 1, 0, 0); + glRotatef(render_yaw, 0, 1, 0); + glTranslatef(location[0], location[1], location[2]); + + glEnable(GL_DEPTH_TEST); + + // Draw a few 'planets' to find and explore + glPushMatrix(); + glTranslatef(1.f, 1.f, 1.f); + glColor3f(1, 0, 0); + glutSolidSphere(0.6336, 20, 20); + glTranslatef(5, 5, 5); + glColor3f(1, 1, 0); + glutSolidSphere(0.4, 20, 20); + glTranslatef(-2.5, -2.5, 2.5); + glColor3f(1, 0, 1); + glutSolidSphere(0.3, 20, 20); + glPopMatrix(); + + // Draw Triangles + + glBegin(GL_TRIANGLES); + for (i = 0; i < NUM_TRIS; i++) + { + glColor3f(tris.colors[i*3], + tris.colors[i*3+1], + tris.colors[i*3+2]); + for (j = 0; j < 3; j++) + { + glVertex3f(tris.vertices[i*9 + j*3], + tris.vertices[i*9 + j*3 + 1], + tris.vertices[i*9 + j*3 + 2]); + } + glNormal3f(tris.normals[i*3], + tris.normals[i*3 + 1], + tris.normals[i*3 + 2]); + } + glEnd(); + + // Show field vectors + if (display_field) field_render(); + + + + // Display floating head in front of viewer + if (display_head) + { + myHead.render(); + } + myHand.render(); + + // Render the world box + render_world_box(); + + glPopMatrix(); + // Render 2D overlay: I/O level bar graphs and text - glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); @@ -666,6 +665,10 @@ void display(void) glBegin(GL_POINTS); glVertex2f(target_x, target_y); glEnd(); + char val[20]; + sprintf(val, "%d,%d", target_x, target_y); + drawtext(target_x, target_y-20, 0.08, 0, 1.0, 0, val, 0, 1, 0); + } if (display_head_mouse) { @@ -676,41 +679,38 @@ void display(void) glVertex2f(head_mouse_x, head_mouse_y); glEnd(); } - /* - if (display_ping) - { - // Draw a green dot to indicate receipt of ping signal - glPointSize(10.f); - if (display_ping == 2) - glColor4f(1.f, 0.f, 0.f, 1.f); - else - glColor4f(0.f, 1.f, 0.f, 1.f); - glBegin(GL_POINTS); - glVertex2f(50, 400); - glEnd(); - display_ping = 0; - } - */ + + // Show detected levels from the serial I/O ADC channel sensors if (display_levels) { - glColor4f(1.f, 1.f, 1.f, 1.f); - glBegin(GL_LINES); - glVertex2f(10, HEIGHT*0.95); - glVertex2f(10, HEIGHT*(0.25 + 0.75f*adc_channels[0]/4096)); - - glVertex2f(20, HEIGHT*0.95); - glVertex2f(20, HEIGHT*(0.25 + 0.75f*adc_channels[1]/4096)); - - glVertex2f(30, HEIGHT*0.95); - glVertex2f(30, HEIGHT*(0.25 + 0.75f*adc_channels[2]/4096)); - - glVertex2f(40, HEIGHT*0.95); - glVertex2f(40, HEIGHT*(0.25 + 0.75f*adc_channels[3]/4096)); - glEnd(); - + int i; + int disp_x = 10; + const int GAP = 16; + char val[10]; + for(i = 0; i < NUM_CHANNELS; i++) + { + // Actual value + glColor4f(1, 1, 1, 1); + glBegin(GL_LINES); + glVertex2f(disp_x, HEIGHT*0.95); + glVertex2f(disp_x, HEIGHT*(0.25 + 0.75f*adc_channels[i]/4096)); + glEnd(); + // Trailing Average value + glColor4f(0, 0, 0.8, 1); + glBegin(GL_LINES); + glVertex2f(disp_x + 2, HEIGHT*0.95); + glVertex2f(disp_x + 2, HEIGHT*(0.25 + 0.75f*avg_adc_channels[i]/4096)); + glEnd(); + + sprintf(val, "%d", adc_channels[i]); + drawtext(disp_x-GAP/2, (HEIGHT*0.95)+2, 0.08, 90, 1.0, 0, val, 0, 1, 0); + + disp_x += GAP; + } } if (stats_on) display_stats(); + glPopMatrix(); glutSwapBuffers(); @@ -768,15 +768,22 @@ void key(unsigned char k, int x, int y) void read_network() { // Receive packets - int bytes_recvd = network_receive(UDP_socket, incoming_packet); + int bytes_recvd = network_receive(UDP_socket, incoming_packet, delay); if (bytes_recvd > 0) { - bytes_in += bytes_recvd; - if (incoming_packet[0] == 'M') - { + packetcount++; + bytescount += bytes_recvd; + // If packet is a Mouse data packet, copy it over + if (incoming_packet[0] == 'M') { sscanf(incoming_packet, "M %d %d", &target_x, &target_y); target_display = 1; printf("X = %d Y = %d\n", target_x, target_y); + } else if (incoming_packet[0] == 'P') { + // Ping packet - check time and record + timeval check; + gettimeofday(&check, NULL); + ping_msecs = (float)diffclock(ping_start, check); + } } } @@ -797,6 +804,13 @@ void idle(void) if (!step_on) glutPostRedisplay(); last_frame = check; + + // Every 30 frames or so, check ping time + ping_count++; + if (ping_count >= 30) { + ping_start = network_send_ping(UDP_socket); + ping_count = 0; + } } // Read network packets @@ -812,21 +826,19 @@ void idle(void) void reshape(int width, int height) { + WIDTH = width; + HEIGHT = height; glViewport(0, 0, width, height); - /* - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - gluOrtho2D(0, width, height, 0); - glMatrixMode(GL_MODELVIEW); - */ - + glMatrixMode(GL_PROJECTION); //hello + glLoadIdentity(); gluPerspective(45, //view angle 1.0, //aspect ratio 1.0, //near clip 200.0);//far clip glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); } @@ -873,29 +885,30 @@ int main(int argc, char** argv) char test_data[] = "Test!"; int bytes_sent = network_send(UDP_socket, test_data, 5); if (bytes_sent) printf("%d bytes sent.", bytes_sent); - int test_recv = network_receive(UDP_socket, incoming_packet); + int test_recv = network_receive(UDP_socket, incoming_packet, delay); printf("Received %i bytes\n", test_recv); // Load textures //Img.Load("/Users/philip/Downloads/galaxy1.tga"); - // Try to setup the serial port I/O - if (serial_on) + // + // Try to connect the serial port I/O + // + if(init_port(115200) == -1) { + perror("Unable to open serial port\n"); + serial_on = 0; + } + else { - //serial_fd = open(SERIAL_PORT_NAME, O_RDWR | O_NOCTTY | O_NDELAY); - // List usbSerial devices using Terminal ls /dev/tty.* - - - if(init_port(115200) == -1) { // Check for port errors - perror("Unable to open serial port\n"); - return (0); - } + printf("Serial Port Initialized\n"); + serial_on = 1; } + glutInit(&argc, argv); glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH); glutInitWindowSize(RIGHT_MARGIN + WIDTH, BOTTOM_MARGIN + HEIGHT); - glutCreateWindow("Interface Test"); + glutCreateWindow("Interface"); printf( "Created Display Window.\n" ); diff --git a/network.cpp b/network.cpp index f89a750885..7ff2361714 100644 --- a/network.cpp +++ b/network.cpp @@ -14,6 +14,14 @@ const int UDP_PORT = 30000; const char DESTINATION_IP[] = "127.0.0.1"; +// Implementation of optional delay behavior using a ring buffer +const int MAX_DELAY_PACKETS = 300; +char delay_buffer[MAX_PACKET_SIZE*MAX_DELAY_PACKETS]; +timeval delay_time_received[MAX_DELAY_PACKETS]; +int delay_size_received[MAX_DELAY_PACKETS]; +int next_to_receive = 0; +int next_to_send = 0; + sockaddr_in address, dest_address, from; socklen_t fromLength = sizeof( from ); @@ -68,6 +76,16 @@ int network_init() return handle; } +// Send a ping packet and mark the time sent +timeval network_send_ping(int handle) { + timeval check; + char packet_data[] = "P"; + sendto(handle, (const char*)packet_data, 1, + 0, (sockaddr*)&dest_address, sizeof(sockaddr_in) ); + gettimeofday(&check, NULL); + return check; +} + int network_send(int handle, char * packet_data, int packet_size) { int sent_bytes = sendto( handle, (const char*)packet_data, packet_size, @@ -81,11 +99,40 @@ int network_send(int handle, char * packet_data, int packet_size) return sent_bytes; } -int network_receive(int handle, char * packet_data) +int network_receive(int handle, char * packet_data, int delay /*msecs*/) { int received_bytes = recvfrom(handle, (char*)packet_data, MAX_PACKET_SIZE, 0, (sockaddr*)&dest_address, &fromLength ); + if (!delay) { + // No delay set, so just return packets immediately! + return received_bytes; + } else { + timeval check; + gettimeofday(&check, NULL); + if (received_bytes > 0) { + // First write received data into ring buffer + delay_time_received[next_to_receive] = check; + delay_size_received[next_to_receive] = received_bytes; + memcpy(&delay_buffer[next_to_receive*MAX_PACKET_SIZE], packet_data, received_bytes); + next_to_receive++; + if (next_to_receive == MAX_DELAY_PACKETS) next_to_receive = 0; + } + // Then check if next to be sent is past due, send if so + if ((next_to_receive != next_to_send) && + (diffclock(delay_time_received[next_to_send], check) > delay)) { + int returned_bytes = delay_size_received[next_to_send]; + memcpy(packet_data, + &delay_buffer[next_to_send*MAX_PACKET_SIZE], + returned_bytes); + next_to_send++; + if (next_to_send == MAX_DELAY_PACKETS) next_to_send = 0; + return returned_bytes; + } else { + return 0; + } + } + + - return received_bytes; } diff --git a/network.h b/network.h index 6364b52af4..8f636f3cfc 100644 --- a/network.h +++ b/network.h @@ -13,11 +13,14 @@ #include #include #include +#include +#include "util.h" -const int MAX_PACKET_SIZE = 65536; +const int MAX_PACKET_SIZE = 1500; int network_init(); int network_send(int handle, char * packet_data, int packet_size); -int network_receive(int handle, char * packet_data); +int network_receive(int handle, char * packet_data, int delay /*msecs*/); +timeval network_send_ping(int handle); #endif diff --git a/particle.cpp b/particle.cpp index a92f619d4f..8311099597 100644 --- a/particle.cpp +++ b/particle.cpp @@ -15,7 +15,7 @@ void ParticleSystem::simulate (float deltaTime) { particles[i].position += particles[i].velocity * deltaTime; // Add gravity - particles[i].velocity.y -= gravity; + particles[i].velocity.y -= GRAVITY; // Drag: decay velocity particles[i].velocity *= 0.99; diff --git a/particle.h b/particle.h index 7d5d0b50d1..bbd52129b6 100644 --- a/particle.h +++ b/particle.h @@ -11,6 +11,8 @@ #include "glm/glm.hpp" +#define GRAVITY 0.0001 + class ParticleSystem { public: void simulate (float deltaTime); @@ -24,7 +26,6 @@ private: glm::vec3 bounds; const static bool wrapBounds = false; - const static float gravity = 0.0001; }; #endif diff --git a/util.cpp b/util.cpp index 7a08321715..9d172aed14 100644 --- a/util.cpp +++ b/util.cpp @@ -17,7 +17,9 @@ void render_world_box() { // Show edge of world - glColor3f(0.1, 0.1, 0.1); + glDisable(GL_LIGHTING); + glColor4f(1.0, 1.0, 1.0, 1.0); + glLineWidth(1.0); glBegin(GL_LINE_STRIP); glVertex3f(0,0,0); glVertex3f(WORLD_SIZE,0,0); @@ -42,3 +44,36 @@ void render_world_box() glVertex3f(WORLD_SIZE,0,WORLD_SIZE); glEnd(); } + + +double diffclock(timeval clock1,timeval clock2) +{ + double diffms = (clock2.tv_sec - clock1.tv_sec) * 1000.0; + diffms += (clock2.tv_usec - clock1.tv_usec) / 1000.0; // us to ms + + return diffms; +} + +void drawtext(int x, int y, float scale, float rotate, float thick, int mono, char *string, + float r=1.0, float g=1.0, float b=1.0) +{ + // + // Draws text on screen as stroked so it can be resized + // + int len, i; + glPushMatrix(); + glTranslatef(x, y, 0); + glColor3f(r,g,b); + glRotated(180+rotate,0,0,1); + glRotated(180,0,1,0); + glLineWidth(thick); + glScalef(scale, scale, 1.0); + len = (int) strlen(string); + for (i = 0; i < len; i++) + { + if (!mono) glutStrokeCharacter(GLUT_STROKE_ROMAN, int(string[i])); + else glutStrokeCharacter(GLUT_STROKE_MONO_ROMAN, int(string[i])); + } + glPopMatrix(); + +} diff --git a/util.h b/util.h index 57ccecb997..466e4a990f 100644 --- a/util.h +++ b/util.h @@ -10,5 +10,8 @@ #define interface_util_h void render_world_box(); +void drawtext(int x, int y, float scale, float rotate, float thick, int mono, char *string, + float r=1.0, float g=1.0, float b=1.0); +double diffclock(timeval clock1,timeval clock2); #endif