diff options
author | Matthew Kosarek <mattkae@protonmail.com> | 2021-06-17 21:32:57 -0400 |
---|---|---|
committer | Matthew Kosarek <mattkae@protonmail.com> | 2021-06-17 21:32:57 -0400 |
commit | 2ab6b6cfe81505b029f2da397cef0bb58989444f (patch) | |
tree | 41c581c80310c060dcbdcb049ce88970ca418d72 | |
parent | 8748228bc9effa4779f4c1062f5fbc07f1d07a60 (diff) |
(mkosarek) Beginning to explain rigidbody physics in a reasonable way
-rw-r--r-- | 2d/rigidbody/rigidbody_1.html | 38 | ||||
-rw-r--r-- | 2d/rigidbody/rigidbody_1.html.content | 38 | ||||
-rw-r--r-- | 2d/rigidbody/rigidbody_1/dist/output.js | 313 | ||||
-rwxr-xr-x | 2d/rigidbody/rigidbody_1/dist/output.wasm | bin | 47023 -> 47394 bytes | |||
-rw-r--r-- | 2d/rigidbody/rigidbody_1/main.cpp | 10 | ||||
-rw-r--r-- | 2d/rigidbody/rigidbody_1/snippet1.cpp | 8 | ||||
-rw-r--r-- | 2d/rigidbody/rigidbody_1/snippet2.cpp | 25 | ||||
-rw-r--r-- | index.html | 32 | ||||
-rw-r--r-- | index.html.content | 32 | ||||
-rw-r--r-- | roadmap.html | 1 | ||||
-rw-r--r-- | roadmap.html.content | 1 | ||||
-rw-r--r-- | shared_cpp/mathlib.h | 1 |
12 files changed, 332 insertions, 167 deletions
diff --git a/2d/rigidbody/rigidbody_1.html b/2d/rigidbody/rigidbody_1.html index 2c17bac..e7e848d 100644 --- a/2d/rigidbody/rigidbody_1.html +++ b/2d/rigidbody/rigidbody_1.html @@ -68,19 +68,51 @@ <h1>Rigidbody #1: Linear Forces</h1> <section> <p> - In this first installment of my 2D rigidbody tutorial, we are going to explore linear forces and how we can begin to simulate them in real time on a computer. As you'll come to see, 2D forces are quite easy to understand and implement if you have some basic knowledge of 2D maths. On top of that, they really add a lot of life into what would otherwise be a static 2D scene. Without further ado, let's jump in. + In this first installment of my 2D rigidbody tutorial, we are going to explore linear forces and how we can begin to simulate them in real time on a computer. As you'll come to see, 2D forces are quite easy to understand and implement if you have some basic knowledge of 2D maths. On top of that, they really add a lot of life into what would otherwise be a static 2D scene. </p> </section> <section> - <h2>What is a Force Anyway?</h2> + <h2>What do we mean by 'Rigid Body Physics'?</h2> + In implementing a rigidy body physics system, we're primarily interested in two sub-fields of physics, namely <b>dynamics</b> and <b>kinematics</b>. Although I'm + far as can be from being an expert in either of these fields, I will explain - from a programmer's perspective - what they mean to me: + + <ul> + <li> + <b>Kinematics</b> 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. Kinematics doesn't care about <i>how</i> a system entered into the state that it is out, but rather that the system <i>is</i> in that state. + </li> + <li> + <b>Dynamics</b> is the study of whats <i>causes</i> kinematic movement. These are the classic force and momentum equations that you may already be familiar with as well. Whereas kinematics only worries itself with the current state of the system, dynamics wants to know <i>how</i> the system entered the state that it is currently in. + </li> + </ul> + + Although the distinction between these two subfields may seem inconsequential, it impacts the conceptual way in which we might begin to setup our 2D rigidbody simulation: <i>the kinematic variables are the data that we act upon, while the dynamics variables are the data that we apply</i>. + + </section> + <section> + <h2>The Kinematics Data Structure</h2> + <p> + Now that we have that understanding, we can begin setting up our rigidbody data structure. + + {{{rigidbody_1/snippet1.cpp}}} + + As you can see, the base data structure exactly mirrors what we already know from 2D newtonian physics. + </p> + </section> + <section> + <h2>The Dynamics Functions</h2> <p> - + Now, let's put that Rigidbody data structure to work! As I mentioned earlier, you can think of dynamics as the <i>input</i> to the system. What we're going to do now is add a way to + + {{{rigidbody_1/snippet2.cpp}}} </p> </section> <section> <h2> Live Example </h2> + <p> + That's all there is to a rigidbody system with 2D linear forces. Now let's see it in action. Click 'Play' on the WebAssembly demo below to see a square bouncing around the screen. When you drag the pointer through the square, we will apply a force equivalent to how fast you were moving your mouse in the direction that you were moving it. (The speed is capped in the demo, or else things get a little out of hand.) + </p> <div class="opengl_canvas_container"> <canvas id="gl_canvas" width="800" height="600"></canvas> <button id="gl_canvas_play" class="play_button"> diff --git a/2d/rigidbody/rigidbody_1.html.content b/2d/rigidbody/rigidbody_1.html.content index 6ca1619..0e75bdc 100644 --- a/2d/rigidbody/rigidbody_1.html.content +++ b/2d/rigidbody/rigidbody_1.html.content @@ -18,19 +18,51 @@ <h1>Rigidbody #1: Linear Forces</h1> <section> <p> - In this first installment of my 2D rigidbody tutorial, we are going to explore linear forces and how we can begin to simulate them in real time on a computer. As you'll come to see, 2D forces are quite easy to understand and implement if you have some basic knowledge of 2D maths. On top of that, they really add a lot of life into what would otherwise be a static 2D scene. Without further ado, let's jump in. + In this first installment of my 2D rigidbody tutorial, we are going to explore linear forces and how we can begin to simulate them in real time on a computer. As you'll come to see, 2D forces are quite easy to understand and implement if you have some basic knowledge of 2D maths. On top of that, they really add a lot of life into what would otherwise be a static 2D scene. </p> </section> <section> - <h2>What is a Force Anyway?</h2> + <h2>What do we mean by 'Rigid Body Physics'?</h2> + In implementing a rigidy body physics system, we're primarily interested in two sub-fields of physics, namely <b>dynamics</b> and <b>kinematics</b>. Although I'm + far as can be from being an expert in either of these fields, I will explain - from a programmer's perspective - what they mean to me: + + <ul> + <li> + <b>Kinematics</b> 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. Kinematics doesn't care about <i>how</i> a system entered into the state that it is out, but rather that the system <i>is</i> in that state. + </li> + <li> + <b>Dynamics</b> is the study of whats <i>causes</i> kinematic movement. These are the classic force and momentum equations that you may already be familiar with as well. Whereas kinematics only worries itself with the current state of the system, dynamics wants to know <i>how</i> the system entered the state that it is currently in. + </li> + </ul> + + Although the distinction between these two subfields may seem inconsequential, it impacts the conceptual way in which we might begin to setup our 2D rigidbody simulation: <i>the kinematic variables are the data that we act upon, while the dynamics variables are the data that we apply</i>. + + </section> + <section> + <h2>The Kinematics Data Structure</h2> + <p> + Now that we have that understanding, we can begin setting up our rigidbody data structure. + + {{{rigidbody_1/snippet1.cpp}}} + + As you can see, the base data structure exactly mirrors what we already know from 2D newtonian physics. + </p> + </section> + <section> + <h2>The Dynamics Functions</h2> <p> - + Now, let's put that Rigidbody data structure to work! As I mentioned earlier, you can think of dynamics as the <i>input</i> to the system. What we're going to do now is add a way to + + {{{rigidbody_1/snippet2.cpp}}} </p> </section> <section> <h2> Live Example </h2> + <p> + That's all there is to a rigidbody system with 2D linear forces. Now let's see it in action. Click 'Play' on the WebAssembly demo below to see a square bouncing around the screen. When you drag the pointer through the square, we will apply a force equivalent to how fast you were moving your mouse in the direction that you were moving it. (The speed is capped in the demo, or else things get a little out of hand.) + </p> <div class="opengl_canvas_container"> <canvas id="gl_canvas" width="800" height="600"></canvas> <button id="gl_canvas_play" class="play_button"> diff --git a/2d/rigidbody/rigidbody_1/dist/output.js b/2d/rigidbody/rigidbody_1/dist/output.js index 9ba383a..9fdc721 100644 --- a/2d/rigidbody/rigidbody_1/dist/output.js +++ b/2d/rigidbody/rigidbody_1/dist/output.js @@ -609,7 +609,7 @@ if (typeof WebAssembly !== 'object') { function setValue(ptr, value, type, noSafe) { type = type || 'i8'; if (type.charAt(type.length-1) === '*') type = 'i32'; // pointers are 32-bit - switch(type) { + switch (type) { case 'i1': HEAP8[((ptr)>>0)] = value; break; case 'i8': HEAP8[((ptr)>>0)] = value; break; case 'i16': HEAP16[((ptr)>>1)] = value; break; @@ -627,7 +627,7 @@ function setValue(ptr, value, type, noSafe) { function getValue(ptr, type, noSafe) { type = type || 'i8'; if (type.charAt(type.length-1) === '*') type = 'i32'; // pointers are 32-bit - switch(type) { + switch (type) { case 'i1': return HEAP8[((ptr)>>0)]; case 'i8': return HEAP8[((ptr)>>0)]; case 'i16': return HEAP16[((ptr)>>1)]; @@ -808,7 +808,7 @@ function UTF8ArrayToString(heap, idx, maxBytesToRead) { if ((u0 & 0xF0) == 0xE0) { u0 = ((u0 & 15) << 12) | (u1 << 6) | u2; } else { - if ((u0 & 0xF8) != 0xF0) warnOnce('Invalid UTF-8 leading byte 0x' + u0.toString(16) + ' encountered when deserializing a UTF-8 string on the asm.js/wasm heap to a JS string!'); + if ((u0 & 0xF8) != 0xF0) warnOnce('Invalid UTF-8 leading byte 0x' + u0.toString(16) + ' encountered when deserializing a UTF-8 string in wasm memory to a JS string!'); u0 = ((u0 & 7) << 18) | (u1 << 12) | (u2 << 6) | (heap[idx++] & 63); } @@ -884,7 +884,7 @@ function stringToUTF8Array(str, heap, outIdx, maxBytesToWrite) { heap[outIdx++] = 0x80 | (u & 63); } else { if (outIdx + 3 >= endIdx) break; - if (u >= 0x200000) warnOnce('Invalid Unicode code point 0x' + u.toString(16) + ' encountered when serializing a JS string to an UTF-8 string on the asm.js/wasm heap! (Valid unicode code points should be in range 0-0x1FFFFF).'); + if (u >= 0x200000) warnOnce('Invalid Unicode code point 0x' + u.toString(16) + ' encountered when serializing a JS string to a UTF-8 string in wasm memory! (Valid unicode code points should be in range 0-0x1FFFFF).'); heap[outIdx++] = 0xF0 | (u >> 18); heap[outIdx++] = 0x80 | ((u >> 12) & 63); heap[outIdx++] = 0x80 | ((u >> 6) & 63); @@ -1252,12 +1252,12 @@ function checkStackCookie() { // include: runtime_assertions.js -// Endianness check (note: assumes compiler arch was little-endian) +// Endianness check (function() { var h16 = new Int16Array(1); var h8 = new Int8Array(h16.buffer); h16[0] = 0x6373; - if (h8[0] !== 0x73 || h8[1] !== 0x63) throw 'Runtime error: expected the system to be little-endian!'; + if (h8[0] !== 0x73 || h8[1] !== 0x63) throw 'Runtime error: expected the system to be little-endian! (Run with -s SUPPORT_BIG_ENDIAN=1 to bypass)'; })(); function abortFnPtrError(ptr, sig) { @@ -1274,8 +1274,6 @@ var __ATPOSTRUN__ = []; // functions called after the main() is called var runtimeInitialized = false; var runtimeExited = false; -__ATINIT__.push({ func: function() { ___wasm_call_ctors() } }); - function preRun() { if (Module['preRun']) { @@ -1292,6 +1290,7 @@ function initRuntime() { checkStackCookie(); assert(!runtimeInitialized); runtimeInitialized = true; + callRuntimeCallbacks(__ATINIT__); } @@ -1616,6 +1615,8 @@ function createWasm() { wasmTable = Module['asm']['__indirect_function_table']; assert(wasmTable, "table not found in wasm exports"); + addOnInit(Module['asm']['__wasm_call_ctors']); + removeRunDependency('wasm-instantiate'); } // we can't run yet (except in a pthread, where we have a custom sync instantiator) @@ -1637,7 +1638,8 @@ function createWasm() { function instantiateArrayBuffer(receiver) { return getBinaryPromise().then(function(binary) { - return WebAssembly.instantiate(binary, info); + var result = WebAssembly.instantiate(binary, info); + return result; }).then(receiver, function(reason) { err('failed to asynchronously prepare wasm: ' + reason); @@ -1704,12 +1706,8 @@ var ASM_CONSTS = { - function abortStackOverflow(allocSize) { - abort('Stack overflow! Attempted to allocate ' + allocSize + ' bytes on the stack, but stack has only ' + (_emscripten_stack_get_free() + allocSize) + ' bytes available!'); - } - function callRuntimeCallbacks(callbacks) { - while(callbacks.length > 0) { + while (callbacks.length > 0) { var callback = callbacks.shift(); if (typeof callback == 'function') { callback(Module); // Pass the module as the first argument. @@ -1760,6 +1758,11 @@ var ASM_CONSTS = { return error.stack.toString(); } + var runtimeKeepaliveCounter=0; + function keepRuntimeAlive() { + return noExitRuntime || runtimeKeepaliveCounter > 0; + } + function stackTrace() { var js = jsStackTrace(); if (Module['extraStackTrace']) js += '\n' + Module['extraStackTrace'](); @@ -1783,10 +1786,6 @@ var ASM_CONSTS = { return requestAnimationFrame(tick); } - function _emscripten_get_heap_size() { - return HEAPU8.length; - } - function emscripten_realloc_buffer(size) { try { // round size grow request up to wasm page size (fixed 64KB per spec) @@ -1800,7 +1799,8 @@ var ASM_CONSTS = { // anyhow) } function _emscripten_resize_heap(requestedSize) { - var oldSize = _emscripten_get_heap_size(); + var oldSize = HEAPU8.length; + requestedSize = requestedSize >>> 0; // With pthreads, races can happen (another thread might increase the size in between), so return a failure, and let the caller retry. assert(requestedSize > oldSize); @@ -1827,7 +1827,7 @@ var ASM_CONSTS = { // Loop through potential heap size increases. If we attempt a too eager reservation that fails, cut down on the // attempted size and reserve a smaller bump instead. (max 3 times, chosen somewhat arbitrarily) - for(var cutDown = 1; cutDown <= 4; cutDown *= 2) { + for (var cutDown = 1; cutDown <= 4; cutDown *= 2) { var overGrownHeapSize = oldSize * (1 + 0.2 / cutDown); // ensure geometric growth // but limit overreserving (default to capping at +96MB overgrowth at most) overGrownHeapSize = Math.min(overGrownHeapSize, requestedSize + 100663296 ); @@ -1845,7 +1845,7 @@ var ASM_CONSTS = { } var JSEvents={inEventHandler:0,removeAllEventListeners:function() { - for(var i = JSEvents.eventHandlers.length-1; i >= 0; --i) { + for (var i = JSEvents.eventHandlers.length-1; i >= 0; --i) { JSEvents._removeHandler(i); } JSEvents.eventHandlers = []; @@ -1859,13 +1859,13 @@ var ASM_CONSTS = { function arraysHaveEqualContent(arrA, arrB) { if (arrA.length != arrB.length) return false; - for(var i in arrA) { + for (var i in arrA) { if (arrA[i] != arrB[i]) return false; } return true; } // Test if the given call was already queued, and if so, don't add it again. - for(var i in JSEvents.deferredCalls) { + for (var i in JSEvents.deferredCalls) { var call = JSEvents.deferredCalls[i]; if (call.targetFunction == targetFunction && arraysHaveEqualContent(call.argsList, argsList)) { return; @@ -1879,7 +1879,7 @@ var ASM_CONSTS = { JSEvents.deferredCalls.sort(function(x,y) { return x.precedence < y.precedence; }); },removeDeferredCalls:function(targetFunction) { - for(var i = 0; i < JSEvents.deferredCalls.length; ++i) { + for (var i = 0; i < JSEvents.deferredCalls.length; ++i) { if (JSEvents.deferredCalls[i].targetFunction == targetFunction) { JSEvents.deferredCalls.splice(i, 1); --i; @@ -1891,14 +1891,14 @@ var ASM_CONSTS = { if (!JSEvents.canPerformEventHandlerRequests()) { return; } - for(var i = 0; i < JSEvents.deferredCalls.length; ++i) { + for (var i = 0; i < JSEvents.deferredCalls.length; ++i) { var call = JSEvents.deferredCalls[i]; JSEvents.deferredCalls.splice(i, 1); --i; call.targetFunction.apply(null, call.argsList); } },eventHandlers:[],removeAllHandlersOnTarget:function(target, eventTypeString) { - for(var i = 0; i < JSEvents.eventHandlers.length; ++i) { + for (var i = 0; i < JSEvents.eventHandlers.length; ++i) { if (JSEvents.eventHandlers[i].target == target && (!eventTypeString || eventTypeString == JSEvents.eventHandlers[i].eventTypeString)) { JSEvents._removeHandler(i--); @@ -1929,7 +1929,7 @@ var ASM_CONSTS = { JSEvents.eventHandlers.push(eventHandler); JSEvents.registerRemoveEventListeners(); } else { - for(var i = 0; i < JSEvents.eventHandlers.length; ++i) { + for (var i = 0; i < JSEvents.eventHandlers.length; ++i) { if (JSEvents.eventHandlers[i].target == eventHandler.target && JSEvents.eventHandlers[i].eventTypeString == eventHandler.eventTypeString) { JSEvents._removeHandler(i--); @@ -2079,7 +2079,7 @@ var ASM_CONSTS = { // Closure is expected to be allowed to minify the '.multiDrawWebgl' property, so not accessing it quoted. return !!(ctx.multiDrawWebgl = ctx.getExtension('WEBGL_multi_draw')); } - var GL={counter:1,buffers:[],mappedBuffers:{},programs:[],framebuffers:[],renderbuffers:[],textures:[],uniforms:[],shaders:[],vaos:[],contexts:[],offscreenCanvases:{},timerQueriesEXT:[],queries:[],samplers:[],transformFeedbacks:[],syncs:[],byteSizeByTypeRoot:5120,byteSizeByType:[1,1,2,2,4,4,4,2,3,4,8],programInfos:{},stringCache:{},stringiCache:{},unpackAlignment:4,recordError:function recordError(errorCode) { + var GL={counter:1,buffers:[],mappedBuffers:{},programs:[],framebuffers:[],renderbuffers:[],textures:[],shaders:[],vaos:[],contexts:[],offscreenCanvases:{},queries:[],samplers:[],transformFeedbacks:[],syncs:[],byteSizeByTypeRoot:5120,byteSizeByType:[1,1,2,2,4,4,4,2,3,4,8],stringCache:{},stringiCache:{},unpackAlignment:4,recordError:function recordError(errorCode) { if (!GL.lastError) { GL.lastError = errorCode; } @@ -2090,7 +2090,7 @@ var ASM_CONSTS = { } return ret; },MAX_TEMP_BUFFER_SIZE:2097152,numTempVertexBuffersPerSize:64,log2ceilLookup:function(i) { - return 32 - Math.clz32(i-1); + return 32 - Math.clz32(i === 0 ? 0 : i - 1); },generateTempBuffers:function(quads, context) { var largestIndex = GL.log2ceilLookup(GL.MAX_TEMP_BUFFER_SIZE); context.tempVertexBufferCounters1 = []; @@ -2218,6 +2218,19 @@ var ASM_CONSTS = { } },createContext:function(canvas, webGLContextAttributes) { + // BUG: Workaround Safari WebGL issue: After successfully acquiring WebGL context on a canvas, + // calling .getContext() will always return that context independent of which 'webgl' or 'webgl2' + // context version was passed. See https://bugs.webkit.org/show_bug.cgi?id=222758 and + // https://github.com/emscripten-core/emscripten/issues/13295. + // TODO: Once the bug is fixed and shipped in Safari, adjust the Safari version field in above check. + if (!canvas.getContextSafariWebGL2Fixed) { + canvas.getContextSafariWebGL2Fixed = canvas.getContext; + canvas.getContext = function(ver, attrs) { + var gl = canvas.getContextSafariWebGL2Fixed(ver, attrs); + return ((ver == 'webgl') == (gl instanceof WebGLRenderingContext)) ? gl : null; + } + } + var ctx = (webGLContextAttributes.majorVersion > 1) ? @@ -2290,7 +2303,21 @@ var ASM_CONSTS = { __webgl_enable_WEBGL_draw_instanced_base_vertex_base_instance(GLctx); __webgl_enable_WEBGL_multi_draw_instanced_base_vertex_base_instance(GLctx); - GLctx.disjointTimerQueryExt = GLctx.getExtension("EXT_disjoint_timer_query"); + // On WebGL 2, EXT_disjoint_timer_query is replaced with an alternative + // that's based on core APIs, and exposes only the queryCounterEXT() + // entrypoint. + if (context.version >= 2) { + GLctx.disjointTimerQueryExt = GLctx.getExtension("EXT_disjoint_timer_query_webgl2"); + } + + // However, Firefox exposes the WebGL 1 version on WebGL 2 as well and + // thus we look for the WebGL 1 version again if the WebGL 2 version + // isn't present. https://bugzilla.mozilla.org/show_bug.cgi?id=1328882 + if (context.version < 2 || !GLctx.disjointTimerQueryExt) + { + GLctx.disjointTimerQueryExt = GLctx.getExtension("EXT_disjoint_timer_query"); + } + __webgl_enable_WEBGL_multi_draw(GLctx); // .getSupportedExtensions() can return null if context is lost, so coerce to empty array. @@ -2302,49 +2329,6 @@ var ASM_CONSTS = { GLctx.getExtension(ext); } }); - },populateUniformTable:function(program) { - var p = GL.programs[program]; - var ptable = GL.programInfos[program] = { - uniforms: {}, - maxUniformLength: 0, // This is eagerly computed below, since we already enumerate all uniforms anyway. - maxAttributeLength: -1, // This is lazily computed and cached, computed when/if first asked, "-1" meaning not computed yet. - maxUniformBlockNameLength: -1 // Lazily computed as well - }; - - var utable = ptable.uniforms; - // A program's uniform table maps the string name of an uniform to an integer location of that uniform. - // The global GL.uniforms map maps integer locations to WebGLUniformLocations. - var numUniforms = GLctx.getProgramParameter(p, 0x8B86/*GL_ACTIVE_UNIFORMS*/); - for (var i = 0; i < numUniforms; ++i) { - var u = GLctx.getActiveUniform(p, i); - - var name = u.name; - ptable.maxUniformLength = Math.max(ptable.maxUniformLength, name.length+1); - - // If we are dealing with an array, e.g. vec4 foo[3], strip off the array index part to canonicalize that "foo", "foo[]", - // and "foo[0]" will mean the same. Loop below will populate foo[1] and foo[2]. - if (name.slice(-1) == ']') { - name = name.slice(0, name.lastIndexOf('[')); - } - - // Optimize memory usage slightly: If we have an array of uniforms, e.g. 'vec3 colors[3];', then - // only store the string 'colors' in utable, and 'colors[0]', 'colors[1]' and 'colors[2]' will be parsed as 'colors'+i. - // Note that for the GL.uniforms table, we still need to fetch the all WebGLUniformLocations for all the indices. - var loc = GLctx.getUniformLocation(p, name); - if (loc) { - var id = GL.getNewId(GL.uniforms); - utable[name] = [u.size, id]; - GL.uniforms[id] = loc; - - for (var j = 1; j < u.size; ++j) { - var n = name + '['+j+']'; - loc = GLctx.getUniformLocation(p, n); - id = GL.getNewId(GL.uniforms); - - GL.uniforms[id] = loc; - } - } - } }}; var __emscripten_webgl_power_preferences=['default', 'low-power', 'high-performance']; @@ -2391,7 +2375,7 @@ var ASM_CONSTS = { function _emscripten_webgl_init_context_attributes(attributes) { assert(attributes); var a = attributes >> 2; - for(var i = 0; i < (56>>2); ++i) { + for (var i = 0; i < (56>>2); ++i) { HEAP32[a+i] = 0; } @@ -2455,8 +2439,7 @@ var ASM_CONSTS = { } function _glAttachShader(program, shader) { - GLctx.attachShader(GL.programs[program], - GL.shaders[shader]); + GLctx.attachShader(GL.programs[program], GL.shaders[shader]); } function _glBindBuffer(target, buffer) { @@ -2516,7 +2499,11 @@ var ASM_CONSTS = { function _glCreateProgram() { var id = GL.getNewId(GL.programs); var program = GLctx.createProgram(); + // Store additional information needed for each shader program: program.name = id; + // Lazy cache results of glGetProgramiv(GL_ACTIVE_UNIFORM_MAX_LENGTH/GL_ACTIVE_ATTRIBUTE_MAX_LENGTH/GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH) + program.maxUniformLength = program.maxAttributeLength = program.maxUniformBlockNameLength = 0; + program.uniformIdCounter = 1; GL.programs[id] = program; return id; } @@ -2524,6 +2511,7 @@ var ASM_CONSTS = { function _glCreateShader(shaderType) { var id = GL.getNewId(GL.shaders); GL.shaders[id] = GLctx.createShader(shaderType); + return id; } @@ -2557,7 +2545,6 @@ var ASM_CONSTS = { GLctx.deleteProgram(program); program.name = 0; GL.programs[id] = null; - GL.programInfos[id] = null; } function _glDeleteShader(id) { @@ -2650,42 +2637,35 @@ var ASM_CONSTS = { return; } - var ptable = GL.programInfos[program]; - if (!ptable) { - GL.recordError(0x502 /* GL_INVALID_OPERATION */); - return; - } + program = GL.programs[program]; if (pname == 0x8B84) { // GL_INFO_LOG_LENGTH - var log = GLctx.getProgramInfoLog(GL.programs[program]); + var log = GLctx.getProgramInfoLog(program); if (log === null) log = '(unknown error)'; HEAP32[((p)>>2)] = log.length + 1; } else if (pname == 0x8B87 /* GL_ACTIVE_UNIFORM_MAX_LENGTH */) { - HEAP32[((p)>>2)] = ptable.maxUniformLength; + if (!program.maxUniformLength) { + for (var i = 0; i < GLctx.getProgramParameter(program, 0x8B86/*GL_ACTIVE_UNIFORMS*/); ++i) { + program.maxUniformLength = Math.max(program.maxUniformLength, GLctx.getActiveUniform(program, i).name.length+1); + } + } + HEAP32[((p)>>2)] = program.maxUniformLength; } else if (pname == 0x8B8A /* GL_ACTIVE_ATTRIBUTE_MAX_LENGTH */) { - if (ptable.maxAttributeLength == -1) { - program = GL.programs[program]; - var numAttribs = GLctx.getProgramParameter(program, 0x8B89/*GL_ACTIVE_ATTRIBUTES*/); - ptable.maxAttributeLength = 0; // Spec says if there are no active attribs, 0 must be returned. - for (var i = 0; i < numAttribs; ++i) { - var activeAttrib = GLctx.getActiveAttrib(program, i); - ptable.maxAttributeLength = Math.max(ptable.maxAttributeLength, activeAttrib.name.length+1); + if (!program.maxAttributeLength) { + for (var i = 0; i < GLctx.getProgramParameter(program, 0x8B89/*GL_ACTIVE_ATTRIBUTES*/); ++i) { + program.maxAttributeLength = Math.max(program.maxAttributeLength, GLctx.getActiveAttrib(program, i).name.length+1); } } - HEAP32[((p)>>2)] = ptable.maxAttributeLength; + HEAP32[((p)>>2)] = program.maxAttributeLength; } else if (pname == 0x8A35 /* GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH */) { - if (ptable.maxUniformBlockNameLength == -1) { - program = GL.programs[program]; - var numBlocks = GLctx.getProgramParameter(program, 0x8A36/*GL_ACTIVE_UNIFORM_BLOCKS*/); - ptable.maxUniformBlockNameLength = 0; - for (var i = 0; i < numBlocks; ++i) { - var activeBlockName = GLctx.getActiveUniformBlockName(program, i); - ptable.maxUniformBlockNameLength = Math.max(ptable.maxUniformBlockNameLength, activeBlockName.length+1); + if (!program.maxUniformBlockNameLength) { + for (var i = 0; i < GLctx.getProgramParameter(program, 0x8A36/*GL_ACTIVE_UNIFORM_BLOCKS*/); ++i) { + program.maxUniformBlockNameLength = Math.max(program.maxUniformBlockNameLength, GLctx.getActiveUniformBlockName(program, i).length+1); } } - HEAP32[((p)>>2)] = ptable.maxUniformBlockNameLength; + HEAP32[((p)>>2)] = program.maxUniformBlockNameLength; } else { - HEAP32[((p)>>2)] = GLctx.getProgramParameter(GL.programs[program], pname); + HEAP32[((p)>>2)] = GLctx.getProgramParameter(program, pname); } } @@ -2728,27 +2708,91 @@ var ASM_CONSTS = { return parseInt(str); } function _glGetUniformLocation(program, name) { + // Returns the index of '[' character in an uniform that represents an array of uniforms (e.g. colors[10]) + // Closure does counterproductive inlining: https://github.com/google/closure-compiler/issues/3203, so prevent + // inlining manually. + /** @noinline */ + function getLeftBracePos(name) { + return name.slice(-1) == ']' && name.lastIndexOf('['); + } + name = UTF8ToString(name); + program = GL.programs[program]; + var uniformLocsById = program.uniformLocsById; // Maps GLuint -> WebGLUniformLocation + var uniformSizeAndIdsByName = program.uniformSizeAndIdsByName; // Maps name -> [uniform array length, GLuint] + var i, j; var arrayIndex = 0; + var uniformBaseName = name; + + // Invariant: when populating integer IDs for uniform locations, we must maintain the precondition that + // arrays reside in contiguous addresses, i.e. for a 'vec4 colors[10];', colors[4] must be at location colors[0]+4. + // However, user might call glGetUniformLocation(program, "colors") for an array, so we cannot discover based on the user + // input arguments whether the uniform we are dealing with is an array. The only way to discover which uniforms are arrays + // is to enumerate over all the active uniforms in the program. + var leftBrace = getLeftBracePos(name); + + // On the first time invocation of glGetUniformLocation on this shader program: + // initialize cache data structures and discover which uniforms are arrays. + if (!uniformLocsById) { + // maps GLint integer locations to WebGLUniformLocations + program.uniformLocsById = uniformLocsById = {}; + // maps integer locations back to uniform name strings, so that we can lazily fetch uniform array locations + program.uniformArrayNamesById = {}; + + for (i = 0; i < GLctx.getProgramParameter(program, 0x8B86/*GL_ACTIVE_UNIFORMS*/); ++i) { + var u = GLctx.getActiveUniform(program, i); + var nm = u.name; + var sz = u.size; + var lb = getLeftBracePos(nm); + var arrayName = lb > 0 ? nm.slice(0, lb) : nm; + + // Assign a new location. + var id = program.uniformIdCounter; + program.uniformIdCounter += sz; + // Eagerly get the location of the uniformArray[0] base element. + // The remaining indices >0 will be left for lazy evaluation to + // improve performance. Those may never be needed to fetch, if the + // application fills arrays always in full starting from the first + // element of the array. + uniformSizeAndIdsByName[arrayName] = [sz, id]; + + // Store placeholder integers in place that highlight that these + // >0 index locations are array indices pending population. + for(j = 0; j < sz; ++j) { + uniformLocsById[id] = j; + program.uniformArrayNamesById[id++] = arrayName; + } + } + } + // If user passed an array accessor "[index]", parse the array index off the accessor. - if (name[name.length - 1] == ']') { - var leftBrace = name.lastIndexOf('['); - arrayIndex = name[leftBrace+1] != ']' ? jstoi_q(name.slice(leftBrace + 1)) : 0; // "index]", parseInt will ignore the ']' at the end; but treat "foo[]" as "foo[0]" - name = name.slice(0, leftBrace); + if (leftBrace > 0) { + arrayIndex = jstoi_q(name.slice(leftBrace + 1)) >>> 0; // "index]", coerce parseInt(']') with >>>0 to treat "foo[]" as "foo[0]" and foo[-1] as unsigned out-of-bounds. + uniformBaseName = name.slice(0, leftBrace); } - var uniformInfo = GL.programInfos[program] && GL.programInfos[program].uniforms[name]; // returns pair [ dimension_of_uniform_array, uniform_location ] - if (uniformInfo && arrayIndex >= 0 && arrayIndex < uniformInfo[0]) { // Check if user asked for an out-of-bounds element, i.e. for 'vec4 colors[3];' user could ask for 'colors[10]' which should return -1. - return uniformInfo[1] + arrayIndex; - } else { - return -1; + // Have we cached the location of this uniform before? + var sizeAndId = uniformSizeAndIdsByName[uniformBaseName]; // A pair [array length, GLint of the uniform location] + + // If an uniform with this name exists, and if its index is within the array limits (if it's even an array), + // query the WebGLlocation, or return an existing cached location. + if (sizeAndId && arrayIndex < sizeAndId[0]) { + arrayIndex += sizeAndId[1]; // Add the base location of the uniform to the array index offset. + if ((uniformLocsById[arrayIndex] = uniformLocsById[arrayIndex] || GLctx.getUniformLocation(program, name))) { + return arrayIndex; + } } + return -1; } function _glLinkProgram(program) { - GLctx.linkProgram(GL.programs[program]); - GL.populateUniformTable(program); + program = GL.programs[program]; + GLctx.linkProgram(program); + // Invalidate earlier computed uniform->ID mappings, those have now become stale + program.uniformLocsById = 0; // Mark as null-like so that glGetUniformLocation() knows to populate this again. + program.uniformSizeAndIdsByName = {}; + } function _glShaderSource(shader, count, string, length) { @@ -2757,11 +2801,25 @@ var ASM_CONSTS = { GLctx.shaderSource(GL.shaders[shader], source); } + function webglGetUniformLocation(location) { + var p = GLctx.currentProgram; + var webglLoc = p.uniformLocsById[location]; + // p.uniformLocsById[location] stores either an integer, or a WebGLUniformLocation. + + // If an integer, we have not yet bound the location, so do it now. The integer value specifies the array index + // we should bind to. + if (webglLoc >= 0) { + p.uniformLocsById[location] = webglLoc = GLctx.getUniformLocation(p, p.uniformArrayNamesById[location] + (webglLoc > 0 ? '[' + webglLoc + ']' : '')); + } + // Else an already cached WebGLUniformLocation, return it. + return webglLoc; + } + var miniTempWebGLFloatBuffers=[]; function _glUniformMatrix4fv(location, count, transpose, value) { if (GL.currentContext.version >= 2) { // WebGL 2 provides new garbage-free entry points to call to WebGL. Use those always when possible. - GLctx.uniformMatrix4fv(GL.uniforms[location], !!transpose, HEAPF32, value>>2, count*16); + GLctx.uniformMatrix4fv(webglGetUniformLocation(location), !!transpose, HEAPF32, value>>2, count*16); return; } @@ -2794,11 +2852,15 @@ var ASM_CONSTS = { { var view = HEAPF32.subarray((value)>>2, (value+count*64)>>2); } - GLctx.uniformMatrix4fv(GL.uniforms[location], !!transpose, view); + GLctx.uniformMatrix4fv(webglGetUniformLocation(location), !!transpose, view); } function _glUseProgram(program) { - GLctx.useProgram(GL.programs[program]); + program = GL.programs[program]; + GLctx.useProgram(program); + // Record the currently active program so that we can access the uniform + // mapping table of that program. + GLctx.currentProgram = program; } function _glVertexAttribPointer(index, size, type, normalized, stride, ptr) { @@ -3010,6 +3072,10 @@ if (!Object.getOwnPropertyDescriptor(Module, "ENV")) Module["ENV"] = function() if (!Object.getOwnPropertyDescriptor(Module, "ERRNO_CODES")) Module["ERRNO_CODES"] = function() { abort("'ERRNO_CODES' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "ERRNO_MESSAGES")) Module["ERRNO_MESSAGES"] = function() { abort("'ERRNO_MESSAGES' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "setErrNo")) Module["setErrNo"] = function() { abort("'setErrNo' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; +if (!Object.getOwnPropertyDescriptor(Module, "inetPton4")) Module["inetPton4"] = function() { abort("'inetPton4' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; +if (!Object.getOwnPropertyDescriptor(Module, "inetNtop4")) Module["inetNtop4"] = function() { abort("'inetNtop4' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; +if (!Object.getOwnPropertyDescriptor(Module, "inetPton6")) Module["inetPton6"] = function() { abort("'inetPton6' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; +if (!Object.getOwnPropertyDescriptor(Module, "inetNtop6")) Module["inetNtop6"] = function() { abort("'inetNtop6' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "readSockaddr")) Module["readSockaddr"] = function() { abort("'readSockaddr' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "writeSockaddr")) Module["writeSockaddr"] = function() { abort("'writeSockaddr' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "DNS")) Module["DNS"] = function() { abort("'DNS' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; @@ -3033,7 +3099,12 @@ if (!Object.getOwnPropertyDescriptor(Module, "dynCallLegacy")) Module["dynCallLe if (!Object.getOwnPropertyDescriptor(Module, "getDynCaller")) Module["getDynCaller"] = function() { abort("'getDynCaller' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "dynCall")) Module["dynCall"] = function() { abort("'dynCall' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "callRuntimeCallbacks")) Module["callRuntimeCallbacks"] = function() { abort("'callRuntimeCallbacks' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; -if (!Object.getOwnPropertyDescriptor(Module, "abortStackOverflow")) Module["abortStackOverflow"] = function() { abort("'abortStackOverflow' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; +if (!Object.getOwnPropertyDescriptor(Module, "runtimeKeepaliveCounter")) Module["runtimeKeepaliveCounter"] = function() { abort("'runtimeKeepaliveCounter' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; +if (!Object.getOwnPropertyDescriptor(Module, "keepRuntimeAlive")) Module["keepRuntimeAlive"] = function() { abort("'keepRuntimeAlive' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; +if (!Object.getOwnPropertyDescriptor(Module, "runtimeKeepalivePush")) Module["runtimeKeepalivePush"] = function() { abort("'runtimeKeepalivePush' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; +if (!Object.getOwnPropertyDescriptor(Module, "runtimeKeepalivePop")) Module["runtimeKeepalivePop"] = function() { abort("'runtimeKeepalivePop' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; +if (!Object.getOwnPropertyDescriptor(Module, "callUserCallback")) Module["callUserCallback"] = function() { abort("'callUserCallback' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; +if (!Object.getOwnPropertyDescriptor(Module, "maybeExit")) Module["maybeExit"] = function() { abort("'maybeExit' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "reallyNegative")) Module["reallyNegative"] = function() { abort("'reallyNegative' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "unSign")) Module["unSign"] = function() { abort("'unSign' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "reSign")) Module["reSign"] = function() { abort("'reSign' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; @@ -3134,6 +3205,7 @@ if (!Object.getOwnPropertyDescriptor(Module, "emscriptenWebGLGet")) Module["emsc if (!Object.getOwnPropertyDescriptor(Module, "computeUnpackAlignedImageSize")) Module["computeUnpackAlignedImageSize"] = function() { abort("'computeUnpackAlignedImageSize' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "emscriptenWebGLGetTexPixelData")) Module["emscriptenWebGLGetTexPixelData"] = function() { abort("'emscriptenWebGLGetTexPixelData' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "emscriptenWebGLGetUniform")) Module["emscriptenWebGLGetUniform"] = function() { abort("'emscriptenWebGLGetUniform' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; +if (!Object.getOwnPropertyDescriptor(Module, "webglGetUniformLocation")) Module["webglGetUniformLocation"] = function() { abort("'webglGetUniformLocation' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "emscriptenWebGLGetVertexAttrib")) Module["emscriptenWebGLGetVertexAttrib"] = function() { abort("'emscriptenWebGLGetVertexAttrib' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "emscriptenWebGLGetBufferBinding")) Module["emscriptenWebGLGetBufferBinding"] = function() { abort("'emscriptenWebGLGetBufferBinding' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; if (!Object.getOwnPropertyDescriptor(Module, "emscriptenWebGLValidateMapBufferTarget")) Module["emscriptenWebGLValidateMapBufferTarget"] = function() { abort("'emscriptenWebGLValidateMapBufferTarget' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") }; @@ -3223,7 +3295,6 @@ function callMain(args) { return; } else if (e == 'unwind') { // running an evented main loop, don't immediately exit - noExitRuntime = true; return; } else { var toLog = e; @@ -3333,17 +3404,19 @@ function checkUnflushedContent() { /** @param {boolean|number=} implicit */ function exit(status, implicit) { + EXITSTATUS = status; + checkUnflushedContent(); // if this is just main exit-ing implicitly, and the status is 0, then we // don't need to do anything here and can just leave. if the status is // non-zero, though, then we need to report it. // (we may have warned about this earlier, if a situation justifies doing so) - if (implicit && noExitRuntime && status === 0) { + if (implicit && keepRuntimeAlive() && status === 0) { return; } - if (noExitRuntime) { + if (keepRuntimeAlive()) { // if exit() was called, we may warn the user if the runtime isn't actually being shut down if (!implicit) { var msg = 'program exited (with status: ' + status + '), but EXIT_RUNTIME is not set, so halting execution but not exiting the runtime or preventing further async execution (build with EXIT_RUNTIME=1, if you want a true shutdown)'; @@ -3351,8 +3424,6 @@ function exit(status, implicit) { } } else { - EXITSTATUS = status; - exitRuntime(); if (Module['onExit']) Module['onExit'](status); diff --git a/2d/rigidbody/rigidbody_1/dist/output.wasm b/2d/rigidbody/rigidbody_1/dist/output.wasm Binary files differindex c091140..c2fa2dd 100755 --- a/2d/rigidbody/rigidbody_1/dist/output.wasm +++ b/2d/rigidbody/rigidbody_1/dist/output.wasm diff --git a/2d/rigidbody/rigidbody_1/main.cpp b/2d/rigidbody/rigidbody_1/main.cpp index 8d4ab0e..321e3e5 100644 --- a/2d/rigidbody/rigidbody_1/main.cpp +++ b/2d/rigidbody/rigidbody_1/main.cpp @@ -11,6 +11,8 @@ #include <cmath> #include <cfloat> +const float32 MAX_VELOCITY = 200.f; + struct Rigidbody { Vector2 force = { 0, 0 }; Vector2 velocity = { 0, 0 }; @@ -35,6 +37,14 @@ struct Rigidbody { Vector2 acceleration = force / mass; velocity += (acceleration * deltaTimeSeconds); + + if (ABS(velocity.x) > MAX_VELOCITY) { + velocity.x = SIGN(velocity.x) * MAX_VELOCITY; + } + if (ABS(velocity.y) > MAX_VELOCITY) { + velocity.y = SIGN(velocity.y) * MAX_VELOCITY; + } + position += (velocity * deltaTimeSeconds); force = Vector2 { 0.f, 0.f }; } diff --git a/2d/rigidbody/rigidbody_1/snippet1.cpp b/2d/rigidbody/rigidbody_1/snippet1.cpp new file mode 100644 index 0000000..77a81b7 --- /dev/null +++ b/2d/rigidbody/rigidbody_1/snippet1.cpp @@ -0,0 +1,8 @@ + +struct Rigidbody { + Vector2 force = { 0, 0 }; + Vector2 acceleration = { 0, 0 }; + Vector2 velocity = { 0, 0 }; + Vector2 position = { 0, 0 }; + float32 mass = 1.f; +}; diff --git a/2d/rigidbody/rigidbody_1/snippet2.cpp b/2d/rigidbody/rigidbody_1/snippet2.cpp new file mode 100644 index 0000000..8ad468c --- /dev/null +++ b/2d/rigidbody/rigidbody_1/snippet2.cpp @@ -0,0 +1,25 @@ + + +struct Rigidbody { + Vector2 force = { 0, 0 }; + Vector2 velocity = { 0, 0 }; + Vector2 position = { 0, 0 }; + float32 mass = 1.f; + + void applyForce(Vector2 f) { + force += f; + } + + void applyGravity(float32 deltaTimeSeconds) { + velocity += (Vector2 { 0.f, -50.f } * deltaTimeSeconds); + } + + void update(float32 deltaTimeSeconds) { + applyGravity(deltaTimeSeconds); + + Vector2 acceleration = force / mass; + velocity += (acceleration * deltaTimeSeconds); + position += (velocity * deltaTimeSeconds); + force = Vector2 { 0.f, 0.f }; + } +}; @@ -52,30 +52,22 @@ <h1>Introduction: Rigid Body Physics</h1> <section> <p> - 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. + You're most likely here because you have some interest in the world of realtime physics simulation. Maybe you have some knowledge of rendering via OpenGL or Vulkan, + and you want to see 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 building a variety of physics simulations - 2D and 3D rigidboy, soft body, collision algorithms - entirely in the web. All of this information will be extendable to other languages, but we will use + C++ and OpenGL compiled to WebAssembly and WebGL in this tutorial series. </p> <p> - In implementing a rigidy body physics system, we're primarily interested in two sub-fields of physics, namely <b>dynamics</b> and <b>kinematics</b>. 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: - <ul> - <li> - <b>Kinematics</b> 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. - </li> - <li> - <b>Dynamics</b> is the study of whats <i>causes</i> kinematic movement. These are the classic force and momentum equations that you may already be familiar - with as well. - </li> - </ul> + My primary goal in this website is to display how a competent programmer might <i>implement</i> different types of believable physics simulations. Because of this, I will not spend too much time discussing physics theory, unless it is absolutely necessary that I do so for a particualr section. Physics - especially the world Newtonian physics where most game engines spend their time - is a well-documented subject, and I would be doing a great injustice to the field if I were to consider myself qualified at explaining it. I am no physicst by trade, so I will leave that talk to the big brains at the universities. + </p> + <p> + Each of the tutorials on the sidebar are meant to be self-contained, so long as you have a good understanding of the fundamentals of vector calculus. Which leads me to my next disclaimer: I am assuming competency in both some programming language as well as vector calculus/linear algebra in these tutorials. The math that you need to know in order to implement these simualtions isn't all that difficult, but, as with fix, I consider myself hardly qualified at explaining them, as I only do math so much as it is useful to me. Many many books and stackoverflow posts have been written on these subjects, and I am sure that you will not encounter any trouble at all if you wish to study them yourself. </p> <p> - 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. + Finally, I feel the need to provide some references up front which I have found particularly useful in my studies of physics simulations. Those being: + <ul> + <li><a href="http://www.chrishecker.com/Rigid_Body_Dynamics">Chris Hecker's Rigid Body Dynamics Papers</a>: I would be a liar and a cheat if I didn't say that I owe Chris Hecker all of my gratitude for getting me interseted in rigid body physics in the first place. Before you even begin to navigate my website, I recommend that you read all of his papers on Rigid Body physics, as he manages to explain the subject in a way that anyone with very minimal math knowledge can understand.</li> + </ul> </p> </section> </article> diff --git a/index.html.content b/index.html.content index dab9097..07eeea2 100644 --- a/index.html.content +++ b/index.html.content @@ -2,30 +2,22 @@ <h1>Introduction: Rigid Body Physics</h1> <section> <p> - 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. + You're most likely here because you have some interest in the world of realtime physics simulation. Maybe you have some knowledge of rendering via OpenGL or Vulkan, + and you want to see 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 building a variety of physics simulations - 2D and 3D rigidboy, soft body, collision algorithms - entirely in the web. All of this information will be extendable to other languages, but we will use + C++ and OpenGL compiled to WebAssembly and WebGL in this tutorial series. </p> <p> - In implementing a rigidy body physics system, we're primarily interested in two sub-fields of physics, namely <b>dynamics</b> and <b>kinematics</b>. 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: - <ul> - <li> - <b>Kinematics</b> 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. - </li> - <li> - <b>Dynamics</b> is the study of whats <i>causes</i> kinematic movement. These are the classic force and momentum equations that you may already be familiar - with as well. - </li> - </ul> + My primary goal in this website is to display how a competent programmer might <i>implement</i> different types of believable physics simulations. Because of this, I will not spend too much time discussing physics theory, unless it is absolutely necessary that I do so for a particualr section. Physics - especially the world Newtonian physics where most game engines spend their time - is a well-documented subject, and I would be doing a great injustice to the field if I were to consider myself qualified at explaining it. I am no physicst by trade, so I will leave that talk to the big brains at the universities. + </p> + <p> + Each of the tutorials on the sidebar are meant to be self-contained, so long as you have a good understanding of the fundamentals of vector calculus. Which leads me to my next disclaimer: I am assuming competency in both some programming language as well as vector calculus/linear algebra in these tutorials. The math that you need to know in order to implement these simualtions isn't all that difficult, but, as with fix, I consider myself hardly qualified at explaining them, as I only do math so much as it is useful to me. Many many books and stackoverflow posts have been written on these subjects, and I am sure that you will not encounter any trouble at all if you wish to study them yourself. </p> <p> - 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. + Finally, I feel the need to provide some references up front which I have found particularly useful in my studies of physics simulations. Those being: + <ul> + <li><a href="http://www.chrishecker.com/Rigid_Body_Dynamics">Chris Hecker's Rigid Body Dynamics Papers</a>: I would be a liar and a cheat if I didn't say that I owe Chris Hecker all of my gratitude for getting me interseted in rigid body physics in the first place. Before you even begin to navigate my website, I recommend that you read all of his papers on Rigid Body physics, as he manages to explain the subject in a way that anyone with very minimal math knowledge can understand.</li> + </ul> </p> </section> </article> diff --git a/roadmap.html b/roadmap.html index 634f894..c2c4aa5 100644 --- a/roadmap.html +++ b/roadmap.html @@ -107,6 +107,7 @@ <li class="done">Remove (or hide) ellipse collision pages for now</li> <li class="done">3D scene setup</li> <li>3D scene basic physics with spheres bouncing around a scene</li> + <li class="halfway">Redo of all 2d rigidbody physics</li> </ul> <p style="font-size: 18px;"> Obviously, the month of April did not go as planned. I got caught up with a number of tasks that I couldn't handle, diff --git a/roadmap.html.content b/roadmap.html.content index ace081c..c20a0ef 100644 --- a/roadmap.html.content +++ b/roadmap.html.content @@ -57,6 +57,7 @@ <li class="done">Remove (or hide) ellipse collision pages for now</li> <li class="done">3D scene setup</li> <li>3D scene basic physics with spheres bouncing around a scene</li> + <li class="halfway">Redo of all 2d rigidbody physics</li> </ul> <p style="font-size: 18px;"> Obviously, the month of April did not go as planned. I got caught up with a number of tasks that I couldn't handle, diff --git a/shared_cpp/mathlib.h b/shared_cpp/mathlib.h index c922032..fc2ef0c 100644 --- a/shared_cpp/mathlib.h +++ b/shared_cpp/mathlib.h @@ -7,6 +7,7 @@ #define MAX(x, y) (((x) > (y)) ? (x) : (y)) #define MIN(x, y) (((x) < (y)) ? (x) : (y)) #define ABS(x) (x < 0 ? -x : x) +#define SIGN(x) (x < 0 ? -1 : 1) struct Vector2 { float x = 0; |