From 376e1a7f9207fffb1ec3027ac1e7f32db5de4922 Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Sat, 6 Feb 2021 18:39:48 -0500 Subject: Initial commit --- frontend/_rigidbody/circle.js | 58 +++++++ frontend/_rigidbody/mat4.js | 43 ++++++ frontend/_rigidbody/program_common.js | 72 +++++++++ frontend/_rigidbody/rigidbody_1.js | 106 +++++++++++++ frontend/_rigidbody/rigidbody_2.js | 78 ++++++++++ frontend/_rigidbody/shader.js | 60 ++++++++ frontend/_rigidbody/shaders/orthographic.frag | 5 + frontend/_rigidbody/shaders/orthographic.vert | 13 ++ frontend/_rigidbody/vec2.js | 41 +++++ frontend/dog-114-203766.png | Bin 0 -> 8343 bytes frontend/dog-120-203766.png | Bin 0 -> 8262 bytes frontend/dog-144-203766.png | Bin 0 -> 9834 bytes frontend/dog-152-203766.png | Bin 0 -> 8748 bytes frontend/dog-16-203766.png | Bin 0 -> 969 bytes frontend/dog-24-203766.png | Bin 0 -> 1203 bytes frontend/dog-32-203766.png | Bin 0 -> 1742 bytes frontend/dog-48-203766.png | Bin 0 -> 3026 bytes frontend/dog-512-203766.png | Bin 0 -> 41072 bytes frontend/dog-57-203766.png | Bin 0 -> 3686 bytes frontend/dog-64-203766.png | Bin 0 -> 4111 bytes frontend/dog-72-203766.png | Bin 0 -> 4493 bytes frontend/favicon.ico | Bin 0 -> 32038 bytes frontend/index.css | 211 ++++++++++++++++++++++++++ frontend/index.html | 23 +++ frontend/index.js | 10 ++ frontend/readme.txt | 26 ++++ frontend/rigidbody.html | 152 +++++++++++++++++++ frontend/scripts/jquery-3.5.1.min.js | 1 + 28 files changed, 899 insertions(+) create mode 100644 frontend/_rigidbody/circle.js create mode 100644 frontend/_rigidbody/mat4.js create mode 100644 frontend/_rigidbody/program_common.js create mode 100644 frontend/_rigidbody/rigidbody_1.js create mode 100644 frontend/_rigidbody/rigidbody_2.js create mode 100644 frontend/_rigidbody/shader.js create mode 100644 frontend/_rigidbody/shaders/orthographic.frag create mode 100644 frontend/_rigidbody/shaders/orthographic.vert create mode 100644 frontend/_rigidbody/vec2.js create mode 100644 frontend/dog-114-203766.png create mode 100644 frontend/dog-120-203766.png create mode 100644 frontend/dog-144-203766.png create mode 100644 frontend/dog-152-203766.png create mode 100644 frontend/dog-16-203766.png create mode 100644 frontend/dog-24-203766.png create mode 100644 frontend/dog-32-203766.png create mode 100644 frontend/dog-48-203766.png create mode 100644 frontend/dog-512-203766.png create mode 100644 frontend/dog-57-203766.png create mode 100644 frontend/dog-64-203766.png create mode 100644 frontend/dog-72-203766.png create mode 100644 frontend/favicon.ico create mode 100644 frontend/index.css create mode 100644 frontend/index.html create mode 100644 frontend/index.js create mode 100644 frontend/readme.txt create mode 100644 frontend/rigidbody.html create mode 100644 frontend/scripts/jquery-3.5.1.min.js (limited to 'frontend') 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 @@ +/// +/// + +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, + 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 @@ +/// +/// +/// +/// +/// +/// + +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 @@ +/// +/// +/// +/// +/// + +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 diff --git a/frontend/dog-114-203766.png b/frontend/dog-114-203766.png new file mode 100644 index 0000000..1bed84f Binary files /dev/null and b/frontend/dog-114-203766.png differ diff --git a/frontend/dog-120-203766.png b/frontend/dog-120-203766.png new file mode 100644 index 0000000..8630006 Binary files /dev/null and b/frontend/dog-120-203766.png differ diff --git a/frontend/dog-144-203766.png b/frontend/dog-144-203766.png new file mode 100644 index 0000000..f99123a Binary files /dev/null and b/frontend/dog-144-203766.png differ diff --git a/frontend/dog-152-203766.png b/frontend/dog-152-203766.png new file mode 100644 index 0000000..c0e2c85 Binary files /dev/null and b/frontend/dog-152-203766.png differ diff --git a/frontend/dog-16-203766.png b/frontend/dog-16-203766.png new file mode 100644 index 0000000..bed9af9 Binary files /dev/null and b/frontend/dog-16-203766.png differ diff --git a/frontend/dog-24-203766.png b/frontend/dog-24-203766.png new file mode 100644 index 0000000..b70836f Binary files /dev/null and b/frontend/dog-24-203766.png differ diff --git a/frontend/dog-32-203766.png b/frontend/dog-32-203766.png new file mode 100644 index 0000000..46b6f77 Binary files /dev/null and b/frontend/dog-32-203766.png differ diff --git a/frontend/dog-48-203766.png b/frontend/dog-48-203766.png new file mode 100644 index 0000000..ba10a5b Binary files /dev/null and b/frontend/dog-48-203766.png differ diff --git a/frontend/dog-512-203766.png b/frontend/dog-512-203766.png new file mode 100644 index 0000000..814c34e Binary files /dev/null and b/frontend/dog-512-203766.png differ diff --git a/frontend/dog-57-203766.png b/frontend/dog-57-203766.png new file mode 100644 index 0000000..0ee3c44 Binary files /dev/null and b/frontend/dog-57-203766.png differ diff --git a/frontend/dog-64-203766.png b/frontend/dog-64-203766.png new file mode 100644 index 0000000..d28a1be Binary files /dev/null and b/frontend/dog-64-203766.png differ diff --git a/frontend/dog-72-203766.png b/frontend/dog-72-203766.png new file mode 100644 index 0000000..25c5c50 Binary files /dev/null and b/frontend/dog-72-203766.png differ diff --git a/frontend/favicon.ico b/frontend/favicon.ico new file mode 100644 index 0000000..7c96d7e Binary files /dev/null and b/frontend/favicon.ico differ diff --git a/frontend/index.css b/frontend/index.css new file mode 100644 index 0000000..9139963 --- /dev/null +++ b/frontend/index.css @@ -0,0 +1,211 @@ +body { + font-size: 16px; + font-family: "Open Sans", sans-serif; + width: 100%; + height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + margin: 0rem; + padding: 0rem; +} + +header { + padding-top: 1rem; + width: 1260px; + flex: 0 1 auto; + text-align: center; +} + +header > h1 { + margin: 0rem; + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} + +main { + color: #1a1a1a; + width: 1260px; + background-color: white; + flex: 1 1 100%; + height: 100%; + margin-bottom: 1rem; + overflow-y: auto; +} + +nav { + width: 1260px; + text-align: left; + font-size: 1rem; + border-bottom: 1px solid #404040; +} + +nav > a { + color: #404040; + text-decoration: none; + display: inline-block; + min-width: 196px; + padding: 0.25rem; +} + +nav > a:hover { + color: #1a1a1a; + cursor: pointer; +} + +.nav-selected { + color: black; + font-weight: bold; +} + +section { + padding: 1rem; +} + +hr { + width: 90%; +} + +.spin-loader { + border: 5px solid #f3f3f3; + border-top: 5px solid #555; + border-radius: 50%; + width: 50px; + height: 50px; + animation: spin 2s linear infinite; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +section > p { + padding-left: 2rem; + padding-right: 2rem; +} + +/* + Helpful inputs +*/ +.vec2_input_group { + display: flex; + flex-direction: row; + width: 100%; + justify-content: space-evenly; + padding: 0.25rem; +} + +.vec2_input_group > input { + width: 45%; +} + +/* + WebGL container styling from here on. +*/ +section .opengl_canvas_container { + position: relative; + width: 100%; + text-align: center; + display: flex; + flex-direction: row; + justify-content: center; +} + +section .opengl_canvas_container .play_button { + all: unset; + font-size: 2rem; + width: 128px; + height: 128px; + position: absolute; + top: calc(50% - 56px); + left: calc(50% - 220px); + border: 1px solid white; + color: white; + background-color: transparent; + border-radius: 50%; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; +} + +section .opengl_canvas_container .play_button:hover { + cursor: pointer; +} + +section .opengl_canvas_container .stop_button { + all: unset; + font-size: 1rem; + background-color: red; + color: white; + position: absolute; + width: 128px; + height: 2rem; + border-radius: 2px; + top: 0.5rem; + left: 612px; + display: none; +} + +section .opengl_canvas_container .stop_button:hover { + cursor: pointer; +} + +section pre { + width: 90%; + overflow-x: auto; + padding-left: 2rem; +} + +.opengl_canvas_sidebar { + width: 312px; + height: 480px; + border: 1px solid #1a1a1a; + border-left: none; + + display: flex; + flex-direction: column; + text-align: left; + justify-content: space-between; +} + +.opengl_canvas_sidebar .opengl_value_tracker { + font-size: 14px; + display: flex; + flex-direction: column; + text-align: left; +} + +.opengl_canvas_sidebar .opengl_value_tracker > li { + padding-bottom: 0.5rem; +} + +.opengl_canvas_sidebar .opengl_value_tracker > li > b { + display: inline-block; + width: 128px; +} + +.opengl_canvas_sidebar .opengl_value_tracker > li > span { + display: inline-block; +} + +/* + Code styles +*/ +.code_keyword { + color: blue; + font-weight: bold; +} + +.code_constant { + font-weight: bold; +} + +.code_str { + color: orange; +} + +.code_comment { + color: green; +} \ No newline at end of file diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..d57d69b --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,23 @@ + + + + + + + + + Simple Website + + +
+

