summaryrefslogtreecommitdiff
path: root/frontend/2d/_rigidbody/rigidbody_3b.js
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/2d/_rigidbody/rigidbody_3b.js')
-rw-r--r--frontend/2d/_rigidbody/rigidbody_3b.js188
1 files changed, 188 insertions, 0 deletions
diff --git a/frontend/2d/_rigidbody/rigidbody_3b.js b/frontend/2d/_rigidbody/rigidbody_3b.js
new file mode 100644
index 0000000..803161a
--- /dev/null
+++ b/frontend/2d/_rigidbody/rigidbody_3b.js
@@ -0,0 +1,188 @@
+/// <reference path="../../scripts/jquery-3.5.1.min.js"/>
+/// <reference path="../../_shared/math/vec2.js" />
+/// <reference path="../../_shared/math/mat4.js" />
+/// <reference path="../../_shared/2d/shader.js" />
+/// <reference path="../../_shared/math/circle.js" />
+/// <reference path="../../_shared/2d/program_common.js" />
+
+(function() {
+ // Define Constants
+ const GRAVITY = 50.0;
+ const COF_OF_RESTITUITION = 0.9;
+
+ var lProgramContext = undefined;
+
+ function main() {
+ lProgramContext = getContext('#rigidbody_3b');
+
+ if (lProgramContext.gl === null) {
+ console.error('Unable to initialize WebGL. Your browser or machine may not support it.');
+ return;
+ }
+
+ lProgramContext.gl.clearColor(0.0, 0.0, 0.0, 1.0);
+ lProgramContext.gl.clear(lProgramContext.gl.COLOR_BUFFER_BIT);
+ lProgramContext.playButton.on('click', run);
+ }
+
+ function run() {
+ console.log('Running Rigid Body 3b');
+ lProgramContext.load().then(function(pProgramInfo) {
+ const lNumHorizontalPegs = lProgramContext.width / 80.0,
+ lNumVerticalPegs = lProgramContext.height / 80.0,
+ lBall = circle(lProgramContext.gl, 16.0, 16,
+ [ { x: 1, y: 0, z: 0, w: 1 }, { x: 0, y: 1, z: 0, w: 1 }, { x: 0, y: 0, z: 1, w: 1 } ],
+ vec2(38 * 8, lProgramContext.height - 24.0), 10.0),
+ lPegList = [];
+
+ lBall.velocity = vec2(0, -50.0);
+
+ // Generate a peg. These pegs will NOT be updated, so that they
+ // dont' fall due to gravity.
+ for (let lRowIdx = 0; lRowIdx < lNumVerticalPegs - 1; lRowIdx++) {
+ for (let lColIdx = 0; lColIdx <= lNumHorizontalPegs; lColIdx++) {
+ lPegList.push(circle(lProgramContext.gl,
+ 16.0, 16,
+ [ { x: 165.0 / 255.0, y: 42.0 / 255.0, z: 42.0 / 255.0, w: 1.0 }],
+ vec2((lRowIdx % 2 ? 40 : 0) + (lColIdx) * 80.0, lRowIdx * 80.0),
+ 10000 // Real big so it's almost negligble
+ ));
+ }
+ }
+
+ function updateCircle(pCircle, pDeltaTimeSeconds) {
+ // Same physics updates from part 1
+ applyForce(pCircle, vec2(0, -1.0 * (pCircle.mass * GRAVITY)));
+ const lCurrentAcceleration = scaleVec2(pCircle.force, 1.0 / pCircle.mass);
+ pCircle.prevVelocity = pCircle.velocity;
+ pCircle.velocity = addVec2(pCircle.velocity, scaleVec2(lCurrentAcceleration, pDeltaTimeSeconds));
+ pCircle.prevPos = { ...pCircle.position };
+ pCircle.position = addVec2(pCircle.position, scaleVec2(pCircle.velocity, pDeltaTimeSeconds));
+ pCircle.force = vec2();
+
+ // Same physics updates from part 2
+
+ const lMomentOfInertia = getMomentOfInertia(pCircle);
+ const lAngularAcceleration = pCircle.torque / lMomentOfInertia;
+ pCircle.rotationVelocity += lAngularAcceleration * pDeltaTimeSeconds;
+ pCircle.rotationRadians += pCircle.rotationVelocity * pDeltaTimeSeconds;
+ pCircle.torque = 0;
+
+ pCircle.model = rotateMatrix2d(translateMatrix(mat4(), pCircle.position.x, pCircle.position.y, 0), pCircle.rotationRadians);
+ }
+
+ function update(pDeltaTimeSeconds) {
+ updateCircle(lBall, pDeltaTimeSeconds);
+ collision(pDeltaTimeSeconds);
+ render();
+ }
+
+ function collision(pDeltaTimeSeconds) {
+ for (let lPegIdx = 0; lPegIdx < lPegList.length; lPegIdx++) {
+ const lPeg = lPegList[lPegIdx];
+
+ if (!doCirclesIntersect(lPeg, lBall)) {
+ continue;
+ }
+
+ // We have an intersection! Let's try and figure out precisely when that happened.
+ let lSubdividedDeltaTime = pDeltaTimeSeconds;
+
+ // Create a ball and move it back to the balls previous position
+ let lSubdividedBall = undefined;
+
+ do {
+ // Move the subdivided ball back to the previous position, and then
+ // advance its position by increasingly smaller timestep. This could be pretty
+ // slow in some circumstances, and it most definitely does not prevent tunneling,
+ // but - for now - this is not so bad.
+ lSubdividedBall = JSON.parse(JSON.stringify(lBall));
+ lSubdividedBall.position = {...lBall.prevPos};
+ lSubdividedBall.velocity = {...lBall.prevVelocity};
+ lSubdividedDeltaTime = lSubdividedDeltaTime / 2.0;
+ updateCircle(lSubdividedBall, lSubdividedDeltaTime);
+ if (lSubdividedDeltaTime === 0) {
+ console.error('This should NOT be happening');
+ break;
+ }
+ } while (doCirclesIntersect(lPeg, lSubdividedBall))
+
+ // The ball is no longer intersecting at the time presented here. That means this is
+ // (nearly) the precise point of intersection.
+
+ const lIntersectionResult = getIntersectionDataForCircles(lPeg, lSubdividedBall);
+
+ const lRelativeVelocity = lIntersectionResult.relativeVelocity,
+ lCollisionNormal = lIntersectionResult.collisionNormal,
+ lFirstPerp = getPerp2(lIntersectionResult.firstPointOfApplication),
+ lSecondPerp = getPerp2(lIntersectionResult.secondPointOfApplication);
+
+ const lNumerator = dot2(scaleVec2(lRelativeVelocity, -(1.0 + COF_OF_RESTITUITION)), lCollisionNormal);
+ const lLinearDenomPart = dot2(lCollisionNormal, (scaleVec2(lCollisionNormal, 1 / lBall.mass)));
+ const lRotationalDenomPart = (Math.pow(dot2(lFirstPerp, lCollisionNormal), 2) / getMomentOfInertia(lPeg))
+ + (Math.pow(dot2(lSecondPerp, lCollisionNormal), 2) / getMomentOfInertia(lBall))
+
+ const lImpulseMagnitude = lNumerator / (lLinearDenomPart + lRotationalDenomPart);
+
+ lBall.position = lSubdividedBall.position; // Move the ball back to its proper subdivided position
+ lBall.velocity = subVec2(lBall.velocity, scaleVec2(lCollisionNormal, lImpulseMagnitude / lBall.mass));
+ lBall.rotationVelocity = lBall.rotationVelocity - dot2(lSecondPerp, scaleVec2(lCollisionNormal, lImpulseMagnitude)) / getMomentOfInertia(lBall);
+
+ // Now we update in our new direction with the remaining time that we have left.
+ updateCircle(lBall, pDeltaTimeSeconds - lSubdividedDeltaTime);
+
+ break;
+ }
+ }
+
+ function render() {
+ lProgramContext.gl.clearColor(0.0, 0.0, 0.0, 1.0);
+ lProgramContext.gl.clearDepth(1.0);
+ lProgramContext.gl.enable(lProgramContext.gl.DEPTH_TEST);
+ lProgramContext.gl.depthFunc(lProgramContext.gl.LEQUAL);
+ lProgramContext.gl.clear(lProgramContext.gl.COLOR_BUFFER_BIT | lProgramContext.gl.DEPTH_BUFFER_BIT);
+ lProgramContext.gl.useProgram(pProgramInfo.program);
+ lProgramContext.gl.uniformMatrix4fv(pProgramInfo.uniformLocations.projection, false, lProgramContext.perspective);
+
+ lPegList.forEach(function(pCircle) {
+ renderCircle(lProgramContext.gl, pProgramInfo, pCircle);
+ });
+
+ renderCircle(lProgramContext.gl, pProgramInfo, lBall);
+ }
+
+ const TORQUE_MULTIPLIER = 100.0; // TODO: This may be unncessary
+ function applyForce(pCircle, pForceVector, pPointOfApplication) {
+ if (pPointOfApplication !== undefined) {
+ const lOriginToPointOfApp = subVec2(vec2(), pPointOfApplication),
+ lPerpVec = vec2(-lOriginToPointOfApp.y, lOriginToPointOfApp.x);
+
+ pCircle.torque += TORQUE_MULTIPLIER * dot2(lPerpVec, pForceVector);
+ }
+
+ pCircle.force = addVec2(pCircle.force, pForceVector);
+ }
+
+ function cleanup() {
+ lPegList.forEach(function(pCircle) { freeCircle(lProgramContext.gl, pCircle); });
+ freeCircle(lProgramContext.gl, lBall);
+
+ lProgramContext.gl.deleteProgram(pProgramInfo.program);
+ lProgramContext.gl.clearColor(0.0, 0.0, 0.0, 1.0);
+ lProgramContext.gl.clear(lProgramContext.gl.COLOR_BUFFER_BIT);
+ }
+
+ function reset() {
+ lExitRequestFunc();
+ lProgramContext.reset();
+ $('#time_step_slider').unbind('change');
+ }
+
+ const lExitRequestFunc = requestUpdateLoop(update, cleanup);
+ lProgramContext.stopButton.on('click', reset);
+ $('#time_step_slider').on('change', function() { lTimeStepScale = $(this).val(); });
+ });
+ }
+
+ $(document).ready(main);
+})() \ No newline at end of file