#ifndef World_H #define World_H #ifdef _WIN32 #include <windows.h> #endif #include "Configuration.h" #include "CurrentImpact.h" #include "RigidBody.h" //includes Edge.h and Vertex.h class World { private: //made private because it is a singleton World(); /** Singleton instance of the World class*/ static World* instance; /** Holds the current direction of gravity in the World **/ Vec2 gravity; /** Holds data about the last impact detected in the World so it can be handled. **/ CurrentImpact currentImpact; /** Number of RigidBodies of type OBSTACLE in the ObstacleArray **/ int totalObstacles; /** Number of RigidBodies of any type in the RigidBodyArray. **/ int totalBodies; /** Number of vertices that make up the RigidBodies in the RigidBodyArray **/ int totalVertices; /** Number of edges that make up the RigidBodies in the RigidBodyArray **/ int totalEdges; /** Holds RigidBodies of type OBSTACLE in the World. These do not move when collided with. **/ RigidBody* ObstacleArray[MAX_OBSTACLES]; /** Holds RigidBodies of any type that are affected by collsions in the World. **/ RigidBody* RigidBodyArray[MAX_OBJECTS]; /** The amount of time between ticks. **/ float tick; /** * The number of times to run the edge correction step each update. Increasing the number * reduces the amount of edge length deformation caused by collisions moving seperate vertices, * but increases the amount of computation needed each update. */ int iterations; //Update Methods void updateForces(); void updateVerlet(); void updateEdges(); bool didCollide(RigidBody* pb1, RigidBody* pb2); bool overlapping(RigidBody* pb1, RigidBody* pb2); void handleCollision(); void processAllCollisions(); float getDistance(float minA, float maxA, float minB, float maxB); public: static World* getInstance(); void Destroy(); void Update(); void Render(); void AddBody(RigidBody* Body); void AddObstacle(RigidBody* obstacle); void setGravity(Vec2 newGravity); int getIterations(); void increaseIterations(); void decreaseIterations(); }; #endif World_H
#include "World.h" //initialize instance to nothing World* World::instance = NULL; /** * Returns the Singleton instance of the World */ World* World::getInstance() { if(!instance) instance = new World(); return instance; } /** * Constructor for a World object * Sets gravity to the bottom of the screen * No bodies, edges, or vertices in the world */ World::World() { gravity = Vec2(0.0f, 0.2f); totalObstacles = 0; totalBodies = 0; totalVertices = 0; totalEdges = 0; tick = TICK; iterations = 12;//number of times to re-run the updateEdges function per tick } void World::Destroy() { for(int i = 0; i < totalBodies; ++i) delete RigidBodyArray[i]; for(int i = 0; i < totalObstacles; ++i) delete ObstacleArray[i]; } int World::getIterations() { return iterations; } void World::increaseIterations() { if(iterations < MAX_ITERATIONS) { iterations++; } } void World::decreaseIterations() { if(iterations > 1) { iterations--; } } void World::setGravity(Vec2 newGravity) { gravity = newGravity; } /** * Add a RigidBody to the World * body1: RigidBody object to add */ void World::AddBody(RigidBody* body1) { if(totalBodies + 1 < MAX_OBJECTS) { RigidBodyArray[totalBodies++] = body1; } } /** * Add an Obstacle to the World * body1: RigidBody obstacle to add */ void World::AddObstacle(RigidBody* obstacle) { if(totalObstacles + 1 < MAX_OBSTACLES) { obstacle->CalculateCenter(); ObstacleArray[totalObstacles++] = obstacle; } } /** * Updates each Vertex in the World based on the forces in the World * Currently, the only force applied is gravity */ void World::updateForces() { for(int i = 0; i < totalBodies; i++) { RigidBody* rb = RigidBodyArray[i]; for(int j = 0; j < rb->vertexCount; j++) { rb->vertexArray[j]->accel = gravity; } } } /** * Updates the position of each Vertex in the World */ void World::updateVerlet() { for(int i = 0; i < totalBodies; i++) { RigidBody* rb = RigidBodyArray[i]; for(int j = 0; j < rb->vertexCount; j++) { Vertex& v = *(rb->vertexArray[j]); //xNew = 2 * x(Current) – x(Old) + a(Current) * t2 //wikipedia.org/wiki/Verlet_integration Vec2 temp = v.pos; v.pos = (v.pos * 2) - v.oldPos + v.accel * tick * tick; v.oldPos = temp; } } } /** * Updates the length of every Edge in the World. * Each update causes the length of the edge to change. * This method adjusts the positions of the vertices of an edge to maintain * the original length. */ void World::updateEdges() { //loop through all possible edges for(int i = 0; i < totalBodies; i++) { RigidBody* rb = RigidBodyArray[i]; for(int j = 0; j < rb->edgeCount; j++) { Edge& e = *(rb->edgeArray[j]); Vec2 edgeVector = e.vertex2->pos - e.vertex1->pos; //move the positions of both vertices so the length of the edge is maintained float edgeVectorLength = edgeVector.length(); float d = edgeVectorLength - e.length; edgeVector.normalize(); //shift each vertex over half the total while maintaining length e.vertex1->pos += edgeVector * d * 0.5f; e.vertex2->pos -= edgeVector * d * 0.5f; } } } /** * Iterates through every RigidBody in the World, updates their edges, keeps them inside the screen, * and handles any collisions that occur. */ void World::processAllCollisions() { //the more times we iterate through all the edges and adjust them, the more accurate the result is //use the iterations variable to determine how many times to iterate for(int i = 0; i < iterations; i++) { //if vertices are outside of screen, move them back inside for(int j = 0; j < totalBodies; j++) { RigidBody* rb = RigidBodyArray[j]; for(int k = 0; k < rb->vertexCount; k++) { Vec2& position = rb->vertexArray[k]->pos; position.x = max(min(position.x, (float)SCREEN_WIDTH), 0.0f); position.y = max(min(position.y, (float)SCREEN_HEIGHT), 0.0f); } } updateEdges(); for(int k = 0; k < totalBodies; k++) { RigidBodyArray[k]->CalculateCenter(); } //iterate through every body in the word for(int body1 = 0; body1 < totalBodies; body1++) { for(int body2 = 0; body2 < totalBodies; body2++) { if(body1 != body2) { if(didCollide(RigidBodyArray[body1], RigidBodyArray[body2])) { handleCollision(); } } } //check against obstacles for(int i = 0; i < totalObstacles; i++) { if(didCollide(RigidBodyArray[body1], ObstacleArray[i])) { handleCollision(); } } } } } /** * Takes two RigidBodies and determines whether they are overlapping. * body1: First body to check for overlap * body2: Second body to check for overlap * return: true if RigidBodies are overlapping * false if RigidBodies are not overlapping */ bool World::overlapping(RigidBody* body1, RigidBody* body2) { if ((body1->minX > (body2->maxX)) || ((body1->maxX) < body2->minX)) return false; if((body1->minY > (body2->maxY)) || ((body1->maxY) < body2->minY)) return false; return true; } /** * Takes two RigidBodies and determines whether the two collided * body1: First RigidBody to check for collision * body2: Second RigidBody to check for collision * return: true if RigidBodies are colliding * false if RigidBodies are not colliding */ bool World::didCollide(RigidBody* body1, RigidBody* body2) { //if they aren't overlapping, they didn't collide if(!overlapping(body1, body2)) return false; //set the minDist to an extreme value float minDist = 10000.0f; //iterate through the edges of both bodies for(int i = 0; i < body1->edgeCount + body2->edgeCount; i++) { Edge* e; if(i < body1->edgeCount) { e = body1->edgeArray[i]; currentImpact.edgeParent = body1; } else { e = body2->edgeArray[i - body1->edgeCount]; currentImpact.edgeParent = body2; } //This will skip edges that lie totally inside the bodies, as they don't matter. if(e->isBorder) { Vec2 axis(e->vertex1->pos.y - e->vertex2->pos.y, e->vertex2->pos.x - e->vertex1->pos.x); axis.normalize(); //project the two bodies float minA, minB, maxA, maxB; body1->ProjectToAxis(axis, minA, maxA); body2->ProjectToAxis(axis, minB, maxB); float dist = getDistance(minA, maxA, minB, maxB); if(dist > 0.0f) return false;//no overlap = no collision else if(abs(dist) < minDist) { minDist = abs(dist); currentImpact.normal = axis; currentImpact.edge = e; } } } currentImpact.depth = minDist; if(currentImpact.edgeParent != body2) { RigidBody* temp = body2; body2 = body1; body1 = temp; } int sign = sgn(currentImpact.normal * (body1->center - body2->center)); if(sign != 1) currentImpact.normal = -currentImpact.normal;//if the normal points away from the other body, switch it Vec2 CollisionVector = currentImpact.normal * currentImpact.depth; //set minDist2 to an extreme value float minDist2 = 10000.0f; for(int j = 0; j < body1->vertexCount; j++) { //get distance of vertex to line float dist2 = currentImpact.normal * (body1->vertexArray[j]->pos); if(dist2 < minDist2) { minDist2 = dist2; currentImpact.vertex = body1->vertexArray[j]; } } //objects collided return true; } /** * Called after a collision between a vertex and an edge has been detected and both structs have been * stored in currentImpact. handleCollision calculates a vector which will push the vertex and the edge apart * so they are no longer touching. If the vertex or edge belongs to a RigidBody of type OBSTACLE, it will not be moved. * Once the vector has been calculated, it will be applied to the appropriate vertex and/or edge. */ void World::handleCollision() { Vertex* vertex1 = currentImpact.edge->vertex1; Vertex* vertex2 = currentImpact.edge->vertex2; Vec2 CollisionVector = currentImpact.normal * currentImpact.depth; //the vertex can hit at any point along the vertex, find out where it hit float edgePos; if(abs(vertex1->pos.x - vertex2->pos.x ) > abs(vertex1->pos.y - vertex2->pos.y)) {//check for divide by 0 error edgePos = (currentImpact.vertex->pos.x - CollisionVector.x - vertex1->pos.x) / (vertex2->pos.x - vertex1->pos.x); } else { edgePos = (currentImpact.vertex->pos.y - CollisionVector.y - vertex1->pos.y) / (vertex2->pos.y - vertex1->pos.y); } //calculate an offset scale so the vertex on the edge closest to the collision vertex is most strongly affected float scale = 1.0f/(edgePos*edgePos + (1 - edgePos) * (1 - edgePos)); //apply vectors for impact if(currentImpact.edgeParent->shape != OBSTACLE) { vertex1->pos -= CollisionVector * (1 - edgePos) * 0.5f * scale; vertex2->pos -= CollisionVector * edgePos * 0.5f * scale; } currentImpact.vertex->pos += CollisionVector * 0.5f; } /** * Returns the distance between a Body at minA and maxA and a Body at minB and maxB */ float World::getDistance(float minA, float maxA, float minB, float maxB) { if(minA < minB) return minB - maxA; else return minA - maxB; } /** * The update loop for the world. For each RigidBody in the world, applies * gravity, updates positions, and handles collision detection and collision * responses. */ void World::Update() { updateForces(); updateVerlet(); processAllCollisions(); } /** * Draws each RigidBody present in the World * Squares are drawn in blue, triangles in green * Call each update to redraw the scene */ void World::Render() { glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_LINE_SMOOTH); //draw quads and tris for each shape for(int j = 0; j < totalBodies; j++) { switch(RigidBodyArray[j]->shape) { case TRIANGLE: glColor3f(0.53f, 0.8f, 0.53f); glBegin(GL_TRIANGLES); glVertex2f(RigidBodyArray[j]->vertexArray[0]->pos.x, RigidBodyArray[j]->vertexArray[0]->pos.y); glVertex2f(RigidBodyArray[j]->vertexArray[1]->pos.x, RigidBodyArray[j]->vertexArray[1]->pos.y); glVertex2f(RigidBodyArray[j]->vertexArray[2]->pos.x, RigidBodyArray[j]->vertexArray[2]->pos.y); glEnd(); break; case SQUARE: glColor3f(0.53f, 0.8f, 1.0f); glBegin(GL_QUADS); glVertex2f(RigidBodyArray[j]->vertexArray[0]->pos.x, RigidBodyArray[j]->vertexArray[0]->pos.y); glVertex2f(RigidBodyArray[j]->vertexArray[1]->pos.x, RigidBodyArray[j]->vertexArray[1]->pos.y); glVertex2f(RigidBodyArray[j]->vertexArray[2]->pos.x, RigidBodyArray[j]->vertexArray[2]->pos.y); glVertex2f(RigidBodyArray[j]->vertexArray[3]->pos.x, RigidBodyArray[j]->vertexArray[3]->pos.y); glEnd(); break; default: break; } } //draw outlines of each shape glLineWidth(2.0f); for(int i = 0; i < totalBodies; i++) { RigidBody* rb = RigidBodyArray[i]; switch(rb->shape) { case TRIANGLE: glColor3f(0.0f, 0.55f, 0.0f);//light green break; case SQUARE: glColor3f(0.0f, 0.5f, 1.0f);//light blue break; default: glColor3f(0.0f, 0.5f, 1.0f);//same as square, light blue break; } for(int j = 0; j < rb->edgeCount; j++) { glBegin(GL_LINES); if(rb->edgeArray[j]->isBorder) { glVertex2f(rb->edgeArray[j]->vertex1->pos.x, rb->edgeArray[j]->vertex1->pos.y); glVertex2f(rb->edgeArray[j]->vertex2->pos.x, rb->edgeArray[j]->vertex2->pos.y); } glEnd(); } } //draw Obstacles in Obstacle Array for(int i = 0; i < totalObstacles; i++) { switch(ObstacleArray[i]->shape) { case OBSTACLE: glColor3f(0.52f, 0.37f, 0.26f);//light brown glBegin(GL_QUADS); glVertex2f(ObstacleArray[i]->vertexArray[0]->pos.x, ObstacleArray[i]->vertexArray[0]->pos.y); glVertex2f(ObstacleArray[i]->vertexArray[1]->pos.x, ObstacleArray[i]->vertexArray[1]->pos.y); glVertex2f(ObstacleArray[i]->vertexArray[2]->pos.x, ObstacleArray[i]->vertexArray[2]->pos.y); glVertex2f(ObstacleArray[i]->vertexArray[3]->pos.x, ObstacleArray[i]->vertexArray[3]->pos.y); glEnd(); break; default: break; } } //print obstacle edges for(int i = 0; i < totalObstacles; i++) { RigidBody* rb = ObstacleArray[i]; switch(rb->shape) { case OBSTACLE: glColor3f(0.65f, 0.5f, 0.39f);//light brown break; default: glColor3f(1.0f, 1.0f, 1.0f);//white break; } for(int j = 0; j < rb->edgeCount; j++) { glBegin(GL_LINES); if(rb->edgeArray[j]->isBorder) { glVertex2f(rb->edgeArray[j]->vertex1->pos.x, rb->edgeArray[j]->vertex1->pos.y); glVertex2f(rb->edgeArray[j]->vertex2->pos.x, rb->edgeArray[j]->vertex2->pos.y); } glEnd(); } } }
#ifndef RIGIDBODY_H #define RIGIDBODY_H #include "Edge.h" //includes Vertex.h /** enum for RigidBody types **/ enum Shape {OBSTACLE, //Rectangle which is not affected by physics, holds other shapes TRIANGLE, SQUARE}; struct RigidBody { /** 2D point describing the center of the RigidBody **/ Vec2 center; /** Creates a square bounding box around the rigidbody **/ int minX, minY, maxX, maxY; /** The maximum number of vertices in the RigidBody. Based on the shape. **/ int maxVerts; /** The maximum number of edges in the RigidBody. Based on the shape. **/ int maxEdges; /** The current number of vertices in the RigidBody. Must not exceed maxVerts **/ int vertexCount; /** The current number of edges in the RigidBody. Must not exceed maxEdges **/ int edgeCount; /** Enum describing the shape of the RigidBody **/ Shape shape; //The number of edges and vertices changes depending on the type of shape //We will initialize these arrays in the constructor Vertex* vertexArray[4]; Edge* edgeArray[6]; /** * Constructor */ RigidBody(Shape s); ~RigidBody(); void finishRigidBody(int x, int y); void AddEdge(Edge* e); void AddVertex(Vertex* v); void ProjectToAxis(Vec2& axis, float& min, float& max); void CalculateCenter(); }; #endif RIGIDBODY_H
#include "RigidBody.h" /** * Constructor for a RigidBody object. * Sets the shape of the RigidBody and the maxVerts and maxEdges based on the shape. * Does not create Vertices or Edges for the shape. This can be done with the finishRigidBody method. */ RigidBody::RigidBody(Shape s) { shape = s; vertexCount = 0; edgeCount = 0; switch(shape) { case OBSTACLE: maxVerts = 4; maxEdges = 6; break; case TRIANGLE: maxVerts = 3; maxEdges = 3; break; case SQUARE: maxVerts = 4; maxEdges = 6; break; default: cout << "Error: Cannot create RigidBody of type " << shape << endl; exit(1); break; } } RigidBody::~RigidBody() { for(int i = 0; i < vertexCount; ++i) delete vertexArray[i]; for(int i = 0; i < edgeCount; ++i) delete edgeArray[i]; } /** * Creates the edges and vertices for the RigidBody based on the shape of the RigidBody. * Also sets the positions of the edges and vertices based on x and y position values. * x: x coordinate of RigidBody in world * y: y coordinate of RigidBody in world */ void RigidBody::finishRigidBody(int x, int y) { switch(shape) { case OBSTACLE: { Vertex* v1 = new Vertex(x, y); Vertex* v2 = new Vertex(x + OBSTACLE_WIDTH, y); Vertex* v3 = new Vertex(x + OBSTACLE_WIDTH, y + OBSTACLE_HEIGHT); Vertex* v4 = new Vertex(x, y + OBSTACLE_HEIGHT); vertexArray[0] = v1; vertexArray[1] = v2; vertexArray[2] = v3; vertexArray[3] = v4; //create border edges edgeArray[0] = new Edge(v1, v2, true); edgeArray[1] = new Edge(v2, v3, true); edgeArray[2] = new Edge(v3, v4, true); edgeArray[3] = new Edge(v4, v1, true); //create cross section edges (non-border) edgeArray[4] = new Edge(v1, v3, false); edgeArray[5] = new Edge(v2, v4, false); vertexCount = 4; edgeCount = 6; break; } case TRIANGLE: { Vertex* v1 = new Vertex(x, y); Vertex* v2 = new Vertex(x - 25, y - 25); Vertex* v3 = new Vertex(x - 50, y); vertexArray[0] = v1; vertexArray[1] = v2; vertexArray[2] = v3; edgeArray[0] = new Edge(v1, v2); edgeArray[1] = new Edge(v2, v3); edgeArray[2] = new Edge(v3, v1); vertexCount = 3; edgeCount = 3; } break; case SQUARE: { Vertex* v1 = new Vertex(x, y); Vertex* v2 = new Vertex(x + SQUARE_LENGTH, y); Vertex* v3 = new Vertex(x + SQUARE_LENGTH, y + SQUARE_LENGTH); Vertex* v4 = new Vertex(x, y + SQUARE_LENGTH); vertexArray[0] = v1; vertexArray[1] = v2; vertexArray[2] = v3; vertexArray[3] = v4; //create border edges edgeArray[0] = new Edge(v1, v2, true); edgeArray[1] = new Edge(v2, v3, true); edgeArray[2] = new Edge(v3, v4, true); edgeArray[3] = new Edge(v4, v1, true); //create cross section edges (non-border) edgeArray[4] = new Edge(v1, v3, false); edgeArray[5] = new Edge(v2, v4, false); vertexCount = 4; edgeCount = 6; } break; default: { cout << "Error: Cannot create RigidBody of type " << shape << endl; exit(1); } break;//should never reach } } /** * Adds the specified edge to the RigidBody. Increases the edge count. * e: Edge to add */ void RigidBody::AddEdge(Edge* e) { if(edgeCount + 1 <= maxEdges) { edgeArray[edgeCount] = e; edgeCount++; } } /** * Adds the specified Vertex to the RigidBody. Increases the vertex count. * v: Vertex to add */ void RigidBody::AddVertex(Vertex *v) { if(vertexCount + 1 <= maxVerts) { vertexArray[vertexCount] = v; vertexCount++; } } /** * Projects the Rigid body to the specified axis. Then stores the min and max * values calculated in the specified min and max variables. */ void RigidBody::ProjectToAxis(Vec2& axis, float& min, float& max) { float DotProduct = axis * vertexArray[0]->pos; //Set the min and max values based on the new projection //start by setting to projection of first vert min = DotProduct; max = DotProduct; //then iterate through remaining verts and update if necessary for(int i = 0; i < vertexCount; i++) { DotProduct = axis * vertexArray[i]->pos; min = min(DotProduct, min); max = max(DotProduct, max); } } /** * Determines the center of the RigidBody and sets the center field. */ void RigidBody::CalculateCenter() { center = Vec2(0.0f, 0.0f); //initialize min and max values to extreme values minX = minY = 10000.0f; maxX = maxY = -10000.0f; //loop through all vertices in the array for(int i = 0; i < vertexCount; i++) { //add the pos of the vertex to the center center += vertexArray[i]->pos; //adjust the min and max x,y values minX = min(minX, vertexArray[i]->pos.x); minY = min(minY, vertexArray[i]->pos.y); maxX = max(maxX, vertexArray[i]->pos.x); maxY = max(maxY, vertexArray[i]->pos.y); } //divide by the number of vertices to find the average, which is the center center = center / vertexCount; }
#ifndef EDGE_H #define EDGE_H #include "Vertex.h" struct RigidBody; struct Edge { /** First vertex that defines the edge **/ Vertex* vertex1; /** Second vertex that defines the edge **/ Vertex* vertex2; /** Length of the edge **/ float length; //a border is an edge that defines the shape of the object //some edges connect points, but do not contribute to the shape // ---- // |\/| The diagonal lines are not borders // |/\| The horizonal and vertical lines are borders // ---- bool isBorder; /** * Constructor */ Edge(); Edge(Vertex* pVertex1, Vertex* pVertex2, bool pBorder = true); }; #endif
#include "Edge.h" /** * Default constructor for an Edge object. * Does not set positions for the two vertices. Length of the edge is set to 0. * Does not mark the edge as a border. */ Edge::Edge() { vertex1 = NULL; vertex2 = NULL; length = 0; isBorder = false; } /** * Constructor for an Edge object. * Uses the specified vertices to calculate the edge length. * pVertex1: First vertex that defines the edge * pVertex2: Second vertex that defines the edge * pBorder: boolean indicating whether the edge is a border edge or not */ Edge::Edge(Vertex* pVertex1, Vertex* pVertex2, bool pBorder) { vertex1 = pVertex1; //Set boundary vertices vertex2 = pVertex2; length = (pVertex2->pos - pVertex1->pos).length(); isBorder = pBorder; }
#ifndef VERTEX_H #define VERTEX_H #include "Configuration.h" struct RigidBody; struct Vertex { /** Current position in the World **/ Vec2 pos; /** Previous position in the World **/ Vec2 oldPos; /** Current acceleration **/ Vec2 accel; /** * Default Constructor */ Vertex(); Vertex(float PosX, float PosY); }; #endif
#include "Vertex.h" /** * Default Vertex constructor. Sets pos and oldPos to (0,0) * All other values are set to default variable values */ Vertex::Vertex() { pos = Vec2(0, 0); oldPos = Vec2(0, 0); } /** * Constructor for Vertex object. * Sets current position and old position to specified values. * All other values set to defaul variable values. * posX: x coordinate of Vertex * posY: y coordinate of Vertex */ Vertex::Vertex(float posX, float posY) { pos = Vec2(posX, posY); oldPos = Vec2(posX, posY); }
#ifndef CURRENT_IMPACT_H #define CURRENT_IMPACT_H struct CurrentImpact { float depth; Vec2 normal; RigidBody* edgeParent; Edge* edge; Vertex* vertex; }; #endif