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 --- .gitignore | 4 +- .../Server/x64/Debug/Server.Build.CppClean.log | 0 frontend/2d_part_1.html | 97 ----------- frontend/2d_part_2.html | 70 -------- frontend/2d_part_3.html | 74 -------- frontend/_collisions/2d/collisions_1.js | 0 frontend/_collisions/2d/part_1.html | 61 +++++++ frontend/_rigidbody/2d/part_1.html | 93 ++++++++++ frontend/_rigidbody/2d/part_2.html | 66 +++++++ frontend/_rigidbody/2d/part_3.html | 71 ++++++++ frontend/_rigidbody/2d/rigidbody_1.js | 117 +++++++++++++ frontend/_rigidbody/2d/rigidbody_2.js | 146 ++++++++++++++++ frontend/_rigidbody/2d/rigidbody_3a.js | 190 ++++++++++++++++++++ frontend/_rigidbody/2d/rigidbody_3b.js | 188 ++++++++++++++++++++ frontend/_rigidbody/circle.js | 117 ------------- frontend/_rigidbody/mat4.js | 43 ----- frontend/_rigidbody/pill.js | 0 frontend/_rigidbody/program_common.js | 75 -------- frontend/_rigidbody/rigidbody_1.js | 117 ------------- frontend/_rigidbody/rigidbody_2.js | 145 ---------------- frontend/_rigidbody/rigidbody_3a.js | 189 -------------------- frontend/_rigidbody/rigidbody_3b.js | 192 --------------------- frontend/_rigidbody/shader.js | 60 ------- frontend/_rigidbody/shaders/orthographic.frag | 5 - frontend/_rigidbody/shaders/orthographic.vert | 13 -- frontend/_rigidbody/vec2.js | 55 ------ frontend/_shared/2d/program_common.js | 75 ++++++++ frontend/_shared/2d/shader.js | 60 +++++++ frontend/_shared/2d/shaders/orthographic.frag | 5 + frontend/_shared/2d/shaders/orthographic.vert | 13 ++ frontend/_shared/math/circle.js | 112 ++++++++++++ frontend/_shared/math/mat4.js | 43 +++++ frontend/_shared/math/vec2.js | 56 ++++++ frontend/index.css | 13 +- frontend/index.html | 6 +- frontend/index.js | 9 +- frontend/navbar.html | 19 ++ frontend/rigidbody.html | 162 ----------------- 38 files changed, 1337 insertions(+), 1424 deletions(-) create mode 100644 backend/Server/Server/x64/Debug/Server.Build.CppClean.log delete mode 100644 frontend/2d_part_1.html delete mode 100644 frontend/2d_part_2.html delete mode 100644 frontend/2d_part_3.html create mode 100644 frontend/_collisions/2d/collisions_1.js create mode 100644 frontend/_collisions/2d/part_1.html create mode 100644 frontend/_rigidbody/2d/part_1.html create mode 100644 frontend/_rigidbody/2d/part_2.html create mode 100644 frontend/_rigidbody/2d/part_3.html create mode 100644 frontend/_rigidbody/2d/rigidbody_1.js create mode 100644 frontend/_rigidbody/2d/rigidbody_2.js create mode 100644 frontend/_rigidbody/2d/rigidbody_3a.js create mode 100644 frontend/_rigidbody/2d/rigidbody_3b.js delete mode 100644 frontend/_rigidbody/circle.js delete mode 100644 frontend/_rigidbody/mat4.js delete mode 100644 frontend/_rigidbody/pill.js delete mode 100644 frontend/_rigidbody/program_common.js delete mode 100644 frontend/_rigidbody/rigidbody_1.js delete mode 100644 frontend/_rigidbody/rigidbody_2.js delete mode 100644 frontend/_rigidbody/rigidbody_3a.js delete mode 100644 frontend/_rigidbody/rigidbody_3b.js delete mode 100644 frontend/_rigidbody/shader.js delete mode 100644 frontend/_rigidbody/shaders/orthographic.frag delete mode 100644 frontend/_rigidbody/shaders/orthographic.vert delete mode 100644 frontend/_rigidbody/vec2.js create mode 100644 frontend/_shared/2d/program_common.js create mode 100644 frontend/_shared/2d/shader.js create mode 100644 frontend/_shared/2d/shaders/orthographic.frag create mode 100644 frontend/_shared/2d/shaders/orthographic.vert create mode 100644 frontend/_shared/math/circle.js create mode 100644 frontend/_shared/math/mat4.js create mode 100644 frontend/_shared/math/vec2.js create mode 100644 frontend/navbar.html delete mode 100644 frontend/rigidbody.html diff --git a/.gitignore b/.gitignore index 84cc797..4422c41 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,6 @@ backend/Server/Debug backend/Server/Release backend/Server/.vs .vs/ -backend/Server/Server/Debug \ No newline at end of file +backend/Server/Server/Debug +backend/Server/x64 +backend/Server/Server/x64 \ No newline at end of file diff --git a/backend/Server/Server/x64/Debug/Server.Build.CppClean.log b/backend/Server/Server/x64/Debug/Server.Build.CppClean.log new file mode 100644 index 0000000..e69de29 diff --git a/frontend/2d_part_1.html b/frontend/2d_part_1.html deleted file mode 100644 index 830f201..0000000 --- a/frontend/2d_part_1.html +++ /dev/null @@ -1,97 +0,0 @@ - - - - - - - - - Physics for Games - - - - - - - - - - -
-

Physics for Games

-
-
- -
-
-

Part 1: Linear Forces

-

- 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. -

-

- Let's begin by recalling the relationships between acceleration, velocity, and position. -

-

- Knowing all this, you should be able to understand the following source code fairly easily; -

-                            
-    function update(dtSeconds) {
-        // Add up the forces acting on the circle
-        const GRAVITY = 9.8;
-        const lGravityForce = vec2(0, -1.0 * (lCircle.mass * GRAVITY));
-        lCircle.force = addVec2(lCircle.force, lGravityForce);
-    
-        // Figure out acceleration (a = F / m)
-        const lCurrentAcceleration = scaleVec2(lCircle.force, 1.0 / lCircle.mass);
-    
-        // Calculate the new velocity: v = v0 + a * t
-        lCircle.velocity = addVec2(lCircle.velocity, scaleVec2(lCurrentAcceleration, dtSeconds));
-    
-        // Update the position based on velocity: x = x0 + v * t
-        lCircle.position = addVec2(lCircle.position, scaleVec2(lCircle.velocity, dtSeconds));
-    
-        // Update the model matrix accordingly
-        lCircle.model = translateMatrix(mat4(), lCircle.position.x, lCircle.position.y, 0);
-    
-        // Reset the force vector for the next update
-        lCircle.force = vec2()
-    }
-                            
-                        
-

-
- -
-
    -
  • Linear Force:N/A
  • -
  • Linear Acceleration:N/A
  • -
  • Linear Velocity:N/A
  • -
  • Linear Position:N/A
  • -
-
-
- - - -
- -
-
- - -
-
-
-
- - \ No newline at end of file diff --git a/frontend/2d_part_2.html b/frontend/2d_part_2.html deleted file mode 100644 index 845985c..0000000 --- a/frontend/2d_part_2.html +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - - - - Physics for Games - - - - - - - - - - -
-

Physics for Games

-
-
- -
-
-

Part 2: Rotational Forces

-

- 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. -

-
- -
-
    -
  • Angular Force:N/A
  • -
  • Angular Acceleration:N/A
  • -
  • Angular Velocity:N/A
  • -
  • Angular Position:N/A
  • -
