summaryrefslogtreecommitdiff
path: root/2d/rigidbody/rigidbody_3
diff options
context:
space:
mode:
Diffstat (limited to '2d/rigidbody/rigidbody_3')
-rwxr-xr-x2d/rigidbody/rigidbody_3/dist/output.wasmbin52832 -> 49458 bytes
-rw-r--r--2d/rigidbody/rigidbody_3/main.cpp366
2 files changed, 155 insertions, 211 deletions
diff --git a/2d/rigidbody/rigidbody_3/dist/output.wasm b/2d/rigidbody/rigidbody_3/dist/output.wasm
index 1a6e153..b89435a 100755
--- a/2d/rigidbody/rigidbody_3/dist/output.wasm
+++ b/2d/rigidbody/rigidbody_3/dist/output.wasm
Binary files 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 <cmath>
#include <cfloat>
+
+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<float>(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();
}
//