diff options
author | Matthew Kosarek <matthew.kosarek@vention.cc> | 2021-02-17 21:06:20 -0500 |
---|---|---|
committer | Matthew Kosarek <matthew.kosarek@vention.cc> | 2021-02-17 21:06:20 -0500 |
commit | 5c409f04470e319f0a57e8791bc96cd724ee601c (patch) | |
tree | fcebe2242106d5d94eb852f90b66ab131c5655ba | |
parent | cc6d3871008a89fcf48814596d7bfec05f2706e4 (diff) |
Proper collisions happening in 2 dimensions
-rw-r--r-- | frontend/2d_part_1.html | 93 | ||||
-rw-r--r-- | frontend/2d_part_2.html | 67 | ||||
-rw-r--r-- | frontend/2d_part_3.html | 52 | ||||
-rw-r--r-- | frontend/_rigidbody/circle.js | 53 | ||||
-rw-r--r-- | frontend/_rigidbody/program_common.js | 15 | ||||
-rw-r--r-- | frontend/_rigidbody/rigidbody_2.js | 18 | ||||
-rw-r--r-- | frontend/_rigidbody/rigidbody_3.js | 158 | ||||
-rw-r--r-- | frontend/_rigidbody/vec2.js | 14 | ||||
-rw-r--r-- | frontend/index.css | 34 | ||||
-rw-r--r-- | frontend/index.html | 44 | ||||
-rw-r--r-- | frontend/rigidbody.html | 40 |
11 files changed, 545 insertions, 43 deletions
diff --git a/frontend/2d_part_1.html b/frontend/2d_part_1.html new file mode 100644 index 0000000..312611a --- /dev/null +++ b/frontend/2d_part_1.html @@ -0,0 +1,93 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="utf-8"> + <script src="scripts/jquery-3.5.1.min.js"></script> + <script src="index.js"></script> + <link rel="stylesheet" href="/../index.css"> + <link href="https://fonts.googleapis.com/css?family=Open+Sans:400,600,300" rel="stylesheet" type="text/css"> + <title>Physics for Games</title> + + <script src="_rigidbody/vec2.js"></script> + <script src="_rigidbody/mat4.js"></script> + <script src="_rigidbody/shader.js"></script> + <script src="_rigidbody/circle.js"></script> + <script src="_rigidbody/program_common.js"></script> + <script src="_rigidbody/rigidbody_1.js"></script> + </head> + <body> + <header> + <h1>Physics for Games</h1> + </header> + <main> + <nav> + <a href="/">Introduction</a> + <a href="2d_part_1.html">2D - Linear Forces</a> + <a href="2d_part_2.html">2D - Rotational Forces</a> + <a href="2d_part_3.html">2D - Collision Forces</a> + </nav> + <section id="linear-forces"> + <h2>Part 1: Linear Forces</h2> + <p> + The first - and perhaps easiest - part of implementing any rigid body physics system is getting the entities in your scene to move in response to linear forces. + With this implementation alone, you can achieve an interesting level of realism in your 2D (and even 3D) scene. + </p> + <p> + Let's begin by recalling the relationships between acceleration, velocity, and position. + </p> + <p> + Knowing all this, you should be able to understand the following source code fairly easily; + <pre> + <code> +<span class="code_keyword">function</span> update(dtSeconds) { + <span class="code_comment">// Add up the forces acting on the circle</span> + <span class="code_keyword">const</span> GRAVITY = 9.8; + <span class="code_keyword">const</span> lGravityForce = vec2(0, -1.0 * (lCircle.mass * <span class="code_constant">GRAVITY</span>)); + lCircle.force = addVec2(lCircle.force, lGravityForce); + + <span class="code_comment">// Figure out acceleration (a = F / m)</span> + <span class="code_keyword">const</span> lCurrentAcceleration = scaleVec2(lCircle.force, 1.0 / lCircle.mass); + + <span class="code_comment">// Calculate the new velocity: v = v0 + a * t</span> + lCircle.velocity = addVec2(lCircle.velocity, scaleVec2(lCurrentAcceleration, dtSeconds)); + + <span class="code_comment">// Update the position based on velocity: x = x0 + v * t</span> + lCircle.position = addVec2(lCircle.position, scaleVec2(lCircle.velocity, dtSeconds)); + + <span class="code_comment">// Update the model matrix accordingly</span> + lCircle.model = translateMatrix(mat4(), lCircle.position.x, lCircle.position.y, 0); + + <span class="code_comment">// Reset the force vector for the next update</span> + lCircle.force = vec2() +} + </code> + </pre> + </p> + <div id="rigidbody_1" class="opengl_canvas_container"> + <canvas width="640" height="480"></canvas> + <div class="opengl_canvas_sidebar"> + <ul class="opengl_value_tracker"> + <li><b>Linear Force:</b><span id="rigidbody_1_force_field">N/A</span></li> + <li><b>Linear Acceleration:</b><span id="rigidbody_1_acceleration_field">N/A</span></li> + <li><b>Linear Velocity:</b><span id="rigidbody_1_velocity_field">N/A</span></li> + <li><b>Linear Position:</b><span id="rigidbody_1_position_field">N/A</span></li> + </ul> + <form id="rigidbody_1_force_submit_button" style="text-align: right; padding: 0.5rem;"> + <div class="vec2_input_group"> + <label>Force Vector</label> + <input class="vec2_x_input" type="number" placeholder="X (Default 0)"/> + <input class="vec2_y_input" type="number" placeholder="Y (Default 5000 N)"/> + </div> + <input type="submit" value="Apply Force"></input> + </form> + </div> + <button class="play_button"> + Play + </button> + <button class="stop_button"> + Stop + </button> + </div> + </main> + </body> +</html>
\ No newline at end of file diff --git a/frontend/2d_part_2.html b/frontend/2d_part_2.html new file mode 100644 index 0000000..01a7767 --- /dev/null +++ b/frontend/2d_part_2.html @@ -0,0 +1,67 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="utf-8"> + <script src="scripts/jquery-3.5.1.min.js"></script> + <script src="index.js"></script> + <link rel="stylesheet" href="/../index.css"> + <link href="https://fonts.googleapis.com/css?family=Open+Sans:400,600,300" rel="stylesheet" type="text/css"> + <title>Physics for Games</title> + + <script src="_rigidbody/vec2.js"></script> + <script src="_rigidbody/mat4.js"></script> + <script src="_rigidbody/shader.js"></script> + <script src="_rigidbody/circle.js"></script> + <script src="_rigidbody/program_common.js"></script> + <script src="_rigidbody/rigidbody_2.js"></script> + </head> + <body> + <header> + <h1>Physics for Games</h1> + </header> + <main> + <nav> + <a href="/">Introduction</a> + <a href="2d_part_1.html">2D - Linear Forces</a> + <a href="2d_part_2.html">2D - Rotational Forces</a> + <a href="2d_part_3.html">2D - Collision Forces</a> + </nav> + <section id="rotational-forces"> + <h2>Part 2: Rotational Forces</h2> + <p> + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. + </p> + <div id="rigidbody_2" class="opengl_canvas_container"> + <canvas width="640" height="480"></canvas> + <div class="opengl_canvas_sidebar"> + <ul class="opengl_value_tracker"> + <li><b>Angular Force:</b><span id="rigidbody_2_force_field">N/A</span></li> + <li><b>Angular Acceleration:</b><span id="rigidbody_2_acceleration_field">N/A</span></li> + <li><b>Angular Velocity:</b><span id="rigidbody_2_velocity_field">N/A</span></li> + <li><b>Angular Position:</b><span id="rigidbody_2_position_field">N/A</span></li> + </ul> + <form id="rigidbody_2_force_submit_button" style="text-align: right; padding: 0.5rem;"> + <div id="rigidbody_2_force_input" class="vec2_input_group"> + <label>Force Vector</label> + <input class="vec2_x_input" type="number" placeholder="X (Default 0)"/> + <input class="vec2_y_input" type="number" placeholder="Y (Default 5000 N)"/> + </div> + <div id="rigidbody_2_position_input" class="vec2_input_group"> + <label>Point of Application (Unit Vector)</label> + <input class="vec2_x_input" type="number" placeholder="X (Default 2 Root 2)"/> + <input class="vec2_y_input" type="number" placeholder="Y (Default 2 Root 2)"/> + </div> + <input type="submit" value="Apply Force"></input> + </form> + </div> + <button class="play_button"> + Play + </button> + <button class="stop_button"> + Stop + </button> + </div> + </section> + </main> + </body> +</html>
\ No newline at end of file diff --git a/frontend/2d_part_3.html b/frontend/2d_part_3.html new file mode 100644 index 0000000..ae00c9b --- /dev/null +++ b/frontend/2d_part_3.html @@ -0,0 +1,52 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="utf-8"> + <script src="scripts/jquery-3.5.1.min.js"></script> + <script src="index.js"></script> + <link rel="stylesheet" href="/../index.css"> + <link href="https://fonts.googleapis.com/css?family=Open+Sans:400,600,300" rel="stylesheet" type="text/css"> + <title>Physics for Games</title> + + <script src="_rigidbody/vec2.js"></script> + <script src="_rigidbody/mat4.js"></script> + <script src="_rigidbody/shader.js"></script> + <script src="_rigidbody/circle.js"></script> + <script src="_rigidbody/program_common.js"></script> + <script src="_rigidbody/rigidbody_3.js"></script> + </head> + <body> + <header> + <h1>Physics for Games</h1> + </header> + <main> + <nav> + <a href="/">Introduction</a> + <a href="2d_part_1.html">2D - Linear Forces</a> + <a href="2d_part_2.html">2D - Rotational Forces</a> + <a href="2d_part_3.html">2D - Collision Forces</a> + </nav> + <section id="collisions"> + <h2>Part 3: Collisions</h2> + <p> + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. + </p> + <div id="rigidbody_3" class="opengl_canvas_container"> + <canvas width="640" height="480"></canvas> + <div class="opengl_canvas_sidebar"> + <div class="slider_container"> + <label for="time_step_slider">Time Step</label> + <input type="range" min="0.1" max="1.0" value="1.0" step="0.1" class="slider" id="time_step_slider"> + </div> + </div> + <button class="play_button"> + Play + </button> + <button class="stop_button"> + Stop + </button> + </div> + </section> + </main> + </body> +</html>
\ No newline at end of file diff --git a/frontend/_rigidbody/circle.js b/frontend/_rigidbody/circle.js index 194a772..35044e3 100644 --- a/frontend/_rigidbody/circle.js +++ b/frontend/_rigidbody/circle.js @@ -1,6 +1,16 @@ const BYTES_PER_FLOAT = 4; -function circle(pGl, pRadius, pSegments, pColorList, pInitialPosition) { +/** + * Initializes a new circle object for the WebGL context. + * + * @param {WebGLRenderingContext} pGl + * @param {number} pRadius + * @param {number} pSegments + * @param {Array<Vector4>} pColorList + * @param {vec2} pInitialPosition + * @param {number} pMass + */ +function circle(pGl, pRadius, pSegments, pColorList, pInitialPosition, pMass) { const lBuffer = pGl.createBuffer(); pGl.bindBuffer(pGl.ARRAY_BUFFER, lBuffer); @@ -30,11 +40,12 @@ function circle(pGl, pRadius, pSegments, pColorList, pInitialPosition) { return { buffer: lBuffer, vertexCount: vertexCount, + prevPos: vec2(), position: pInitialPosition || vec2(), velocity: vec2(), force: vec2(), torque: 0, - mass: 1, + mass: pMass || 1, rotationVelocity: 0, rotationRadians: 0, model: mat4(), @@ -43,6 +54,7 @@ function circle(pGl, pRadius, pSegments, pColorList, pInitialPosition) { } function renderCircle(pGl, pProgramInfo, pCircle) { + pGl.uniformMatrix4fv(pProgramInfo.uniformLocations.model, false, pCircle.model); pGl.bindBuffer(pGl.ARRAY_BUFFER, pCircle.buffer); { pGl.enableVertexAttribArray(pProgramInfo.attributeLocations.position); @@ -57,4 +69,41 @@ function renderCircle(pGl, pProgramInfo, pCircle) { function getMomentOfInertia(pCircle) { return (Math.PI * Math.pow(pCircle.radius, 4)) / 4; +} + +function doCirclesIntersect(pFirst, pSecond) { + const lDistanceBetween = Math.pow(pFirst.position.x - pSecond.position.x, 2) + + Math.pow(pFirst.position.y - pSecond.position.y, 2) + return lDistanceBetween <= Math.pow(pFirst.radius + pSecond.radius, 2); +} + +/** + * Returns intersection information about the intersecting circles. + * + * Warning! Only use this if doCirclesIntersect returned true for these circles. + * + * @param {circle} pFirst + * @param {circle} pSecond + */ +function getIntersectionDataForCircles(pFirst, pSecond) { + // The collision normal is simply the difference between the two current positions + const lCollisionNormal = normalize2(subVec2(pFirst.position, pSecond.position)); + + // TODO: Might be useful in the future? + const lCollisionPoint = vec2( + ((pFirst.position.x * pSecond.radius) + (pSecond.position.x * pFirst.radius)) / (pFirst.radius + pSecond.radius), + ((pFirst.position.y * pSecond.radius) + (pSecond.position.y * pFirst.radius)) / (pFirst.radius + pSecond.radius) + ); + + const lSecondDeepestPointInFirst = (subVec2(pSecond.position, scaleVec2(lCollisionNormal, pSecond.radius))); + const lFirstDeepestPointInSecond = (addVec2(pFirst.position, scaleVec2(lCollisionNormal, pFirst.radius))); + const lLengthOfOverlapByTwo = length2(subVec2(lFirstDeepestPointInSecond, lSecondDeepestPointInFirst)) / 2.0; + const lMedianIntersectingPoint = addVec2(lSecondDeepestPointInFirst, scaleVec2(lCollisionNormal, lLengthOfOverlapByTwo)); + + return { + relativeVelocity: subVec2(pFirst.velocity, pSecond.velocity), + collisionNormal: lCollisionNormal, + firstPointOfApplication: subVec2(lMedianIntersectingPoint, pFirst.position), + secondPointOfApplication: subVec2(lMedianIntersectingPoint, pSecond.position) + } }
\ No newline at end of file diff --git a/frontend/_rigidbody/program_common.js b/frontend/_rigidbody/program_common.js index 9d097e2..988056f 100644 --- a/frontend/_rigidbody/program_common.js +++ b/frontend/_rigidbody/program_common.js @@ -36,7 +36,7 @@ function getContext(pId, pOnRun) { function requestUpdateLoop(pFunction, pOnExit) { let lDeltaTimeSeconds = undefined, - lStartTimeSeconds = undefined, + lLastTimeSeconds = undefined, lIsRunning = true; function update(pTimeStamp) { @@ -50,15 +50,18 @@ function requestUpdateLoop(pFunction, pOnExit) { pTimeStamp = pTimeStamp / 1000.0; // Convert to seconds // Time calculation - if (lStartTimeSeconds === undefined) { - lStartTimeSeconds = pTimeStamp; + if (lLastTimeSeconds === undefined) { + lLastTimeSeconds = pTimeStamp; lDeltaTimeSeconds = 0; } else { - lDeltaTimeSeconds = lStartTimeSeconds - pTimeStamp; - lStartTimeSeconds = pTimeStamp; + lDeltaTimeSeconds = pTimeStamp - lLastTimeSeconds; + lLastTimeSeconds = pTimeStamp; } - pFunction(lDeltaTimeSeconds); + while (lDeltaTimeSeconds > 0) { + pFunction(lDeltaTimeSeconds); + lDeltaTimeSeconds -= 0.16; + } requestAnimationFrame(update); } diff --git a/frontend/_rigidbody/rigidbody_2.js b/frontend/_rigidbody/rigidbody_2.js index c41461e..1c6b79f 100644 --- a/frontend/_rigidbody/rigidbody_2.js +++ b/frontend/_rigidbody/rigidbody_2.js @@ -31,22 +31,29 @@ function main() { ], vec2(lProgramContext.width / 2.0, lProgramContext.height / 2.0)); function update(pDeltaTimeSeconds) { - // Same physics updates from previously + // Same physics updates from part 1 applyForce(vec2(0, -1.0 * (lCircle.mass * GRAVITY))); const lCurrentAcceleration = scaleVec2(lCircle.force, 1.0 / lCircle.mass); lCircle.velocity = addVec2(lCircle.velocity, scaleVec2(lCurrentAcceleration, pDeltaTimeSeconds)); lCircle.position = addVec2(lCircle.position, scaleVec2(lCircle.velocity, pDeltaTimeSeconds)); lCircle.force = vec2(); - // Calculate rotation + // Angular code starts here + + // Retrieve the moment of inertia for our shape (Ours is a circle by default) const lMomentOfInertia = getMomentOfInertia(lCircle); + + // Calculate the angular acceperation (omega = T / I) const lAngularAcceleration = lCircle.torque / lMomentOfInertia; + // Calculate the rotation in radians lCircle.rotationVelocity += lAngularAcceleration * pDeltaTimeSeconds; lCircle.rotationRadians += lCircle.rotationVelocity * pDeltaTimeSeconds; - lCircle.model = rotateMatrix2d(translateMatrix(mat4(), lCircle.position.x, lCircle.position.y, 0), lCircle.rotationRadians); lCircle.torque = 0; + // Calculate the model as previously, but this time, also rotate it + lCircle.model = rotateMatrix2d(translateMatrix(mat4(), lCircle.position.x, lCircle.position.y, 0), lCircle.rotationRadians); + // Render Code only lProgramContext.gl.clearColor(0.0, 0.0, 0.0, 1.0); lProgramContext.gl.clearDepth(1.0); @@ -65,9 +72,10 @@ function main() { function applyForce(pForceVector, pPointOfApplication) { if (pPointOfApplication !== undefined) { - const lOriginToPointOfApp = subVec2(vec2(), pPointOfApplication), // The point of application is relative to the model (i.e. the center of the circle, not the scene) - lPerpVec = vec2(-lOriginToPointOfApp.y, lOriginToPointOfApp.x); + const lOriginToPointOfApp = subVec2(vec2(), pPointOfApplication), // The point of application is relative to the model (i.e. the center of the circle, not the scene) + lPerpVec = vec2(-lOriginToPointOfApp.y, lOriginToPointOfApp.x); // Retrieve the perpendicular vector + // Calculate the torque from the perp dot (T = r_perp . F) lCircle.torque += TORQUE_MULTIPLIER * dot2(lPerpVec, pForceVector); } diff --git a/frontend/_rigidbody/rigidbody_3.js b/frontend/_rigidbody/rigidbody_3.js new file mode 100644 index 0000000..0316268 --- /dev/null +++ b/frontend/_rigidbody/rigidbody_3.js @@ -0,0 +1,158 @@ +/// <reference path="../scripts/jquery-3.5.1.min.js"/> +/// <reference path="vec2.js" /> +/// <reference path="mat4.js" /> +/// <reference path="shader.js" /> +/// <reference path="circle.js" /> + +function main() { + // Define Constants + const CIRCLE_RADIUS = 16; + const GRAVITY = 9.8; + const COF_OF_RESTITUITION = 0.7; + + // Retrieve context + const lProgramContext = getContext('#rigidbody_3'); + + 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); + + function run() { + console.log('Running Rigid Body 3'); + lProgramContext.load().then(function(pProgramInfo) { + // Circile initialization + const horizontalCircle = circle(lProgramContext.gl, CIRCLE_RADIUS, 30, [ + { x: 1, y: 1, z: 0, w: 1 }, + { x: 1, y: 0, z: 1, w: 1 }, + { x: 0, y: 1, z: 1, w: 1 }, + { x: 0, y: 1, z: 0, w: 1 } + ], vec2(400, lProgramContext.height / 2.0)); + + const verticalCircle = circle(lProgramContext.gl, CIRCLE_RADIUS, 30, [ + { 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(lProgramContext.width / 2.0, lProgramContext.height / 2.0 + 100)); + + horizontalCircle.velocity = vec2(-100, 0); + verticalCircle.velocity = vec2(0, -100); + + lTimeStepScale = $('#time_step_slider').val(); + + /** + * Run the update method of a single circle + * + * @param {circle} pCircle + * @param {number} pDeltaTimeSeconds + */ + 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.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) { + pDeltaTimeSeconds = pDeltaTimeSeconds * lTimeStepScale; + updateCircle(horizontalCircle, pDeltaTimeSeconds); + updateCircle(verticalCircle, pDeltaTimeSeconds); + collision(); + render(); + } + + function collision() { + if (!doCirclesIntersect(horizontalCircle, verticalCircle)) { + return false; + } + + const lIntersectionResult = getIntersectionDataForCircles(horizontalCircle, verticalCircle); + + console.log('We have a collision'); + const lRelativeVelocity = lIntersectionResult.relativeVelocity, + lCollisionNormal = lIntersectionResult.collisionNormal, + lFirstPerp = getPerp2(lIntersectionResult.firstPointOfApplication), + lSecondPerp = getPerp2(lIntersectionResult.secondPointOfApplication); + + console.log(lIntersectionResult); + + const lNumerator = dot2(scaleVec2(lRelativeVelocity, -(1.0 + COF_OF_RESTITUITION)), lCollisionNormal); + const lLinearDenomPart = dot2(lCollisionNormal, (scaleVec2(lCollisionNormal, 1 / horizontalCircle.mass + 1 / verticalCircle.mass))); + const lRotationalDenomPart = (Math.pow(dot2(lFirstPerp, lCollisionNormal), 2) / getMomentOfInertia(horizontalCircle)) + + (Math.pow(dot2(lSecondPerp, lCollisionNormal), 2) / getMomentOfInertia(verticalCircle)) + + const lImpulseMagnitude = lNumerator / (lLinearDenomPart + lRotationalDenomPart); + + horizontalCircle.velocity = addVec2(horizontalCircle.velocity, scaleVec2(lCollisionNormal, lImpulseMagnitude / horizontalCircle.mass)); + verticalCircle.velocity = subVec2(verticalCircle.velocity, scaleVec2(lCollisionNormal, lImpulseMagnitude / verticalCircle.mass)); + + horizontalCircle.rotationVelocity = horizontalCircle.rotationVelocity + dot2(lFirstPerp, scaleVec2(lCollisionNormal, lImpulseMagnitude)) / getMomentOfInertia(horizontalCircle); + verticalCircle.rotationVelocity = verticalCircle.rotationVelocity - dot2(lSecondPerp, scaleVec2(lCollisionNormal, lImpulseMagnitude)) / getMomentOfInertia(verticalCircle); + + return true; + } + + 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); + + renderCircle(lProgramContext.gl, pProgramInfo, horizontalCircle); + renderCircle(lProgramContext.gl, pProgramInfo, verticalCircle); + } + + 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() { + lProgramContext.gl.deleteBuffer(horizontalCircle.buffer); + lProgramContext.gl.deleteBuffer(verticalCircle.buffer); + 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(); }); + }); + } + + lProgramContext.playButton.on('click', run); +} + +$(document).ready(main);
\ No newline at end of file diff --git a/frontend/_rigidbody/vec2.js b/frontend/_rigidbody/vec2.js index 3b24371..2db42e0 100644 --- a/frontend/_rigidbody/vec2.js +++ b/frontend/_rigidbody/vec2.js @@ -38,4 +38,18 @@ function normalize2(v) { function vec2str(v) { return `(${v.x.toFixed(2)}, ${v.y.toFixed(2)})`; +} + +function getPerp2(v) { + return { + x: -v.y, + y: v.x + }; +} + +function negate2(v) { + return { + x: -v.x, + y: -v.y + }; }
\ No newline at end of file diff --git a/frontend/index.css b/frontend/index.css index a718784..40c74bd 100644 --- a/frontend/index.css +++ b/frontend/index.css @@ -24,20 +24,28 @@ header > h1 { } main { + display: flex; + flex-direction: row; color: #1a1a1a; width: 1260px; background-color: white; flex: 1 1 100%; height: 100%; margin-bottom: 1rem; + overflow-y: hidden; +} + +section { overflow-y: auto; } nav { - width: 1260px; + width: 25%; + height: 100%; + display: flex; + flex-direction: column; text-align: left; font-size: 1rem; - border-bottom: 1px solid #404040; } nav > a { @@ -45,7 +53,6 @@ nav > a { text-decoration: none; display: inline-block; min-width: 196px; - padding: 0.25rem; } nav > a:hover { @@ -58,14 +65,6 @@ nav > a:hover { font-weight: bold; } -section { - padding: 1rem; -} - -hr { - width: 90%; -} - .spin-loader { border: 5px solid #f3f3f3; border-top: 5px solid #555; @@ -86,7 +85,7 @@ section > p { } /* - Helpful inputs + Helpful widgets */ .vec2_input_group { position: relative; @@ -111,6 +110,15 @@ section > p { width: 45%; } +.slider_container { + display: flex; + flex-direction: column; +} + +.slider_container > label { + font-weight: 500; +} + /* WebGL container styling from here on. */ @@ -155,7 +163,7 @@ section .opengl_canvas_container .stop_button { height: 2rem; border-radius: 2px; top: 0.5rem; - left: 612px; + left: 536px; display: none; } diff --git a/frontend/index.html b/frontend/index.html index d57d69b..1694121 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -6,18 +6,48 @@ <script src="index.js"></script> <link rel="stylesheet" href="index.css"> <link href="https://fonts.googleapis.com/css?family=Open+Sans:400,600,300" rel="stylesheet" type="text/css"> - <title>Simple Website</title> + <title>Physics for Games</title> </head> <body> <header> - <h1>Matt Kae's Programming Blog</h1> + <h1>Physics for Games</h1> </header> - <nav> - <a href="/">Home</a> - <a href="rigidbody.html">Rigid Body</a> - <a href="references.html">Books n' References</a> - </nav> <main> + <nav> + <a href="/">Introduction</a> + <a href="2d_part_1.html">2D - Linear Forces</a> + <a href="2d_part_2.html">2D - Rotational Forces</a> + <a href="2d_part_3.html">2D - Collision Forces</a> + </nav> + <section id='introduction'> + <h2>Introduction: Rigid Body Physics</h2> + <p> + You're most likely here because you have some interest in the world of rigid body physics. Maybe you have some knowledge of rendering via OpenGL or Vulkan, + and you want to begin watching your up-until-now static scene come to life. Well, you're in the right place! In the course of this tutorial series I will walk + you through the entirety of a 2D rigid body physics system entirely in the web. All of this information will be extendable to other languages, but we will use + JavaScript and WebGL in these blog posts. Additionally, much of the information presented here can be extended to 3 dimensions, but 3D carries some complications + with it, that we will discuss in future blog posts. + </p> + <p> + In implementing a rigidy body physics system, we're primarily interested in two sub-fields of physics, namely <b>dynamics</b> and <b>kinematics</b>. Although I'm + far as can be from being an expert in either of these fields, I will explain - from a programmer's persepctive - what they mean to me: + <ul> + <li> + <b>Kinematics</b> is the study of how an object's movement changes over time. These are the classic position, velocity, and acceleration equations + that you're most likely familiar with from high school or college physics. + </li> + <li> + <b>Dynamics</b> is the study of whats <i>causes</i> kinematic movement. These are the classic force and momentum equations that you may already be familiar + with as well. + </li> + </ul> + </p> + <p> + Finally, I must provide a disclaimer that all of rigid body systems are very math-y. You will need to know a decent amount of vector calculus and linear algebra to really understand + what's going on here. I am going to assume that you have this knowledge. If you don't already have this knowledge, I will try and provide some resources on the Books + n' References page of the website. + </p> + </section> </main> </body> </html>
\ No newline at end of file diff --git a/frontend/rigidbody.html b/frontend/rigidbody.html index 2cc32b7..d83e29e 100644 --- a/frontend/rigidbody.html +++ b/frontend/rigidbody.html @@ -6,7 +6,7 @@ <script src="index.js"></script> <link rel="stylesheet" href="/../index.css"> <link href="https://fonts.googleapis.com/css?family=Open+Sans:400,600,300" rel="stylesheet" type="text/css"> - <title>Simple Website</title> + <title>Physics for Games</title> <script src="_rigidbody/vec2.js"></script> <script src="_rigidbody/mat4.js"></script> @@ -15,10 +15,11 @@ <script src="_rigidbody/program_common.js"></script> <script src="_rigidbody/rigidbody_1.js"></script> <script src="_rigidbody/rigidbody_2.js"></script> + <script src="_rigidbody/rigidbody_3.js"></script> </head> <body> <header> - <h1>Matt Kae's Programming Blog</h1> + <h1>Physics for Games</h1> </header> <nav> <a href="/">Home</a> @@ -32,6 +33,7 @@ <li><a href="#introduction">Introduction</a></li> <li><a href="#linear-forces">Part 1: Linear Forces</a></li> <li><a href="#rotational-forces">Part 2: Rotational Forces</a></li> + <li><a href="#collisions">Part 3: Collisions</a></li> </ul> </p> </section> @@ -106,10 +108,10 @@ <canvas width="640" height="480"></canvas> <div class="opengl_canvas_sidebar"> <ul class="opengl_value_tracker"> - <li><b>Force:</b><span id="rigidbody_1_force_field">N/A</span></li> - <li><b>Acceleration:</b><span id="rigidbody_1_acceleration_field">N/A</span></li> - <li><b>Velocity:</b><span id="rigidbody_1_velocity_field">N/A</span></li> - <li><b>Position:</b><span id="rigidbody_1_position_field">N/A</span></li> + <li><b>Linear Force:</b><span id="rigidbody_1_force_field">N/A</span></li> + <li><b>Linear Acceleration:</b><span id="rigidbody_1_acceleration_field">N/A</span></li> + <li><b>Linear Velocity:</b><span id="rigidbody_1_velocity_field">N/A</span></li> + <li><b>Linear Position:</b><span id="rigidbody_1_position_field">N/A</span></li> </ul> <form id="rigidbody_1_force_submit_button" style="text-align: right; padding: 0.5rem;"> <div class="vec2_input_group"> @@ -132,15 +134,16 @@ <section id="rotational-forces"> <h2>Part 2: Rotational Forces</h2> <p> + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. </p> <div id="rigidbody_2" class="opengl_canvas_container"> <canvas width="640" height="480"></canvas> <div class="opengl_canvas_sidebar"> <ul class="opengl_value_tracker"> - <li><b>Force:</b><span id="rigidbody_1_force_field">N/A</span></li> - <li><b>Acceleration:</b><span id="rigidbody_2_acceleration_field">N/A</span></li> - <li><b>Velocity:</b><span id="rigidbody_2_velocity_field">N/A</span></li> - <li><b>Position:</b><span id="rigidbody_2_position_field">N/A</span></li> + <li><b>Angular Force:</b><span id="rigidbody_2_force_field">N/A</span></li> + <li><b>Angular Acceleration:</b><span id="rigidbody_2_acceleration_field">N/A</span></li> + <li><b>Angular Velocity:</b><span id="rigidbody_2_velocity_field">N/A</span></li> + <li><b>Angular Position:</b><span id="rigidbody_2_position_field">N/A</span></li> </ul> <form id="rigidbody_2_force_submit_button" style="text-align: right; padding: 0.5rem;"> <div id="rigidbody_2_force_input" class="vec2_input_group"> @@ -164,6 +167,23 @@ </button> </div> </section> + <section id="collisions"> + <h2>Part 3: Collisions</h2> + <p> + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. + </p> + <div id="rigidbody_3" class="opengl_canvas_container"> + <canvas width="640" height="480"></canvas> + <div class="opengl_canvas_sidebar"> + </div> + <button class="play_button"> + Play + </button> + <button class="stop_button"> + Stop + </button> + </div> + </section> <section style="height: 50vh;"></section> </main> |