#include "../../../shared_cpp/Renderer2d.h" #include "../../../shared_cpp/types.h" #include "../../../shared_cpp/mathlib.h" struct SoftbodyUpdateVertexData { Vector2 position; // Position is in world coordinates, for math's sake. Vector2 displacement; Vector2 initialVelocity; Vector2 initialDisplacement; bool isHovered = false; SoftbodyUpdateVertexData* left; SoftbodyUpdateVertexData* right; SoftbodyUpdateVertexData* top; SoftbodyUpdateVertexData* bottom; }; EM_BOOL onMouseMove(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData); EM_BOOL onMouseDown(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData); EM_BOOL onMouseUp(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData); struct SoftbodyRectangle { float32 width = 200; float32 height = 200; Vector2 position; int32 springDensity = 10; float32 springConstant = 4.f; // in N /m float32 jointMassKg = 10; float32 angularVelocity = 0.f; // Runtime data float32 totalTimeSeconds = 0.f; SoftbodyUpdateVertexData* updateData = NULL; bool hasLastPositionChanged = false; Vector2 lastMousePosition; SoftbodyUpdateVertexData* draggedVertex = NULL; // Render data Mesh2d mesh; Mesh2d pointsMesh; Vertex2d* vertices = NULL; Vertex2d* pointsVertices = NULL; void load(Renderer2d* renderer) { position = Vector2(800 / 2 - width / 2, 600 / 2 - height / 2); angularVelocity = sqrtf(springConstant / jointMassKg); int32 numVertices = springDensity * springDensity; // Each subdivision is a square. int32 numIndices = 6 * ((springDensity - 1) * (springDensity - 1)); vertices = new Vertex2d[numVertices]; updateData = new SoftbodyUpdateVertexData[numVertices]; auto indices = new GLuint[numIndices]; // Load a square with the desired density int32 vIdx = 0; int32 iIdx = 0; float32 inverseDensity = 1.f / springDensity; float32 halfInv = inverseDensity / 2.f; for (int32 y = 0; y < springDensity; y++) { // Rows for (int32 x = 0; x < springDensity; x++) { // Columns Vector2 vpos = Vector2(x * inverseDensity - halfInv, y * inverseDensity- halfInv); vpos.x = vpos.x * width + position.x; vpos.y = vpos.y * height + position.y; vertices[vIdx] = { vpos, Vector4(1, 0, 0, 1) }; updateData[vIdx].position = vpos; updateData[vIdx].initialVelocity = Vector2(randomFloatBetween(0, 1.0) * 0.1, randomFloatBetween(0, 1.0) * 0.1); updateData[vIdx].initialDisplacement = Vector2(randomFloatBetween(0, 1.0) * 0.1, randomFloatBetween(0, 1.0) * 0.1); if (x != 0) updateData[vIdx].left = &updateData[vIdx - 1]; if (x != springDensity - 1) updateData[vIdx].right = &updateData[vIdx + 1]; if (y != 0) updateData[vIdx].top = &updateData[vIdx - springDensity]; if (y != springDensity - 1) updateData[vIdx].bottom = &updateData[vIdx + springDensity]; if (y != springDensity - 1 && x != springDensity - 1) { indices[iIdx++] = vIdx; indices[iIdx++] = vIdx + 1; indices[iIdx++] = vIdx + springDensity; indices[iIdx++] = vIdx + springDensity; indices[iIdx++] = vIdx + springDensity + 1; indices[iIdx++] = vIdx + 1; } vIdx++; } } mesh.load(vertices, numVertices, indices, numIndices, renderer, GL_DYNAMIC_DRAW); // mesh.model = Mat4x4().scale(Vector3(width, height, 0)).translateByVec2(position); pointsVertices = new Vertex2d[numVertices]; for (int32 v = 0; v < numVertices; v++) { pointsVertices[v].position = vertices[v].position; pointsVertices[v].color = Vector4(0, 0, 0, 1); } //pointsMesh.model = mesh.model; pointsMesh.load(pointsVertices, numVertices, renderer, GL_DYNAMIC_DRAW); delete [] indices; // Setup callbacks emscripten_set_mousemove_callback("#gl_canvas", this, false, onMouseMove); emscripten_set_mousedown_callback("#gl_canvas", this, false, onMouseDown); emscripten_set_mouseup_callback("#gl_canvas", this, false, onMouseUp); } void update(float32 dtSeconds) { totalTimeSeconds += dtSeconds; for (int32 v = 0; v < pointsMesh.numVertices; v++) { auto springWeight = &updateData[v]; //springWeight->displacement.x = springWeight->initialDisplacement.x * cosf(angularVelocity * totalTimeSeconds - springWeight->initialVelocity.x); //springWeight->displacement.y = springWeight->initialDisplacement.y * cosf(angularVelocity * totalTimeSeconds - springWeight->initialVelocity.y); //Vector2 nextPosition = springWeight->position + springWeight->displacement; //vertices[v].position = nextPosition; //pointsVertices[v].position = nextPosition; if (springWeight == draggedVertex && hasLastPositionChanged) { hasLastPositionChanged = false; vertices[v].position = lastMousePosition; pointsVertices[v].position = lastMousePosition; } if (!draggedVertex && hasLastPositionChanged) { if ((springWeight->position - lastMousePosition).length() < 10.f) { pointsVertices[v].color = Vector4(1, 1, 0, 1); springWeight->isHovered = true; } else { pointsVertices[v].color = Vector4(0, 0, 0, 1); springWeight->isHovered = false; } } } if (hasLastPositionChanged) hasLastPositionChanged = false; mesh.updateVertices(vertices); pointsMesh.updateVertices(pointsVertices); } void render(Renderer2d* renderer) { mesh.render(renderer); pointsMesh.render(renderer, GL_POINTS); } void unload() { mesh.unload(); pointsMesh.unload(); delete [] vertices; delete [] pointsVertices; } }; EM_BOOL onMouseMove(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData) { SoftbodyRectangle* rectangle = (SoftbodyRectangle*)userData; rectangle->hasLastPositionChanged = true; rectangle->lastMousePosition = Vector2(static_cast(mouseEvent->targetX), static_cast(600 - mouseEvent->targetY)); return true; } EM_BOOL onMouseDown(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData) { SoftbodyRectangle* rectangle = (SoftbodyRectangle*)userData; for (int32 v = 0; v < rectangle->pointsMesh.numVertices; v++) { auto springWeight = &rectangle->updateData[v]; if (springWeight->isHovered) { rectangle->draggedVertex = springWeight; break; } } return true; } EM_BOOL onMouseUp(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData) { SoftbodyRectangle* rectangle = (SoftbodyRectangle*)userData; rectangle->draggedVertex = NULL; return true; }