From a00c0aab1eb5a7a55bef8ca08115bdd722ab5699 Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Sun, 16 May 2021 19:50:15 -0400 Subject: Moved the frontend directory up so that it no longer exists --- _shared/2d/program_common.js | 75 +++++++++++++++++++++++ _shared/2d/shader.js | 76 ++++++++++++++++++++++++ _shared/2d/shaders/orthographic.frag | 5 ++ _shared/2d/shaders/orthographic.vert | 13 ++++ _shared/math/circle.js | 112 +++++++++++++++++++++++++++++++++++ _shared/math/collision.js | 26 ++++++++ _shared/math/line2.js | 63 ++++++++++++++++++++ _shared/math/mat4.js | 63 ++++++++++++++++++++ _shared/math/point2.js | 16 +++++ _shared/math/rectangle.js | 32 ++++++++++ _shared/math/rigidbody2.js | 57 ++++++++++++++++++ _shared/math/vec2.js | 64 ++++++++++++++++++++ 12 files changed, 602 insertions(+) create mode 100644 _shared/2d/program_common.js create mode 100644 _shared/2d/shader.js create mode 100644 _shared/2d/shaders/orthographic.frag create mode 100644 _shared/2d/shaders/orthographic.vert create mode 100644 _shared/math/circle.js create mode 100644 _shared/math/collision.js create mode 100644 _shared/math/line2.js create mode 100644 _shared/math/mat4.js create mode 100644 _shared/math/point2.js create mode 100644 _shared/math/rectangle.js create mode 100644 _shared/math/rigidbody2.js create mode 100644 _shared/math/vec2.js (limited to '_shared') 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 @@ +/// +/// + +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($('
').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 @@ +/// +/// + +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} 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 @@ +/// +/// +/// +/// +/// + +/** + * + * @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 @@ +/// +/// + +/** + * 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 @@ +/// + +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 @@ +/// +/// +/// +/// +/// + +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 -- cgit v1.2.1