diff options
Diffstat (limited to 'frontend/_rigidbody')
-rw-r--r-- | frontend/_rigidbody/circle.js | 58 | ||||
-rw-r--r-- | frontend/_rigidbody/mat4.js | 43 | ||||
-rw-r--r-- | frontend/_rigidbody/program_common.js | 72 | ||||
-rw-r--r-- | frontend/_rigidbody/rigidbody_1.js | 106 | ||||
-rw-r--r-- | frontend/_rigidbody/rigidbody_2.js | 78 | ||||
-rw-r--r-- | frontend/_rigidbody/shader.js | 60 | ||||
-rw-r--r-- | frontend/_rigidbody/shaders/orthographic.frag | 5 | ||||
-rw-r--r-- | frontend/_rigidbody/shaders/orthographic.vert | 13 | ||||
-rw-r--r-- | frontend/_rigidbody/vec2.js | 41 |
9 files changed, 476 insertions, 0 deletions
diff --git a/frontend/_rigidbody/circle.js b/frontend/_rigidbody/circle.js new file mode 100644 index 0000000..c044597 --- /dev/null +++ b/frontend/_rigidbody/circle.js @@ -0,0 +1,58 @@ +const BYTES_PER_FLOAT = 4; + +function circle(pGl, pRadius, pSegments, pColorList, pInitialPosition) { + 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, + position: pInitialPosition || vec2(), + velocity: vec2(), + force: vec2(), + mass: 1, + rotationRadians: 0, + model: mat4(), + radius: pRadius + }; +} + +function renderCircle(pGl, pProgramInfo, pCircle) { + 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; +}
\ No newline at end of file diff --git a/frontend/_rigidbody/mat4.js b/frontend/_rigidbody/mat4.js new file mode 100644 index 0000000..6ab29e2 --- /dev/null +++ b/frontend/_rigidbody/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/_rigidbody/program_common.js b/frontend/_rigidbody/program_common.js new file mode 100644 index 0000000..9d097e2 --- /dev/null +++ b/frontend/_rigidbody/program_common.js @@ -0,0 +1,72 @@ +/// <reference path="shader.js" /> +/// <reference path="mat4.js" /> + +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($('<div>').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, + lStartTimeSeconds = undefined, + lIsRunning = true; + + function update(pTimeStamp) { + if (!lIsRunning) { + if (pOnExit) { + pOnExit(); + } + return; + } + + pTimeStamp = pTimeStamp / 1000.0; // Convert to seconds + + // Time calculation + if (lStartTimeSeconds === undefined) { + lStartTimeSeconds = pTimeStamp; + lDeltaTimeSeconds = 0; + } else { + lDeltaTimeSeconds = lStartTimeSeconds - pTimeStamp; + lStartTimeSeconds = pTimeStamp; + } + + pFunction(lDeltaTimeSeconds); + 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 new file mode 100644 index 0000000..5021162 --- /dev/null +++ b/frontend/_rigidbody/rigidbody_1.js @@ -0,0 +1,106 @@ +/// <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" /> +/// <reference path="program_common.js" /> + +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(); + + const lXValue = $(this).find('.vec2_x_input').val(), + lYValue = $(this).find('.vec2_y_input').val(); + + 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 new file mode 100644 index 0000000..b41fa92 --- /dev/null +++ b/frontend/_rigidbody/rigidbody_2.js @@ -0,0 +1,78 @@ +/// <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; + + // 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 previously + const lGravityForce = vec2(0, -1.0 * (lCircle.mass * GRAVITY)); + lCircle.force = addVec2(lCircle.force, lGravityForce); + 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.rotationRadians += Math.PI * pDeltaTimeSeconds; + lCircle.model = rotateMatrix2d(translateMatrix(mat4(), lCircle.position.x, lCircle.position.y, 0), lCircle.rotationRadians); + lCircle.force = vec2(); + + // 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); + } + + 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(); + } + + 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/_rigidbody/shader.js b/frontend/_rigidbody/shader.js new file mode 100644 index 0000000..bf08638 --- /dev/null +++ b/frontend/_rigidbody/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('/_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 new file mode 100644 index 0000000..84b6b2e --- /dev/null +++ b/frontend/_rigidbody/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/_rigidbody/shaders/orthographic.vert b/frontend/_rigidbody/shaders/orthographic.vert new file mode 100644 index 0000000..0356f9c --- /dev/null +++ b/frontend/_rigidbody/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/_rigidbody/vec2.js b/frontend/_rigidbody/vec2.js new file mode 100644 index 0000000..3b24371 --- /dev/null +++ b/frontend/_rigidbody/vec2.js @@ -0,0 +1,41 @@ +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)})`; +}
\ No newline at end of file |