#include #include #include #include #include #define NUM_POINTS_X 20 #define NUM_POINTS_Y 20 #define NUM_POINTS (NUM_POINTS_X * NUM_POINTS_Y) #define NUM_LINKS (NUM_POINTS_X * NUM_POINTS_Y * 2 - NUM_POINTS_X - NUM_POINTS_Y) #define LINK_DIST 0.5 typedef struct point { float x, y, z; float prev_x, prev_y, prev_z; float vel_x, vel_y, vel_z; int anchor; //boolean, true if immovable } Point; typedef struct link { float resting_distance; float tear_distance; int stiffness; Point *a; Point *b; int broken; //booelan, true if broken } Link; void init(void); void shut_down(int return_code); void main_loop(); void draw_square(Point **array); void draw(); void update_point(Point *p, double delta); void update_link(Link *l); void check_boundaries(Point *p); void update_mouse(Point **mp, int *mouse_held); void draw_barrier(); int main_point(Point *p); Point *point_array[NUM_POINTS]; Point *barrier_array[4]; Link *link_array[NUM_LINKS]; float rotate_y = 0, rotate_z = 0; int main(void) { init(); main_loop(); shut_down(0); } void init(void) { srand(time(0)); const int window_width = 800, window_height = 600; int x = NUM_POINTS_X; int y = NUM_POINTS_Y; if (glfwInit() != GL_TRUE) shut_down(1); // 800 x 600, 16 bit color, no depth, alpha or stencil buffers, windowed if (glfwOpenWindow(window_width, window_height, 5, 6, 5, 0, 0, 0, GLFW_WINDOW) != GL_TRUE) shut_down(1); glfwSetWindowTitle("Cloth"); // set the projection matrix to a normal frustum with a max depth of 50 glMatrixMode(GL_PROJECTION); glLoadIdentity(); float aspect_ratio = ((float)window_height) / window_width; glFrustum(.5, -.5, -.5 * aspect_ratio, .5 * aspect_ratio, 1, 50); glMatrixMode(GL_MODELVIEW); //create the Point grid for(int i = 0; i < y; i++) { for(int j = 0; j < x; j++) { Point *p = malloc(sizeof(Point)); p->x = j*LINK_DIST; p->y = i*LINK_DIST + 5;//put them off the ground a bit p->z = 0.0; p->vel_x = 0.0; p->vel_y = 0.0; p->vel_z = 0.0; p->prev_x = p->x; p->prev_y = p->y; p->prev_z = p->z; p->anchor = 0; int invert = y-i-1; //inverts y axis if(invert == 0) { p->anchor = 1; } point_array[(invert*x)+j] = p; } } //link the points together in a mesh int q = 0; for(int i = 0; i < y; i++) { for(int j = 0; j < x; j++) { int invert = y-i-1; //link upward if(i != 0) { //not top row Link *l1 = malloc(sizeof(Link)); l1->resting_distance = LINK_DIST;//10.0/x; l1->tear_distance = 15*LINK_DIST; l1->stiffness = 1; l1->a = point_array[invert*x+j]; l1->b = point_array[(invert+1)*x+j]; l1->broken = 0; link_array[q] = l1; q++; } //link leftward (is that a word?) if(j != 0) { //not left edge Link *l2 = malloc(sizeof(Link)); l2->resting_distance = LINK_DIST;//10.0/x; l2->tear_distance = 15*LINK_DIST; l2->stiffness = 1; l2->a = point_array[invert*x+j]; l2->b = point_array[invert*x+j-1]; l2->broken = 0; link_array[q] = l2; q++; } } } //make an invisible barrier for the mouse to click on Point *p0 = malloc(sizeof(Point)); p0->x = -100; p0->y = -100; p0->z = 0; Point *p1 = malloc(sizeof(Point)); p1->x = -100; p1->y = 100; p1->z = 0; Point *p2 = malloc(sizeof(Point)); p2->x = 100; p2->y = 100; p2->z = 0; Point *p3 = malloc(sizeof(Point)); p3->x = 100; p3->y = -100; p3->z = 0; barrier_array[0] = p0; barrier_array[1] = p1; barrier_array[2] = p2; barrier_array[3] = p3; } void shut_down(int return_code) { glfwTerminate(); for(int i = 0; i < NUM_POINTS; i++) { free(point_array[i]); } for(int i = 0; i < NUM_LINKS; i++) { free(link_array[i]); } for(int i = 0; i < 4; i++) { free(barrier_array[i]); } exit(return_code); } void main_loop() { // the time of the previous frame double old_time = glfwGetTime(); // point that the mouse grabs Point *mouse_point = malloc(sizeof(Point)); // temporary int mouse_held = 0; // boolean // this just loops as long as the program runs while(1) { // calculate time elapsed double current_time = glfwGetTime(), delta_time = (current_time - old_time); old_time = current_time; // escape to quit, arrow keys to rotate view if (glfwGetKey(GLFW_KEY_ESC) == GLFW_PRESS) { break; } if (glfwGetKey(GLFW_KEY_LEFT) == GLFW_PRESS) { rotate_y += delta_time*30; } if (glfwGetKey(GLFW_KEY_RIGHT) == GLFW_PRESS) { rotate_y -= delta_time*30; } //randomly push the mesh in a random direction with SPACE if(glfwGetKey(GLFW_KEY_SPACE) == GLFW_PRESS) { int random_point = rand()%(NUM_POINTS-NUM_POINTS_X-1) + NUM_POINTS_X+1; Point *p = point_array[random_point]; p->x += rand()%10000 / 10000.0 - .5; p->y += rand()%10000 / 10000.0 - .5; p->z += rand()%10000 / 10000.0 - .5; } //update link constraints multiple times for(int q = 0; q < 3; q++) { for(int i = 0; i < NUM_LINKS; i++) { if(!link_array[i]->broken) { update_link(link_array[i]); } } } //update all points except top row (fixed points) for(int i = 0; i < NUM_POINTS; i++) { if(!point_array[i]->anchor) { update_point(point_array[i], delta_time); } } // clear the buffer glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // draw the figure draw(link_array); // mouse stuffs update_mouse(&mouse_point, &mouse_held); // swap back and front buffers glfwSwapBuffers(); } } void draw_square(Point **array) { // draws a square given array of 4 points glBegin(GL_QUADS); { for(int i = 0; i < 4; i++) { glVertex3f(array[i]->x, array[i]->y, array[i]->z); } } glEnd(); } void draw() { // reset view matrix glLoadIdentity(); // move view back a bit and over glTranslatef(0, -5, -30); // draw a square for the mouse to hit (when clicked) draw_barrier(); // apply rotation glRotatef(rotate_y, 0, 1, 0); glRotatef(rotate_z, 0, 0, 1); //move over a bit glTranslatef(-(NUM_POINTS_X-1)*LINK_DIST/2.0, 0, 0); //draw cloth grid glColor3f(0.0f, 1.0f, 0.0f); for (int i = 0; i < NUM_LINKS; i++) { if(!link_array[i]->broken) { Point *a = link_array[i]->a; Point *b = link_array[i]->b; glBegin(GL_LINES); glVertex3f(a->x, a->y, a->z); glVertex3f(b->x, b->y, b->z); glEnd(); } } } void update_point(Point *p, double delta) { //Euler version /*p->x += p->vel_x*delta; p->y += p->vel_y*delta; p->z += p->vel_z*delta; p->vel_y -= 0.98;//gravity*/ //Verlet Integration p->vel_x = p->x - p->prev_x; p->vel_y = p->y - p->prev_y; p->vel_z = p->z - p->prev_z; //friction if(p->y <= 0.1) { p->vel_x *= 0.85; p->vel_z *= 0.85; } float next_x = p->x + p->vel_x; float next_y = p->y + p->vel_y - 0.98*delta; float next_z = p->z + p->vel_z; p->prev_x = p->x; p->prev_y = p->y; p->prev_z = p->z; p->x = next_x; p->y = next_y; p->z = next_z; check_boundaries(p); } void update_link(Link *l) { // calculate the distance float diff_x = l->a->x - l->b->x; float diff_y = l->a->y - l->b->y; float diff_z = l->a->z - l->b->z; float d = sqrt(diff_x*diff_x + diff_y*diff_y + diff_z*diff_z); // tear if(d > l->tear_distance) { l->broken = 1; return; } // difference scalar float difference = 0; if(d != 0) { //DON'T DIVIDE BY ZERO! difference = (l->resting_distance - d) / d; } // translation for each PointMass. They'll be pushed 1/2 the required distance to match their resting distances. float translate_x = diff_x * 0.5 * difference; float translate_y = diff_y * 0.5 * difference; float translate_z = diff_z * 0.5 * difference; if(!l->a->anchor) { l->a->x += translate_x; l->a->y += translate_y; l->a->z += translate_z; } if(!l->b->anchor) { l->b->x -= translate_x; l->b->y -= translate_y; l->b->z -= translate_z; } } void check_boundaries(Point *p) { if(p->x > 100) { p->x = 100; if(p->vel_x > 0) { p->vel_x *= -.9; } } if(p->x < -100) { p->x = -100; if(p->vel_x < 0) { p->vel_x *= -.9; } } if(p->y > 100) { p->y = 100; if(p->vel_y > 0) { p->vel_y *= -.9; } } if(p->y < 0) { p->y = 0; if(p->vel_y < 0) { p->vel_y *= -.9; } } if(p->z > 10) { p->z = 10; if(p->vel_z > 0) { p->vel_z *= -.9; } } if(p->z < -10) { p->z = -10; if(p->vel_z < 0) { p->vel_z *= -.9; } } } void update_mouse(Point **mp, int *mouse_held) { // mp is a pointer to a Point pointer so that I can change // the pointer to save the Point grabbed by the mouse // between function calls...pointer! Point *mouse_point = *mp; if(!glfwGetMouseButton(GLFW_MOUSE_BUTTON_LEFT)) { *mouse_held = 0; *mp = malloc(sizeof(Point)); } else { int x, y; glfwGetMousePos(&x, &y); GLint viewport[4]; GLdouble modelview[16]; GLdouble projection[16]; GLfloat winX, winY; GLdouble posX, posY, posZ; glGetDoublev( GL_MODELVIEW_MATRIX, modelview ); glGetDoublev( GL_PROJECTION_MATRIX, projection ); glGetIntegerv( GL_VIEWPORT, viewport ); winX = (float)x; winY = (float)(viewport[3] - y); gluUnProject( winX, winY, 0.99, modelview, projection, viewport, &posX, &posY, &posZ); //find nearest point to mouse click if(!(*mouse_held)) { free(*mp); *mouse_held = 1; Point *nearest; float d = 100000; for(int i = 0; i < NUM_POINTS; i++) { if(!point_array[i]->anchor) { float diff_x = point_array[i]->x - posX; float diff_y = point_array[i]->y - posY; float diff_z = point_array[i]->z - posZ; float dist = sqrt(diff_x*diff_x + diff_y*diff_y + diff_z*diff_z); if(dist < d) { d = dist; nearest = point_array[i]; } } } *mp = nearest; mouse_point = nearest; } //drag point around if(*mouse_held) { mouse_point->x = posX; mouse_point->y = posY; mouse_point->z = posZ; } } } void draw_barrier() { glColor4f(0, 0, 0, 0); draw_square(barrier_array); } int main_point(Point *p) { for(int i = 0; i < NUM_POINTS; i++) { if(point_array[i] == p) { return 1; } } return 0; }