From ff82253a66ee51fe2f0c088ca964402d53545845 Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Thu, 24 Jun 2021 11:30:36 -0400 Subject: (mkosarek) Rigidbody with rotations --- 2d/_collisions/polygon_polygon.html | 23 +- 2d/_collisions/polygon_polygon.html.content | 23 +- 2d/_collisions/polygon_polygon/dist/output.js | 313 ++++++++++++-------- 2d/_collisions/polygon_polygon/dist/output.wasm | Bin 56585 -> 57485 bytes 2d/_collisions/polygon_polygon/main.cpp | 77 +++-- 2d/rigidbody/rigidbody_2/dist/output.wasm | Bin 48126 -> 50226 bytes 2d/rigidbody/rigidbody_2/main.cpp | 80 ++++-- 2d/rigidbody/rigidbody_3.html | 2 +- 2d/rigidbody/rigidbody_3.html.content | 2 +- 2d/rigidbody/rigidbody_3/dist/output.wasm | Bin 52832 -> 49458 bytes 2d/rigidbody/rigidbody_3/main.cpp | 366 ++++++++++-------------- 11 files changed, 513 insertions(+), 373 deletions(-) (limited to '2d') 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 @@

Given two polygons A and B:

    -
  1. For each edge on A, get the normal n of that edge.
  2. +
  3. For each edge on A, get the normal n of that edge.
  4. Project each vertex v of A onto n. Return the minimum and maximum projection of all vertices.
  5. Repeat Step 2 for polygon B.
  6. If the min and max projections found in Steps 2 and 3 do NOT overlap, the polygons are not intersecting. Return false.
  7. If the projections overlap for each edge of both shapes, the shapes are intersecting. Return true.
+ + And that is all there is to finding the intersection between two convex polygons. +

+ +
+

SAT Collision Resolution

+

+ 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: +

+ +

Collision Normal

+

+ +

-

- Live Example + Live Example of Intersection Detection

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 @@

Given two polygons A and B:

    -
  1. For each edge on A, get the normal n of that edge.
  2. +
  3. For each edge on A, get the normal n of that edge.
  4. Project each vertex v of A onto n. Return the minimum and maximum projection of all vertices.
  5. Repeat Step 2 for polygon B.
  6. If the min and max projections found in Steps 2 and 3 do NOT overlap, the polygons are not intersecting. Return false.
  7. If the projections overlap for each edge of both shapes, the shapes are intersecting. Return true.
+ + And that is all there is to finding the intersection between two convex polygons. +

+
+
+

SAT Collision Resolution

+

+ 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: +

+ +

Collision Normal

+

+ +

-

- Live Example + Live Example of Intersection Detection

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 index fd9538c..a30f0c4 100755 Binary files a/2d/_collisions/polygon_polygon/dist/output.wasm and b/2d/_collisions/polygon_polygon/dist/output.wasm differ 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 #include -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 index f7dcbd9..29c319a 100755 Binary files a/2d/rigidbody/rigidbody_2/dist/output.wasm and b/2d/rigidbody/rigidbody_2/dist/output.wasm differ 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 #include +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(mouseEvent->movementX) * 1000.f; - pointer.body.linearForce.y = static_cast(-mouseEvent->movementY) * 1000.f; + pointer.force.x = static_cast(mouseEvent->movementX) * 1000.f; + pointer.force.y = static_cast(-mouseEvent->movementY) * 1000.f; pointer.body.position.x = static_cast(mouseEvent->targetX); pointer.body.position.y = static_cast(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 @@ -3 + +