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