From d1b528b01796601c2bfea7b1a9813e5907e1c728 Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Sat, 27 Feb 2021 17:32:32 -0500 Subject: Close to being done on line-circle collisions, but it appears we're running into a rotation problem. Going to work on square-line collisions in the meantime --- frontend/2d/_collisions/circle_line.html | 51 ++++++++ frontend/2d/_collisions/circle_line.js | 178 ++++++++++++++++++++++++++++ frontend/2d/_collisions/collisions_1.js | 125 ------------------- frontend/2d/_collisions/part_1.html | 63 ---------- frontend/2d/_collisions/rectangle_line.html | 52 ++++++++ frontend/2d/_collisions/rectangle_line.js | 105 ++++++++++++++++ frontend/2d/_rigidbody/rigidbody_2.js | 2 +- frontend/2d/_rigidbody/rigidbody_3a.js | 10 +- frontend/2d/_rigidbody/rigidbody_3b.js | 8 +- frontend/_shared/2d/shader.js | 18 ++- frontend/_shared/math/circle.js | 2 +- frontend/_shared/math/collision.js | 25 ++-- frontend/_shared/math/line2.js | 12 +- frontend/_shared/math/point2.js | 16 +++ frontend/_shared/math/rectangle.js | 32 +++++ frontend/_shared/math/rigidbody2.js | 57 +++++++++ frontend/index.css | 11 ++ frontend/index.js | 2 +- frontend/navbar.html | 9 +- 19 files changed, 559 insertions(+), 219 deletions(-) create mode 100644 frontend/2d/_collisions/circle_line.html create mode 100644 frontend/2d/_collisions/circle_line.js delete mode 100644 frontend/2d/_collisions/collisions_1.js delete mode 100644 frontend/2d/_collisions/part_1.html create mode 100644 frontend/2d/_collisions/rectangle_line.html create mode 100644 frontend/2d/_collisions/rectangle_line.js create mode 100644 frontend/_shared/math/point2.js create mode 100644 frontend/_shared/math/rectangle.js create mode 100644 frontend/_shared/math/rigidbody2.js (limited to 'frontend') diff --git a/frontend/2d/_collisions/circle_line.html b/frontend/2d/_collisions/circle_line.html new file mode 100644 index 0000000..038c759 --- /dev/null +++ b/frontend/2d/_collisions/circle_line.html @@ -0,0 +1,51 @@ + + + + + + + + + + Physics for Games + + + + + + + + + + + + +
+

Physics for Games

+
+
+ +
+

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

