summaryrefslogtreecommitdiff
path: root/_shared
diff options
context:
space:
mode:
Diffstat (limited to '_shared')
-rw-r--r--_shared/2d/program_common.js75
-rw-r--r--_shared/2d/shader.js76
-rw-r--r--_shared/2d/shaders/orthographic.frag5
-rw-r--r--_shared/2d/shaders/orthographic.vert13
-rw-r--r--_shared/math/circle.js112
-rw-r--r--_shared/math/collision.js26
-rw-r--r--_shared/math/line2.js63
-rw-r--r--_shared/math/mat4.js63
-rw-r--r--_shared/math/point2.js16
-rw-r--r--_shared/math/rectangle.js32
-rw-r--r--_shared/math/rigidbody2.js57
-rw-r--r--_shared/math/vec2.js64
12 files changed, 602 insertions, 0 deletions
diff --git a/_shared/2d/program_common.js b/_shared/2d/program_common.js
new file mode 100644
index 0000000..48d0a6b
--- /dev/null
+++ b/_shared/2d/program_common.js
@@ -0,0 +1,75 @@
+/// <reference path="shader.js" />
+/// <reference path="../math/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,
+ lLastTimeSeconds = undefined,
+ lIsRunning = true;
+
+ function update(pTimeStamp) {
+ if (!lIsRunning) {
+ if (pOnExit) {
+ pOnExit();
+ }
+ return;
+ }
+
+ pTimeStamp = pTimeStamp / 1000.0; // Convert to seconds
+
+ // Time calculation
+ if (lLastTimeSeconds === undefined) {
+ lLastTimeSeconds = pTimeStamp;
+ lDeltaTimeSeconds = 0;
+ } else {
+ lDeltaTimeSeconds = pTimeStamp - lLastTimeSeconds;
+ lLastTimeSeconds = pTimeStamp;
+ }
+
+ while (lDeltaTimeSeconds > 0) {
+ pFunction(lDeltaTimeSeconds);
+ lDeltaTimeSeconds -= 0.16;
+ }
+ requestAnimationFrame(update);
+ }
+
+ requestAnimationFrame(update);
+
+ function lExit() {
+ lIsRunning = false;
+ }
+
+ return lExit;
+} \ No newline at end of file
diff --git a/_shared/2d/shader.js b/_shared/2d/shader.js
new file mode 100644
index 0000000..7e85a9e
--- /dev/null
+++ b/_shared/2d/shader.js
@@ -0,0 +1,76 @@
+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;
+ }
+
+ var lRetval = {
+ program: lShaderProgram,
+ attributeLocations: {
+ position: pGl.getAttribLocation(lShaderProgram, 'position'),
+ color: pGl.getAttribLocation(lShaderProgram, 'color')
+ },
+ uniformLocations: {
+ 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) {
+ if (pResponse.status === 200) {
+ return pResponse.text().then(function(pShader) {
+ lVertex = pShader;
+
+ return fetch('/_shared/2d/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/_shared/2d/shaders/orthographic.frag b/_shared/2d/shaders/orthographic.frag
new file mode 100644
index 0000000..84b6b2e
--- /dev/null
+++ b/_shared/2d/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/_shared/2d/shaders/orthographic.vert b/_shared/2d/shaders/orthographic.vert
new file mode 100644
index 0000000..0356f9c
--- /dev/null
+++ b/_shared/2d/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/_shared/math/circle.js b/_shared/math/circle.js
new file mode 100644
index 0000000..ba33021
--- /dev/null
+++ b/_shared/math/circle.js
@@ -0,0 +1,112 @@
+/// <reference path="mat4.js" />
+/// <reference path="vec2.js" />
+
+const BYTES_PER_FLOAT = 4;
+
+/**
+ * Initializes a new circle object for the WebGL context.
+ *
+ * @param {WebGLRenderingContext} pGl
+ * @param {number} pRadius
+ * @param {number} pSegments
+ * @param {Array<Vector4>} pColorList
+ * @param {vec2} pInitialPosition
+ * @param {number} pMass
+ */
+function circle(pGl, pRadius, pSegments, pColorList, pInitialPosition, pMass) {
+ const lBuffer = pGl.createBuffer();
+
+ pGl.bindBuffer(pGl.ARRAY_BUFFER, lBuffer);
+
+ 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,
+ prevPos: vec2(),
+ position: pInitialPosition || vec2(),
+ prevVelocity: vec2(),
+ velocity: vec2(),
+ force: vec2(),
+ torque: 0,
+ mass: pMass === undefined ? 1 : pMass,
+ rotationVelocity: 0,
+ rotationRadians: 0,
+ model: translateMatrix(mat4(), pInitialPosition ? pInitialPosition.x : 0, pInitialPosition ? pInitialPosition.y : 0, 0),
+ radius: pRadius
+ };
+}
+
+function renderCircle(pGl, pProgramInfo, pCircle) {
+ pGl.uniformMatrix4fv(pProgramInfo.uniformLocations.model, false, pCircle.model);
+ pGl.bindBuffer(pGl.ARRAY_BUFFER, pCircle.buffer);
+ {
+ pGl.enableVertexAttribArray(pProgramInfo.attributeLocations.position);
+ 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 getCircleMomentOfInertia(pCircle) {
+ return (Math.PI * Math.pow(pCircle.radius, 4)) / 4;
+}
+
+function doCirclesIntersect(pFirst, pSecond) {
+ const lDistanceBetween = Math.pow(pFirst.position.x - pSecond.position.x, 2)
+ + Math.pow(pFirst.position.y - pSecond.position.y, 2)
+ return lDistanceBetween <= Math.pow(pFirst.radius + pSecond.radius, 2);
+}
+
+/**
+ * Returns intersection information about the intersecting circles.
+ *
+ * Warning! Only use this if doCirclesIntersect returned true for these circles.
+ *
+ * @param {circle} pFirst
+ * @param {circle} pSecond
+ */
+function getIntersectionDataForCircles(pFirst, pSecond) {
+ // The collision normal is simply the difference between the two current positions
+ const lCollisionNormal = normalize2(subVec2(pFirst.position, pSecond.position));
+ const lCollisionPoint = vec2(
+ ((pFirst.position.x * pSecond.radius) + (pSecond.position.x * pFirst.radius)) / (pFirst.radius + pSecond.radius),
+ ((pFirst.position.y * pSecond.radius) + (pSecond.position.y * pFirst.radius)) / (pFirst.radius + pSecond.radius)
+ );
+
+ return {
+ relativeVelocity: subVec2(pFirst.velocity, pSecond.velocity),
+ collisionNormal: lCollisionNormal,
+ //firstPointOfApplication: addVec2(scaleVec2(normalize2(pFirst.velocity), pFirst.radius), pFirst.position),
+ //secondPointOfApplication: addVec2(scaleVec2(normalize2(pSecond.velocity), pSecond.radius), pSecond.position)
+ firstPointOfApplication: subVec2(lCollisionPoint, pFirst.position),
+ secondPointOfApplication: subVec2(lCollisionPoint, pSecond.position)
+ }
+}
+
+function freeCircle(pGl, pCircle) {
+ pGl.deleteBuffer(pCircle.buffer);
+} \ No newline at end of file
diff --git a/_shared/math/collision.js b/_shared/math/collision.js
new file mode 100644
index 0000000..8e4be7d
--- /dev/null
+++ b/_shared/math/collision.js
@@ -0,0 +1,26 @@
+/// <reference path="vec2.js" />
+/// <reference path="line2.js" />
+/// <reference path="circle.js" />
+/// <reference path="mat4.js" />
+/// <reference path="point2.js" />
+
+/**
+ *
+ * @param {circle} pCircle
+ * @param {line2} pLine
+ */
+function lineCircleCollision2(pCircle, pLine) {
+ return distanceFromPoint2ToLine2(pCircle.position, pLine) <= pCircle.radius;
+}
+
+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/_shared/math/line2.js b/_shared/math/line2.js
new file mode 100644
index 0000000..8b30c1c
--- /dev/null
+++ b/_shared/math/line2.js
@@ -0,0 +1,63 @@
+/// <reference path="vec2.js" />
+/// <reference path="mat4.js" />
+
+/**
+ * Creates a new line object
+ * @param {vec2} pStart
+ * @param {vec2} pEnd
+ * @param {WebGLRenderingContext} pGl
+ */
+function line2(pStart, pEnd, pGl, pColor, pMass) {
+ const lDiffVector = subVec2(pEnd, pStart);
+
+ const lBuffer = pGl.createBuffer();
+ pGl.bindBuffer(pGl.ARRAY_BUFFER, lBuffer);
+
+ var lBufferedData = [
+ pStart.x, pStart.y, pColor.x, pColor.y, pColor.z, pColor.w,
+ pEnd.x, pEnd.y, pColor.x, pColor.y, pColor.z, pColor.w
+ ];
+
+ pGl.bufferData(pGl.ARRAY_BUFFER, new Float32Array(lBufferedData), pGl.STATIC_DRAW)
+ pGl.bindBuffer(pGl.ARRAY_BUFFER, undefined);
+
+ var lSlope = (pEnd.y - pStart.y) / (pEnd.x - pStart.x);
+
+ return {
+ buffer: lBuffer,
+ start: pStart,
+ end: pEnd,
+ slope: lSlope,
+ normal: normalize2(getPerp2(lDiffVector)),
+ length: length2(lDiffVector),
+ mass: pMass,
+ direction: normalize2(lDiffVector),
+ velocity: vec2(0, 0),
+ position: vec2()
+ };
+}
+
+function getLine2MomentOfInertia(pLine) {
+ return (1.0 / 12.0) * pLine.mass * Math.pow(pLine.length, 2);
+}
+
+function getLine2MidPoint(pLine) {
+ return {
+ x: (pLine.end.x + pLine.start.x) / 2.0,
+ y: (pLine.end.y + pLine.start.y) / 2.0
+ }
+}
+
+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);
+ {
+ 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.LINES, 0, 2);
+} \ No newline at end of file
diff --git a/_shared/math/mat4.js b/_shared/math/mat4.js
new file mode 100644
index 0000000..d31a20e
--- /dev/null
+++ b/_shared/math/mat4.js
@@ -0,0 +1,63 @@
+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;
+}
+
+function multMat4ByVec2(matrix, vec) {
+ var lInnerVec = {...vec};
+ lInnerVec.z = 0.0;
+ lInnerVec.w = 1.0;
+
+ return {
+ x: lInnerVec.x * matrix[0] + lInnerVec.y * matrix[4] + lInnerVec.z * matrix[8] + lInnerVec.w * matrix[12],
+ y: lInnerVec.x * matrix[1] + lInnerVec.y * matrix[5] + lInnerVec.z * matrix[9] + lInnerVec.w * matrix[13]
+ };
+}
+
+function multMat4ByVec4(matrix, vec) {
+ return {
+ x: vec.x * matrix[0] + vec.y * matrix[4] + vec.z * matrix[8] + vec.w * matrix[12],
+ y: vec.x * matrix[1] + vec.y * matrix[5] + vec.z * matrix[9] + vec.w * matrix[13],
+ z: vec.x * matrix[2] + vec.y * matrix[6] + vec.z * matrix[10] + vec.w * matrix[14],
+ w: vec.x * matrix[3] + vec.y * matrix[7] + vec.z * matrix[11] + vec.w * matrix[15]
+ };
+} \ No newline at end of file
diff --git a/_shared/math/point2.js b/_shared/math/point2.js
new file mode 100644
index 0000000..eeacd91
--- /dev/null
+++ b/_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/_shared/math/rectangle.js b/_shared/math/rectangle.js
new file mode 100644
index 0000000..012c460
--- /dev/null
+++ b/_shared/math/rectangle.js
@@ -0,0 +1,32 @@
+/// <reference path="rigidbody2.js" />
+
+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 = [
+ -pData.width / 2.0, -pData.height / 2.0, lColor.x, lColor.y, lColor.z, lColor.w,
+ -pData.width / 2.0, pData.height / 2.0, lColor.x, lColor.y, lColor.z, lColor.w,
+ pData.width / 2.0, pData.height / 2.0, lColor.x, lColor.y, lColor.z, lColor.w,
+ pData.width / 2.0, pData.height /2.0, lColor.x, lColor.y, lColor.z, lColor.w,
+ pData.width / 2.0, -pData.height / 2.0, lColor.x, lColor.y, lColor.z, lColor.w,
+ -pData.width / 2.0, -pData.height / 2.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 (1.0 / 12.0) * pData.mass * (pData.height * pData.height + pData.width * pData.width);
+ };
+
+ 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/_shared/math/rigidbody2.js b/_shared/math/rigidbody2.js
new file mode 100644
index 0000000..0b18d0c
--- /dev/null
+++ b/_shared/math/rigidbody2.js
@@ -0,0 +1,57 @@
+/// <reference path="vec2.js" />
+/// <reference path="mat4.js" />
+/// <reference path="circle.js" />
+/// <reference path="line2.js" />
+/// <reference path="collision.js" />
+
+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/_shared/math/vec2.js b/_shared/math/vec2.js
new file mode 100644
index 0000000..11e71cd
--- /dev/null
+++ b/_shared/math/vec2.js
@@ -0,0 +1,64 @@
+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 = length2(v);
+ const lInverseLength = lLength === 0 ? 1.0 : 1.0 / length2(v);
+ return { x: v.x * lInverseLength, y: v.y * lInverseLength };
+}
+
+function vec2str(v) {
+ return `(${v.x.toFixed(2)}, ${v.y.toFixed(2)})`;
+}
+
+function getPerp2(v) {
+ return {
+ x: -v.y,
+ y: v.x
+ };
+}
+
+function negate2(v) {
+ return {
+ x: -v.x,
+ y: -v.y
+ };
+}
+
+// Algorithm plucked from: https://matthew-brett.github.io/teaching/rotation_2d.html
+function rotateAboutOrigin2(v, angle) {
+ return {
+ x: v.x * Math.cos(angle) - v.y * Math.sin(angle),
+ y: v.x * Math.sin(angle) + v.y * Math.cos(angle),
+ };
+} \ No newline at end of file