#include "damped.h" #include "undamped.h" #include "../../../shared_cpp/Renderer2d.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 namespace Damped { struct DampedSpringWeight { Mesh2d shape; float32 radius; float32 mass = 1.f; void load(Renderer2d* renderer, float32 inRadius, Vector4 startColor, Vector4 endColor); void update(float32 dtSeconds); void render(Renderer2d* renderer); void unload(); }; struct DampedSpring { DampedSpringWeight* weight; Mesh2d shape; Vertex2d* vertices = NULL; int32 numSegments = 0; int32 numVertices = 0; // Initialization variables float32 k = 4; // DampedSpring Constant, in N / m (Hooke's Law) float32 c = 1.f; // Viscous damping coefficient (Damping force) // Update variables float32 force = 0.f; float32 velocity = 0.f; float32 position = 0.f; void load(Renderer2d* renderer, DampedSpringWeight* inWieight, float32 length, float32 loopRadius, float32 initialDisplacement, float32 inK, float32 inC); void update(float32 dtSeconds); void render(Renderer2d* renderer); void unload(); }; DampedInitVariables initVariables; void setInitVariables(DampedInitVariables newVariables) { initVariables = newVariables; } DampedInitVariables getInitVariables() { return initVariables; } WebglContext* context; Renderer2d renderer; MainLoop mainLoop; DampedSpringWeight weight; DampedSpring spring; 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 deltaTimeSeconds, void* userData); void unload(); void init(WebglContext* inContext) { context = inContext; emscripten_set_click_callback("#gl_canvas_play_damped", NULL, false, onPlayClicked); emscripten_set_click_callback("#gl_canvas_stop_damped", NULL, false, onStopClicked); } void load() { context->init("#gl_canvas_damped"); renderer.load(context); weight.load(&renderer, initVariables.mass, Vector4 { 55.f, 235.f, 35.f, 255.f }, Vector4 { 235.f, 5.f, 235.f, 255.f }); spring.load(&renderer, &weight, initVariables.springLength, 16.f, initVariables.initialDisplacement, initVariables.k, initVariables.c); mainLoop.run(update); } void update(float32 deltaTimeSeconds, void* userData) { // -- Update spring.update(deltaTimeSeconds); weight.update(deltaTimeSeconds); // -- Render renderer.render(); weight.render(&renderer); spring.render(&renderer); } void unload() { mainLoop.stop(); renderer.unload(); weight.unload(); spring.unload(); context->destroy(); } void DampedSpringWeight::load(Renderer2d* renderer, float32 inMass, Vector4 startColor, Vector4 endColor) { mass = inMass; radius = mass * 8.f; if (radius > 42.f) radius = 42.f; const int32 numSegments = 96; const float32 radiansPerSegment = (2.f * PI) / static_cast(numSegments); const int32 numVertices = numSegments * 3; float32 t = 0.f; float32 tIncrement = 1.f / (numSegments / 2.f); startColor = startColor.toNormalizedColor(); endColor = endColor.toNormalizedColor(); Vertex2d vertices[numVertices]; for (int idx = 0; idx < numSegments; idx++) { int vIdx = idx * 3; Vector4 color = lerp(startColor, endColor, t); if (idx >= numSegments / 2) { t -= tIncrement; } else { t += tIncrement; } 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); } void DampedSpringWeight::update(float32 dtSeconds) { } void DampedSpringWeight::render(Renderer2d* renderer) { shape.render(renderer); } void DampedSpringWeight::unload() { shape.unload(); } const float32 epsilon = 0.0001f; void DampedSpring::load(Renderer2d* renderer, DampedSpringWeight* inWeight, float32 length, float32 loopRadius, float32 initialDisplacement, float32 inK, float32 inC) { weight = inWeight; k = inK; c = inC; // Set the quadratic terms to something that we're familiar with. float32 A = weight->mass; float32 B = c; float32 C = k; float32 discriminant = B * B - (4 * A * C); velocity = 0; position = -initialDisplacement; force = -c * velocity - k * position; if (discriminant < epsilon && discriminant > -epsilon) { // Real repeated root (~ zero): Critically damped motion printf("Critically damped motion.\n"); } else if (discriminant > 0) { // Two real roots (greater than zero): Overdamped motion printf("Overdamped motion.\n"); } else { // Complex pair (less than zero): Underdamped motion printf("Underdamped motion.\n"); } const int32 verticesPerSegment = 6; numSegments = 256; numVertices = numSegments * verticesPerSegment; vertices = new Vertex2d[numVertices]; float32 lengthIncrement = length / static_cast(numSegments); const float32 frequency = 0.25f; const float32 loopWidth = 20.f; const float32 offset = 0.25f; int32 vidx = 0; float32 dx = position; for (int pidx = 0; pidx < numSegments; pidx++) { float32 y1 = lengthIncrement * pidx; float32 x1 = loopWidth * sinf(frequency * y1 + offset); float32 y2 = y1 + lengthIncrement; float32 x2 = loopWidth * sinf(frequency * y2 + offset); float32 y1Offset = dx * (1.f - pidx / static_cast(numSegments)); float32 y2Offset = dx * (1.f - (pidx + 1) / static_cast(numSegments)); y1 += y1Offset; y2 += y2Offset; vertices[vidx++].position = Vector2(x1, y1); vertices[vidx++].position = Vector2(x1, y2); vertices[vidx++].position = Vector2(x2, y1); vertices[vidx++].position = Vector2(x2, y1); vertices[vidx++].position = Vector2(x1, y2); vertices[vidx++].position = Vector2(x2, y2); } shape.load(vertices, numVertices, renderer, GL_DYNAMIC_DRAW); shape.model = Mat4x4().translateByVec2(Vector2(400, 300)); weight->shape.model = shape.model.translateByVec2(Vector2(0, -weight->radius + dx)); } void DampedSpring::update(float32 dtSeconds) { force = -c * velocity - k * position; float32 acceleration = force / weight->mass; velocity = velocity + acceleration * dtSeconds; float32 lastPosition = position; position = position + velocity * dtSeconds; float32 dx = position - lastPosition; int32 vidx = 0; for (int pidx = 0; pidx < numSegments; pidx++) { float32 y1Offset = dx * (1.f - pidx / static_cast(numSegments)); float32 y2Offset = dx * (1.f - (pidx + 1) / static_cast(numSegments)); vertices[vidx++].position.y += y1Offset; vertices[vidx++].position.y += y2Offset; vertices[vidx++].position.y += y1Offset; vertices[vidx++].position.y += y1Offset; vertices[vidx++].position.y += y2Offset; vertices[vidx++].position.y += y2Offset; } weight->shape.model = weight->shape.model.translateByVec2(Vector2(0, dx)); } void DampedSpring::render(Renderer2d* renderer) { glBindBuffer(GL_ARRAY_BUFFER, shape.vbo); glBufferSubData(GL_ARRAY_BUFFER, 0, numVertices * sizeof(Vertex2d), &vertices[0]); shape.render(renderer); } void DampedSpring::unload() { shape.unload(); delete[] vertices; } 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; } }