Matt Kae's Programming Blog

+
+ +
+
+ + \ No newline at end of file diff --git a/frontend/index.js b/frontend/index.js new file mode 100644 index 0000000..0cbd896 --- /dev/null +++ b/frontend/index.js @@ -0,0 +1,10 @@ +/// + +function main() { + console.log('Document ready.'); + const lPathName = window.location.pathname.length > 1 && window.location.pathname.startsWith('/') ? window.location.pathname.substr(1) : window.location.pathname; + console.log(lPathName); + $('nav').find('a[href="' + lPathName + '"').addClass('nav-selected'); +} + +$(document).ready(main); \ No newline at end of file diff --git a/frontend/readme.txt b/frontend/readme.txt new file mode 100644 index 0000000..aa00c22 --- /dev/null +++ b/frontend/readme.txt @@ -0,0 +1,26 @@ +Thank you for downloading a favicon from Free Favicon! This favicon was created from an image at http://openclipart.org/ and converted to a favicon and other sizes for you to use in your projects. For more details about this image visit this page: https://openclipart.org/detail/203766/dog + +For more information about how you can use this image from OpenClipArt see this page: http://openclipart.org/may-clipart-be-used-comparison + +Here are the contents of this compressed package: + +* favicon.ico -- The favicon file (supports both 16*16 and 32*32 dimensions). You will need to rename this to favicon.ico and upload to your web site. + + * You can add a favicon to your web page by uploading favicon.ico to Root of your website and inserting the following HTML tag between the ... tags of your web page. + + + +* There are many different sized png files for use a icons for smartphones, tablets and desktop use. + +If you are having any problems installing your favicon we have tips on the Free Favicon blog. http://www.freefavicon.com/blog/ + +Thank you once again for download from Free Favicon! If you like your favicon we appreciate you taking the time to mention our service to others, blogging about us and of course by linking to us. + +We also encourage you to check out Backblaze online backup. We use Backblaze to backup our computers and keep our data safe. They offer a free 15 day trial and are the easiest online backup service we have used. + +Use this link http://www.freefavicon.com/blog/outgoing/backblaze.php to try Backblaze for Free! + +By signing up for Backblaze you help keep Free Favicon free! + +Thank you, +FreeFavicon.com \ No newline at end of file diff --git a/frontend/rigidbody.html b/frontend/rigidbody.html new file mode 100644 index 0000000..510c667 --- /dev/null +++ b/frontend/rigidbody.html @@ -0,0 +1,152 @@ + + + + + + + + + Simple Website + + + + + + + + + + +
+

