From f0d1398b0d1b1a7c5bd1d4e0b3488b7f1aa74364 Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Tue, 23 Feb 2021 19:39:16 -0500 Subject: Big rework of the directory structure to make it more orderly for my brain --- frontend/_rigidbody/2d/rigidbody_3b.js | 188 +++++++++++++++++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 frontend/_rigidbody/2d/rigidbody_3b.js (limited to 'frontend/_rigidbody/2d/rigidbody_3b.js') diff --git a/frontend/_rigidbody/2d/rigidbody_3b.js b/frontend/_rigidbody/2d/rigidbody_3b.js new file mode 100644 index 0000000..803161a --- /dev/null +++ b/frontend/_rigidbody/2d/rigidbody_3b.js @@ -0,0 +1,188 @@ +/// +/// +/// +/// +/// +/// + +(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 -- cgit v1.2.1