#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 #include struct Rigidbody { Vector2 linearForce = { 0, 0 }; 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; void reset() { linearForce = { 0, 0 }; velocity = { 0, 0 }; rotationalVelocity = 0.f; rotation = 0.f; } void applyForce(Vector2 force, Vector2 pointOfApplication) { linearForce += force; torque += pointOfApplication.getPerp().dot(force); } void applyGravity() { applyForce(Vector2 { 0.f, -100.f }, Vector2 { 0.f, 0.f }); } void update(float32 deltaTimeSeconds) { applyGravity(); Vector2 acceleration = linearForce / 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 Rectangle { OrthographicShape shape; Rigidbody body; Vector2 originalPoints[4]; Vector2 transformedPoints[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 }; for (int32 i = 0; i < 6; i++) { vertices[i].color = color; } 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); body.reset(); body.momentOfInertia = (width * width + height * height) * (body.mass / 12.f); } void update(float32 dtSeconds) { body.update(dtSeconds); shape.model = Mat4x4().translateByVec2(body.position).rotate2D(body.rotation); // Note: This helps us check if the cursor is within the bounds of the // rectangle later on. for (int idx = 0; idx < 4; idx++) { transformedPoints[idx] = shape.model * originalPoints[idx]; } } void render(OrthographicRenderer* renderer) { shape.render(renderer); } void unload() { shape.unload(); } }; struct Circle { OrthographicShape shape; Rigidbody body; float32 radius = 5.f; void load(OrthographicRenderer* renderer, Vector4 color) { const int32 numSegments = 36; const float32 radiansPerSegment = (2.f * PI) / static_cast(numSegments); const int32 numVertices = numSegments * 3; color = color.toNormalizedColor(); OrthographicVertex vertices[numSegments * 3]; for (int idx = 0; idx < numSegments; idx++) { int vIdx = idx * 3; 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)) }; } shape.load(vertices, numVertices, renderer); body.reset(); } void update(float32 dtSeconds) { shape.model = Mat4x4().translateByVec2(body.position); } void render(OrthographicRenderer* renderer) { shape.render(renderer); } void unload() { shape.unload(); } }; EM_BOOL onPlayClicked(int eventType, const EmscriptenMouseEvent* mouseEvent, void* userData); EM_BOOL onStopClicked(int eventType, const EmscriptenMouseEvent* mouseEvent, void* userData); EM_BOOL onMouseMove(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData); void load(); void update(float32 time, void* userData); void unload(); WebglContext context; OrthographicRenderer renderer; MainLoop mainLoop; Rectangle rectangle; Circle pointer; bool isIntersectingPointer = false; 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); emscripten_set_mousemove_callback("#gl_canvas", NULL, false, onMouseMove); return 0; } void load() { renderer.load(&context); rectangle.load(&renderer, Vector4 { 55.f, 235.f, 35.f, 255.f }, 128.f, 64.f); rectangle.body.position = Vector2 { context.width / 3.f, context.height / 3.f }; rectangle.body.velocity = Vector2 { 100.f, 250.f }; pointer.load(&renderer, Vector4 { 25.f, 235.f, 235.f, 255.f }); mainLoop.run(update); } bool isPointInRectangle(Vector2 p, Rectangle r) { // https://math.stackexchange.com/a/190373 Vector2 A = r.transformedPoints[0]; Vector2 B = r.transformedPoints[1]; Vector2 D = r.transformedPoints[3]; float32 amDotAb = (p - A).dot(B - A); float32 abDotAb = (B - A).dot(B - A); float32 amDotAd = (p - A).dot(D - A); float32 aDDotAd = (D - A).dot(D - A); return amDotAb > 0 && amDotAb < abDotAb && amDotAd > 0 && amDotAd < aDDotAd; } void update(float32 deltaTimeSeconds, void* userData) { rectangle.update(deltaTimeSeconds); pointer.update(deltaTimeSeconds); if (isPointInRectangle(pointer.body.position, rectangle)) { if (!isIntersectingPointer) { isIntersectingPointer = true; Vector2 pointOfApplication = pointer.body.position - rectangle.body.position; rectangle.body.applyForce(pointer.body.linearForce, pointOfApplication); } } else if (isIntersectingPointer) { isIntersectingPointer = false; } // Check collisions with walls so we don't go out of the scene. Simply reflect here. if (rectangle.body.position.x <= 0.f) { rectangle.body.position.x = 0.f; rectangle.body.velocity = rectangle.body.velocity - Vector2 { 1.f, 0.f } * (2 * (rectangle.body.velocity.dot(Vector2 { 1.f, 0.f }))); } if (rectangle.body.position.y <= 0.f) { rectangle.body.position.y = 0.f; rectangle.body.velocity = rectangle.body.velocity - Vector2 { 0.f, 1.f } * (2 * (rectangle.body.velocity.dot(Vector2 { 0.f, 1.f }))); } if (rectangle.body.position.x >= 800.f) { rectangle.body.position.x = 800.f; rectangle.body.velocity = rectangle.body.velocity - Vector2 { -1.f, 0.f } * (2 * (rectangle.body.velocity.dot(Vector2{ -1.f, 0.f }))); } if (rectangle.body.position.y >= 600.f) { rectangle.body.position.y = 600.f; rectangle.body.velocity = rectangle.body.velocity - Vector2 { 0.f, -1.f } * (2 * (rectangle.body.velocity.dot(Vector2 { 0.f, -1.f }))) ; } // Renderer renderer.render(); rectangle.render(&renderer); pointer.render(&renderer); } void unload() { mainLoop.stop(); renderer.unload(); rectangle.unload(); pointer.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; } EM_BOOL onMouseMove(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData) { if (!mainLoop.isRunning) { return true; } pointer.body.linearForce.x = static_cast(mouseEvent->movementX) * 1000.f; pointer.body.linearForce.y = static_cast(-mouseEvent->movementY) * 1000.f; pointer.body.position.x = static_cast(mouseEvent->targetX); pointer.body.position.y = static_cast(600.f - mouseEvent->targetY); return true; }