Matt Kae's Programming Blog

+
+ +
+
+

+

+

+
+
+

Introduction: Rigid Body Physics

+

+ You're most likely here because you have some interest in the world of rigid body physics. Maybe you have some knowledge of rendering via OpenGL or Vulkan, + and you want to begin watching your up-until-now static scene come to life. Well, you're in the right place! In the course of this tutorial series I will walk + you through the entirety of a 2D rigid body physics system entirely in the web. All of this information will be extendable to other languages, but we will use + JavaScript and WebGL in these blog posts. Additionally, much of the information presented here can be extended to 3 dimensions, but 3D carries some complications + with it, that we will discuss in future blog posts. +

+

+ In implementing a rigidy body physics system, we're primarily interested in two sub-fields of physics, namely dynamics and kinematics. Although I'm + far as can be from being an expert in either of these fields, I will explain - from a programmer's persepctive - what they mean to me: +

    +
  • + Kinematics is the study of how an object's movement changes over time. These are the classic position, velocity, and acceleration equations + that you're most likely familiar with from high school or college physics. +
  • +
  • + Dynamics is the study of whats causes kinematic movement. These are the classic force and momentum equations that you may already be familiar + with as well. +
  • +
+

+

+ Finally, I must provide a disclaimer that all of rigid body systems are very math-y. You will need to know a decent amount of vector calculus and linear algebra to really understand + what's going on here. I am going to assume that you have this knowledge. If you don't already have this knowledge, I will try and provide some resources on the Books + n' References page of the website. +

