summaryrefslogtreecommitdiff
path: root/frontend/_rigidbody
diff options
context:
space:
mode:
authorMatthew Kosarek <mattkae@protonmail.com>2021-02-06 18:39:48 -0500
committerMatthew Kosarek <mattkae@protonmail.com>2021-02-06 18:39:48 -0500
commit376e1a7f9207fffb1ec3027ac1e7f32db5de4922 (patch)
tree8bc6c381a10d8b62ff33cdcff6daa1d17cee0f9a /frontend/_rigidbody
Initial commit
Diffstat (limited to 'frontend/_rigidbody')
-rw-r--r--frontend/_rigidbody/circle.js58
-rw-r--r--frontend/_rigidbody/mat4.js43
-rw-r--r--frontend/_rigidbody/program_common.js72
-rw-r--r--frontend/_rigidbody/rigidbody_1.js106
-rw-r--r--frontend/_rigidbody/rigidbody_2.js78
-rw-r--r--frontend/_rigidbody/shader.js60
-rw-r--r--frontend/_rigidbody/shaders/orthographic.frag5
-rw-r--r--frontend/_rigidbody/shaders/orthographic.vert13
-rw-r--r--frontend/_rigidbody/vec2.js41
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