summaryrefslogtreecommitdiff
path: root/frontend/_rigidbody
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/_rigidbody')
-rw-r--r--frontend/_rigidbody/circle.js53
-rw-r--r--frontend/_rigidbody/program_common.js15
-rw-r--r--frontend/_rigidbody/rigidbody_2.js18
-rw-r--r--frontend/_rigidbody/rigidbody_3.js158
-rw-r--r--frontend/_rigidbody/vec2.js14
5 files changed, 245 insertions, 13 deletions
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