+
+
+
+

Part 1: Linear Forces

+

+ The first - and perhaps easiest - part of implementing any rigid body physics system is getting the entities in your scene to move in response to linear forces. + With this implementation alone, you can achieve an interesting level of realism in your 2D (and even 3D) scene. +

+

+ Let's begin by recalling the relationships between acceleration, velocity, and position. +

+

+ Knowing all this, you should be able to understand the following source code fairly easily; +

+                        
+function update(dtSeconds) {
+    // Add up the forces acting on the circle
+    const GRAVITY = 9.8;
+    const lGravityForce = vec2(0, -1.0 * (lCircle.mass * GRAVITY));
+    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, dtSeconds));
+
+    // Update the position based on velocity: x = x0 + v * t
+    lCircle.position = addVec2(lCircle.position, scaleVec2(lCircle.velocity, dtSeconds));
+
+    // Update the model matrix accordingly
+    lCircle.model = translateMatrix(mat4(), lCircle.position.x, lCircle.position.y, 0);
+
+    // Reset the force vector for the next update
+    lCircle.force = vec2()
+}
+                        
+                    
+

+
+ +
+
    +
  • Force:N/A
  • +
  • Acceleration:N/A
  • +
  • Velocity:N/A
  • +
  • Position:N/A
  • +
+
+
+ + +
+ +
+
+ + +
+
+
+
+

Part 2: Rotational Forces

+

+

+
+ +
+ +
+ + +
+
+ +
+
+ + \ No newline at end of file diff --git a/frontend/scripts/jquery-3.5.1.min.js b/frontend/scripts/jquery-3.5.1.min.js new file mode 100644 index 0000000..4e83e2d --- /dev/null +++ b/frontend/scripts/jquery-3.5.1.min.js @@ -0,0 +1 @@ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.5.1",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function D(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||j,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,j=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function qe(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function Le(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function He(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Oe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Ut,Xt=[],Vt=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Xt.pop()||S.expando+"_"+Ct.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Vt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Vt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Vt,"$1"+r):!1!==e.jsonp&&(e.url+=(Et.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Xt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((Ut=E.implementation.createHTMLDocument("").body).innerHTML="
",2===Ut.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):("number"==typeof f.top&&(f.top+="px"),"number"==typeof f.left&&(f.left+="px"),c.css(f))}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=$e(y.pixelPosition,function(e,t){if(t)return t=Be(e,n),Me.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0