From ff82253a66ee51fe2f0c088ca964402d53545845 Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Thu, 24 Jun 2021 11:30:36 -0400 Subject: (mkosarek) Rigidbody with rotations --- 2d/rigidbody/rigidbody_3/dist/output.wasm | Bin 52832 -> 49458 bytes 2d/rigidbody/rigidbody_3/main.cpp | 366 +++++++++++++----------------- 2 files changed, 155 insertions(+), 211 deletions(-) (limited to '2d/rigidbody/rigidbody_3') diff --git a/2d/rigidbody/rigidbody_3/dist/output.wasm b/2d/rigidbody/rigidbody_3/dist/output.wasm index 1a6e153..b89435a 100755 Binary files a/2d/rigidbody/rigidbody_3/dist/output.wasm and b/2d/rigidbody/rigidbody_3/dist/output.wasm differ diff --git a/2d/rigidbody/rigidbody_3/main.cpp b/2d/rigidbody/rigidbody_3/main.cpp index 75379c4..e34f444 100644 --- a/2d/rigidbody/rigidbody_3/main.cpp +++ b/2d/rigidbody/rigidbody_3/main.cpp @@ -11,114 +11,139 @@ #include #include + +struct Impulse { + Vector2 force = { 0, 0 }; + Vector2 pointOfApplication = { 0, 0 }; + float32 timeOfApplicationSeconds = 0.25f; + float32 timeAppliedSeconds = 0.f; + bool isDead = false; +}; + +const int32 NUM_IMPULSES = 4; + struct Rigidbody { - Vector2 linearForce = { 0, 0 }; + int32 numImpulses = 0; + Impulse activeImpulses[NUM_IMPULSES]; Vector2 velocity = { 0, 0 }; Vector2 position = { 0, 0 }; float32 mass = 1.f; - float32 torque = 0.f; float32 rotationalVelocity = 0.f; float32 rotation = 0.f; float32 momentOfInertia = 1.f; - float32 cofOfRestitution = 1.f; + + float32 cofOfRestitution = 1.f; void reset() { - linearForce = { 0, 0 }; + numImpulses = 0; velocity = { 0, 0 }; rotationalVelocity = 0.f; rotation = 0.f; } - void applyForce(Vector2 force, Vector2 pointOfApplication) { - linearForce += force; - torque += pointOfApplication.getPerp().dot(force); + void applyImpulse(Impulse i) { + if (numImpulses > NUM_IMPULSES) { + printf("Unable to apply impulse. Buffer full.\n"); + return; + } + + activeImpulses[numImpulses] = i; + numImpulses++; } - void applyGravity() { - applyForce(Vector2 { 0.f, -100.f }, Vector2 { 0.f, 0.f }); + void applyGravity(float32 deltaTimeSeconds) { + velocity += (Vector2 { 0.f, -9.8f } * deltaTimeSeconds); } void update(float32 deltaTimeSeconds) { - applyGravity(); + applyGravity(deltaTimeSeconds); + + Vector2 force; + float32 torque = 0.f; + for (int32 idx = 0; idx < numImpulses; idx++) { + Impulse& i = activeImpulses[idx]; + + float32 nextTimeAppliedSeconds = i.timeAppliedSeconds + deltaTimeSeconds; + if (nextTimeAppliedSeconds >= i.timeOfApplicationSeconds) { + nextTimeAppliedSeconds = i.timeOfApplicationSeconds; // Do the remainder of the time + i.isDead = true; + } + + float32 impulseDtSeconds = nextTimeAppliedSeconds - i.timeAppliedSeconds; + Vector2 forceToApply = i.force * (impulseDtSeconds / i.timeOfApplicationSeconds); + force += forceToApply * impulseDtSeconds; + torque += i.pointOfApplication.getPerp().dot(forceToApply); + + i.timeAppliedSeconds = nextTimeAppliedSeconds; + } - Vector2 acceleration = linearForce / mass; + Vector2 acceleration = force / mass; velocity += (acceleration * deltaTimeSeconds); position += (velocity * deltaTimeSeconds); - linearForce = Vector2 { 0.f, 0.f }; - // New: Update the rotational velocity as well float32 rotationalAcceleration = torque / momentOfInertia; rotationalVelocity += (rotationalAcceleration * deltaTimeSeconds); rotation += (rotationalVelocity * deltaTimeSeconds); - torque = 0.f; - } -}; -struct Edge { - Vector2 normal; - Vector2 start; - Vector2 end; + for (int32 idx = 0; idx < numImpulses; idx++) { + if (activeImpulses[idx].isDead) { + for (int j = idx + 1; j < numImpulses; j++) { + activeImpulses[j - 1] = activeImpulses[j]; + } + + idx = idx - 1; + numImpulses--; + } + } + } }; -struct Rectangle { +struct Circle { OrthographicShape shape; Rigidbody body; - Rigidbody previousBody; - Vector2 originalPoints[4]; - Vector2 transformedPoints[4]; - Edge edges[4]; - - void load(OrthographicRenderer* renderer, Vector4 color, float32 width, float32 height) { - color = color.toNormalizedColor(); - - float32 halfWidth = width / 2.f; - float32 halfHeight = height / 2.f; - - OrthographicVertex vertices[6]; - vertices[0].position = Vector2 { -halfWidth, -halfHeight }; - vertices[1].position = Vector2 { -halfWidth, halfHeight }; - vertices[2].position = Vector2 { halfWidth, halfHeight }; - vertices[3].position = Vector2 { -halfWidth, -halfHeight }; - vertices[4].position = Vector2 { halfWidth, -halfHeight }; - vertices[5].position = Vector2 { halfWidth, halfHeight }; + Rigidbody previousBody; + Vector2 force; + float32 radius; + + void load(OrthographicRenderer* renderer, float32 inRadius, Vector4 startColor, Vector4 endColor) { + radius = inRadius; + const int32 numSegments = 36; + const float32 radiansPerSegment = (2.f * PI) / static_cast(numSegments); + const int32 numVertices = numSegments * 3; - for (int32 i = 0; i < 6; i++) { - vertices[i].color = color; + startColor = startColor.toNormalizedColor(); + endColor = endColor.toNormalizedColor(); + + OrthographicVertex vertices[numSegments * 3]; + for (int idx = 0; idx < numSegments; idx++) { + int vIdx = idx * 3; + + Vector4 color; + if (idx >= numSegments / 2) { + color = endColor; + } else { + color = startColor; + } + + vertices[vIdx].color = color; + vertices[vIdx].position = Vector2 { radius * cosf(radiansPerSegment * idx), radius * sinf(radiansPerSegment * idx) }; + vertices[vIdx + 1].color = color; + vertices[vIdx + 1].position = Vector2 { 0.f, 0.f }; + vertices[vIdx + 2].color = color; + vertices[vIdx + 2].position = Vector2 { radius * cosf(radiansPerSegment * (idx + 1)), radius * sinf(radiansPerSegment * (idx + 1)) }; } - originalPoints[0] = vertices[0].position; - originalPoints[1] = vertices[1].position; - originalPoints[2] = vertices[2].position; - originalPoints[3] = vertices[4].position; - - shape.load(vertices, 6, renderer); + shape.load(vertices, numVertices, renderer); body.reset(); - - body.momentOfInertia = (width * width + height * height) * (body.mass / 12.f); + body.momentOfInertia = (PI * (radius * radius)) / 4.f; } void update(float32 dtSeconds) { - previousBody = body; - body.update(dtSeconds); - shape.model = Mat4x4().translateByVec2(body.position).rotate2D(body.rotation); - - // Note: This helps us check rectangle collisions using SAT later on. - // This is probably a slightly slow way of doing this, but we will ignore - // that for now. - for (int idx = 0; idx < 4; idx++) { - transformedPoints[idx] = shape.model * originalPoints[idx]; - } - - for (int eidx = 0; eidx < 4; eidx++) { - edges[eidx].start = transformedPoints[eidx]; - edges[eidx].end = transformedPoints[eidx == 3 ? 0 : eidx + 1]; - edges[eidx].normal = (edges[eidx].end - edges[eidx].start).getPerp().normalize(); - } - } + previousBody = body; - void restorePreviousBody() { - body = previousBody; + shape.model = Mat4x4().translateByVec2(body.position).rotate2D(body.rotation); + body.update(dtSeconds); } void render(OrthographicRenderer* renderer) { @@ -128,6 +153,10 @@ struct Rectangle { void unload() { shape.unload(); } + + void restorePreviousBody() { + body = previousBody; + } }; struct IntersectionResult { @@ -148,8 +177,8 @@ void unload(); WebglContext context; OrthographicRenderer renderer; MainLoop mainLoop; -Rectangle r1; -Rectangle r2; +Circle c1; +Circle c2; int main() { context.init("#gl_canvas"); @@ -161,145 +190,60 @@ int main() { void load() { renderer.load(&context); - r1.load(&renderer, Vector4 { 55.f, 235.f, 35.f, 255.f }, 128.f, 64.f); - r1.body.mass = 3.f; - r1.body.position = Vector2 { context.width / 4.f, context.height / 4.f }; - r1.body.velocity = Vector2 { 100.f, 250.f }; + c1.load(&renderer, 32.f, Vector4 { 55.f, 235.f, 35.f, 255.f }, Vector4 { 235.f, 5.f, 235.f, 255.f }); + c1.body.mass = 3.f; + c1.body.position = Vector2 { context.width / 4.f, context.height / 4.f }; + c1.body.velocity = Vector2 { 100.f, 250.f }; - r2.load(&renderer, Vector4 { 235.f, 5.f, 35.f, 255.f }, 96.f, 64.f); - r2.body.mass = 1.f; - r2.body.position = Vector2 { context.width * (3.f / 4.f), context.height * (3.f / 4.f) }; - r2.body.velocity = Vector2 { -300.f, -150.f }; - r2.body.rotationalVelocity = 0.9f; + c2.load(&renderer, 64.f, Vector4 { 235.f, 5.f, 35.f, 255.f }, Vector4 { 5.f, 35.f, 235.f, 255.f }); + c2.body.mass = 1.f; + c2.body.position = Vector2 { context.width * (3.f / 4.f), context.height * (3.f / 4.f) }; + c2.body.velocity = Vector2 { -300.f, -150.f }; mainLoop.run(update); } -void handleCollisionWithWall(Rectangle* r) { - if (r->body.position.x <= 0.f) { - r->body.position.x = 0.f; - r->body.velocity = r->body.velocity - Vector2 { 1.f, 0.f } * (2 * (r->body.velocity.dot(Vector2 { 1.f, 0.f }))); +void handleCollisionWithWall(Circle* c) { + if (c->body.position.x <= 0.f) { + c->body.position.x = 0.f; + c->body.velocity = c->body.velocity - Vector2 { 1.f, 0.f } * (2 * (c->body.velocity.dot(Vector2 { 1.f, 0.f }))); } - if (r->body.position.y <= 0.f) { - r->body.position.y = 0.f; - r->body.velocity = r->body.velocity - Vector2 { 0.f, 1.f } * (2 * (r->body.velocity.dot(Vector2 { 0.f, 1.f }))); + if (c->body.position.y <= 0.f) { + c->body.position.y = 0.f; + c->body.velocity = c->body.velocity - Vector2 { 0.f, 1.f } * (2 * (c->body.velocity.dot(Vector2 { 0.f, 1.f }))); } - if (r->body.position.x >= 800.f) { - r->body.position.x = 800.f; - r->body.velocity = r->body.velocity - Vector2 { -1.f, 0.f } * (2 * (r->body.velocity.dot(Vector2{ -1.f, 0.f }))); + if (c->body.position.x >= 800.f) { + c->body.position.x = 800.f; + c->body.velocity = c->body.velocity - Vector2 { -1.f, 0.f } * (2 * (c->body.velocity.dot(Vector2{ -1.f, 0.f }))); } - if (r->body.position.y >= 600.f) { - r->body.position.y = 600.f; - r->body.velocity = r->body.velocity - Vector2 { 0.f, -1.f } * (2 * (r->body.velocity.dot(Vector2 { 0.f, -1.f }))) ; + if (c->body.position.y >= 600.f) { + c->body.position.y = 600.f; + c->body.velocity = c->body.velocity - Vector2 { 0.f, -1.f } * (2 * (c->body.velocity.dot(Vector2 { 0.f, -1.f }))) ; } } -/* - Do not worry about how w are exactly finding the intersection here, for now. - We are using the Separating Axis Theorem to do so here. In the 2D -> Collisions - section of the website, we describe this method at length. -*/ -Vector2 getProjection(Vector2* vertices, Vector2 axis) { - float32 min = axis.dot(vertices[0]); - float32 max = min; - - for (int v = 1; v < 4; v++) { - float32 d = axis.dot(vertices[v]); - - if (d < min) { - min = d; - } else if (d > max) { - max = d; - } - } - - return Vector2 { min, max }; -} - -inline bool projectionsOverlap(Vector2 first, Vector2 second) { - return first.x <= second.y && second.x <= first.y; -} - -inline float32 getProjectionOverlap(Vector2 first, Vector2 second) { - float32 firstOverlap = (first.x - second.y); // TODO: Does this need to be absolute value? - float32 secondOverlap = (second.x - first.y); - return firstOverlap > secondOverlap ? secondOverlap : firstOverlap; -} - -struct IntermediateIntersectionResult { - float32 minOverlap = FLT_MAX; - Edge* minOverlapEdge; - bool isOverlapOnFirstEdge = true; -}; - -bool checkEdgeOverlap(Edge* edges, Rectangle* first, Rectangle* second, IntermediateIntersectionResult* iir, bool isFirstEdge) { - // Returns true if SAT passes for the provided set of edges. - for (int i = 0; i < 4; i++) { - Vector2 normal = edges[i].normal; - - Vector2 firstProj = getProjection(first->transformedPoints, normal); - Vector2 secondProj = getProjection(second->transformedPoints, normal); - - if (!projectionsOverlap(firstProj, secondProj)) { - return false; - } - - float32 overlap = getProjectionOverlap(firstProj, secondProj); - if (overlap < iir->minOverlap) { - iir->minOverlap = overlap; - iir->minOverlapEdge = &edges[i]; - iir->isOverlapOnFirstEdge = isFirstEdge; - } - } - - return true; -} - -const float32 EPSILON = 1.f; -IntersectionResult getIntersection(Rectangle* first, Rectangle* second) { - IntersectionResult ir; - - IntermediateIntersectionResult iir; - if (!checkEdgeOverlap(first->edges, first, second, &iir, true)) { - return ir; - } - - if (!checkEdgeOverlap(second->edges, first, second, &iir, false)) { - return ir; - } - - ir.intersect = true; - ir.relativeVelocity = first->body.velocity - second->body.velocity; - ir.collisionNormal = iir.minOverlapEdge->normal; - - float32 minDistanceFromEdge = FLT_MAX; - Vector2 pointOfContact; - Vector2* pointsToCheck = iir.isOverlapOnFirstEdge ? second->transformedPoints : first->transformedPoints; - for (int p = 0; p < 4; p++) { - Vector2 point = pointsToCheck[p]; - - float32 distanceFromEdge = MIN((iir.minOverlapEdge->start - point).length(), (iir.minOverlapEdge->end - point).length()); - - if (distanceFromEdge < minDistanceFromEdge) { - minDistanceFromEdge = distanceFromEdge; - pointOfContact = point; - } - } - - - ir.firstPointOfApplication = pointOfContact - first->body.position; - ir.secondPointOfApplication = pointOfContact - second->body.position;; +IntersectionResult getIntersection(Circle* first, Circle* second) { + IntersectionResult ir; + Vector2 positionDiff = (first->body.position - second->body.position); + if (positionDiff.length() > first->radius + second->radius) { + return ir; // Not intersecting + } + Vector2 positionDirection = positionDiff.normalize(); + ir.relativeVelocity = first->body.velocity - second->body.velocity; + + // The positionDirection could represent the normal at which our circles intersect, but then we would + // never get any rotation on them. At the same time, this is not an entirely great selection because, in the real + // world, two circles wouldn't hit one another at exactly the normal. To fix this, we offset the positionDirection + // by the relative velocity. This gives a normal that is slightly more believable, and allows our spin to take place. + ir.collisionNormal = (positionDirection + ir.relativeVelocity.negate().normalize()).normalize(); + ir.firstPointOfApplication = positionDirection * first->radius; + ir.secondPointOfApplication = positionDirection * second->radius; + ir.intersect = true; + return ir; } -/** -In this method, we resolve the collision of two rigidbodies using the IntersectionResult -that we gathered from the collision information. Note that this particular tutorial -is not about how we find this collision, but rather how we use this collision. To see the -variety of ways of how this IntersectionResult can be calculated go to the 2D->Collision -section of the website. -***/ void resolveCollision(Rigidbody* first, Rigidbody* second, IntersectionResult* ir) { Vector2 relativeVelocity = ir->relativeVelocity; Vector2 collisionNormal = ir->collisionNormal; @@ -322,29 +266,29 @@ void resolveCollision(Rigidbody* first, Rigidbody* second, IntersectionResult* i } void update(float32 deltaTimeSeconds, void* userData) { - r1.update(deltaTimeSeconds); - r2.update(deltaTimeSeconds); + c1.update(deltaTimeSeconds); + c2.update(deltaTimeSeconds); // Let's backtrack the simulation to find the precise point at which we collided. // There exists many ways to find this precise point. This is by far the most // expensive, but it gets the job done. - IntersectionResult ir = getIntersection(&r1, &r2); + IntersectionResult ir = getIntersection(&c1, &c2); if (ir.intersect) { IntersectionResult irCopy = ir; float32 copyDt = deltaTimeSeconds; float32 subdivisionAmountSeconds = deltaTimeSeconds / 16.f; do { - r1.restorePreviousBody(); - r2.restorePreviousBody(); + c1.restorePreviousBody(); + c2.restorePreviousBody(); ir = irCopy; copyDt = copyDt - subdivisionAmountSeconds; - r1.update(copyDt); - r2.update(copyDt); + c1.update(copyDt); + c2.update(copyDt); - irCopy = getIntersection(&r1, &r2); + irCopy = getIntersection(&c1, &c2); if (copyDt <= 0.f) { printf("Error: Should not be happening.\n"); @@ -358,28 +302,28 @@ void update(float32 deltaTimeSeconds, void* userData) { // The following function is the main one that we're talking about in this tutorial. // This function will take the collision data, and repel the objects away from one // another using what we know from physics. - resolveCollision(&r1.body, &r2.body, &ir); + resolveCollision(&c1.body, &c2.body, &ir); float32 frameTimeRemaining = deltaTimeSeconds - copyDt; - r1.update(frameTimeRemaining); - r2.update(frameTimeRemaining); + c1.update(frameTimeRemaining); + c2.update(frameTimeRemaining); } // Keep within the bounds - handleCollisionWithWall(&r1); - handleCollisionWithWall(&r2); + handleCollisionWithWall(&c1); + handleCollisionWithWall(&c2); // Renderer renderer.render(); - r1.render(&renderer); - r2.render(&renderer); + c1.render(&renderer); + c2.render(&renderer); } void unload() { mainLoop.stop(); renderer.unload(); - r1.unload(); - r2.unload(); + c1.unload(); + c2.unload(); } // -- cgit v1.2.1