+
+ +
+ +
+ + +
+
+
+
+ + \ No newline at end of file diff --git a/frontend/2d/_collisions/circle_line.js b/frontend/2d/_collisions/circle_line.js new file mode 100644 index 0000000..e5898f0 --- /dev/null +++ b/frontend/2d/_collisions/circle_line.js @@ -0,0 +1,178 @@ +/// +/// +/// +/// +/// +/// +/// +/// + +(function() { + // Define Constants + const CIRCLE_RADIUS = 16; + const GRAVITY = 20.0; + const COF_OF_RESTITUITION = 0.75; + const TORQUE_MULTIPLIER = 100.0; // TODO: This may be unncessary + + var programContext, + circleObject, + lineObjectList, + programInfo, + exitRequestFunc; + + function main() { + programContext = getContext('#circle_line_collision'); + + if (programContext.gl === null) { + console.error('Unable to initialize WebGL. Your browser or machine may not support it.'); + return; + } + + programContext.gl.clearColor(0.1, 0.15, 0.2, 1.0); + programContext.gl.clear(programContext.gl.COLOR_BUFFER_BIT); + programContext.playButton.on('click', run); + } + + function run() { + console.log('Running Circle-Line Collisions'); + programContext.load().then(function(pProgramInfo) { + programInfo = pProgramInfo; + circleObject = circle(programContext.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(programContext.width * (3.0 / 4.0), programContext.height / 2.0 + 100)); + + circleObject.velocity = vec2(0, -50); + + lineObjectList = []; + + lineObjectList.push(line2({ x: 100, y: 100 }, { x: programContext.width - 100, y: 200 }, programContext.gl, + { x: 1, y: 0, z: 0, w: 1 }, 2.0)); + + lineObjectList.push(line2({ x: 100, y: 200 }, { x: programContext.width - 100, y: 100 }, programContext.gl, + { x: 1, y: 1, z: 0, w: 1 }, 2.0)); + + lineObjectList.push(line2({ x: 100, y: 0 }, { x: 100, y: programContext.height }, programContext.gl, + { x: 0, y: 1, z: 0, w: 1 }, 2.0)); + + lineObjectList.push(line2({ x: programContext.width - 100, y: 0 }, { x: programContext.width - 100, y: programContext.height }, programContext.gl, + { x: 0, y: 1, z: 0, w: 1 }, 2.0)); + + + exitRequestFunc = requestUpdateLoop(update, cleanup); + programContext.stopButton.on('click', reset); + }); + } + + function update(pDeltaTimeSeconds) { + pDeltaTimeSeconds = pDeltaTimeSeconds; + updateCircle(circleObject, pDeltaTimeSeconds); + collision(pDeltaTimeSeconds); + render(); + } + + function updateCircle(pCircle, pDeltaTimeSeconds) { + 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(); + + const lMomentOfInertia = getCircleMomentOfInertia(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 collision(pDeltaTimeSeconds) { + lineObjectList.forEach(function(lineObject) { + if (!lineCircleCollision2(circleObject, lineObject)) { + return; + } + + var lSubdividedDeltaTime = pDeltaTimeSeconds, + lSubdividedCircle = undefined; + + do { + lSubdividedCircle = JSON.parse(JSON.stringify(circleObject)); + lSubdividedCircle.position = {...circleObject.prevPos}; + lSubdividedCircle.velocity = {...circleObject.prevVelocity}; + lSubdividedDeltaTime = lSubdividedDeltaTime / 2.0; + updateCircle(lSubdividedCircle, lSubdividedDeltaTime); + if (lSubdividedDeltaTime === 0) { + console.error('This should NOT be happening'); + break; + } + } while (lineCircleCollision2(lSubdividedCircle, lineObject)) + + const lIntersectionResult = getLineCircleCollison2Data(lSubdividedCircle, lineObject), + 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 / circleObject.mass))); + const lRotationalDenomPart = (Math.pow(dot2(lFirstPerp, lCollisionNormal), 2) / getCircleMomentOfInertia(circleObject)); + + const lImpulseMagnitude = lNumerator / (lLinearDenomPart + lRotationalDenomPart); + + circleObject.position = lSubdividedCircle.position; + circleObject.velocity = addVec2(lSubdividedCircle.velocity, scaleVec2(lCollisionNormal, lImpulseMagnitude / circleObject.mass)); + circleObject.rotationVelocity = lSubdividedCircle.rotationVelocity + dot2(lFirstPerp, scaleVec2(lCollisionNormal, lImpulseMagnitude)); + + updateCircle(circleObject, pDeltaTimeSeconds - lSubdividedDeltaTime); + + return; + }) + } + + 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 render() { + programContext.gl.clearColor(0.1, 0.15, 0.2, 1.0); + programContext.gl.clearDepth(1.0); + programContext.gl.enable(programContext.gl.DEPTH_TEST); + programContext.gl.depthFunc(programContext.gl.LEQUAL); + programContext.gl.clear(programContext.gl.COLOR_BUFFER_BIT | programContext.gl.DEPTH_BUFFER_BIT); + programContext.gl.useProgram(programInfo.program); + programContext.gl.uniformMatrix4fv(programInfo.uniformLocations.projection, false, programContext.perspective); + + renderCircle(programContext.gl, programInfo, circleObject); + lineObjectList.forEach(function(lineObject) { + renderLine2(programContext.gl, programInfo, lineObject); + }); + } + + function cleanup() { + programContext.gl.deleteBuffer(circleObject.buffer); + lineObjectList.forEach(function(lineObject) { + programContext.gl.deleteBuffer(lineObject.buffer); + }); + programContext.gl.deleteProgram(programInfo.program); + programContext.gl.clearColor(0.1, 0.15, 0.2, 1.0); + programContext.gl.clear(programContext.gl.COLOR_BUFFER_BIT); + } + + function reset() { + exitRequestFunc(); + programContext.reset(); + } + + $(document).ready(main); +})() \ No newline at end of file diff --git a/frontend/2d/_collisions/collisions_1.js b/frontend/2d/_collisions/collisions_1.js deleted file mode 100644 index ac325c2..0000000 --- a/frontend/2d/_collisions/collisions_1.js +++ /dev/null @@ -1,125 +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('#collision_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 Circle-Line Collisions'); - lProgramContext.load().then(function(pProgramInfo) { - const mCircle = 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)); - - const mline = line2({ x: 100, y: 100 }, { x: lProgramContext.width - 100, y: 200 }, lProgramContext.gl, - { x: 1, y: 0, z: 0, w: 1 }, 2.0); - - /** - * 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; - updateCircle(mCircle, pDeltaTimeSeconds); - collision(pDeltaTimeSeconds); - render(); - } - - function collision(pDeltaTimeSeconds) { - lineCircleCollision2(mCircle, mline); - return false; - } - - 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, mCircle); - renderLine2(lProgramContext.gl, pProgramInfo, mline); - } - - 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(mCircle.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(); - } - - const lExitRequestFunc = requestUpdateLoop(update, cleanup); - lProgramContext.stopButton.on('click', reset); - }); - } - - lProgramContext.playButton.on('click', run); - } - - $(document).ready(main); -})() \ No newline at end of file diff --git a/frontend/2d/_collisions/part_1.html b/frontend/2d/_collisions/part_1.html deleted file mode 100644 index 06a4b3d..0000000 --- a/frontend/2d/_collisions/part_1.html +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - - - - - 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/2d/_collisions/rectangle_line.html b/frontend/2d/_collisions/rectangle_line.html new file mode 100644 index 0000000..5db958c --- /dev/null +++ b/frontend/2d/_collisions/rectangle_line.html @@ -0,0 +1,52 @@ + + + + + + + + + + Physics for Games + + + + + + + + + + + + + +
+

