diff options
author | Matthew Kosarek <mattkae@protonmail.com> | 2021-06-24 11:30:36 -0400 |
---|---|---|
committer | Matthew Kosarek <mattkae@protonmail.com> | 2021-06-24 11:30:36 -0400 |
commit | ff82253a66ee51fe2f0c088ca964402d53545845 (patch) | |
tree | 69cd50843b0865dd521bff129a73400729254137 | |
parent | 322df8c2a1aa32210102b2924b44be6e20cdf8ae (diff) |
(mkosarek) Rigidbody with rotations
-rw-r--r-- | 2d/_collisions/polygon_polygon.html | 23 | ||||
-rw-r--r-- | 2d/_collisions/polygon_polygon.html.content | 23 | ||||
-rw-r--r-- | 2d/_collisions/polygon_polygon/dist/output.js | 313 | ||||
-rwxr-xr-x | 2d/_collisions/polygon_polygon/dist/output.wasm | bin | 56585 -> 57485 bytes | |||
-rw-r--r-- | 2d/_collisions/polygon_polygon/main.cpp | 77 | ||||
-rwxr-xr-x | 2d/rigidbody/rigidbody_2/dist/output.wasm | bin | 48126 -> 50226 bytes | |||
-rw-r--r-- | 2d/rigidbody/rigidbody_2/main.cpp | 80 | ||||
-rw-r--r-- | 2d/rigidbody/rigidbody_3.html | 2 | ||||
-rw-r--r-- | 2d/rigidbody/rigidbody_3.html.content | 2 | ||||
-rwxr-xr-x | 2d/rigidbody/rigidbody_3/dist/output.wasm | bin | 52832 -> 49458 bytes | |||
-rw-r--r-- | 2d/rigidbody/rigidbody_3/main.cpp | 366 |
11 files changed, 513 insertions, 373 deletions
diff --git a/2d/_collisions/polygon_polygon.html b/2d/_collisions/polygon_polygon.html index ce26f62..e8fe7ea 100644 --- a/2d/_collisions/polygon_polygon.html +++ b/2d/_collisions/polygon_polygon.html @@ -93,18 +93,35 @@ <p> Given two polygons <b>A</b> and <b>B</b>: <ol> - <li>For each edge on A, get the normal <i>n</i> of that edge.</li> + <li>For each edge on <b>A</b>, get the normal <i>n</i> of that edge.</li> <li>Project each vertex <i>v</i> of <b>A</b> onto <i>n</i>. Return the minimum and maximum projection of all vertices.</li> <li>Repeat Step 2 for polygon <b>B</b>.</li> <li>If the min and max projections found in Steps 2 and 3 do <b>NOT</b> overlap, the polygons are not intersecting. Return false.</li> <li>If the projections overlap for each edge of both shapes, the shapes are intersecting. Return true.</li> </ol> + + And that is all there is to <i>finding</i> the intersection between two convex polygons. + </p> + </section> + <section> + <h2>SAT Collision Resolution</h2> + <p> + Now that we know our objects have intersecting, we want to be able to send them tumbling away from each other to simulate a collision. To do this, we will need to find the following things: + <ul> + <li><b>Collision Normal</b>: in what direction, point towards object <b>A</b>, did the polygons intersect</li> + <li><b>Point of Application</b>: at what point on each object did the objects first intersect</li> + <li><b>Relative Velocity</b>: easily found by taking the difference between the two velocities. + </ul> + + <h3>Collision Normal</h3> + <p> + + </p> </p> - </section> <section> <h2> - Live Example + Live Example of Intersection Detection </h2> <div class="opengl_canvas_container"> <canvas id="gl_canvas" width="800" height="600"></canvas> diff --git a/2d/_collisions/polygon_polygon.html.content b/2d/_collisions/polygon_polygon.html.content index 2d6f6b7..ffa9b15 100644 --- a/2d/_collisions/polygon_polygon.html.content +++ b/2d/_collisions/polygon_polygon.html.content @@ -41,18 +41,35 @@ <p> Given two polygons <b>A</b> and <b>B</b>: <ol> - <li>For each edge on A, get the normal <i>n</i> of that edge.</li> + <li>For each edge on <b>A</b>, get the normal <i>n</i> of that edge.</li> <li>Project each vertex <i>v</i> of <b>A</b> onto <i>n</i>. Return the minimum and maximum projection of all vertices.</li> <li>Repeat Step 2 for polygon <b>B</b>.</li> <li>If the min and max projections found in Steps 2 and 3 do <b>NOT</b> overlap, the polygons are not intersecting. Return false.</li> <li>If the projections overlap for each edge of both shapes, the shapes are intersecting. Return true.</li> </ol> + + And that is all there is to <i>finding</i> the intersection between two convex polygons. + </p> + </section> + <section> + <h2>SAT Collision Resolution</h2> + <p> + Now that we know our objects have intersecting, we want to be able to send them tumbling away from each other to simulate a collision. To do this, we will need to find the following things: + <ul> + <li><b>Collision Normal</b>: in what direction, point towards object <b>A</b>, did the polygons intersect</li> + <li><b>Point of Application</b>: at what point on each object did the objects first intersect</li> + <li><b>Relative Velocity</b>: easily found by taking the difference between the two velocities. + </ul> + + <h3>Collision Normal</h3> + <p> + + </p> </p> - </section> <section> <h2> - Live Example + Live Example of Intersection Detection </h2> <div class="opengl_canvas_container"> <canvas id="gl_canvas" width="800" height="600"></canvas> diff --git a/2d/_collisions/polygon_polygon/dist/output.js b/2d/_collisions/polygon_polygon/dist/output.js index e1dfee3..42a4ed5 100644 --- a/2d/_collisions/polygon_polygon/dist/output.js +++ b/2d/_collisions/polygon_polygon/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--); @@ -2074,7 +2074,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; } @@ -2085,7 +2085,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 = []; @@ -2213,6 +2213,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) ? @@ -2285,7 +2298,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. @@ -2297,49 +2324,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']; @@ -2386,7 +2370,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; } @@ -2450,8 +2434,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) { @@ -2511,7 +2494,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; } @@ -2519,6 +2506,7 @@ var ASM_CONSTS = { function _glCreateShader(shaderType) { var id = GL.getNewId(GL.shaders); GL.shaders[id] = GLctx.createShader(shaderType); + return id; } @@ -2552,7 +2540,6 @@ var ASM_CONSTS = { GLctx.deleteProgram(program); program.name = 0; GL.programs[id] = null; - GL.programInfos[id] = null; } function _glDeleteShader(id) { @@ -2645,42 +2632,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); } } @@ -2723,27 +2703,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) { @@ -2752,11 +2796,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; } @@ -2789,11 +2847,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) { @@ -3004,6 +3066,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)") }; @@ -3027,7 +3093,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)") }; @@ -3128,6 +3199,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)") }; @@ -3217,7 +3289,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; @@ -3327,17 +3398,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)'; @@ -3345,8 +3418,6 @@ function exit(status, implicit) { } } else { - EXITSTATUS = status; - exitRuntime(); if (Module['onExit']) Module['onExit'](status); diff --git a/2d/_collisions/polygon_polygon/dist/output.wasm b/2d/_collisions/polygon_polygon/dist/output.wasm Binary files differindex fd9538c..a30f0c4 100755 --- a/2d/_collisions/polygon_polygon/dist/output.wasm +++ b/2d/_collisions/polygon_polygon/dist/output.wasm diff --git a/2d/_collisions/polygon_polygon/main.cpp b/2d/_collisions/polygon_polygon/main.cpp index 1633530..c458299 100644 --- a/2d/_collisions/polygon_polygon/main.cpp +++ b/2d/_collisions/polygon_polygon/main.cpp @@ -11,44 +11,86 @@ #include <cmath> #include <cfloat> -struct Rigidbody { +struct Impulse { Vector2 force = { 0, 0 }; + float32 timeOfApplicationSeconds = 0.25f; + float32 timeAppliedSeconds = 0.f; + bool isDead = false; +}; + +const int32 NUM_IMPULSES = 4; + +struct Rigidbody { + int32 numImpulses = 0; + Impulse activeImpulses[NUM_IMPULSES]; Vector2 velocity = { 0, 0 }; Vector2 position = { 0, 0 }; + float32 mass = 1.f; float32 rotationalVelocity = 0.f; float32 rotation = 0.f; - float32 mass = 1.f; float32 cofOfRestition = 1.f; float32 momentOfInertia = 1.f; void reset() { - force = { 0, 0 }; + numImpulses = 0; velocity = { 0, 0 }; - rotationalVelocity = 0.f; - rotation = 0.f; } - void applyForce(Vector2 f) { - force += f; + void setMomentOfInertia(float32 moi) { + momentOfInertia = moi; + } + + void applyImpulse(Impulse i) { + if (numImpulses > NUM_IMPULSES) { + printf("Unable to apply impulse. Buffer full.\n"); + return; + } + + activeImpulses[numImpulses] = i; + numImpulses++; } void applyGravity(float32 deltaTimeSeconds) { - velocity += (Vector2 { 0.f, -50.f } * deltaTimeSeconds); + velocity += (Vector2 { 0.f, -9.8f } * deltaTimeSeconds); } void update(float32 deltaTimeSeconds) { applyGravity(deltaTimeSeconds); - + + Vector2 force; + for (int32 idx = 0; idx < numImpulses; idx++) { + Impulse& i = activeImpulses[idx]; + + float32 nextTimeAppliedSeconds = i.timeAppliedSeconds + deltaTimeSeconds; + if (nextTimeAppliedSeconds >= i.timeOfApplicationSeconds) { + nextTimeAppliedSeconds = i.timeOfApplicationSeconds; // Do the remainder of the time + i.isDead = true; + } + + float32 impulseDtSeconds = nextTimeAppliedSeconds - i.timeAppliedSeconds; + Vector2 forceToApply = i.force * (impulseDtSeconds / i.timeOfApplicationSeconds); + force += forceToApply * impulseDtSeconds; + + + i.timeAppliedSeconds = nextTimeAppliedSeconds; + } + Vector2 acceleration = force / mass; velocity += (acceleration * deltaTimeSeconds); position += (velocity * deltaTimeSeconds); - force = Vector2 { 0.f, 0.f }; - rotation += (rotationalVelocity * deltaTimeSeconds); - } - void setMomentOfInertia(float32 moi) { - momentOfInertia = moi; + // Cleanup any impulses that have expired in the mean time + for (int32 idx = 0; idx < numImpulses; idx++) { + if (activeImpulses[idx].isDead) { + for (int j = idx + 1; j < numImpulses; j++) { + activeImpulses[j - 1] = activeImpulses[j]; + } + + idx = idx - 1; + numImpulses--; + } + } } }; @@ -125,13 +167,14 @@ struct ConvexPolygon { body.update(dtSeconds); shape.model = Mat4x4().translateByVec2(body.position).rotate2D(body.rotation); - // Populate the current position of our edges + // Populate the current position of our edges. Note that this might be slow depending + // on how many edges your shaped have. for (int vidx = 0; vidx < numVertices; vidx++) { Vector2 start = shape.model * originalVertices[vidx]; transformedVertices[vidx] = start; Vector2 end = shape.model * originalVertices[vidx == numVertices - 1 ? 0 : vidx + 1]; - edges[vidx] = { (end - start).getPerp().normalize(), start, end }; + edges[vidx] = { (end - start).getPerp(), start, end }; } } @@ -287,6 +330,7 @@ IntersectionResult getIntersection(ConvexPolygon* first, ConvexPolygon* second) if (overlap < minOverlap) { minOverlap = overlap; minOverlapEdge = &second->edges[i]; + minOverlapWasFirst = false; } } @@ -297,7 +341,6 @@ IntersectionResult getIntersection(ConvexPolygon* first, ConvexPolygon* second) // Time to find just where we intersected Vector2 closestPoint; float32 minDistance = FLT_MAX; - for (int p = 0; p < (minOverlapWasFirst ? second->numVertices : first->numVertices); p++) { Vector2 point = minOverlapWasFirst ? second->transformedVertices[p] : first->transformedVertices[p]; diff --git a/2d/rigidbody/rigidbody_2/dist/output.wasm b/2d/rigidbody/rigidbody_2/dist/output.wasm Binary files differindex f7dcbd9..29c319a 100755 --- a/2d/rigidbody/rigidbody_2/dist/output.wasm +++ b/2d/rigidbody/rigidbody_2/dist/output.wasm diff --git a/2d/rigidbody/rigidbody_2/main.cpp b/2d/rigidbody/rigidbody_2/main.cpp index 5f875b8..bad6949 100644 --- a/2d/rigidbody/rigidbody_2/main.cpp +++ b/2d/rigidbody/rigidbody_2/main.cpp @@ -11,46 +11,90 @@ #include <cmath> #include <cfloat> +struct Impulse { + Vector2 force = { 0, 0 }; + Vector2 pointOfApplication = { 0, 0 }; + float32 timeOfApplicationSeconds = 0.25f; + float32 timeAppliedSeconds = 0.f; + bool isDead = false; +}; + +const int32 NUM_IMPULSES = 4; + struct Rigidbody { - Vector2 linearForce = { 0, 0 }; + int32 numImpulses = 0; + Impulse activeImpulses[NUM_IMPULSES]; Vector2 velocity = { 0, 0 }; Vector2 position = { 0, 0 }; float32 mass = 1.f; - float32 torque = 0.f; float32 rotationalVelocity = 0.f; float32 rotation = 0.f; float32 momentOfInertia = 1.f; void reset() { - linearForce = { 0, 0 }; + numImpulses = 0; velocity = { 0, 0 }; rotationalVelocity = 0.f; rotation = 0.f; } - void applyForce(Vector2 force, Vector2 pointOfApplication) { - linearForce += force; - torque += pointOfApplication.getPerp().dot(force); + void applyImpulse(Impulse i) { + if (numImpulses > NUM_IMPULSES) { + printf("Unable to apply impulse. Buffer full.\n"); + return; + } + + activeImpulses[numImpulses] = i; + numImpulses++; } - void applyGravity() { - applyForce(Vector2 { 0.f, -100.f }, Vector2 { 0.f, 0.f }); + void applyGravity(float32 deltaTimeSeconds) { + velocity += (Vector2 { 0.f, -9.8f } * deltaTimeSeconds); } void update(float32 deltaTimeSeconds) { - applyGravity(); + applyGravity(deltaTimeSeconds); + + Vector2 force; + float32 torque = 0.f; + for (int32 idx = 0; idx < numImpulses; idx++) { + Impulse& i = activeImpulses[idx]; + + float32 nextTimeAppliedSeconds = i.timeAppliedSeconds + deltaTimeSeconds; + if (nextTimeAppliedSeconds >= i.timeOfApplicationSeconds) { + nextTimeAppliedSeconds = i.timeOfApplicationSeconds; // Do the remainder of the time + i.isDead = true; + } + + float32 impulseDtSeconds = nextTimeAppliedSeconds - i.timeAppliedSeconds; + Vector2 forceToApply = i.force * (impulseDtSeconds / i.timeOfApplicationSeconds); + force += forceToApply * impulseDtSeconds; + torque += i.pointOfApplication.getPerp().dot(forceToApply); + + i.timeAppliedSeconds = nextTimeAppliedSeconds; + } - Vector2 acceleration = linearForce / mass; + Vector2 acceleration = force / mass; velocity += (acceleration * deltaTimeSeconds); position += (velocity * deltaTimeSeconds); - linearForce = Vector2 { 0.f, 0.f }; - // New: Update the rotational velocity as well + // New: Update the rotational velocity as well float32 rotationalAcceleration = torque / momentOfInertia; rotationalVelocity += (rotationalAcceleration * deltaTimeSeconds); rotation += (rotationalVelocity * deltaTimeSeconds); - torque = 0.f; + + // Cleanup any impulses that have expired in the mean time + for (int32 idx = 0; idx < numImpulses; idx++) { + if (activeImpulses[idx].isDead) { + for (int j = idx + 1; j < numImpulses; j++) { + activeImpulses[j - 1] = activeImpulses[j]; + } + + idx = idx - 1; + numImpulses--; + } + } } }; @@ -112,6 +156,7 @@ struct Rectangle { struct Circle { OrthographicShape shape; Rigidbody body; + Vector2 force; float32 radius = 5.f; @@ -211,7 +256,10 @@ void update(float32 deltaTimeSeconds, void* userData) { if (!isIntersectingPointer) { isIntersectingPointer = true; Vector2 pointOfApplication = pointer.body.position - rectangle.body.position; - rectangle.body.applyForce(pointer.body.linearForce, pointOfApplication); + Impulse i; + i.force = pointer.force; + i.pointOfApplication = pointOfApplication; + rectangle.body.applyImpulse(i); } } else if (isIntersectingPointer) { isIntersectingPointer = false; @@ -270,8 +318,8 @@ EM_BOOL onMouseMove(int eventType, const EmscriptenMouseEvent *mouseEvent, void return true; } - pointer.body.linearForce.x = static_cast<float32>(mouseEvent->movementX) * 1000.f; - pointer.body.linearForce.y = static_cast<float32>(-mouseEvent->movementY) * 1000.f; + pointer.force.x = static_cast<float32>(mouseEvent->movementX) * 1000.f; + pointer.force.y = static_cast<float32>(-mouseEvent->movementY) * 1000.f; pointer.body.position.x = static_cast<float32>(mouseEvent->targetX); pointer.body.position.y = static_cast<float32>(600.f - mouseEvent->targetY); diff --git a/2d/rigidbody/rigidbody_3.html b/2d/rigidbody/rigidbody_3.html index 9cb1041..60ff03f 100644 --- a/2d/rigidbody/rigidbody_3.html +++ b/2d/rigidbody/rigidbody_3.html @@ -50,7 +50,7 @@ </li> </ul> </nav> -3<script src="./rigidbody_3/dist/output.js"></script> +<script src="./rigidbody_3/dist/output.js"></script> <script> window.onload = function() { var lPlayElement = document.getElementById('gl_canvas_play'), diff --git a/2d/rigidbody/rigidbody_3.html.content b/2d/rigidbody/rigidbody_3.html.content index 8985532..f79db4f 100644 --- a/2d/rigidbody/rigidbody_3.html.content +++ b/2d/rigidbody/rigidbody_3.html.content @@ -1,4 +1,4 @@ -3<script src="./rigidbody_3/dist/output.js"></script> +<script src="./rigidbody_3/dist/output.js"></script> <script> window.onload = function() { var lPlayElement = document.getElementById('gl_canvas_play'), diff --git a/2d/rigidbody/rigidbody_3/dist/output.wasm b/2d/rigidbody/rigidbody_3/dist/output.wasm Binary files differindex 1a6e153..b89435a 100755 --- a/2d/rigidbody/rigidbody_3/dist/output.wasm +++ b/2d/rigidbody/rigidbody_3/dist/output.wasm diff --git a/2d/rigidbody/rigidbody_3/main.cpp b/2d/rigidbody/rigidbody_3/main.cpp index 75379c4..e34f444 100644 --- a/2d/rigidbody/rigidbody_3/main.cpp +++ b/2d/rigidbody/rigidbody_3/main.cpp @@ -11,114 +11,139 @@ #include <cmath> #include <cfloat> + +struct Impulse { + Vector2 force = { 0, 0 }; + Vector2 pointOfApplication = { 0, 0 }; + float32 timeOfApplicationSeconds = 0.25f; + float32 timeAppliedSeconds = 0.f; + bool isDead = false; +}; + +const int32 NUM_IMPULSES = 4; + struct Rigidbody { - Vector2 linearForce = { 0, 0 }; + int32 numImpulses = 0; + Impulse activeImpulses[NUM_IMPULSES]; Vector2 velocity = { 0, 0 }; Vector2 position = { 0, 0 }; float32 mass = 1.f; - float32 torque = 0.f; float32 rotationalVelocity = 0.f; float32 rotation = 0.f; float32 momentOfInertia = 1.f; - float32 cofOfRestitution = 1.f; + + float32 cofOfRestitution = 1.f; void reset() { - linearForce = { 0, 0 }; + numImpulses = 0; velocity = { 0, 0 }; rotationalVelocity = 0.f; rotation = 0.f; } - void applyForce(Vector2 force, Vector2 pointOfApplication) { - linearForce += force; - torque += pointOfApplication.getPerp().dot(force); + void applyImpulse(Impulse i) { + if (numImpulses > NUM_IMPULSES) { + printf("Unable to apply impulse. Buffer full.\n"); + return; + } + + activeImpulses[numImpulses] = i; + numImpulses++; } - void applyGravity() { - applyForce(Vector2 { 0.f, -100.f }, Vector2 { 0.f, 0.f }); + void applyGravity(float32 deltaTimeSeconds) { + velocity += (Vector2 { 0.f, -9.8f } * deltaTimeSeconds); } void update(float32 deltaTimeSeconds) { - applyGravity(); + applyGravity(deltaTimeSeconds); + + Vector2 force; + float32 torque = 0.f; + for (int32 idx = 0; idx < numImpulses; idx++) { + Impulse& i = activeImpulses[idx]; + + float32 nextTimeAppliedSeconds = i.timeAppliedSeconds + deltaTimeSeconds; + if (nextTimeAppliedSeconds >= i.timeOfApplicationSeconds) { + nextTimeAppliedSeconds = i.timeOfApplicationSeconds; // Do the remainder of the time + i.isDead = true; + } + + float32 impulseDtSeconds = nextTimeAppliedSeconds - i.timeAppliedSeconds; + Vector2 forceToApply = i.force * (impulseDtSeconds / i.timeOfApplicationSeconds); + force += forceToApply * impulseDtSeconds; + torque += i.pointOfApplication.getPerp().dot(forceToApply); + + i.timeAppliedSeconds = nextTimeAppliedSeconds; + } - Vector2 acceleration = linearForce / mass; + Vector2 acceleration = force / mass; velocity += (acceleration * deltaTimeSeconds); position += (velocity * deltaTimeSeconds); - linearForce = Vector2 { 0.f, 0.f }; - // New: Update the rotational velocity as well float32 rotationalAcceleration = torque / momentOfInertia; rotationalVelocity += (rotationalAcceleration * deltaTimeSeconds); rotation += (rotationalVelocity * deltaTimeSeconds); - torque = 0.f; - } -}; -struct Edge { - Vector2 normal; - Vector2 start; - Vector2 end; + for (int32 idx = 0; idx < numImpulses; idx++) { + if (activeImpulses[idx].isDead) { + for (int j = idx + 1; j < numImpulses; j++) { + activeImpulses[j - 1] = activeImpulses[j]; + } + + idx = idx - 1; + numImpulses--; + } + } + } }; -struct Rectangle { +struct Circle { OrthographicShape shape; Rigidbody body; - Rigidbody previousBody; - Vector2 originalPoints[4]; - Vector2 transformedPoints[4]; - Edge edges[4]; - - void load(OrthographicRenderer* renderer, Vector4 color, float32 width, float32 height) { - color = color.toNormalizedColor(); - - float32 halfWidth = width / 2.f; - float32 halfHeight = height / 2.f; - - OrthographicVertex vertices[6]; - vertices[0].position = Vector2 { -halfWidth, -halfHeight }; - vertices[1].position = Vector2 { -halfWidth, halfHeight }; - vertices[2].position = Vector2 { halfWidth, halfHeight }; - vertices[3].position = Vector2 { -halfWidth, -halfHeight }; - vertices[4].position = Vector2 { halfWidth, -halfHeight }; - vertices[5].position = Vector2 { halfWidth, halfHeight }; + Rigidbody previousBody; + Vector2 force; + float32 radius; + + void load(OrthographicRenderer* renderer, float32 inRadius, Vector4 startColor, Vector4 endColor) { + radius = inRadius; + const int32 numSegments = 36; + const float32 radiansPerSegment = (2.f * PI) / static_cast<float>(numSegments); + const int32 numVertices = numSegments * 3; - for (int32 i = 0; i < 6; i++) { - vertices[i].color = color; + startColor = startColor.toNormalizedColor(); + endColor = endColor.toNormalizedColor(); + + OrthographicVertex vertices[numSegments * 3]; + for (int idx = 0; idx < numSegments; idx++) { + int vIdx = idx * 3; + + Vector4 color; + if (idx >= numSegments / 2) { + color = endColor; + } else { + color = startColor; + } + + vertices[vIdx].color = color; + vertices[vIdx].position = Vector2 { radius * cosf(radiansPerSegment * idx), radius * sinf(radiansPerSegment * idx) }; + vertices[vIdx + 1].color = color; + vertices[vIdx + 1].position = Vector2 { 0.f, 0.f }; + vertices[vIdx + 2].color = color; + vertices[vIdx + 2].position = Vector2 { radius * cosf(radiansPerSegment * (idx + 1)), radius * sinf(radiansPerSegment * (idx + 1)) }; } - originalPoints[0] = vertices[0].position; - originalPoints[1] = vertices[1].position; - originalPoints[2] = vertices[2].position; - originalPoints[3] = vertices[4].position; - - shape.load(vertices, 6, renderer); + shape.load(vertices, numVertices, renderer); body.reset(); - - body.momentOfInertia = (width * width + height * height) * (body.mass / 12.f); + body.momentOfInertia = (PI * (radius * radius)) / 4.f; } void update(float32 dtSeconds) { - previousBody = body; - body.update(dtSeconds); - shape.model = Mat4x4().translateByVec2(body.position).rotate2D(body.rotation); - - // Note: This helps us check rectangle collisions using SAT later on. - // This is probably a slightly slow way of doing this, but we will ignore - // that for now. - for (int idx = 0; idx < 4; idx++) { - transformedPoints[idx] = shape.model * originalPoints[idx]; - } - - for (int eidx = 0; eidx < 4; eidx++) { - edges[eidx].start = transformedPoints[eidx]; - edges[eidx].end = transformedPoints[eidx == 3 ? 0 : eidx + 1]; - edges[eidx].normal = (edges[eidx].end - edges[eidx].start).getPerp().normalize(); - } - } + previousBody = body; - void restorePreviousBody() { - body = previousBody; + shape.model = Mat4x4().translateByVec2(body.position).rotate2D(body.rotation); + body.update(dtSeconds); } void render(OrthographicRenderer* renderer) { @@ -128,6 +153,10 @@ struct Rectangle { void unload() { shape.unload(); } + + void restorePreviousBody() { + body = previousBody; + } }; struct IntersectionResult { @@ -148,8 +177,8 @@ void unload(); WebglContext context; OrthographicRenderer renderer; MainLoop mainLoop; -Rectangle r1; -Rectangle r2; +Circle c1; +Circle c2; int main() { context.init("#gl_canvas"); @@ -161,145 +190,60 @@ int main() { void load() { renderer.load(&context); - r1.load(&renderer, Vector4 { 55.f, 235.f, 35.f, 255.f }, 128.f, 64.f); - r1.body.mass = 3.f; - r1.body.position = Vector2 { context.width / 4.f, context.height / 4.f }; - r1.body.velocity = Vector2 { 100.f, 250.f }; + c1.load(&renderer, 32.f, Vector4 { 55.f, 235.f, 35.f, 255.f }, Vector4 { 235.f, 5.f, 235.f, 255.f }); + c1.body.mass = 3.f; + c1.body.position = Vector2 { context.width / 4.f, context.height / 4.f }; + c1.body.velocity = Vector2 { 100.f, 250.f }; - r2.load(&renderer, Vector4 { 235.f, 5.f, 35.f, 255.f }, 96.f, 64.f); - r2.body.mass = 1.f; - r2.body.position = Vector2 { context.width * (3.f / 4.f), context.height * (3.f / 4.f) }; - r2.body.velocity = Vector2 { -300.f, -150.f }; - r2.body.rotationalVelocity = 0.9f; + c2.load(&renderer, 64.f, Vector4 { 235.f, 5.f, 35.f, 255.f }, Vector4 { 5.f, 35.f, 235.f, 255.f }); + c2.body.mass = 1.f; + c2.body.position = Vector2 { context.width * (3.f / 4.f), context.height * (3.f / 4.f) }; + c2.body.velocity = Vector2 { -300.f, -150.f }; mainLoop.run(update); } -void handleCollisionWithWall(Rectangle* r) { - if (r->body.position.x <= 0.f) { - r->body.position.x = 0.f; - r->body.velocity = r->body.velocity - Vector2 { 1.f, 0.f } * (2 * (r->body.velocity.dot(Vector2 { 1.f, 0.f }))); +void handleCollisionWithWall(Circle* c) { + if (c->body.position.x <= 0.f) { + c->body.position.x = 0.f; + c->body.velocity = c->body.velocity - Vector2 { 1.f, 0.f } * (2 * (c->body.velocity.dot(Vector2 { 1.f, 0.f }))); } - if (r->body.position.y <= 0.f) { - r->body.position.y = 0.f; - r->body.velocity = r->body.velocity - Vector2 { 0.f, 1.f } * (2 * (r->body.velocity.dot(Vector2 { 0.f, 1.f }))); + if (c->body.position.y <= 0.f) { + c->body.position.y = 0.f; + c->body.velocity = c->body.velocity - Vector2 { 0.f, 1.f } * (2 * (c->body.velocity.dot(Vector2 { 0.f, 1.f }))); } - if (r->body.position.x >= 800.f) { - r->body.position.x = 800.f; - r->body.velocity = r->body.velocity - Vector2 { -1.f, 0.f } * (2 * (r->body.velocity.dot(Vector2{ -1.f, 0.f }))); + if (c->body.position.x >= 800.f) { + c->body.position.x = 800.f; + c->body.velocity = c->body.velocity - Vector2 { -1.f, 0.f } * (2 * (c->body.velocity.dot(Vector2{ -1.f, 0.f }))); } - if (r->body.position.y >= 600.f) { - r->body.position.y = 600.f; - r->body.velocity = r->body.velocity - Vector2 { 0.f, -1.f } * (2 * (r->body.velocity.dot(Vector2 { 0.f, -1.f }))) ; + if (c->body.position.y >= 600.f) { + c->body.position.y = 600.f; + c->body.velocity = c->body.velocity - Vector2 { 0.f, -1.f } * (2 * (c->body.velocity.dot(Vector2 { 0.f, -1.f }))) ; } } -/* - Do not worry about how w are exactly finding the intersection here, for now. - We are using the Separating Axis Theorem to do so here. In the 2D -> Collisions - section of the website, we describe this method at length. -*/ -Vector2 getProjection(Vector2* vertices, Vector2 axis) { - float32 min = axis.dot(vertices[0]); - float32 max = min; - - for (int v = 1; v < 4; v++) { - float32 d = axis.dot(vertices[v]); - - if (d < min) { - min = d; - } else if (d > max) { - max = d; - } - } - - return Vector2 { min, max }; -} - -inline bool projectionsOverlap(Vector2 first, Vector2 second) { - return first.x <= second.y && second.x <= first.y; -} - -inline float32 getProjectionOverlap(Vector2 first, Vector2 second) { - float32 firstOverlap = (first.x - second.y); // TODO: Does this need to be absolute value? - float32 secondOverlap = (second.x - first.y); - return firstOverlap > secondOverlap ? secondOverlap : firstOverlap; -} - -struct IntermediateIntersectionResult { - float32 minOverlap = FLT_MAX; - Edge* minOverlapEdge; - bool isOverlapOnFirstEdge = true; -}; - -bool checkEdgeOverlap(Edge* edges, Rectangle* first, Rectangle* second, IntermediateIntersectionResult* iir, bool isFirstEdge) { - // Returns true if SAT passes for the provided set of edges. - for (int i = 0; i < 4; i++) { - Vector2 normal = edges[i].normal; - - Vector2 firstProj = getProjection(first->transformedPoints, normal); - Vector2 secondProj = getProjection(second->transformedPoints, normal); - - if (!projectionsOverlap(firstProj, secondProj)) { - return false; - } - - float32 overlap = getProjectionOverlap(firstProj, secondProj); - if (overlap < iir->minOverlap) { - iir->minOverlap = overlap; - iir->minOverlapEdge = &edges[i]; - iir->isOverlapOnFirstEdge = isFirstEdge; - } - } - - return true; -} - -const float32 EPSILON = 1.f; -IntersectionResult getIntersection(Rectangle* first, Rectangle* second) { - IntersectionResult ir; - - IntermediateIntersectionResult iir; - if (!checkEdgeOverlap(first->edges, first, second, &iir, true)) { - return ir; - } - - if (!checkEdgeOverlap(second->edges, first, second, &iir, false)) { - return ir; - } - - ir.intersect = true; - ir.relativeVelocity = first->body.velocity - second->body.velocity; - ir.collisionNormal = iir.minOverlapEdge->normal; - - float32 minDistanceFromEdge = FLT_MAX; - Vector2 pointOfContact; - Vector2* pointsToCheck = iir.isOverlapOnFirstEdge ? second->transformedPoints : first->transformedPoints; - for (int p = 0; p < 4; p++) { - Vector2 point = pointsToCheck[p]; - - float32 distanceFromEdge = MIN((iir.minOverlapEdge->start - point).length(), (iir.minOverlapEdge->end - point).length()); - - if (distanceFromEdge < minDistanceFromEdge) { - minDistanceFromEdge = distanceFromEdge; - pointOfContact = point; - } - } - - - ir.firstPointOfApplication = pointOfContact - first->body.position; - ir.secondPointOfApplication = pointOfContact - second->body.position;; +IntersectionResult getIntersection(Circle* first, Circle* second) { + IntersectionResult ir; + Vector2 positionDiff = (first->body.position - second->body.position); + if (positionDiff.length() > first->radius + second->radius) { + return ir; // Not intersecting + } + Vector2 positionDirection = positionDiff.normalize(); + ir.relativeVelocity = first->body.velocity - second->body.velocity; + + // The positionDirection could represent the normal at which our circles intersect, but then we would + // never get any rotation on them. At the same time, this is not an entirely great selection because, in the real + // world, two circles wouldn't hit one another at exactly the normal. To fix this, we offset the positionDirection + // by the relative velocity. This gives a normal that is slightly more believable, and allows our spin to take place. + ir.collisionNormal = (positionDirection + ir.relativeVelocity.negate().normalize()).normalize(); + ir.firstPointOfApplication = positionDirection * first->radius; + ir.secondPointOfApplication = positionDirection * second->radius; + ir.intersect = true; + return ir; } -/** -In this method, we resolve the collision of two rigidbodies using the IntersectionResult -that we gathered from the collision information. Note that this particular tutorial -is not about how we find this collision, but rather how we use this collision. To see the -variety of ways of how this IntersectionResult can be calculated go to the 2D->Collision -section of the website. -***/ void resolveCollision(Rigidbody* first, Rigidbody* second, IntersectionResult* ir) { Vector2 relativeVelocity = ir->relativeVelocity; Vector2 collisionNormal = ir->collisionNormal; @@ -322,29 +266,29 @@ void resolveCollision(Rigidbody* first, Rigidbody* second, IntersectionResult* i } void update(float32 deltaTimeSeconds, void* userData) { - r1.update(deltaTimeSeconds); - r2.update(deltaTimeSeconds); + c1.update(deltaTimeSeconds); + c2.update(deltaTimeSeconds); // Let's backtrack the simulation to find the precise point at which we collided. // There exists many ways to find this precise point. This is by far the most // expensive, but it gets the job done. - IntersectionResult ir = getIntersection(&r1, &r2); + IntersectionResult ir = getIntersection(&c1, &c2); if (ir.intersect) { IntersectionResult irCopy = ir; float32 copyDt = deltaTimeSeconds; float32 subdivisionAmountSeconds = deltaTimeSeconds / 16.f; do { - r1.restorePreviousBody(); - r2.restorePreviousBody(); + c1.restorePreviousBody(); + c2.restorePreviousBody(); ir = irCopy; copyDt = copyDt - subdivisionAmountSeconds; - r1.update(copyDt); - r2.update(copyDt); + c1.update(copyDt); + c2.update(copyDt); - irCopy = getIntersection(&r1, &r2); + irCopy = getIntersection(&c1, &c2); if (copyDt <= 0.f) { printf("Error: Should not be happening.\n"); @@ -358,28 +302,28 @@ void update(float32 deltaTimeSeconds, void* userData) { // The following function is the main one that we're talking about in this tutorial. // This function will take the collision data, and repel the objects away from one // another using what we know from physics. - resolveCollision(&r1.body, &r2.body, &ir); + resolveCollision(&c1.body, &c2.body, &ir); float32 frameTimeRemaining = deltaTimeSeconds - copyDt; - r1.update(frameTimeRemaining); - r2.update(frameTimeRemaining); + c1.update(frameTimeRemaining); + c2.update(frameTimeRemaining); } // Keep within the bounds - handleCollisionWithWall(&r1); - handleCollisionWithWall(&r2); + handleCollisionWithWall(&c1); + handleCollisionWithWall(&c2); // Renderer renderer.render(); - r1.render(&renderer); - r2.render(&renderer); + c1.render(&renderer); + c2.render(&renderer); } void unload() { mainLoop.stop(); renderer.unload(); - r1.unload(); - r2.unload(); + c1.unload(); + c2.unload(); } // |