From dbd32f11e2a3df38162c70f946b5bfa9a8dedbfa Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Thu, 1 Apr 2021 17:02:44 -0400 Subject: Lots of work in progress on pill intersections, but will have to read more up on it --- frontend/2d/_collisions/pill_line/main.cpp | 348 ++++++++++++++++++++--------- 1 file changed, 241 insertions(+), 107 deletions(-) (limited to 'frontend/2d/_collisions/pill_line/main.cpp') diff --git a/frontend/2d/_collisions/pill_line/main.cpp b/frontend/2d/_collisions/pill_line/main.cpp index 4187435..9123b50 100644 --- a/frontend/2d/_collisions/pill_line/main.cpp +++ b/frontend/2d/_collisions/pill_line/main.cpp @@ -4,94 +4,167 @@ #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); + } +}; -// -// Pill object -// struct Pill { - OrthographicShape shape; - float32 width = 1.f; - float32 height = 1.f; - - void load(OrthographicRenderer* renderer, float32 numSegments) { - // 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]; - - float32 a = width / 2.f; - float32 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); - delete[] vertices; - } - - void render(OrthographicRenderer* renderer) { - shape.render(renderer); - } - - void unload() { - shape.unload(); - } - - float32 getArea() { - return 0.f; - } + 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; - OrthographicVertex vertices[2]; - - void load(OrthographicRenderer* renderer, Vector4 color, Vector2 start, Vector2 end) { - vertices[0].position = start; - vertices[0].color = color; - vertices[1].position = end; - vertices[1].color = color; - shape.load(vertices, 2, renderer); - } - - void render(OrthographicRenderer* renderer) { - shape.render(renderer, GL_LINES); - } + 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); -EM_BOOL update(float time, void* userData); + +void load(); +void update(float32 time, void* userData); +void unload(); +IntersectionResult getIntersection(Pill* pill, LineSegment* segment); // Global Variables WebglContext context; @@ -101,51 +174,112 @@ 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; + 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; } -EM_BOOL update(float deltaTimeSeconds, void* userData) { - renderer.render(); - pill.shape.render(&renderer); +void update(float32 deltaTimeSeconds, void* userData) { + // Input + pill.body.applyGravity(); + + // Update + pill.update(deltaTimeSeconds); - for (int segmentIndex = 0; segmentIndex < 4; segmentIndex++) { - segmentList[segmentIndex].render(&renderer); - } + // 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); + } +} - return true; +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"); - - renderer.load(&context); - - // Add the pill - pill.width = 100.f; - pill.height = 50.f; - pill.shape.model = Mat4x4().translateByVec2(Vector2 { context.width / 2.f, context.height / 2.f }); - pill.load(&renderer, 64); - - // Add the lines - 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); - return true; + printf("Play clicked\n"); + + load(); + return true; } EM_BOOL onStopClicked(int eventType, const EmscriptenMouseEvent* mouseEvent, void* userData) { - printf("Stop clicked\n"); - mainLoop.stop(); - pill.unload(); - renderer.unload(); - return true; + printf("Stop clicked\n"); + unload(); + return true; } \ No newline at end of file -- cgit v1.2.1