-
-
- - - -
-
- - - -
- -
-
- - -
-
-
-
- - \ No newline at end of file diff --git a/frontend/2d_part_3.html b/frontend/2d_part_3.html deleted file mode 100644 index 6e46843..0000000 --- a/frontend/2d_part_3.html +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - - - Physics for Games - - - - - - - - - - - -
-

Physics for Games

-
-
- -
-
-

Part 3: Collisions

-

- 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. -

-
- -
-
- - -
-
- - -
-
-
-

Fun Example: Plinko Game

-

- 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. -

-
- -
- -
- - -
-
-
-
- - \ No newline at end of file diff --git a/frontend/_collisions/2d/collisions_1.js b/frontend/_collisions/2d/collisions_1.js new file mode 100644 index 0000000..e69de29 diff --git a/frontend/_collisions/2d/part_1.html b/frontend/_collisions/2d/part_1.html new file mode 100644 index 0000000..ab60b17 --- /dev/null +++ b/frontend/_collisions/2d/part_1.html @@ -0,0 +1,61 @@ + + + + + + + + + + Physics for Games + + + + + + + + + +
+

Physics for Games

+
+
+ +
+

Part 1: Circle-Line

+
+

+ 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. +

+
+ +
+
    +
  • Linear Force:N/A
  • +
  • Linear Acceleration:N/A
  • +
  • Linear Velocity:N/A
  • +
  • Linear Position:N/A
  • +
+
+
+ + + +
+ +
+
+ + +
+
+
+
+ + \ No newline at end of file diff --git a/frontend/_rigidbody/2d/part_1.html b/frontend/_rigidbody/2d/part_1.html new file mode 100644 index 0000000..ec0acbf --- /dev/null +++ b/frontend/_rigidbody/2d/part_1.html @@ -0,0 +1,93 @@ + + + + + + + + + + Physics for Games + + + + + + + + + +
+

Physics for Games

+
+
+ +
+

Part 1: Linear Forces

+
+

+ 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. +

+

+ Let's begin by recalling the relationships between acceleration, velocity, and position. +

+

+ Knowing all this, you should be able to understand the following source code fairly easily; +

+                            
+    function update(dtSeconds) {
+        // Add up the forces acting on the circle
+        const GRAVITY = 9.8;
+        const lGravityForce = vec2(0, -1.0 * (lCircle.mass * GRAVITY));
+        lCircle.force = addVec2(lCircle.force, lGravityForce);
+    
+        // Figure out acceleration (a = F / m)
+        const lCurrentAcceleration = scaleVec2(lCircle.force, 1.0 / lCircle.mass);
+    
+        // Calculate the new velocity: v = v0 + a * t
+        lCircle.velocity = addVec2(lCircle.velocity, scaleVec2(lCurrentAcceleration, dtSeconds));
+    
+        // Update the position based on velocity: x = x0 + v * t
+        lCircle.position = addVec2(lCircle.position, scaleVec2(lCircle.velocity, dtSeconds));
+    
+        // Update the model matrix accordingly
+        lCircle.model = translateMatrix(mat4(), lCircle.position.x, lCircle.position.y, 0);
+    
+        // Reset the force vector for the next update
+        lCircle.force = vec2()
+    }
+                            
+                        
+

+
+ +
+
    +
  • Linear Force:N/A
  • +
  • Linear Acceleration:N/A
  • +
  • Linear Velocity:N/A
  • +
  • Linear Position:N/A
  • +
+
+
+ + + +
+ +
+
+ + +
+
+
+
+ + \ No newline at end of file diff --git a/frontend/_rigidbody/2d/part_2.html b/frontend/_rigidbody/2d/part_2.html new file mode 100644 index 0000000..0291290 --- /dev/null +++ b/frontend/_rigidbody/2d/part_2.html @@ -0,0 +1,66 @@ + + + + + + + + + + Physics for Games + + + + + + + + + +
+

Physics for Games

+
+
+ +
+

Part 2: Rotational Forces

+
+

+ 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. +

+
+ +
+
    +
  • Angular Force:N/A
  • +
  • Angular Acceleration:N/A
  • +
  • Angular Velocity:N/A
  • +
  • Angular Position:N/A
  • +
+
+
+ + + +
+
+ + + +
+ +
+
+ + +
+
+
+
+ + \ No newline at end of file diff --git a/frontend/_rigidbody/2d/part_3.html b/frontend/_rigidbody/2d/part_3.html new file mode 100644 index 0000000..c1479da --- /dev/null +++ b/frontend/_rigidbody/2d/part_3.html @@ -0,0 +1,71 @@ + + + + + + + + + + Physics for Games + + + + + + + + + + +
+

Physics for Games

+
+
+ +
+

Part 3: Collisions

+
+

Implementation

+

+ 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. +

+
+ +
+
+ + +
+
+ + +
+
+
+

Plinko Game

+

+ 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. +

