summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthew Kosarek <mattkae@protonmail.com>2021-04-01 17:02:44 -0400
committerMatthew Kosarek <mattkae@protonmail.com>2021-04-01 17:02:44 -0400
commitdbd32f11e2a3df38162c70f946b5bfa9a8dedbfa (patch)
treedf3c1baf5818a641f7c15acbc59d90abcf0360c5
parentb17c518155fae64083eb5b56b78b9eec57603ac0 (diff)
Lots of work in progress on pill intersections, but will have to read more up on it
-rw-r--r--frontend/2d/_collisions/pill_line.html9
-rw-r--r--frontend/2d/_collisions/pill_line/dist/output.js90
-rwxr-xr-xfrontend/2d/_collisions/pill_line/dist/output.wasmbin40465 -> 45699 bytes
-rw-r--r--frontend/2d/_collisions/pill_line/main.cpp348
-rw-r--r--frontend/shared_cpp/MainLoop.cpp3
-rw-r--r--frontend/shared_cpp/MainLoop.h4
-rw-r--r--frontend/shared_cpp/mathlib.h29
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
index e282f2c..2056ca5 100755
--- a/frontend/2d/_collisions/pill_line/dist/output.wasm
+++ b/frontend/2d/_collisions/pill_line/dist/output.wasm
Binary files differ
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 {