From ece2b67aa689aee0b881bac17a62c16e0469bc56 Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Sun, 21 Feb 2021 18:32:04 -0500 Subject: Proper support for favicons, rigid body intersections are no longer broken, palinko game --- frontend/_rigidbody/circle.js | 12 +- frontend/_rigidbody/pill.js | 0 frontend/_rigidbody/rigidbody_1.js | 211 ++++++++++++++--------------- frontend/_rigidbody/rigidbody_2.js | 260 ++++++++++++++++++------------------ frontend/_rigidbody/rigidbody_3.js | 158 ---------------------- frontend/_rigidbody/rigidbody_3a.js | 189 ++++++++++++++++++++++++++ frontend/_rigidbody/rigidbody_3b.js | 192 ++++++++++++++++++++++++++ 7 files changed, 629 insertions(+), 393 deletions(-) create mode 100644 frontend/_rigidbody/pill.js delete mode 100644 frontend/_rigidbody/rigidbody_3.js create mode 100644 frontend/_rigidbody/rigidbody_3a.js create mode 100644 frontend/_rigidbody/rigidbody_3b.js (limited to 'frontend/_rigidbody') diff --git a/frontend/_rigidbody/circle.js b/frontend/_rigidbody/circle.js index 35044e3..1cec7ce 100644 --- a/frontend/_rigidbody/circle.js +++ b/frontend/_rigidbody/circle.js @@ -1,3 +1,6 @@ +/// +/// + const BYTES_PER_FLOAT = 4; /** @@ -42,13 +45,14 @@ function circle(pGl, pRadius, pSegments, pColorList, pInitialPosition, pMass) { vertexCount: vertexCount, prevPos: vec2(), position: pInitialPosition || vec2(), + prevVelocity: vec2(), velocity: vec2(), force: vec2(), torque: 0, - mass: pMass || 1, + mass: pMass === undefined ? 1 : pMass, rotationVelocity: 0, rotationRadians: 0, - model: mat4(), + model: translateMatrix(mat4(), pInitialPosition ? pInitialPosition.x : 0, pInitialPosition ? pInitialPosition.y : 0, 0), radius: pRadius }; } @@ -106,4 +110,8 @@ function getIntersectionDataForCircles(pFirst, pSecond) { 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/pill.js b/frontend/_rigidbody/pill.js new file mode 100644 index 0000000..e69de29 diff --git a/frontend/_rigidbody/rigidbody_1.js b/frontend/_rigidbody/rigidbody_1.js index c5e4f0a..697822d 100644 --- a/frontend/_rigidbody/rigidbody_1.js +++ b/frontend/_rigidbody/rigidbody_1.js @@ -5,110 +5,113 @@ /// /// -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; +(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); } - - if (lYValue.length === 0) { - lYValue = 5000; + + 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))); } - - 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); - }); + + 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); + } - - lProgramContext.playButton.on('click', run); - $('#rigidbody_1_force_submit_button').submit(false); - -} - -$(document).ready(main); \ No newline at end of file + + $(document).ready(main); +})() \ No newline at end of file diff --git a/frontend/_rigidbody/rigidbody_2.js b/frontend/_rigidbody/rigidbody_2.js index 1c6b79f..c878c1a 100644 --- a/frontend/_rigidbody/rigidbody_2.js +++ b/frontend/_rigidbody/rigidbody_2.js @@ -4,140 +4,142 @@ /// /// -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 +(function() { + function main() { + // Define Constants + const CIRCLE_RADIUS = 16; + const GRAVITY = 9.8; - // 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; + // 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); } - - if (lForceVectorY.length === 0) { - lForceVectorY = 5000; + + 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); } - - // 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; + + 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); } - - if (lPositionGroupY.length === 0) { - lPositionGroupY = -Math.sqrt(2) / 2; + + function reset() { + lExitRequestFunc(); + lProgramContext.reset(); + $('#rigidbody_2_force_submit_button').unbind('submit').submit(false); } - - const lForceVector = vec2(Number(lForceVectorX), Number(lForceVectorY)); - const lPointOfApplication = scaleVec2(normalize2(vec2(Number(lPositionGroupX), Number(lPositionGroupY))), lCircle.radius); - - applyForce(lForceVector, lPointOfApplication); + + 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); } - lProgramContext.playButton.on('click', run); - $('#rigidbody_2_force_submit_button').submit(false); -} - -$(document).ready(main); \ No newline at end of file + $(document).ready(main); +})() \ No newline at end of file diff --git a/frontend/_rigidbody/rigidbody_3.js b/frontend/_rigidbody/rigidbody_3.js deleted file mode 100644 index 0316268..0000000 --- a/frontend/_rigidbody/rigidbody_3.js +++ /dev/null @@ -1,158 +0,0 @@ -/// -/// -/// -/// -/// - -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/rigidbody_3a.js b/frontend/_rigidbody/rigidbody_3a.js new file mode 100644 index 0000000..74d309b --- /dev/null +++ b/frontend/_rigidbody/rigidbody_3a.js @@ -0,0 +1,189 @@ +/// +/// +/// +/// +/// + +(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 new file mode 100644 index 0000000..df81582 --- /dev/null +++ b/frontend/_rigidbody/rigidbody_3b.js @@ -0,0 +1,192 @@ +/// +/// +/// +/// +/// +/// + +(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 -- cgit v1.2.1