+
+ +
+ +
+ + +
+
+
+
+ + \ No newline at end of file diff --git a/frontend/_rigidbody/2d/rigidbody_1.js b/frontend/_rigidbody/2d/rigidbody_1.js new file mode 100644 index 0000000..e49c7c9 --- /dev/null +++ b/frontend/_rigidbody/2d/rigidbody_1.js @@ -0,0 +1,117 @@ +/// +/// +/// +/// +/// +/// + +(function() { + + function main() { + // Define Constants + const CIRCLE_RADIUS = 16; + const GRAVITY = 9.8; + + // Retrieve context + const lProgramContext = getContext('#rigidbody_1'); + + 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 1'); + lProgramContext.load().then(function(pProgramInfo) { + const lCircle = circle(lProgramContext.gl, CIRCLE_RADIUS, 30, [{ x: 0.3, y: 0.3, z: 0.3, w: 1 }], vec2(lProgramContext.width / 2.0, lProgramContext.height / 2.0)); + + function update(pDeltaTimeSeconds) { + // Physics updates + const lGravityForce = vec2(0, -1.0 * (lCircle.mass * GRAVITY)); + + // Add up the forces acting on the circle + lCircle.force = addVec2(lCircle.force, lGravityForce); + + // Figure out acceleration (a = F / m) + const lCurrentAcceleration = scaleVec2(lCircle.force, 1.0 / lCircle.mass); + + // Calculate the new velocity: v = v0 + a * t + lCircle.velocity = addVec2(lCircle.velocity, scaleVec2(lCurrentAcceleration, pDeltaTimeSeconds)); + + // Update the position based on velocity: x = x0 + v * t + lCircle.position = addVec2(lCircle.position, scaleVec2(lCircle.velocity, pDeltaTimeSeconds)); + + // Update the model matrix accordingly + lCircle.model = translateMatrix(mat4(), lCircle.position.x, lCircle.position.y, 0); + + // Report the current state to the frontend + $('#rigidbody_1_force_field').text(vec2str(lCircle.force)); + $('#rigidbody_1_acceleration_field').text(vec2str(lCurrentAcceleration)); + $('#rigidbody_1_velocity_field').text(vec2str(lCircle.velocity)); + $('#rigidbody_1_position_field').text(vec2str(lCircle.position)); + + // Reset the circle's force vector + lCircle.force = vec2(); + + // Render Code only + lProgramContext.gl.clearColor(0.0, 0.0, 0.0, 1.0); // Clear to black, fully opaque + lProgramContext.gl.clearDepth(1.0); // Clear everything + lProgramContext.gl.enable(lProgramContext.gl.DEPTH_TEST); // Enable depth testing + lProgramContext.gl.depthFunc(lProgramContext.gl.LEQUAL); // Near things obscure far things + 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); + lProgramContext.gl.uniformMatrix4fv(pProgramInfo.uniformLocations.model, false, lCircle.model); + + renderCircle(lProgramContext.gl, pProgramInfo, lCircle); + } + + function addForce(ev) { + ev.preventDefault(); + ev.stopPropagation(); + + let lXValue = $(this).find('.vec2_x_input').val(), + lYValue = $(this).find('.vec2_y_input').val(); + + if (lXValue.length === 0) { + lXValue = 0; + } + + if (lYValue.length === 0) { + lYValue = 5000; + } + + console.log('Applying force: ' + lXValue + ', ' + lYValue); + lCircle.force = addVec2(lCircle.force, vec2(Number(lXValue), Number(lYValue))); + } + + function cleanup() { + lProgramContext.gl.deleteBuffer(lCircle.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(); + $('#rigidbody_1_force_submit_button').unbind('submit').submit(false); + } + + const lExitRequestFunc = requestUpdateLoop(update, cleanup); + lProgramContext.stopButton.on('click', reset); + $('#rigidbody_1_force_submit_button').submit(addForce); + }); + } + + lProgramContext.playButton.on('click', run); + $('#rigidbody_1_force_submit_button').submit(false); + + } + + $(document).ready(main); +})() \ No newline at end of file diff --git a/frontend/_rigidbody/2d/rigidbody_2.js b/frontend/_rigidbody/2d/rigidbody_2.js new file mode 100644 index 0000000..4632b11 --- /dev/null +++ b/frontend/_rigidbody/2d/rigidbody_2.js @@ -0,0 +1,146 @@ +/// +/// +/// +/// +/// +/// + +(function() { + function main() { + // Define Constants + const CIRCLE_RADIUS = 16; + const GRAVITY = 9.8; + + // Retrieve context + const lProgramContext = getContext('#rigidbody_2'); + + 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 2'); + lProgramContext.load().then(function(pProgramInfo) { + const lCircle = 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(lProgramContext.width / 2.0, lProgramContext.height / 2.0)); + + function update(pDeltaTimeSeconds) { + // 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(); + + // 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.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); + 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); + lProgramContext.gl.uniformMatrix4fv(pProgramInfo.uniformLocations.model, false, lCircle.model); + + renderCircle(lProgramContext.gl, pProgramInfo, lCircle); + } + + const TORQUE_MULTIPLIER = 100.0; // TODO: This may be unncessary + + 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); // Retrieve the perpendicular vector + + // Calculate the torque from the perp dot (T = r_perp . F) + lCircle.torque += TORQUE_MULTIPLIER * dot2(lPerpVec, pForceVector); + } + + lCircle.force = addVec2(lCircle.force, pForceVector); + } + + function cleanup() { + lProgramContext.gl.deleteBuffer(lCircle.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(); + $('#rigidbody_2_force_submit_button').unbind('submit').submit(false); + } + + const lExitRequestFunc = requestUpdateLoop(update, cleanup); + lProgramContext.stopButton.on('click', reset); + $('#rigidbody_2_force_submit_button').submit(function(pEv) { + pEv.preventDefault(); + pEv.stopPropagation(); + + // Read in the force vector from the form + const lForceGroup = $('#rigidbody_2_force_input'), + lPositionGroup = $('#rigidbody_2_position_input'); + + let lForceVectorX = lForceGroup.find('.vec2_x_input').val(), + lForceVectorY = lForceGroup.find('.vec2_y_input').val(); + + if (lForceVectorX.length === 0) { + lForceVectorX = 0; + } + + if (lForceVectorY.length === 0) { + lForceVectorY = 5000; + } + + // Read in the point of application vector from the form + let lPositionGroupX = lPositionGroup.find('.vec2_x_input').val(), + lPositionGroupY = lPositionGroup.find('.vec2_y_input').val(); + + if (lPositionGroupX.length === 0) { + lPositionGroupX = -Math.sqrt(2) / 2; + } + + if (lPositionGroupY.length === 0) { + lPositionGroupY = -Math.sqrt(2) / 2; + } + + const lForceVector = vec2(Number(lForceVectorX), Number(lForceVectorY)); + const lPointOfApplication = scaleVec2(normalize2(vec2(Number(lPositionGroupX), Number(lPositionGroupY))), lCircle.radius); + + applyForce(lForceVector, lPointOfApplication); + }); + }); + } + + lProgramContext.playButton.on('click', run); + $('#rigidbody_2_force_submit_button').submit(false); + } + + $(document).ready(main); +})() \ No newline at end of file diff --git a/frontend/_rigidbody/2d/rigidbody_3a.js b/frontend/_rigidbody/2d/rigidbody_3a.js new file mode 100644 index 0000000..d38f1da --- /dev/null +++ b/frontend/_rigidbody/2d/rigidbody_3a.js @@ -0,0 +1,190 @@ +/// +/// +/// +/// +/// +/// + +(function() { + function main() { + // Define Constants + const CIRCLE_RADIUS = 16; + const GRAVITY = 9.8; + const COF_OF_RESTITUITION = 0.7; + + // Retrieve context + const lProgramContext = getContext('#rigidbody_3a'); + + 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 3a'); + 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.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) { + pDeltaTimeSeconds = pDeltaTimeSeconds * lTimeStepScale; + updateCircle(horizontalCircle, pDeltaTimeSeconds); + updateCircle(verticalCircle, pDeltaTimeSeconds); + collision(pDeltaTimeSeconds); + render(); + } + + function collision(pDeltaTimeSeconds) { + if (!doCirclesIntersect(horizontalCircle, verticalCircle)) { + return false; + } + + // We have an intersection! Let's try and figure out precisely when that happened. + let lSubdividedDeltaTime = pDeltaTimeSeconds; + let lSubdivideHorizontalBall = undefined, + lSubdivideVerticalBall = undefined; + + do { + lSubdivideHorizontalBall = JSON.parse(JSON.stringify(horizontalCircle)); + lSubdivideHorizontalBall.position = {...horizontalCircle.prevPos}; + lSubdivideHorizontalBall.velocity = {...horizontalCircle.prevVelocity}; + + lSubdivideVerticalBall = JSON.parse(JSON.stringify(verticalCircle)); + lSubdivideVerticalBall.position = {...verticalCircle.prevPos}; + lSubdivideVerticalBall.velocity = {...verticalCircle.prevVelocity}; + + lSubdividedDeltaTime = lSubdividedDeltaTime / 2.0; + updateCircle(lSubdivideHorizontalBall, lSubdividedDeltaTime); + updateCircle(lSubdivideVerticalBall, lSubdividedDeltaTime); + + if (lSubdividedDeltaTime === 0) { + console.error('This should NOT be happening'); + break; + } + } while (doCirclesIntersect(lSubdivideHorizontalBall, lSubdivideVerticalBall)) + + const lIntersectionResult = getIntersectionDataForCircles(lSubdivideHorizontalBall, lSubdivideVerticalBall); + + console.log('We have a collision'); + 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 / 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.position = lSubdivideHorizontalBall.position; + verticalCircle.position = lSubdivideVerticalBall.position; + + 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); + + updateCircle(horizontalCircle, pDeltaTimeSeconds - lSubdividedDeltaTime); + updateCircle(verticalCircle, pDeltaTimeSeconds - lSubdividedDeltaTime); + + 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/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 diff --git a/frontend/_rigidbody/circle.js b/frontend/_rigidbody/circle.js deleted file mode 100644 index 1cec7ce..0000000 --- a/frontend/_rigidbody/circle.js +++ /dev/null @@ -1,117 +0,0 @@ -/// -/// - -const BYTES_PER_FLOAT = 4; - -/** - * Initializes a new circle object for the WebGL context. - * - * @param {WebGLRenderingContext} pGl - * @param {number} pRadius - * @param {number} pSegments - * @param {Array} 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); - - var lBufferedData = []; - vertexCount = 0; - - const lAngleIncrements = (360.0 / pSegments) * (Math.PI / 180.0); - for (let lSegIdx = 0; lSegIdx < pSegments; lSegIdx++) { - const lAngle = lAngleIncrements * lSegIdx, - lNextAngle = lAngleIncrements * (lSegIdx + 1), - lColorIndex = Math.floor(pColorList.length * (lSegIdx / pSegments)), - lColor = pColorList[lColorIndex]; // TODO: Calculate which one to use - - lBufferedData = lBufferedData.concat([ - 0, 0, lColor.x, lColor.y, lColor.z, lColor.w, - pRadius * Math.sin(lAngle), pRadius * Math.cos(lAngle), lColor.x, lColor.y, lColor.z, lColor.w, - pRadius * Math.sin(lNextAngle), pRadius * Math.cos(lNextAngle), lColor.x, lColor.y, lColor.z, lColor.w - ]); - - vertexCount += 3; - } - - pGl.bufferData(pGl.ARRAY_BUFFER, new Float32Array(lBufferedData), pGl.STATIC_DRAW) - pGl.bindBuffer(pGl.ARRAY_BUFFER, undefined); - - return { - buffer: lBuffer, - vertexCount: vertexCount, - prevPos: vec2(), - position: pInitialPosition || vec2(), - prevVelocity: vec2(), - velocity: vec2(), - force: vec2(), - torque: 0, - mass: pMass === undefined ? 1 : pMass, - rotationVelocity: 0, - rotationRadians: 0, - model: translateMatrix(mat4(), pInitialPosition ? pInitialPosition.x : 0, pInitialPosition ? pInitialPosition.y : 0, 0), - radius: pRadius - }; -} - -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); - pGl.vertexAttribPointer(pProgramInfo.attributeLocations.position, 2, pGl.FLOAT, false, BYTES_PER_FLOAT * 6, 0); - - pGl.enableVertexAttribArray(pProgramInfo.attributeLocations.color); - pGl.vertexAttribPointer(pProgramInfo.attributeLocations.color, 4, pGl.FLOAT, false, BYTES_PER_FLOAT * 6, BYTES_PER_FLOAT * 2); - } - - pGl.drawArrays(pGl.TRIANGLE_STRIP, 0, pCircle.vertexCount); -} - -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) - } -} - -function freeCircle(pGl, pCircle) { - pGl.deleteBuffer(pCircle.buffer); -} \ No newline at end of file diff --git a/frontend/_rigidbody/mat4.js b/frontend/_rigidbody/mat4.js deleted file mode 100644 index 6ab29e2..0000000 --- a/frontend/_rigidbody/mat4.js +++ /dev/null @@ -1,43 +0,0 @@ -function mat4() { - return [ - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1 - ] -} - -function orthographic(pLeft, pRight, pBottom, pTop) { - const lResult = mat4(); - lResult[0] = 2.0 / (pRight - pLeft); - lResult[5] = 2.0 / (pTop - pBottom); - lResult[10] = 1.0; - lResult[12] = -(pRight + pLeft) / (pRight - pLeft); - lResult[13] = -(pTop + pBottom) / (pTop - pBottom); - return lResult; -} - -function translateMatrix(m, x, y, z) { - m = [...m]; - m[12] += x; - m[13] += y; - m[14] += z; - return m; -} - -function rotateMatrix2d(m, angle) { - m = [...m]; - m[0] = Math.cos(angle); - m[1] = -Math.sin(angle); - m[4] = Math.sin(angle); - m[5] = Math.cos(angle); - return m; -} - -function scaleMatrix(m, x, y, z) { - m = [...m]; - m[0] = m[0] * x; - m[5] = m[5] * y; - m[10] = m[10] * z; - return m; -} \ No newline at end of file diff --git a/frontend/_rigidbody/pill.js b/frontend/_rigidbody/pill.js deleted file mode 100644 index e69de29..0000000 diff --git a/frontend/_rigidbody/program_common.js b/frontend/_rigidbody/program_common.js deleted file mode 100644 index 988056f..0000000 --- a/frontend/_rigidbody/program_common.js +++ /dev/null @@ -1,75 +0,0 @@ -/// -/// - -function getContext(pId, pOnRun) { - const lCanvas = $(pId).find('canvas'), - lPlayButton = $(pId).find('.play_button'), - lStopButton = $(pId).find('.stop_button'), - lGl = lCanvas[0].getContext('webgl'), - lWidth = lCanvas.width(), - lHeight = lCanvas.height(); - - return { - canvas: lCanvas, - playButton: lPlayButton, - stopButton: lStopButton, - gl: lGl, - width: lWidth, - height: lHeight, - perspective: orthographic(0, lWidth, 0, lHeight), - load: function() { - lPlayButton.empty().append($('
').addClass('spin-loader')); - return loadOrthographicShader(lGl).then(function(pProgramInfo) { - lPlayButton.css('display', 'none'); - lStopButton.css('display', 'block'); - return pProgramInfo; - }); - }, - reset: function() { - lPlayButton.css('display', 'block'); - lPlayButton.empty().text('Play'); - lStopButton.css('display', 'none'); - lStopButton.on('click', undefined); - } - }; -} - -function requestUpdateLoop(pFunction, pOnExit) { - let lDeltaTimeSeconds = undefined, - lLastTimeSeconds = undefined, - lIsRunning = true; - - function update(pTimeStamp) { - if (!lIsRunning) { - if (pOnExit) { - pOnExit(); - } - return; - } - - pTimeStamp = pTimeStamp / 1000.0; // Convert to seconds - - // Time calculation - if (lLastTimeSeconds === undefined) { - lLastTimeSeconds = pTimeStamp; - lDeltaTimeSeconds = 0; - } else { - lDeltaTimeSeconds = pTimeStamp - lLastTimeSeconds; - lLastTimeSeconds = pTimeStamp; - } - - while (lDeltaTimeSeconds > 0) { - pFunction(lDeltaTimeSeconds); - lDeltaTimeSeconds -= 0.16; - } - requestAnimationFrame(update); - } - - requestAnimationFrame(update); - - function lExit() { - lIsRunning = false; - } - - return lExit; -} \ No newline at end of file diff --git a/frontend/_rigidbody/rigidbody_1.js b/frontend/_rigidbody/rigidbody_1.js deleted file mode 100644 index 697822d..0000000 --- a/frontend/_rigidbody/rigidbody_1.js +++ /dev/null @@ -1,117 +0,0 @@ -/// -/// -/// -/// -/// -/// - -(function() { - - function main() { - // Define Constants - const CIRCLE_RADIUS = 16; - const GRAVITY = 9.8; - - // Retrieve context - const lProgramContext = getContext('#rigidbody_1'); - - 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 1'); - lProgramContext.load().then(function(pProgramInfo) { - const lCircle = circle(lProgramContext.gl, CIRCLE_RADIUS, 30, [{ x: 0.3, y: 0.3, z: 0.3, w: 1 }], vec2(lProgramContext.width / 2.0, lProgramContext.height / 2.0)); - - function update(pDeltaTimeSeconds) { - // Physics updates - const lGravityForce = vec2(0, -1.0 * (lCircle.mass * GRAVITY)); - - // Add up the forces acting on the circle - lCircle.force = addVec2(lCircle.force, lGravityForce); - - // Figure out acceleration (a = F / m) - const lCurrentAcceleration = scaleVec2(lCircle.force, 1.0 / lCircle.mass); - - // Calculate the new velocity: v = v0 + a * t - lCircle.velocity = addVec2(lCircle.velocity, scaleVec2(lCurrentAcceleration, pDeltaTimeSeconds)); - - // Update the position based on velocity: x = x0 + v * t - lCircle.position = addVec2(lCircle.position, scaleVec2(lCircle.velocity, pDeltaTimeSeconds)); - - // Update the model matrix accordingly - lCircle.model = translateMatrix(mat4(), lCircle.position.x, lCircle.position.y, 0); - - // Report the current state to the frontend - $('#rigidbody_1_force_field').text(vec2str(lCircle.force)); - $('#rigidbody_1_acceleration_field').text(vec2str(lCurrentAcceleration)); - $('#rigidbody_1_velocity_field').text(vec2str(lCircle.velocity)); - $('#rigidbody_1_position_field').text(vec2str(lCircle.position)); - - // Reset the circle's force vector - lCircle.force = vec2(); - - // Render Code only - lProgramContext.gl.clearColor(0.0, 0.0, 0.0, 1.0); // Clear to black, fully opaque - lProgramContext.gl.clearDepth(1.0); // Clear everything - lProgramContext.gl.enable(lProgramContext.gl.DEPTH_TEST); // Enable depth testing - lProgramContext.gl.depthFunc(lProgramContext.gl.LEQUAL); // Near things obscure far things - 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); - lProgramContext.gl.uniformMatrix4fv(pProgramInfo.uniformLocations.model, false, lCircle.model); - - renderCircle(lProgramContext.gl, pProgramInfo, lCircle); - } - - function addForce(ev) { - ev.preventDefault(); - ev.stopPropagation(); - - let lXValue = $(this).find('.vec2_x_input').val(), - lYValue = $(this).find('.vec2_y_input').val(); - - if (lXValue.length === 0) { - lXValue = 0; - } - - if (lYValue.length === 0) { - lYValue = 5000; - } - - console.log('Applying force: ' + lXValue + ', ' + lYValue); - lCircle.force = addVec2(lCircle.force, vec2(Number(lXValue), Number(lYValue))); - } - - function cleanup() { - lProgramContext.gl.deleteBuffer(lCircle.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(); - $('#rigidbody_1_force_submit_button').unbind('submit').submit(false); - } - - const lExitRequestFunc = requestUpdateLoop(update, cleanup); - lProgramContext.stopButton.on('click', reset); - $('#rigidbody_1_force_submit_button').submit(addForce); - }); - } - - lProgramContext.playButton.on('click', run); - $('#rigidbody_1_force_submit_button').submit(false); - - } - - $(document).ready(main); -})() \ No newline at end of file diff --git a/frontend/_rigidbody/rigidbody_2.js b/frontend/_rigidbody/rigidbody_2.js deleted file mode 100644 index c878c1a..0000000 --- a/frontend/_rigidbody/rigidbody_2.js +++ /dev/null @@ -1,145 +0,0 @@ -/// -/// -/// -/// -/// - -(function() { - function main() { - // Define Constants - const CIRCLE_RADIUS = 16; - const GRAVITY = 9.8; - - // Retrieve context - const lProgramContext = getContext('#rigidbody_2'); - - 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 2'); - lProgramContext.load().then(function(pProgramInfo) { - const lCircle = 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(lProgramContext.width / 2.0, lProgramContext.height / 2.0)); - - function update(pDeltaTimeSeconds) { - // 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(); - - // 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.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); - 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); - lProgramContext.gl.uniformMatrix4fv(pProgramInfo.uniformLocations.model, false, lCircle.model); - - renderCircle(lProgramContext.gl, pProgramInfo, lCircle); - } - - const TORQUE_MULTIPLIER = 100.0; // TODO: This may be unncessary - - 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); // Retrieve the perpendicular vector - - // Calculate the torque from the perp dot (T = r_perp . F) - lCircle.torque += TORQUE_MULTIPLIER * dot2(lPerpVec, pForceVector); - } - - lCircle.force = addVec2(lCircle.force, pForceVector); - } - - function cleanup() { - lProgramContext.gl.deleteBuffer(lCircle.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(); - $('#rigidbody_2_force_submit_button').unbind('submit').submit(false); - } - - const lExitRequestFunc = requestUpdateLoop(update, cleanup); - lProgramContext.stopButton.on('click', reset); - $('#rigidbody_2_force_submit_button').submit(function(pEv) { - pEv.preventDefault(); - pEv.stopPropagation(); - - // Read in the force vector from the form - const lForceGroup = $('#rigidbody_2_force_input'), - lPositionGroup = $('#rigidbody_2_position_input'); - - let lForceVectorX = lForceGroup.find('.vec2_x_input').val(), - lForceVectorY = lForceGroup.find('.vec2_y_input').val(); - - if (lForceVectorX.length === 0) { - lForceVectorX = 0; - } - - if (lForceVectorY.length === 0) { - lForceVectorY = 5000; - } - - // Read in the point of application vector from the form - let lPositionGroupX = lPositionGroup.find('.vec2_x_input').val(), - lPositionGroupY = lPositionGroup.find('.vec2_y_input').val(); - - if (lPositionGroupX.length === 0) { - lPositionGroupX = -Math.sqrt(2) / 2; - } - - if (lPositionGroupY.length === 0) { - lPositionGroupY = -Math.sqrt(2) / 2; - } - - const lForceVector = vec2(Number(lForceVectorX), Number(lForceVectorY)); - const lPointOfApplication = scaleVec2(normalize2(vec2(Number(lPositionGroupX), Number(lPositionGroupY))), lCircle.radius); - - applyForce(lForceVector, lPointOfApplication); - }); - }); - } - - lProgramContext.playButton.on('click', run); - $('#rigidbody_2_force_submit_button').submit(false); - } - - $(document).ready(main); -})() \ No newline at end of file diff --git a/frontend/_rigidbody/rigidbody_3a.js b/frontend/_rigidbody/rigidbody_3a.js deleted file mode 100644 index 74d309b..0000000 --- a/frontend/_rigidbody/rigidbody_3a.js +++ /dev/null @@ -1,189 +0,0 @@ -/// -/// -/// -/// -/// - -(function() { - function main() { - // Define Constants - const CIRCLE_RADIUS = 16; - const GRAVITY = 9.8; - const COF_OF_RESTITUITION = 0.7; - - // Retrieve context - const lProgramContext = getContext('#rigidbody_3a'); - - 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 3a'); - 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.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) { - pDeltaTimeSeconds = pDeltaTimeSeconds * lTimeStepScale; - updateCircle(horizontalCircle, pDeltaTimeSeconds); - updateCircle(verticalCircle, pDeltaTimeSeconds); - collision(pDeltaTimeSeconds); - render(); - } - - function collision(pDeltaTimeSeconds) { - if (!doCirclesIntersect(horizontalCircle, verticalCircle)) { - return false; - } - - // We have an intersection! Let's try and figure out precisely when that happened. - let lSubdividedDeltaTime = pDeltaTimeSeconds; - let lSubdivideHorizontalBall = undefined, - lSubdivideVerticalBall = undefined; - - do { - lSubdivideHorizontalBall = JSON.parse(JSON.stringify(horizontalCircle)); - lSubdivideHorizontalBall.position = {...horizontalCircle.prevPos}; - lSubdivideHorizontalBall.velocity = {...horizontalCircle.prevVelocity}; - - lSubdivideVerticalBall = JSON.parse(JSON.stringify(verticalCircle)); - lSubdivideVerticalBall.position = {...verticalCircle.prevPos}; - lSubdivideVerticalBall.velocity = {...verticalCircle.prevVelocity}; - - lSubdividedDeltaTime = lSubdividedDeltaTime / 2.0; - updateCircle(lSubdivideHorizontalBall, lSubdividedDeltaTime); - updateCircle(lSubdivideVerticalBall, lSubdividedDeltaTime); - - if (lSubdividedDeltaTime === 0) { - console.error('This should NOT be happening'); - break; - } - } while (doCirclesIntersect(lSubdivideHorizontalBall, lSubdivideVerticalBall)) - - const lIntersectionResult = getIntersectionDataForCircles(lSubdivideHorizontalBall, lSubdivideVerticalBall); - - console.log('We have a collision'); - 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 / 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.position = lSubdivideHorizontalBall.position; - verticalCircle.position = lSubdivideVerticalBall.position; - - 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); - - updateCircle(horizontalCircle, pDeltaTimeSeconds - lSubdividedDeltaTime); - updateCircle(verticalCircle, pDeltaTimeSeconds - lSubdividedDeltaTime); - - 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/rigidbody_3b.js b/frontend/_rigidbody/rigidbody_3b.js deleted file mode 100644 index df81582..0000000 --- a/frontend/_rigidbody/rigidbody_3b.js +++ /dev/null @@ -1,192 +0,0 @@ -/// -/// -/// -/// -/// -/// - -(function() { - // Define Constants - const GRAVITY = 50.0; - const COF_OF_RESTITUITION = 0.2; - - 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 = 1; lRowIdx < lNumVerticalPegs - 1; lRowIdx++) { - for (let lColIdx = 0; lColIdx < lNumHorizontalPegs -1; 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((lColIdx + 1) * 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); - - console.log(lIntersectionResult); - - 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); - - console.log(lBall.rotationVelocity); - - // 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 diff --git a/frontend/_rigidbody/shader.js b/frontend/_rigidbody/shader.js deleted file mode 100644 index bf08638..0000000 --- a/frontend/_rigidbody/shader.js +++ /dev/null @@ -1,60 +0,0 @@ -function loadShader(pGl, pShaderType, pShaderSource) { - const lShader = pGl.createShader(pShaderType); - pGl.shaderSource(lShader, pShaderSource); - pGl.compileShader(lShader); - if (!pGl.getShaderParameter(lShader, pGl.COMPILE_STATUS)) { - alert('An error occurred compiling the shaders: ' + pGl.getShaderInfoLog(lShader)); - pGl.deleteShader(lShader); - return null; - } - - return lShader; -} - -function loadOrthographicShader(pGl) { - var lVertex, lFragment; - - function lLoadShaders() { - const lVertexShader = loadShader(pGl, pGl.VERTEX_SHADER, lVertex); - const lFragmentShader = loadShader(pGl, pGl.FRAGMENT_SHADER, lFragment); - - const lShaderProgram = pGl.createProgram(); - pGl.attachShader(lShaderProgram, lVertexShader); - pGl.attachShader(lShaderProgram, lFragmentShader); - pGl.linkProgram(lShaderProgram); - - if (!pGl.getProgramParameter(lShaderProgram, pGl.LINK_STATUS)) { - alert('Unable to initialize the shader program: ' + pGl.getProgramInfoLog(lShaderProgram)); - return null; - } - - return { - program: lShaderProgram, - attributeLocations: { - position: pGl.getAttribLocation(lShaderProgram, 'position'), - color: pGl.getAttribLocation(lShaderProgram, 'color') - }, - uniformLocations: { - projection: pGl.getUniformLocation(lShaderProgram, 'projection'), - model: pGl.getUniformLocation(lShaderProgram, 'model') - } - } - } - - return fetch('/_rigidbody/shaders/orthographic.vert').then(function(pResponse) { - if (pResponse.status === 200) { - return pResponse.text().then(function(pShader) { - lVertex = pShader; - - return fetch('/_rigidbody/shaders/orthographic.frag').then(function(pResponse) { - if (pResponse.status === 200) { - return pResponse.text().then(function(pShader) { - lFragment = pShader; - return lLoadShaders(); - }); - } - }); - }); - } - }); -} \ No newline at end of file diff --git a/frontend/_rigidbody/shaders/orthographic.frag b/frontend/_rigidbody/shaders/orthographic.frag deleted file mode 100644 index 84b6b2e..0000000 --- a/frontend/_rigidbody/shaders/orthographic.frag +++ /dev/null @@ -1,5 +0,0 @@ -varying lowp vec4 VertexColor; - -void main() { - gl_FragColor = VertexColor; -} \ No newline at end of file diff --git a/frontend/_rigidbody/shaders/orthographic.vert b/frontend/_rigidbody/shaders/orthographic.vert deleted file mode 100644 index 0356f9c..0000000 --- a/frontend/_rigidbody/shaders/orthographic.vert +++ /dev/null @@ -1,13 +0,0 @@ -attribute vec2 position; -attribute vec4 color; - -uniform mat4 projection; -uniform mat4 model; - -varying lowp vec4 VertexColor; - -void main() { - vec4 fragmentPosition = projection * model * vec4(position, 1, 1); - gl_Position = fragmentPosition; - VertexColor = color; -} \ No newline at end of file diff --git a/frontend/_rigidbody/vec2.js b/frontend/_rigidbody/vec2.js deleted file mode 100644 index 2db42e0..0000000 --- a/frontend/_rigidbody/vec2.js +++ /dev/null @@ -1,55 +0,0 @@ -function vec2(x = 0, y = 0) { - return { x: x, y: y}; -} - -function addVec2(v1, v2) { - return { - x: v1.x + v2.x, - y: v1.y + v2.y - }; -} - -function subVec2(v1, v2) { - return { - x: v1.x - v2.x, - y: v1.y - v2.y - }; -} - -function scaleVec2(v, s) { - return { - x: v.x * s, - y: v.y * s - }; -} - -function dot2(v1, v2) { - return v1.x * v2.x + v1.y * v2.y; -} - -function length2(v) { - return Math.sqrt(v.x * v.x + v.y * v.y); -} - -function normalize2(v) { - const lLength = 1.0 / length2(v); - return { x: v.x * lLength, y: v.y * lLength }; -} - -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/_shared/2d/program_common.js b/frontend/_shared/2d/program_common.js new file mode 100644 index 0000000..48d0a6b --- /dev/null +++ b/frontend/_shared/2d/program_common.js @@ -0,0 +1,75 @@ +/// +/// + +function getContext(pId, pOnRun) { + const lCanvas = $(pId).find('canvas'), + lPlayButton = $(pId).find('.play_button'), + lStopButton = $(pId).find('.stop_button'), + lGl = lCanvas[0].getContext('webgl'), + lWidth = lCanvas.width(), + lHeight = lCanvas.height(); + + return { + canvas: lCanvas, + playButton: lPlayButton, + stopButton: lStopButton, + gl: lGl, + width: lWidth, + height: lHeight, + perspective: orthographic(0, lWidth, 0, lHeight), + load: function() { + lPlayButton.empty().append($('
').addClass('spin-loader')); + return loadOrthographicShader(lGl).then(function(pProgramInfo) { + lPlayButton.css('display', 'none'); + lStopButton.css('display', 'block'); + return pProgramInfo; + }); + }, + reset: function() { + lPlayButton.css('display', 'block'); + lPlayButton.empty().text('Play'); + lStopButton.css('display', 'none'); + lStopButton.on('click', undefined); + } + }; +} + +function requestUpdateLoop(pFunction, pOnExit) { + let lDeltaTimeSeconds = undefined, + lLastTimeSeconds = undefined, + lIsRunning = true; + + function update(pTimeStamp) { + if (!lIsRunning) { + if (pOnExit) { + pOnExit(); + } + return; + } + + pTimeStamp = pTimeStamp / 1000.0; // Convert to seconds + + // Time calculation + if (lLastTimeSeconds === undefined) { + lLastTimeSeconds = pTimeStamp; + lDeltaTimeSeconds = 0; + } else { + lDeltaTimeSeconds = pTimeStamp - lLastTimeSeconds; + lLastTimeSeconds = pTimeStamp; + } + + while (lDeltaTimeSeconds > 0) { + pFunction(lDeltaTimeSeconds); + lDeltaTimeSeconds -= 0.16; + } + requestAnimationFrame(update); + } + + requestAnimationFrame(update); + + function lExit() { + lIsRunning = false; + } + + return lExit; +} \ No newline at end of file diff --git a/frontend/_shared/2d/shader.js b/frontend/_shared/2d/shader.js new file mode 100644 index 0000000..4deddb5 --- /dev/null +++ b/frontend/_shared/2d/shader.js @@ -0,0 +1,60 @@ +function loadShader(pGl, pShaderType, pShaderSource) { + const lShader = pGl.createShader(pShaderType); + pGl.shaderSource(lShader, pShaderSource); + pGl.compileShader(lShader); + if (!pGl.getShaderParameter(lShader, pGl.COMPILE_STATUS)) { + alert('An error occurred compiling the shaders: ' + pGl.getShaderInfoLog(lShader)); + pGl.deleteShader(lShader); + return null; + } + + return lShader; +} + +function loadOrthographicShader(pGl) { + var lVertex, lFragment; + + function lLoadShaders() { + const lVertexShader = loadShader(pGl, pGl.VERTEX_SHADER, lVertex); + const lFragmentShader = loadShader(pGl, pGl.FRAGMENT_SHADER, lFragment); + + const lShaderProgram = pGl.createProgram(); + pGl.attachShader(lShaderProgram, lVertexShader); + pGl.attachShader(lShaderProgram, lFragmentShader); + pGl.linkProgram(lShaderProgram); + + if (!pGl.getProgramParameter(lShaderProgram, pGl.LINK_STATUS)) { + alert('Unable to initialize the shader program: ' + pGl.getProgramInfoLog(lShaderProgram)); + return null; + } + + return { + program: lShaderProgram, + attributeLocations: { + position: pGl.getAttribLocation(lShaderProgram, 'position'), + color: pGl.getAttribLocation(lShaderProgram, 'color') + }, + uniformLocations: { + projection: pGl.getUniformLocation(lShaderProgram, 'projection'), + model: pGl.getUniformLocation(lShaderProgram, 'model') + } + } + } + + return fetch('/_shared/2d/shaders/orthographic.vert').then(function(pResponse) { + if (pResponse.status === 200) { + return pResponse.text().then(function(pShader) { + lVertex = pShader; + + return fetch('/_shared/2d/shaders/orthographic.frag').then(function(pResponse) { + if (pResponse.status === 200) { + return pResponse.text().then(function(pShader) { + lFragment = pShader; + return lLoadShaders(); + }); + } + }); + }); + } + }); +} \ No newline at end of file diff --git a/frontend/_shared/2d/shaders/orthographic.frag b/frontend/_shared/2d/shaders/orthographic.frag new file mode 100644 index 0000000..84b6b2e --- /dev/null +++ b/frontend/_shared/2d/shaders/orthographic.frag @@ -0,0 +1,5 @@ +varying lowp vec4 VertexColor; + +void main() { + gl_FragColor = VertexColor; +} \ No newline at end of file diff --git a/frontend/_shared/2d/shaders/orthographic.vert b/frontend/_shared/2d/shaders/orthographic.vert new file mode 100644 index 0000000..0356f9c --- /dev/null +++ b/frontend/_shared/2d/shaders/orthographic.vert @@ -0,0 +1,13 @@ +attribute vec2 position; +attribute vec4 color; + +uniform mat4 projection; +uniform mat4 model; + +varying lowp vec4 VertexColor; + +void main() { + vec4 fragmentPosition = projection * model * vec4(position, 1, 1); + gl_Position = fragmentPosition; + VertexColor = color; +} \ No newline at end of file diff --git a/frontend/_shared/math/circle.js b/frontend/_shared/math/circle.js new file mode 100644 index 0000000..340d7dc --- /dev/null +++ b/frontend/_shared/math/circle.js @@ -0,0 +1,112 @@ +/// +/// + +const BYTES_PER_FLOAT = 4; + +/** + * Initializes a new circle object for the WebGL context. + * + * @param {WebGLRenderingContext} pGl + * @param {number} pRadius + * @param {number} pSegments + * @param {Array} 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); + + var lBufferedData = []; + vertexCount = 0; + + const lAngleIncrements = (360.0 / pSegments) * (Math.PI / 180.0); + for (let lSegIdx = 0; lSegIdx < pSegments; lSegIdx++) { + const lAngle = lAngleIncrements * lSegIdx, + lNextAngle = lAngleIncrements * (lSegIdx + 1), + lColorIndex = Math.floor(pColorList.length * (lSegIdx / pSegments)), + lColor = pColorList[lColorIndex]; // TODO: Calculate which one to use + + lBufferedData = lBufferedData.concat([ + 0, 0, lColor.x, lColor.y, lColor.z, lColor.w, + pRadius * Math.sin(lAngle), pRadius * Math.cos(lAngle), lColor.x, lColor.y, lColor.z, lColor.w, + pRadius * Math.sin(lNextAngle), pRadius * Math.cos(lNextAngle), lColor.x, lColor.y, lColor.z, lColor.w + ]); + + vertexCount += 3; + } + + pGl.bufferData(pGl.ARRAY_BUFFER, new Float32Array(lBufferedData), pGl.STATIC_DRAW) + pGl.bindBuffer(pGl.ARRAY_BUFFER, undefined); + + return { + buffer: lBuffer, + vertexCount: vertexCount, + prevPos: vec2(), + position: pInitialPosition || vec2(), + prevVelocity: vec2(), + velocity: vec2(), + force: vec2(), + torque: 0, + mass: pMass === undefined ? 1 : pMass, + rotationVelocity: 0, + rotationRadians: 0, + model: translateMatrix(mat4(), pInitialPosition ? pInitialPosition.x : 0, pInitialPosition ? pInitialPosition.y : 0, 0), + radius: pRadius + }; +} + +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); + pGl.vertexAttribPointer(pProgramInfo.attributeLocations.position, 2, pGl.FLOAT, false, BYTES_PER_FLOAT * 6, 0); + + pGl.enableVertexAttribArray(pProgramInfo.attributeLocations.color); + pGl.vertexAttribPointer(pProgramInfo.attributeLocations.color, 4, pGl.FLOAT, false, BYTES_PER_FLOAT * 6, BYTES_PER_FLOAT * 2); + } + + pGl.drawArrays(pGl.TRIANGLE_STRIP, 0, pCircle.vertexCount); +} + +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)); + 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) + ); + + return { + relativeVelocity: subVec2(pFirst.velocity, pSecond.velocity), + collisionNormal: lCollisionNormal, + //firstPointOfApplication: addVec2(scaleVec2(normalize2(pFirst.velocity), pFirst.radius), pFirst.position), + //secondPointOfApplication: addVec2(scaleVec2(normalize2(pSecond.velocity), pSecond.radius), pSecond.position) + firstPointOfApplication: subVec2(lCollisionPoint, pFirst.position), + secondPointOfApplication: subVec2(lCollisionPoint, pSecond.position) + } +} + +function freeCircle(pGl, pCircle) { + pGl.deleteBuffer(pCircle.buffer); +} \ No newline at end of file diff --git a/frontend/_shared/math/mat4.js b/frontend/_shared/math/mat4.js new file mode 100644 index 0000000..6ab29e2 --- /dev/null +++ b/frontend/_shared/math/mat4.js @@ -0,0 +1,43 @@ +function mat4() { + return [ + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + ] +} + +function orthographic(pLeft, pRight, pBottom, pTop) { + const lResult = mat4(); + lResult[0] = 2.0 / (pRight - pLeft); + lResult[5] = 2.0 / (pTop - pBottom); + lResult[10] = 1.0; + lResult[12] = -(pRight + pLeft) / (pRight - pLeft); + lResult[13] = -(pTop + pBottom) / (pTop - pBottom); + return lResult; +} + +function translateMatrix(m, x, y, z) { + m = [...m]; + m[12] += x; + m[13] += y; + m[14] += z; + return m; +} + +function rotateMatrix2d(m, angle) { + m = [...m]; + m[0] = Math.cos(angle); + m[1] = -Math.sin(angle); + m[4] = Math.sin(angle); + m[5] = Math.cos(angle); + return m; +} + +function scaleMatrix(m, x, y, z) { + m = [...m]; + m[0] = m[0] * x; + m[5] = m[5] * y; + m[10] = m[10] * z; + return m; +} \ No newline at end of file diff --git a/frontend/_shared/math/vec2.js b/frontend/_shared/math/vec2.js new file mode 100644 index 0000000..cff38ce --- /dev/null +++ b/frontend/_shared/math/vec2.js @@ -0,0 +1,56 @@ +function vec2(x = 0, y = 0) { + return { x: x, y: y}; +} + +function addVec2(v1, v2) { + return { + x: v1.x + v2.x, + y: v1.y + v2.y + }; +} + +function subVec2(v1, v2) { + return { + x: v1.x - v2.x, + y: v1.y - v2.y + }; +} + +function scaleVec2(v, s) { + return { + x: v.x * s, + y: v.y * s + }; +} + +function dot2(v1, v2) { + return v1.x * v2.x + v1.y * v2.y; +} + +function length2(v) { + return Math.sqrt(v.x * v.x + v.y * v.y); +} + +function normalize2(v) { + const lLength = length2(v); + const lInverseLength = lLength === 0 ? 1.0 : 1.0 / length2(v); + return { x: v.x * lInverseLength, y: v.y * lInverseLength }; +} + +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 c8cc394..471f6f3 100644 --- a/frontend/index.css +++ b/frontend/index.css @@ -48,14 +48,23 @@ nav { font-size: 1rem; } -nav > a { +nav > ul { + list-style: none; + padding-left: 0px; +} + +nav > ul > li > ul { + padding-left: 1rem; +} + +nav a { color: #404040; text-decoration: none; display: inline-block; min-width: 196px; } -nav > a:hover { +nav a:hover { color: #1a1a1a; cursor: pointer; } diff --git a/frontend/index.html b/frontend/index.html index c62696d..22fbe2c 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -15,14 +15,10 @@
+

