#include "../../../shared_cpp/OrthographicRenderer.h" #include "../../../shared_cpp/types.h" #include "../../../shared_cpp/WebglContext.h" #include "../../../shared_cpp/mathlib.h" #include "../../../shared_cpp/MainLoop.h" #include #include #include #include #include #include // Side note: It is Eastertime, so I chose this easter color palette. Enjoy: https://htmlcolors.com/palette/144/easter struct Rigidbody { Vector2 force = { 0, 0 }; Vector2 velocity = { 0, 0 }; Vector2 position = { 0, 0 }; float32 rotationalVelocity = 0.f; float32 rotation = 0.f; float32 mass = 1.f; void reset() { force = { 0, 0 }; velocity = { 0, 0 }; } void applyForce(Vector2 f) { force += f; } void applyGravity() { force += Vector2 { 0.f, -100.f }; } void update(float32 deltaTimeSeconds) { Vector2 acceleration = force / mass; velocity += (acceleration * deltaTimeSeconds); position += (velocity * deltaTimeSeconds); force = Vector2 { 0.f, 0.f }; rotation += (rotationalVelocity * deltaTimeSeconds); } }; struct Pill { OrthographicShape shape; Rigidbody body; float32 a = 0; float32 b = 0; Pill copy() { Pill retval; retval.shape = shape; retval.body = body; retval.a = a; retval.b = b; return retval; } void load(OrthographicRenderer* renderer, float32 numSegments, float32 width, float32 height) { // Note that a so-called "pill" is simply an ellipse. // Equation of an ellipse is: // // x^2 / a^2 + y^2 / b^2 = 1 // // or, in parametric form: // // x = a * cos(t), y = b * sin(t) // float32 angleIncrements = (2.f * PI) / numSegments; uint32 numVertices = static_cast(numSegments * 3.f); OrthographicVertex* vertices = new OrthographicVertex[numVertices]; a = width / 2.f; b = height / 2.f; Vector4 color = Vector4().fromColor(243,166,207, 255); for (uint32 vertexIndex = 0; vertexIndex < numVertices; vertexIndex += 3) { // Create a single "slice" of the ellipse (like a pizza) float32 currAngle = (vertexIndex / 3.f) * angleIncrements; float32 nextAngle = (vertexIndex / 3.f + 1.f) * angleIncrements; vertices[vertexIndex].position = Vector2 { 0.f, 0.f }; vertices[vertexIndex].color = color; vertices[vertexIndex + 1].position = Vector2 { a * cosf(currAngle), b * sinf(currAngle) }; vertices[vertexIndex + 1].color = color; vertices[vertexIndex + 2].position = Vector2 { a * cosf(nextAngle), b * sinf(nextAngle) }; vertices[vertexIndex + 2].color = color; } shape.load(vertices, numVertices, renderer); body.reset(); a = width / 2.f; b = height / 2.f; delete[] vertices; } void update(float32 deltaTimeSeconds) { body.update(deltaTimeSeconds); shape.model = Mat4x4().translateByVec2(body.position).rotate2D(body.rotation); } void render(OrthographicRenderer* renderer) { shape.render(renderer); } void unload() { shape.unload(); } float32 getArea() { return 0.f; } }; struct LineSegment { OrthographicShape shape; Vector2 start; Vector2 end; Vector2 normal; OrthographicVertex vertices[2]; void load(OrthographicRenderer* renderer, Vector4 color, Vector2 inStart, Vector2 inEnd) { start = inStart; end = inEnd; vertices[0].position = start; vertices[0].color = color; vertices[1].position = end; vertices[1].color = color; normal = (end - start).getPerp().normalize(); shape.load(vertices, 2, renderer); } void render(OrthographicRenderer* renderer) { shape.render(renderer, GL_LINES); } void unload() { shape.unload(); } bool isPointOnLine(Vector2 p) { // If the dot product is nearly zero, that means it is in the direction of the line if (ABS((end - start).dot(p - start)) <= 0.001) { // We're on the general line, now let's see if we're within the proper bounds: return p.x >= start.x && p.x <= end.x && p.x >= start.y && p.y <= start.y; } return false; } }; struct IntersectionResult { bool intersect = false; Vector2 pointOfIntersection; Vector2 collisionNormal; Vector2 relativeVelocity; }; EM_BOOL onPlayClicked(int eventType, const EmscriptenMouseEvent* mouseEvent, void* userData); EM_BOOL onStopClicked(int eventType, const EmscriptenMouseEvent* mouseEvent, void* userData); void load(); void update(float32 time, void* userData); void unload(); IntersectionResult getIntersection(Pill* pill, LineSegment* segment); // Global Variables WebglContext context; OrthographicRenderer renderer; Pill pill; MainLoop mainLoop; LineSegment segmentList[4]; int main() { context.init("#gl_canvas"); emscripten_set_click_callback("#gl_canvas_play", NULL, false, onPlayClicked); emscripten_set_click_callback("#gl_canvas_stop", NULL, false, onStopClicked); return 0; } void load() { renderer.load(&context); pill.body.position = Vector2 { context.width / 2.f, context.height / 2.f }; pill.body.rotationalVelocity = 0.3f; pill.load(&renderer, 64, 100.f, 50.f); segmentList[0].load(&renderer, Vector4().fromColor(191, 251, 146, 255.f), Vector2 { 50.f, 0.f }, Vector2 { 50.f, static_cast(context.height) }); segmentList[1].load(&renderer, Vector4().fromColor(159, 224, 210, 255.f), Vector2 { context.width - 50.f, 0.f }, Vector2 { context.width - 50.f, static_cast(context.height) }); segmentList[2].load(&renderer, Vector4().fromColor(248, 255, 156, 255.f), Vector2 { 50.f, 50.f }, Vector2 { context.width - 50.f, 150.f }); segmentList[3].load(&renderer, Vector4().fromColor(205, 178, 214, 255.f), Vector2 { 50.f, 150.f }, Vector2 { context.width - 50.f, 50.f }); mainLoop.run(update); } float32 areaOfTriangle(Vector2 a, Vector2 b, Vector2 c) { // Refernce for this for the formula: https://www.onlinemath4all.com/area-of-triangle-using-determinant-formula.html return ABS(0.5 * (a.x * b.y - b.x * a.y + b.x * c.y - c.x * b.y + c.x * a.y - a.x * c.y)); } IntersectionResult getIntersection(Pill* pill, LineSegment* segment) { IntersectionResult result; /*float32 rotationAngleOfPill = pill->body.rotation; Vector2 translationOfPill = pill->body.position; float32 cosRotation = cosf(rotationAngleOfPill); float32 sinRotation = sinf(rotationAngleOfPill); float32 cosRotationSquared = cosRotation * cosRotation; float32 sinRotationSquared = sinRotation * sinRotation; float32 aSquared = pill->a * pill->a; float32 bSquared = pill->b * pill->b; float32 x = (segment->end.x - segment->start.x) - translationOfPill.x; float32 xSquared = x * x; float32 y = (segment->end.y - segment->start.y) - translationOfPill.y; float32 ySquared = y * y; float32 A_XPart = (segment->end.x - segment->start.x - translationOfPill.x) * cosRotationSquared; float32 A_YPart = (segment->end.y - segment->start.y - translationOfPill.y) * sinRotationSquared; float32 A = ((A_XPart * A_XPart) / aSquared) + ((A_YPart * A_YPart) / bSquared); float32 B_XPart = 2 * (segment->start.x - translationOfPill.x) * (segment->end.x - segment->start.x - translationOfPill.x); float32 B = 2 * cosRotation * sinRotation * (1.f / aSquared - 1.f / bSquared) * (x * y); float32 C = (sinRotationSquared / aSquared + cosRotationSquared / bSquared) * ySquared;*/ Mat4x4 inverseModel = pill->shape.model.inverse(); Vector2 start = inverseModel * segment->start; Vector2 end = inverseModel * segment->end; float32 A = pow((end.x - start.x), 2.f) / pow(pill->a, 2) + pow((end.y - start.y), 2.f) / pow(pill->b, 2); float32 B = ((2 * start.x) * (end.x - start.x)) / pow(pill->a, 2) + ((2 * start.y) * (end.y - start.y)) / pow(pill->b, 2); float32 C = pow(start.x, 2) / pow(pill->a, 2) + pow(start.y, 2) / pow(pill->b, 2) - 1.f; float32 determinant = B * B - 4 * A * C; if (determinant < 0.f) { result.intersect = false; return result; } float32 t1 = -B + determinant / (2 * A); float32 t2 = -B - determinant / (2 * A); Vector2 pointAtT1 = { segment->start.x + (t1 * (segment->end.x - segment->start.x)), segment->start.y + (t1 * (segment->end.y - segment->start.y)) }; Vector2 pointAtT2 = { segment->start.x + (t2 * (segment->end.x - segment->start.x)), segment->start.y + (t2 * (segment->end.y - segment->start.y)) }; printf("Intersecting\n"); result.intersect = true; result.pointOfIntersection = pointAtT1 - pill->body.position;; result.relativeVelocity = Vector2() - pill->body.velocity; result.collisionNormal = (pointAtT1 - pill->body.position).normalize(); return result; } void update(float32 deltaTimeSeconds, void* userData) { // Input pill.body.applyGravity(); // Update Pill copyPill = pill.copy(); pill.update(deltaTimeSeconds); // Intersections for (int32 lineIdx = 0; lineIdx < 4; lineIdx++) { IntersectionResult ir = getIntersection(&pill, &segmentList[lineIdx]); if (ir.intersect) { // Find the exact moment that the intersection happens by rewinding the simulation until we're not intersecting while (ir.intersect) { pill = copyPill.copy(); deltaTimeSeconds /= 2.f; pill.update(deltaTimeSeconds); ir = getIntersection(&pill, &segmentList[lineIdx]); if (deltaTimeSeconds <= 0.f) { printf("Error: Should not be happening.\n"); break; } } printf("Found intersection at timestamp: %f\n", deltaTimeSeconds); } } // Render renderer.render(); pill.shape.render(&renderer); for (int32 segmentIndex = 0; segmentIndex < 4; segmentIndex++) { segmentList[segmentIndex].render(&renderer); } } void unload() { mainLoop.stop(); pill.unload(); renderer.unload(); for (int32 segmentIndex = 0; segmentIndex < 4; segmentIndex++) { segmentList[segmentIndex].unload(); } } // // Interactions with DOM handled below // EM_BOOL onPlayClicked(int eventType, const EmscriptenMouseEvent* mouseEvent, void* userData) { printf("Play clicked\n"); load(); return true; } EM_BOOL onStopClicked(int eventType, const EmscriptenMouseEvent* mouseEvent, void* userData) { printf("Stop clicked\n"); unload(); return true; }