Physics for Games

+
+
+ +
+

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

+
+ +
+ +
+ + +
+
+
+
+ + \ No newline at end of file diff --git a/frontend/2d/_collisions/rectangle_line.js b/frontend/2d/_collisions/rectangle_line.js new file mode 100644 index 0000000..ac73f71 --- /dev/null +++ b/frontend/2d/_collisions/rectangle_line.js @@ -0,0 +1,105 @@ +/// +/// +/// +/// +/// +/// +/// +/// + +(function() { + var programContext, + rectangleObject, + lineObjectList, + programInfo, + exitRequestFunc; + + function main() { + programContext = getContext('#rectangle_line_collision'); + + if (programContext.gl === null) { + console.error('Unable to initialize WebGL. Your browser or machine may not support it.'); + return; + } + + programContext.gl.clearColor(0.1, 0.15, 0.2, 1.0); + programContext.gl.clear(programContext.gl.COLOR_BUFFER_BIT); + programContext.playButton.on('click', run); + } + + function run() { + console.log('Running Rectangle-Line Collisions'); + programContext.load().then(function(pProgramInfo) { + programInfo = pProgramInfo; + + rectangleObject = rectangle(programContext.gl, { + width: 50, + height: 25, + color: { x: 0.3, y: 0.5, z: 0.1, w: 0.7 }, + position: vec2(programContext.width / 2.0 - 100, programContext.height / 2.0 + 100) + }); + rectangleObject.velocity = vec2(0, -50); + + lineObjectList = []; + + lineObjectList.push(line2({ x: 100, y: 100 }, { x: programContext.width - 100, y: 200 }, programContext.gl, + { x: 1, y: 0, z: 0, w: 1 }, 2.0)); + + lineObjectList.push(line2({ x: 100, y: 200 }, { x: programContext.width - 100, y: 100 }, programContext.gl, + { x: 1, y: 1, z: 0, w: 1 }, 2.0)); + + lineObjectList.push(line2({ x: 100, y: 0 }, { x: 100, y: programContext.height }, programContext.gl, + { x: 0, y: 1, z: 0, w: 1 }, 2.0)); + + lineObjectList.push(line2({ x: programContext.width - 100, y: 0 }, { x: programContext.width - 100, y: programContext.height }, programContext.gl, + { x: 0, y: 1, z: 0, w: 1 }, 2.0)); + + + exitRequestFunc = requestUpdateLoop(update, cleanup); + programContext.stopButton.on('click', reset); + }); + } + + function update(pDeltaTimeSeconds) { + pDeltaTimeSeconds = pDeltaTimeSeconds; + updateRigidBody2(rectangleObject, pDeltaTimeSeconds); + collision(pDeltaTimeSeconds); + render(); + } + + function collision(pDeltaTimeSeconds) { + + } + + function render() { + programContext.gl.clearColor(0.1, 0.15, 0.2, 1.0); + programContext.gl.clearDepth(1.0); + programContext.gl.enable(programContext.gl.DEPTH_TEST); + programContext.gl.depthFunc(programContext.gl.LEQUAL); + programContext.gl.clear(programContext.gl.COLOR_BUFFER_BIT | programContext.gl.DEPTH_BUFFER_BIT); + programContext.gl.useProgram(programInfo.program); + programContext.gl.uniformMatrix4fv(programInfo.uniformLocations.projection, false, programContext.perspective); + + programInfo.renderShape(rectangleObject); + lineObjectList.forEach(function(lineObject) { + renderLine2(programContext.gl, programInfo, lineObject); + }); + } + + function cleanup() { + programContext.gl.deleteBuffer(rectangleObject.buffer); + lineObjectList.forEach(function(lineObject) { + programContext.gl.deleteBuffer(lineObject.buffer); + }); + programContext.gl.deleteProgram(programInfo.program); + programContext.gl.clearColor(0.1, 0.15, 0.2, 1.0); + programContext.gl.clear(programContext.gl.COLOR_BUFFER_BIT); + } + + function reset() { + exitRequestFunc(); + programContext.reset(); + } + + $(document).ready(main); +})() \ No newline at end of file diff --git a/frontend/2d/_rigidbody/rigidbody_2.js b/frontend/2d/_rigidbody/rigidbody_2.js index 4632b11..76d8047 100644 --- a/frontend/2d/_rigidbody/rigidbody_2.js +++ b/frontend/2d/_rigidbody/rigidbody_2.js @@ -43,7 +43,7 @@ // Angular code starts here // Retrieve the moment of inertia for our shape (Ours is a circle by default) - const lMomentOfInertia = getMomentOfInertia(lCircle); + const lMomentOfInertia = getCircleMomentOfInertia(lCircle); // Calculate the angular acceperation (omega = T / I) const lAngularAcceleration = lCircle.torque / lMomentOfInertia; diff --git a/frontend/2d/_rigidbody/rigidbody_3a.js b/frontend/2d/_rigidbody/rigidbody_3a.js index d38f1da..ef9577b 100644 --- a/frontend/2d/_rigidbody/rigidbody_3a.js +++ b/frontend/2d/_rigidbody/rigidbody_3a.js @@ -62,7 +62,7 @@ pCircle.force = vec2(); // Same physics updates from part 2 - const lMomentOfInertia = getMomentOfInertia(pCircle); + const lMomentOfInertia = getCircleMomentOfInertia(pCircle); const lAngularAcceleration = pCircle.torque / lMomentOfInertia; pCircle.rotationVelocity += lAngularAcceleration * pDeltaTimeSeconds; pCircle.rotationRadians += pCircle.rotationVelocity * pDeltaTimeSeconds; @@ -118,8 +118,8 @@ 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 lRotationalDenomPart = (Math.pow(dot2(lFirstPerp, lCollisionNormal), 2) / getCircleMomentOfInertia(horizontalCircle)) + + (Math.pow(dot2(lSecondPerp, lCollisionNormal), 2) / getCircleMomentOfInertia(verticalCircle)) const lImpulseMagnitude = lNumerator / (lLinearDenomPart + lRotationalDenomPart); @@ -129,8 +129,8 @@ 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); + horizontalCircle.rotationVelocity = horizontalCircle.rotationVelocity + dot2(lFirstPerp, scaleVec2(lCollisionNormal, lImpulseMagnitude)) / getCircleMomentOfInertia(horizontalCircle); + verticalCircle.rotationVelocity = verticalCircle.rotationVelocity - dot2(lSecondPerp, scaleVec2(lCollisionNormal, lImpulseMagnitude)) / getCircleMomentOfInertia(verticalCircle); updateCircle(horizontalCircle, pDeltaTimeSeconds - lSubdividedDeltaTime); updateCircle(verticalCircle, pDeltaTimeSeconds - lSubdividedDeltaTime); diff --git a/frontend/2d/_rigidbody/rigidbody_3b.js b/frontend/2d/_rigidbody/rigidbody_3b.js index 803161a..ddb4c70 100644 --- a/frontend/2d/_rigidbody/rigidbody_3b.js +++ b/frontend/2d/_rigidbody/rigidbody_3b.js @@ -62,7 +62,7 @@ // Same physics updates from part 2 - const lMomentOfInertia = getMomentOfInertia(pCircle); + const lMomentOfInertia = getCircleMomentOfInertia(pCircle); const lAngularAcceleration = pCircle.torque / lMomentOfInertia; pCircle.rotationVelocity += lAngularAcceleration * pDeltaTimeSeconds; pCircle.rotationRadians += pCircle.rotationVelocity * pDeltaTimeSeconds; @@ -119,14 +119,14 @@ 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 lRotationalDenomPart = (Math.pow(dot2(lFirstPerp, lCollisionNormal), 2) / getCircleMomentOfInertia(lPeg)) + + (Math.pow(dot2(lSecondPerp, lCollisionNormal), 2) / getCircleMomentOfInertia(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); + lBall.rotationVelocity = lBall.rotationVelocity - dot2(lSecondPerp, scaleVec2(lCollisionNormal, lImpulseMagnitude)) / getCircleMomentOfInertia(lBall); // Now we update in our new direction with the remaining time that we have left. updateCircle(lBall, pDeltaTimeSeconds - lSubdividedDeltaTime); diff --git a/frontend/_shared/2d/shader.js b/frontend/_shared/2d/shader.js index 4deddb5..7e85a9e 100644 --- a/frontend/_shared/2d/shader.js +++ b/frontend/_shared/2d/shader.js @@ -28,7 +28,7 @@ function loadOrthographicShader(pGl) { return null; } - return { + var lRetval = { program: lShaderProgram, attributeLocations: { position: pGl.getAttribLocation(lShaderProgram, 'position'), @@ -38,7 +38,23 @@ function loadOrthographicShader(pGl) { projection: pGl.getUniformLocation(lShaderProgram, 'projection'), model: pGl.getUniformLocation(lShaderProgram, 'model') } + }; + + lRetval.renderShape = function(pShape) { + pGl.uniformMatrix4fv(lRetval.uniformLocations.model, false, pShape.model); + pGl.bindBuffer(pGl.ARRAY_BUFFER, pShape.buffer); + { + pGl.enableVertexAttribArray(lRetval.attributeLocations.position); + pGl.vertexAttribPointer(lRetval.attributeLocations.position, 2, pGl.FLOAT, false, BYTES_PER_FLOAT * 6, 0); + + pGl.enableVertexAttribArray(lRetval.attributeLocations.color); + pGl.vertexAttribPointer(lRetval.attributeLocations.color, 4, pGl.FLOAT, false, BYTES_PER_FLOAT * 6, BYTES_PER_FLOAT * 2); + } + + pGl.drawArrays(pGl.TRIANGLES, 0, pShape.vertexCount); } + + return lRetval; } return fetch('/_shared/2d/shaders/orthographic.vert').then(function(pResponse) { diff --git a/frontend/_shared/math/circle.js b/frontend/_shared/math/circle.js index 340d7dc..ba33021 100644 --- a/frontend/_shared/math/circle.js +++ b/frontend/_shared/math/circle.js @@ -71,7 +71,7 @@ function renderCircle(pGl, pProgramInfo, pCircle) { pGl.drawArrays(pGl.TRIANGLE_STRIP, 0, pCircle.vertexCount); } -function getMomentOfInertia(pCircle) { +function getCircleMomentOfInertia(pCircle) { return (Math.PI * Math.pow(pCircle.radius, 4)) / 4; } diff --git a/frontend/_shared/math/collision.js b/frontend/_shared/math/collision.js index 74ec5d8..8e4be7d 100644 --- a/frontend/_shared/math/collision.js +++ b/frontend/_shared/math/collision.js @@ -2,22 +2,25 @@ /// /// /// +/// /** * - * @param {circle} pCricle + * @param {circle} pCircle * @param {line2} pLine */ -function lineCircleCollision2(pCricle, pLine) { - // We have a triangle formed by circle's position P and the two points of the line, A and B. - var lSlope = (pCricle.position.y - pLine.start.y) / (pCricle.position.x - pLine.start.x), - lAngle = Math.atan(lSlope), - lAngleDiff = lAngle - pLine.angle, - lPositionToStart = subVec2(pCricle.position, pLine.start), - lPosToStartLength = length2(lPositionToStart), - lHeight = lPosToStartLength * Math.sin(lAngleDiff); +function lineCircleCollision2(pCircle, pLine) { + return distanceFromPoint2ToLine2(pCircle.position, pLine) <= pCircle.radius; +} - if (Math.abs(lHeight - pCricle.radius) < pCricle.radius) { - console.log('Intersection'); +function getLineCircleCollison2Data(pCircle, pLine) { + const lCollisionNormal = pLine.normal, + lCollisionPoint = addVec2(pCircle.position, scaleVec2(negate2(lCollisionNormal), pCircle.radius)); + + return { + relativeVelocity: subVec2(pCircle.velocity, pLine.velocity), + collisionNormal: lCollisionNormal, + firstPointOfApplication: subVec2(lCollisionPoint, pCircle.position), + secondPointOfApplication: subVec2(lCollisionPoint, pLine.start) } } \ No newline at end of file diff --git a/frontend/_shared/math/line2.js b/frontend/_shared/math/line2.js index d8ab096..a9e72be 100644 --- a/frontend/_shared/math/line2.js +++ b/frontend/_shared/math/line2.js @@ -7,7 +7,7 @@ * @param {vec2} pEnd * @param {WebGLRenderingContext} pGl */ -function line2(pStart, pEnd, pGl, pColor, pThickness) { +function line2(pStart, pEnd, pGl, pColor, pMass) { const lDiffVector = subVec2(pEnd, pStart); const lBuffer = pGl.createBuffer(); @@ -28,12 +28,18 @@ function line2(pStart, pEnd, pGl, pColor, pThickness) { start: pStart, end: pEnd, slope: lSlope, - angle: Math.atan(lSlope), + normal: normalize2(getPerp2(lDiffVector)), length: length2(lDiffVector), - direction: normalize2(lDiffVector) + mass: pMass, + direction: normalize2(lDiffVector), + velocity: vec2(0, 0) }; } +function getLine2MomentOfInertia(pLine) { + return (1.0 / 12.0) * pLine.mass * Math.pow(pLine.length, 2); +} + function renderLine2(pGl, pProgramInfo, pLine) { pGl.uniformMatrix4fv(pProgramInfo.uniformLocations.model, false, mat4()); // Model on a line is always default matrix pGl.bindBuffer(pGl.ARRAY_BUFFER, pLine.buffer); diff --git a/frontend/_shared/math/point2.js b/frontend/_shared/math/point2.js new file mode 100644 index 0000000..eeacd91 --- /dev/null +++ b/frontend/_shared/math/point2.js @@ -0,0 +1,16 @@ +function point2(x = 0, y = 0) { + return { x: x, y: y }; +} + +function distanceFromPoint2ToLine2(pPoint, pLine) { + // Check out this wikapedia article for more information: + // https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line + var x0 = pPoint.x, + y0 = pPoint.y, + x1 = pLine.start.x, + y1 = pLine.start.y, + x2 = pLine.end.x, + y2 = pLine.end.y; + + return Math.abs((x2 - x1) * (y1 - y0) - (x1 - x0) * (y2 - y1)) / Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)); +} \ No newline at end of file diff --git a/frontend/_shared/math/rectangle.js b/frontend/_shared/math/rectangle.js new file mode 100644 index 0000000..c24fa12 --- /dev/null +++ b/frontend/_shared/math/rectangle.js @@ -0,0 +1,32 @@ +/// + +function rectangle(pGl, pData) { + var lBuffer = pGl.createBuffer(), + lColor = pData.color || { x: 1, y: 0, z: 0, w: 1 }; + + pGl.bindBuffer(pGl.ARRAY_BUFFER, lBuffer); + + var lBufferedData = [ + 0, 0, lColor.x, lColor.y, lColor.z, lColor.w, + 0, pData.height, lColor.x, lColor.y, lColor.z, lColor.w, + pData.width, pData.height, lColor.x, lColor.y, lColor.z, lColor.w, + pData.width, pData.height, lColor.x, lColor.y, lColor.z, lColor.w, + pData.width, 0, lColor.x, lColor.y, lColor.z, lColor.w, + 0, 0, lColor.x, lColor.y, lColor.z, lColor.w + ]; + + pGl.bufferData(pGl.ARRAY_BUFFER, new Float32Array(lBufferedData), pGl.STATIC_DRAW) + pGl.bindBuffer(pGl.ARRAY_BUFFER, undefined); + + pData.getMomentOfInertia = function() { + return Math.pow(pData.width * pData.height, 3.0) / 12.0; + }; + + return makeRigidBody2({ + vertexCount: 6, + buffer: lBuffer, + width: pData.width, + height: pData.height, + model: translateMatrix(mat4(), pData.position ? pData.position.x : 0, pData.position ? pData.position.y : 0, 0), + }, pData); +} \ No newline at end of file diff --git a/frontend/_shared/math/rigidbody2.js b/frontend/_shared/math/rigidbody2.js new file mode 100644 index 0000000..0b18d0c --- /dev/null +++ b/frontend/_shared/math/rigidbody2.js @@ -0,0 +1,57 @@ +/// +/// +/// +/// +/// + +const GRAVITY = 9.8; +const BYTES_PER_FLOAT = 4; + +/** + * + * @param {Shape} pObject a 2D shape like a circle, rectangle, or line + * @param {Object} pData initial data for the rigidboy + */ +function makeRigidBody2(pObject, pData) { + pObject.velocity = vec2(); + pObject.prevVelocity = pObject.velocity; + pObject.position = pData.position || vec2(); + pObject.prevPosition = pObject.position; + pObject.force = vec2(); + pObject.rotationVelocity = 0; + pObject.rotationRadians = 0; + pObject.torque = 0; + pObject.mass = pData.mass !== undefined ? pData.mass : 1.0; + pObject.getMomentOfInertia = pData.getMomentOfInertia; + + return pObject; +} + +function updateRigidBody2(pRigidbody, pDeltaTimeSeconds) { + applyForceRigidbody2(pRigidbody, vec2(0, -1.0 * (pRigidbody.mass * GRAVITY))); + const lCurrentAcceleration = scaleVec2(pRigidbody.force, 1.0 / pRigidbody.mass); + pRigidbody.prevVelocity = pRigidbody.velocity; + pRigidbody.velocity = addVec2(pRigidbody.velocity, scaleVec2(lCurrentAcceleration, pDeltaTimeSeconds)); + pRigidbody.prevPos = { ...pRigidbody.position }; + pRigidbody.position = addVec2(pRigidbody.position, scaleVec2(pRigidbody.velocity, pDeltaTimeSeconds)); + pRigidbody.force = vec2(); + + const lMomentOfInertia = pRigidbody.getMomentOfInertia(pRigidbody); + const lAngularAcceleration = pRigidbody.torque / lMomentOfInertia; + pRigidbody.rotationVelocity += lAngularAcceleration * pDeltaTimeSeconds; + pRigidbody.rotationRadians += pRigidbody.rotationVelocity * pDeltaTimeSeconds; + pRigidbody.torque = 0; + + pRigidbody.model = rotateMatrix2d(translateMatrix(mat4(), pRigidbody.position.x, pRigidbody.position.y, 0), pRigidbody.rotationRadians); +} + +function applyForceRigidbody2(pRigidbody, pForceVector, pPointOfApplication) { + if (pPointOfApplication !== undefined) { + const lOriginToPointOfApp = subVec2(vec2(), pPointOfApplication), + lPerpVec = vec2(-lOriginToPointOfApp.y, lOriginToPointOfApp.x); + + pRigidbody.torque += TORQUE_MULTIPLIER * dot2(lPerpVec, pForceVector); + } + + pRigidbody.force = addVec2(pRigidbody.force, pForceVector); +} diff --git a/frontend/index.css b/frontend/index.css index 729768c..00ea6e9 100644 --- a/frontend/index.css +++ b/frontend/index.css @@ -77,6 +77,7 @@ nav { } .outer-tree > li > span > i { + transform: rotate(0deg); transition: 100ms ease-in-out transform; } @@ -85,6 +86,10 @@ nav { transform: rotate(180deg); } +.outer-tree li > span > span { + padding-left: 1rem;; +} + .inner-tree { padding-left: 1rem; display: none; @@ -186,6 +191,7 @@ article .opengl_canvas_container { display: flex; flex-direction: row; justify-content: center; + border-radius: 3px; } article .opengl_canvas_container .play_button { @@ -206,6 +212,11 @@ article .opengl_canvas_container .play_button { justify-content: center; } +.opengl_canvas_container canvas { + border-radius: 3px 0px 0px 3px; + border: 1px solid black; +} + article .opengl_canvas_container .play_button:hover { cursor: pointer; } diff --git a/frontend/index.js b/frontend/index.js index b943463..d0f27f3 100644 --- a/frontend/index.js +++ b/frontend/index.js @@ -26,7 +26,7 @@ } if (lSplitPath[1] === '2d') { - $('nav > ul > li:first-child').addClass('expanded'); + $('nav > ul > li:nth-child(2)').addClass('expanded'); $('nav > ul > li:last-child').removeClass('expanded'); } }); diff --git a/frontend/navbar.html b/frontend/navbar.html index 73c4fd3..01cdc72 100644 --- a/frontend/navbar.html +++ b/frontend/navbar.html @@ -1,18 +1,19 @@ \ No newline at end of file -- cgit v1.2.1