Introduction: Rigid Body Physics

-

Introduction: Rigid Body Physics

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 diff --git a/frontend/index.js b/frontend/index.js index f481bc6..d14ca95 100644 --- a/frontend/index.js +++ b/frontend/index.js @@ -2,8 +2,13 @@ (function() { function main() { - const lPathName = window.location.pathname.length > 1 && window.location.pathname.startsWith('/') ? window.location.pathname.substr(1) : window.location.pathname; - $('nav').find('a[href="' + lPathName + '"').addClass('nav-selected'); + + $.get('/navbar.html', function(pData) { + $('nav').html(pData); + + const lPathName = window.location.pathname.length > 1 && window.location.pathname.startsWith('/') ? window.location.pathname.substr(1) : window.location.pathname; + $('nav').find('a[href="' + lPathName + '"').addClass('nav-selected'); + }); } $(document).ready(main); diff --git a/frontend/navbar.html b/frontend/navbar.html new file mode 100644 index 0000000..c55439b --- /dev/null +++ b/frontend/navbar.html @@ -0,0 +1,19 @@ + +

\ No newline at end of file diff --git a/frontend/rigidbody.html b/frontend/rigidbody.html deleted file mode 100644 index f3616b5..0000000 --- a/frontend/rigidbody.html +++ /dev/null @@ -1,162 +0,0 @@ - - - - - - - - - Physics for Games - - - - - - - - - - - -
-

Physics for Games

-
- -
-
-

-

-

-
-
-
-

Part 1: Linear Forces

-

- 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. -

-

- Let's begin by recalling the relationships between acceleration, velocity, and position. -

-

- Knowing all this, you should be able to understand the following source code fairly easily; -

-                        
-function update(dtSeconds) {
-    // Add up the forces acting on the circle
-    const GRAVITY = 9.8;
-    const lGravityForce = vec2(0, -1.0 * (lCircle.mass * GRAVITY));
-    lCircle.force = addVec2(lCircle.force, lGravityForce);
-
-    // Figure out acceleration (a = F / m)
-    const lCurrentAcceleration = scaleVec2(lCircle.force, 1.0 / lCircle.mass);
-
-    // Calculate the new velocity: v = v0 + a * t
-    lCircle.velocity = addVec2(lCircle.velocity, scaleVec2(lCurrentAcceleration, dtSeconds));
-
-    // Update the position based on velocity: x = x0 + v * t
-    lCircle.position = addVec2(lCircle.position, scaleVec2(lCircle.velocity, dtSeconds));
-
-    // Update the model matrix accordingly
-    lCircle.model = translateMatrix(mat4(), lCircle.position.x, lCircle.position.y, 0);
-
-    // Reset the force vector for the next update
-    lCircle.force = vec2()
-}
-                        
-                    
-

-
- -
-
    -
  • Linear Force:N/A
  • -
  • Linear Acceleration:N/A
  • -
  • Linear Velocity:N/A
  • -
  • Linear Position:N/A
  • -
-
-
- - - -
- -
-
- - -
-
-
-
-

Part 2: Rotational Forces

-

- 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. -

-
- -
-
    -
  • Angular Force:N/A
  • -
  • Angular Acceleration:N/A
  • -
  • Angular Velocity:N/A
  • -
  • Angular Position:N/A
  • -
-
-
- - - -
-
- - - -
- -
-
- - -
-
-
-

Part 3: Collisions

-

- 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. -

-
- -
-
- - -
-
- -
-
- - \ No newline at end of file -- cgit v1.2.1