#include "../../shared_cpp/Renderer3d.h" #include "../../shared_cpp/RenderShared.h" #include "../../shared_cpp/Camera3d.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 Impulse { Vector3 force = { 0, 0, 0 }; Vector3 pointOfApplication = { 0, 0, 0 }; float32 timeOfApplicationSeconds = 0.016f; float32 timeAppliedSeconds = 0.f; bool isDead = false; }; const int32 MAX_IMPULSES = 4; struct Rigidbody3d { int32 numImpulses = 0; Impulse activeImpulses[MAX_IMPULSES]; Vector3 velocity; Vector3 position; float32 mass = 1.f; Vector3 rotationalVelocity; Quaternion rotation; float32 momentOfInertia = 1.f; void reset() { numImpulses = 0; velocity = { 0, 0, 0}; rotationalVelocity = { 0, 0, 0 }; } void applyImpulse(Impulse i) { if (numImpulses > MAX_IMPULSES) { printf("Unable to apply impulse. Buffer full.\n"); return; } activeImpulses[numImpulses] = i; numImpulses++; } void update(float32 deltaTimeSeconds) { // Apply gravity velocity += (Vector3 { 0.f, -9.8f, 0.f } * deltaTimeSeconds); Vector3 force = { 0.f, 0.f, 0.f }; Vector3 torque = { 0.f, 0.f, 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; i.isDead = true; } float32 impulseDtSeconds = nextTimeAppliedSeconds - i.timeAppliedSeconds; Vector3 forceToApply = i.force * (impulseDtSeconds / i.timeOfApplicationSeconds); force += forceToApply; torque += i.pointOfApplication.cross(force).dot(forceToApply); i.timeAppliedSeconds = nextTimeAppliedSeconds; } Vector3 acceleration = force / mass; velocity += (acceleration * deltaTimeSeconds); position += (velocity * deltaTimeSeconds); Vector3 rotationalAcceleration = torque / momentOfInertia; rotationalVelocity += (rotationalAcceleration * deltaTimeSeconds); Vector3 drDt = rotationalVelocity * deltaTimeSeconds; Quaternion rotationVelocityQuat = quaternionFromRotation(Vector3(1, 0, 0), drDt.x) * quaternionFromRotation(Vector3(0, 1, 0), drDt.y) * quaternionFromRotation(Vector3(0, 0, 1), drDt.z); rotation = rotation * rotationVelocityQuat; 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--; } } } }; float32 bounds = 20.f; struct Sphere { Mesh3d mesh; Rigidbody3d body; void load(Renderer3d* renderer) { const float32 scale = 3.f; const float32 angleIncrements = 2.f; const int32 numFaces = static_cast((180.f / angleIncrements + 1) * (360.f / angleIncrements + 1)); const int32 numVertices = 4.f * numFaces; const int32 numIndices = 6 * numFaces; Vertex3d* vertices = new Vertex3d[numVertices]; GLuint* indices = new GLuint[numIndices]; // Generate vertices and indices GLint index = 0; int32 vidx = 0; int32 iidx = 0; for (float phi = 0.0; phi <= 180; phi += angleIncrements) { const auto cosPhi = cos(DEG_TO_RAD(phi)); const auto sinPhi = sin(DEG_TO_RAD(phi)); const auto nextCosPhi = cos(DEG_TO_RAD(phi + angleIncrements)); const auto nextSinPhi = sin(DEG_TO_RAD(phi + angleIncrements)); for (float theta = 0.0; theta <= 360; theta += angleIncrements) { auto color = colorFromHex(randomFloatBetween(0.f, 255.f), randomFloatBetween(0.f, 255.f), randomFloatBetween(0.f, 255.f), 255.f); const auto cosTheta = cos(DEG_TO_RAD(theta)); const auto sinTheta = sin(DEG_TO_RAD(theta)); const auto nextSinTheta = sin(DEG_TO_RAD(theta + angleIncrements)); const auto nextCosTheta = cos(DEG_TO_RAD(theta + angleIncrements)); // Top Left Point auto topLeftPoint = Vector3(scale * sinPhi * cosTheta, scale * sinPhi * sinTheta, scale * cosPhi); auto topLeftIdx = index++; vertices[vidx++] = { topLeftPoint, topLeftPoint.normalize(), color }; // Bottom Left Point auto bottomLeftPoint = Vector3(scale * nextSinPhi * cosTheta, scale * nextSinPhi * sinTheta, scale * nextCosPhi); auto bottomLeftIdx = index++; vertices[vidx++] = { bottomLeftPoint, bottomLeftPoint.normalize(), color }; // Bottom Right Point auto bottomRightPoint = Vector3(scale * nextSinPhi * nextCosTheta, scale * nextSinPhi * nextSinTheta, scale * nextCosPhi); auto bottomRightIdx = index++; vertices[vidx++] = { bottomRightPoint, bottomRightPoint.normalize(), color }; // Top Right Point auto topRightPoint = Vector3(scale * sinPhi * nextCosTheta, scale * sinPhi * nextSinTheta, scale * cosPhi); auto topRightIdx = index++; vertices[vidx++] = { topRightPoint, topRightPoint.normalize(), color }; indices[iidx++] = (topLeftIdx); indices[iidx++] = (bottomLeftIdx); indices[iidx++] = (bottomRightIdx); indices[iidx++] = (bottomRightIdx); indices[iidx++] = (topLeftIdx); indices[iidx++] = (topRightIdx); } } mesh.load(vertices, numVertices, indices, numIndices, renderer); body.position = Vector3 { 0.f, 0.f, 0.f }; body.velocity = Vector3 { 0.f, 0.f, 0.f }; float32 singleFaceArea = scale; body.momentOfInertia = (body.mass * singleFaceArea) / 6.f; delete [] vertices; delete [] indices; } void update(float32 dtSeconds) { body.update(dtSeconds); if (body.position.x > bounds) { body.position.x = bounds; body.velocity.x = -body.velocity.x; } else if (body.position.x < -bounds) { body.position.x = -bounds; body.velocity.x = -body.velocity.x; } if (body.position.y > bounds) { body.position.y = bounds; body.velocity.y = -body.velocity.y; } else if (body.position.y < -bounds) { body.position.y = -bounds; body.velocity.y = -body.velocity.y; } if (body.position.z > bounds) { body.position.z = bounds; body.velocity.z = -body.velocity.z; } else if (body.position.z < -bounds) { body.position.z = -bounds; body.velocity.z = -body.velocity.z; } mesh.model = body.rotation.toMatrix().translate(body.position); } void render(Renderer3d* renderer) { mesh.render(renderer); } void unload() { mesh.unload(); } }; EM_BOOL onPlayClicked(int eventType, const EmscriptenMouseEvent* mouseEvent, void* userData); EM_BOOL onStopClicked(int eventType, const EmscriptenMouseEvent* mouseEvent, void* userData); EM_BOOL onForceApplicationRequested(int eventType, const EmscriptenMouseEvent* mouseEvent, void* userData); void load(); void update(float32 time, void* userData); void unload(); WebglContext context; Renderer3d renderer; Camera3d camera; MainLoop mainLoop; bool isIntersectingPointer = false; const int32 numSpheres = 2; Sphere sphereList[numSpheres]; 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_click_callback("#force_apply", NULL, false, onForceApplicationRequested); return 0; } void load() { renderer.load(&context); sphereList[0].load(&renderer); sphereList[0].body.mass = 10.f; sphereList[0].body.position = Vector3(10.f, 0, -5); sphereList[0].body.velocity = Vector3(-10.f, 10.f, 10.f); sphereList[0].body.rotationalVelocity = Vector3(1.f, 1.f, 1.f); sphereList[1].load(&renderer); sphereList[1].body.mass = 6.f; sphereList[1].body.position = Vector3(-10.f, 0, 5); sphereList[1].body.velocity = Vector3(10.f, 10.f, -10.f); sphereList[1].body.rotationalVelocity = Vector3(1.f, 1.f, 1.f); camera.projection = Mat4x4().getPerspectiveProjection(0.1f, 10000.f, DEG_TO_RAD(45.f), 800.f / 600.f); camera.view = Mat4x4().translate({ 0, 0, -40.f }); mainLoop.run(update); } void update(float32 deltaTimeSeconds, void* userData) { for (int32 idx = 0; idx < numSpheres; idx++) { sphereList[idx].update(deltaTimeSeconds); } // Renderer renderer.render(&camera); for (int32 idx = 0; idx < numSpheres; idx++) { sphereList[idx].render(&renderer); } } void unload() { mainLoop.stop(); renderer.unload(); for (int32 idx = 0; idx < numSpheres; idx++) { sphereList[idx].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 onForceApplicationRequested(int eventType, const EmscriptenMouseEvent* mouseEvent, void* userData) { printf("Force applied\n"); Impulse base; base.force = { 0, 10000, 0 }; base.pointOfApplication = { -15, -15, 0 }; for (int32 idx = 0; idx < numSpheres; idx++) { sphereList[idx].body.applyImpulse(base); } return true; }