#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, -25.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;; 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; float32 slope; float32 yIntercept; OrthographicVertex vertices[2]; void load(OrthographicRenderer* renderer, Vector4 color, Vector2 inStart, Vector2 inEnd) { start = inStart; end = inEnd; slope = (end.y - start.y) / (end.x - start.x); yIntercept = (end.y - slope * end.x); 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; // Solve quadratic equation to see if the imaginary line defined by our line segment intersects the ellipse. // Equation: x^2 / a^2 + y^2 / b^2 = 1 // (x^2 / a^2) + (line equation) ^2 / b^2 = 1 // => x^2 / (pill->a * pill->a) + (segment->slope * x + segment->yIntercept) / (pill->b * pill ->b) = 1.f; // Build a triangle defined by the following to vectors: Vector2 startToCenter = pill->body.position - segment->start; Vector2 startToEnd = segment->end - segment->start; Vector2 endToCenter = pill->body.position - segment->end; // Get the area of this triangle using the properties of the determinant: float32 area = areaOfTriangle(startToCenter, startToEnd, endToCenter); float32 base = startToEnd.length(); // Knowning that Area = 0.5 Base * Height float32 height = (2.f * area) / base; // Distance from center of pill to line if (height <= MAX(pill->b, pill->a) && height >= MIN(pill->b, pill->a)) { // We at least have an intersection: Half the problem solved! result.intersect = true; } else { result.intersect = false; return result; } // Time to find where we intersected. We can do this by getting the intersection depth. Vector2 transformedX = pill->shape.model.multByVec2(Vector2 { pill->a / 2.f, 0.f }); Vector2 transformedY = pill->shape.model.multByVec2(Vector2 { pill->b / 2.f, 0.f }); return result; } void update(float32 deltaTimeSeconds, void* userData) { // Input pill.body.applyGravity(); // Update pill.update(deltaTimeSeconds); // Intersections for (int32 lineIdx = 0; lineIdx < 4; lineIdx++) { getIntersection(&pill, &segmentList[lineIdx]); } // 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; }