diff options
-rw-r--r-- | frontend/2d/_collisions/pill_line.html | 9 | ||||
-rw-r--r-- | frontend/2d/_collisions/pill_line/dist/output.js | 90 | ||||
-rwxr-xr-x | frontend/2d/_collisions/pill_line/dist/output.wasm | bin | 40465 -> 45699 bytes | |||
-rw-r--r-- | frontend/2d/_collisions/pill_line/main.cpp | 348 | ||||
-rw-r--r-- | frontend/shared_cpp/MainLoop.cpp | 3 | ||||
-rw-r--r-- | frontend/shared_cpp/MainLoop.h | 4 | ||||
-rw-r--r-- | frontend/shared_cpp/mathlib.h | 29 |
7 files changed, 335 insertions, 148 deletions
diff --git a/frontend/2d/_collisions/pill_line.html b/frontend/2d/_collisions/pill_line.html index 7f7027e..2afa802 100644 --- a/frontend/2d/_collisions/pill_line.html +++ b/frontend/2d/_collisions/pill_line.html @@ -24,9 +24,6 @@ </p> <div class="opengl_canvas_container"> <canvas id="gl_canvas" width="640" height="480"></canvas> - <div class="opengl_canvas_sidebar"> - - </div> <button id="gl_canvas_play" class="play_button"> Play </button> @@ -34,6 +31,12 @@ Stop </button> </div> + <p> + References + <ul> + <li><a href="http://csharphelper.com/blog/2017/08/calculate-where-a-line-segment-and-an-ellipse-intersect-in-c/#intersection_code">Line Segment-Ellipse Intersection</a></li> + </ul> + </p> </article> </section> </main> diff --git a/frontend/2d/_collisions/pill_line/dist/output.js b/frontend/2d/_collisions/pill_line/dist/output.js index e1dfee3..eb8b559 100644 --- a/frontend/2d/_collisions/pill_line/dist/output.js +++ b/frontend/2d/_collisions/pill_line/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,7 @@ var ASM_CONSTS = { // anyhow) } function _emscripten_resize_heap(requestedSize) { - var oldSize = _emscripten_get_heap_size(); + var oldSize = HEAPU8.length; // 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 +1826,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 +1844,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 +1858,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 +1878,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 +1890,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 +1928,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--); @@ -2085,7 +2084,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 +2212,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) ? @@ -2386,7 +2398,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; } @@ -3004,6 +3016,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 +3043,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)") }; @@ -3217,7 +3238,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 +3347,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 +3367,6 @@ function exit(status, implicit) { } } else { - EXITSTATUS = status; - exitRuntime(); if (Module['onExit']) Module['onExit'](status); diff --git a/frontend/2d/_collisions/pill_line/dist/output.wasm b/frontend/2d/_collisions/pill_line/dist/output.wasm Binary files differindex e282f2c..2056ca5 100755 --- a/frontend/2d/_collisions/pill_line/dist/output.wasm +++ b/frontend/2d/_collisions/pill_line/dist/output.wasm diff --git a/frontend/2d/_collisions/pill_line/main.cpp b/frontend/2d/_collisions/pill_line/main.cpp index 4187435..9123b50 100644 --- a/frontend/2d/_collisions/pill_line/main.cpp +++ b/frontend/2d/_collisions/pill_line/main.cpp @@ -4,94 +4,167 @@ #include "../../../shared_cpp/mathlib.h" #include "../../../shared_cpp/MainLoop.h" #include <cstdio> +#include <cmath> #include <emscripten/html5.h> #include <unistd.h> #include <pthread.h> #include <cmath> // Side note: It is Eastertime, so I chose this easter color palette. Enjoy: https://htmlcolors.com/palette/144/easter +struct Rigidbody { + Vector2 force = { 0, 0 }; + Vector2 velocity = { 0, 0 }; + Vector2 position = { 0, 0 }; + float32 rotationalVelocity = 0.f; + float32 rotation = 0.f; + float32 mass = 1.f; + + void reset() { + force = { 0, 0 }; + velocity = { 0, 0 }; + } + + void applyForce(Vector2 f) { + force += f; + } + + void applyGravity() { + force += Vector2 { 0.f, -25.f }; + } + + void update(float32 deltaTimeSeconds) { + Vector2 acceleration = force / mass; + velocity += (acceleration * deltaTimeSeconds); + position += (velocity * deltaTimeSeconds); + force = Vector2 { 0.f, 0.f }; + + rotation += (rotationalVelocity * deltaTimeSeconds); + } +}; -// -// Pill object -// struct Pill { - OrthographicShape shape; - float32 width = 1.f; - float32 height = 1.f; - - void load(OrthographicRenderer* renderer, float32 numSegments) { - // Note that a so-called "pill" is simply an ellipse. - // Equation of an ellipse is: - // - // x^2 / a^2 + y^2 / b^2 = 1 - // - // or, in parametric form: - // - // x = a * cos(t), y = b * sin(t) - // - float32 angleIncrements = (2.f * PI) / numSegments; - uint32 numVertices = static_cast<uint32>(numSegments * 3.f); - OrthographicVertex* vertices = new OrthographicVertex[numVertices]; - - float32 a = width / 2.f; - float32 b = height / 2.f; - - Vector4 color = Vector4().fromColor(243,166,207, 255); - - for (uint32 vertexIndex = 0; vertexIndex < numVertices; vertexIndex += 3) { - // Create a single "slice" of the ellipse (like a pizza) - float32 currAngle = (vertexIndex / 3.f) * angleIncrements; - float32 nextAngle = (vertexIndex / 3.f + 1.f) * angleIncrements; - - vertices[vertexIndex].position = Vector2 { 0.f, 0.f }; - vertices[vertexIndex].color = color; - - vertices[vertexIndex + 1].position = Vector2 { a * cosf(currAngle), b * sinf(currAngle) }; - vertices[vertexIndex + 1].color = color; - - vertices[vertexIndex + 2].position = Vector2 { a * cosf(nextAngle), b * sinf(nextAngle) }; - vertices[vertexIndex + 2].color = color; - } - - shape.load(vertices, numVertices, renderer); - delete[] vertices; - } - - void render(OrthographicRenderer* renderer) { - shape.render(renderer); - } - - void unload() { - shape.unload(); - } - - float32 getArea() { - return 0.f; - } + OrthographicShape shape; + Rigidbody body; + float32 a = 0; + float32 b = 0;; + + void load(OrthographicRenderer* renderer, float32 numSegments, float32 width, float32 height) { + // Note that a so-called "pill" is simply an ellipse. + // Equation of an ellipse is: + // + // x^2 / a^2 + y^2 / b^2 = 1 + // + // or, in parametric form: + // + // x = a * cos(t), y = b * sin(t) + // + float32 angleIncrements = (2.f * PI) / numSegments; + uint32 numVertices = static_cast<uint32>(numSegments * 3.f); + OrthographicVertex* vertices = new OrthographicVertex[numVertices]; + + a = width / 2.f; + b = height / 2.f; + + Vector4 color = Vector4().fromColor(243,166,207, 255); + + for (uint32 vertexIndex = 0; vertexIndex < numVertices; vertexIndex += 3) { + // Create a single "slice" of the ellipse (like a pizza) + float32 currAngle = (vertexIndex / 3.f) * angleIncrements; + float32 nextAngle = (vertexIndex / 3.f + 1.f) * angleIncrements; + + vertices[vertexIndex].position = Vector2 { 0.f, 0.f }; + vertices[vertexIndex].color = color; + + vertices[vertexIndex + 1].position = Vector2 { a * cosf(currAngle), b * sinf(currAngle) }; + vertices[vertexIndex + 1].color = color; + + vertices[vertexIndex + 2].position = Vector2 { a * cosf(nextAngle), b * sinf(nextAngle) }; + vertices[vertexIndex + 2].color = color; + } + + shape.load(vertices, numVertices, renderer); + body.reset(); + + a = width / 2.f; + b = height / 2.f; + + delete[] vertices; + } + + void update(float32 deltaTimeSeconds) { + body.update(deltaTimeSeconds); + shape.model = Mat4x4().translateByVec2(body.position).rotate2D(body.rotation); + } + + void render(OrthographicRenderer* renderer) { + shape.render(renderer); + } + + void unload() { + shape.unload(); + } + + float32 getArea() { + return 0.f; + } }; struct LineSegment { - OrthographicShape shape; - Vector2 start; - Vector2 end; - OrthographicVertex vertices[2]; - - void load(OrthographicRenderer* renderer, Vector4 color, Vector2 start, Vector2 end) { - vertices[0].position = start; - vertices[0].color = color; - vertices[1].position = end; - vertices[1].color = color; - shape.load(vertices, 2, renderer); - } - - void render(OrthographicRenderer* renderer) { - shape.render(renderer, GL_LINES); - } + OrthographicShape shape; + Vector2 start; + Vector2 end; + Vector2 normal; + float32 slope; + float32 yIntercept; + OrthographicVertex vertices[2]; + + void load(OrthographicRenderer* renderer, Vector4 color, Vector2 inStart, Vector2 inEnd) { + start = inStart; + end = inEnd; + slope = (end.y - start.y) / (end.x - start.x); + yIntercept = (end.y - slope * end.x); + vertices[0].position = start; + vertices[0].color = color; + vertices[1].position = end; + vertices[1].color = color; + normal = (end - start).getPerp().normalize(); + shape.load(vertices, 2, renderer); + } + + void render(OrthographicRenderer* renderer) { + shape.render(renderer, GL_LINES); + } + + void unload() { + shape.unload(); + } + + bool isPointOnLine(Vector2 p) { + // If the dot product is nearly zero, that means it is in the direction of the line + if (ABS((end - start).dot(p - start)) <= 0.001) { + + // We're on the general line, now let's see if we're within the proper bounds: + return p.x >= start.x && p.x <= end.x && p.x >= start.y && p.y <= start.y; + } + + return false; + } +}; + +struct IntersectionResult { + bool intersect = false; + Vector2 pointOfIntersection; + Vector2 collisionNormal; + Vector2 relativeVelocity; }; EM_BOOL onPlayClicked(int eventType, const EmscriptenMouseEvent* mouseEvent, void* userData); EM_BOOL onStopClicked(int eventType, const EmscriptenMouseEvent* mouseEvent, void* userData); -EM_BOOL update(float time, void* userData); + +void load(); +void update(float32 time, void* userData); +void unload(); +IntersectionResult getIntersection(Pill* pill, LineSegment* segment); // Global Variables WebglContext context; @@ -101,51 +174,112 @@ MainLoop mainLoop; LineSegment segmentList[4]; int main() { - context.init("#gl_canvas"); - emscripten_set_click_callback("#gl_canvas_play", NULL, false, onPlayClicked); - emscripten_set_click_callback("#gl_canvas_stop", NULL, false, onStopClicked); - return 0; + context.init("#gl_canvas"); + emscripten_set_click_callback("#gl_canvas_play", NULL, false, onPlayClicked); + emscripten_set_click_callback("#gl_canvas_stop", NULL, false, onStopClicked); + return 0; +} + +void load() { + renderer.load(&context); + + pill.body.position = Vector2 { context.width / 2.f, context.height / 2.f }; + pill.body.rotationalVelocity = 0.3f; + pill.load(&renderer, 64, 100.f, 50.f); + + segmentList[0].load(&renderer, Vector4().fromColor(191, 251, 146, 255.f), Vector2 { 50.f, 0.f }, Vector2 { 50.f, static_cast<float>(context.height) }); + segmentList[1].load(&renderer, Vector4().fromColor(159, 224, 210, 255.f), Vector2 { context.width - 50.f, 0.f }, Vector2 { context.width - 50.f, static_cast<float>(context.height) }); + segmentList[2].load(&renderer, Vector4().fromColor(248, 255, 156, 255.f), Vector2 { 50.f, 50.f }, Vector2 { context.width - 50.f, 150.f }); + segmentList[3].load(&renderer, Vector4().fromColor(205, 178, 214, 255.f), Vector2 { 50.f, 150.f }, Vector2 { context.width - 50.f, 50.f }); + + mainLoop.run(update); +} + +float32 areaOfTriangle(Vector2 a, Vector2 b, Vector2 c) { + // Refernce for this for the formula: https://www.onlinemath4all.com/area-of-triangle-using-determinant-formula.html + return ABS(0.5 * (a.x * b.y - b.x * a.y + b.x * c.y - c.x * b.y + c.x * a.y - a.x * c.y)); +} + +IntersectionResult getIntersection(Pill* pill, LineSegment* segment) { + IntersectionResult result; + + // Solve quadratic equation to see if the imaginary line defined by our line segment intersects the ellipse. + // Equation: x^2 / a^2 + y^2 / b^2 = 1 + // (x^2 / a^2) + (line equation) ^2 / b^2 = 1 + // => x^2 / (pill->a * pill->a) + (segment->slope * x + segment->yIntercept) / (pill->b * pill ->b) = 1.f; + + + // Build a triangle defined by the following to vectors: + Vector2 startToCenter = pill->body.position - segment->start; + Vector2 startToEnd = segment->end - segment->start; + Vector2 endToCenter = pill->body.position - segment->end; + + // Get the area of this triangle using the properties of the determinant: + float32 area = areaOfTriangle(startToCenter, startToEnd, endToCenter); + float32 base = startToEnd.length(); + + // Knowning that Area = 0.5 Base * Height + float32 height = (2.f * area) / base; // Distance from center of pill to line + + if (height <= MAX(pill->b, pill->a) && height >= MIN(pill->b, pill->a)) { + // We at least have an intersection: Half the problem solved! + result.intersect = true; + } else { + result.intersect = false; + return result; + } + + // Time to find where we intersected. We can do this by getting the intersection depth. + + + Vector2 transformedX = pill->shape.model.multByVec2(Vector2 { pill->a / 2.f, 0.f }); + Vector2 transformedY = pill->shape.model.multByVec2(Vector2 { pill->b / 2.f, 0.f }); + + return result; } -EM_BOOL update(float deltaTimeSeconds, void* userData) { - renderer.render(); - pill.shape.render(&renderer); +void update(float32 deltaTimeSeconds, void* userData) { + // Input + pill.body.applyGravity(); + + // Update + pill.update(deltaTimeSeconds); - for (int segmentIndex = 0; segmentIndex < 4; segmentIndex++) { - segmentList[segmentIndex].render(&renderer); - } + // Intersections + for (int32 lineIdx = 0; lineIdx < 4; lineIdx++) { + getIntersection(&pill, &segmentList[lineIdx]); + } + + // Render + renderer.render(); + pill.shape.render(&renderer); + + for (int32 segmentIndex = 0; segmentIndex < 4; segmentIndex++) { + segmentList[segmentIndex].render(&renderer); + } +} - return true; +void unload() { + mainLoop.stop(); + pill.unload(); + renderer.unload(); + for (int32 segmentIndex = 0; segmentIndex < 4; segmentIndex++) { + segmentList[segmentIndex].unload(); + } } // // Interactions with DOM handled below // EM_BOOL onPlayClicked(int eventType, const EmscriptenMouseEvent* mouseEvent, void* userData) { - printf("Play clicked\n"); - - renderer.load(&context); - - // Add the pill - pill.width = 100.f; - pill.height = 50.f; - pill.shape.model = Mat4x4().translateByVec2(Vector2 { context.width / 2.f, context.height / 2.f }); - pill.load(&renderer, 64); - - // Add the lines - segmentList[0].load(&renderer, Vector4().fromColor(191, 251, 146, 255.f), Vector2 { 50.f, 0.f }, Vector2 { 50.f, static_cast<float>(context.height) }); - segmentList[1].load(&renderer, Vector4().fromColor(159, 224, 210, 255.f), Vector2 { context.width - 50.f, 0.f }, Vector2 { context.width - 50.f, static_cast<float>(context.height) }); - segmentList[2].load(&renderer, Vector4().fromColor(248, 255, 156, 255.f), Vector2 { 50.f, 50.f }, Vector2 { context.width - 50.f, 150.f }); - segmentList[3].load(&renderer, Vector4().fromColor(205, 178, 214, 255.f), Vector2 { 50.f, 150.f }, Vector2 { context.width - 50.f, 50.f }); - - mainLoop.run(update); - return true; + printf("Play clicked\n"); + + load(); + return true; } EM_BOOL onStopClicked(int eventType, const EmscriptenMouseEvent* mouseEvent, void* userData) { - printf("Stop clicked\n"); - mainLoop.stop(); - pill.unload(); - renderer.unload(); - return true; + printf("Stop clicked\n"); + unload(); + return true; }
\ No newline at end of file diff --git a/frontend/shared_cpp/MainLoop.cpp b/frontend/shared_cpp/MainLoop.cpp index 82a24b5..09aa643 100644 --- a/frontend/shared_cpp/MainLoop.cpp +++ b/frontend/shared_cpp/MainLoop.cpp @@ -26,5 +26,6 @@ EM_BOOL loop(double time, void* loop) { mainLoop->numFrames = 0; } - return mainLoop->updateFunc(deltaTimeSeconds, NULL); + mainLoop->updateFunc(deltaTimeSeconds, NULL); + return true; }
\ No newline at end of file diff --git a/frontend/shared_cpp/MainLoop.h b/frontend/shared_cpp/MainLoop.h index 7300f6b..2573bb8 100644 --- a/frontend/shared_cpp/MainLoop.h +++ b/frontend/shared_cpp/MainLoop.h @@ -11,9 +11,9 @@ struct MainLoop { bool isRunning = false; double lastTime = 0, elapsedTime = 0; int numFrames = 0; - EM_BOOL (*updateFunc)(float dtSeconds, void *userData); + void (*updateFunc)(float dtSeconds, void *userData); - void run(EM_BOOL (*cb)(float dtSeconds, void *userData)) { + void run(void (*cb)(float dtSeconds, void *userData)) { isRunning = true; lastTime = 0; elapsedTime = 0; diff --git a/frontend/shared_cpp/mathlib.h b/frontend/shared_cpp/mathlib.h index 7595045..2784f6a 100644 --- a/frontend/shared_cpp/mathlib.h +++ b/frontend/shared_cpp/mathlib.h @@ -2,6 +2,11 @@ #include <cstdio> #include <cstdlib> #include <cstring> +#include <cmath> + +#define MAX(x, y) (((x) > (y)) ? (x) : (y)) +#define MIN(x, y) (((x) < (y)) ? (x) : (y)) +#define ABS(x) (x < 0 ? -x : x) struct Vector2 { float x = 0; @@ -11,6 +16,12 @@ struct Vector2 { return { x + other.x, y + other.y }; } + Vector2& operator+=(Vector2 other) { + x += other.x; + y += other.y; + return *this; + } + Vector2 operator-(Vector2 other) { return { x - other.x, y - other.y }; } @@ -19,6 +30,10 @@ struct Vector2 { return { x * s, y * s }; } + Vector2 operator/(float s) { + return { x / s, y / s }; + } + float dot(Vector2 other) { return x * other.x + y * other.y; } @@ -45,6 +60,20 @@ struct Vector2 { void printDebug(const char* name) { printf("%s=Vector2(%f, %f)\n", name, x, y); } + + float determinant(Vector2 other) { + // + // [ a b ] + // [ c d ] + // + // [ x other.x ] + // [ y other.y ] + // + // det = a * d - b * c + // det = x * other.y - other.x * y + // + return x * other.y - other.x * y; + } }; struct Vector3 { |