Skip to content

Instantly share code, notes, and snippets.

@foldi
Last active March 3, 2018 00:56
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save foldi/5867007 to your computer and use it in GitHub Desktop.
Save foldi/5867007 to your computer and use it in GitHub Desktop.
SimpleSim step 8

Step 8

  • Add applyForce() function to Item and update Item.step().
  • Add 'wind' and 'thermal' properties to World.
  • Add 'mass' property to Item.
  • Add Item.checkWorldEdges() function to keep items inside browser viewport.
  • Use a cache vector in Item.applyForce() as an optimization.
<!DOCTYPE html>
<html>
<head>
<meta http-equiv='content-type' content='text/html; charset=UTF-8' />
<meta name='viewport' content='user-scalable=no, width=device-width, initial-scale=1.0, maximum-scale=1.0' />
<meta name='apple-mobile-web-app-capable' content='yes' />
<title>Simple Simulator</title>
<link rel='stylesheet' href='main.css' type='text/css' charset='utf-8' />
<script src='modernizr.js' type='text/javascript' charset='utf-8'></script>
<script src='simplesim.js' type='text/javascript' charset='utf-8'></script>
</head>
<body>
<script type='text/javascript' charset='utf-8'>
var system = SimpleSim.System;
system.init(function() {
for (var i = 1; i < 11; i++) {
var size = i * 10;
system.add('Item', {
width: size,
height: size
});
}
}, null, {
csstransforms3d: Modernizr.csstransforms3d,
csstransforms: Modernizr.csstransforms
});
</script>
</body>
</html>
.world {
position: relative;
margin: 0;
padding: 0;
overflow: hidden;
}
.item {
position: absolute;
top: 0; left: 0;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-o-box-sizing: border-box;
-ms-box-sizing: border-box;
box-sizing: border-box;
}
/* Modernizr 2.6.2 (Custom Build) | MIT & BSD
* Build: http://modernizr.com/download/#-csstransforms-csstransforms3d-teststyles-testprop-testallprops-prefixes-domprefixes
*/
;window.Modernizr=function(a,b,c){function y(a){i.cssText=a}function z(a,b){return y(l.join(a+";")+(b||""))}function A(a,b){return typeof a===b}function B(a,b){return!!~(""+a).indexOf(b)}function C(a,b){for(var d in a){var e=a[d];if(!B(e,"-")&&i[e]!==c)return b=="pfx"?e:!0}return!1}function D(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:A(f,"function")?f.bind(d||b):f}return!1}function E(a,b,c){var d=a.charAt(0).toUpperCase()+a.slice(1),e=(a+" "+n.join(d+" ")+d).split(" ");return A(b,"string")||A(b,"undefined")?C(e,b):(e=(a+" "+o.join(d+" ")+d).split(" "),D(e,b,c))}var d="2.6.2",e={},f=b.documentElement,g="modernizr",h=b.createElement(g),i=h.style,j,k={}.toString,l=" -webkit- -moz- -o- -ms- ".split(" "),m="Webkit Moz O ms",n=m.split(" "),o=m.toLowerCase().split(" "),p={},q={},r={},s=[],t=s.slice,u,v=function(a,c,d,e){var h,i,j,k,l=b.createElement("div"),m=b.body,n=m||b.createElement("body");if(parseInt(d,10))while(d--)j=b.createElement("div"),j.id=e?e[d]:g+(d+1),l.appendChild(j);return h=["&#173;",'<style id="s',g,'">',a,"</style>"].join(""),l.id=g,(m?l:n).innerHTML+=h,n.appendChild(l),m||(n.style.background="",n.style.overflow="hidden",k=f.style.overflow,f.style.overflow="hidden",f.appendChild(n)),i=c(l,a),m?l.parentNode.removeChild(l):(n.parentNode.removeChild(n),f.style.overflow=k),!!i},w={}.hasOwnProperty,x;!A(w,"undefined")&&!A(w.call,"undefined")?x=function(a,b){return w.call(a,b)}:x=function(a,b){return b in a&&A(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=t.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(t.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(t.call(arguments)))};return e}),p.csstransforms=function(){return!!E("transform")},p.csstransforms3d=function(){var a=!!E("perspective");return a&&"webkitPerspective"in f.style&&v("@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}",function(b,c){a=b.offsetLeft===9&&b.offsetHeight===3}),a};for(var F in p)x(p,F)&&(u=F.toLowerCase(),e[u]=p[F](),s.push((e[u]?"":"no-")+u));return e.addTest=function(a,b){if(typeof a=="object")for(var d in a)x(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,typeof enableClasses!="undefined"&&enableClasses&&(f.className+=" "+(b?"":"no-")+a),e[a]=b}return e},y(""),h=j=null,e._version=d,e._prefixes=l,e._domPrefixes=o,e._cssomPrefixes=n,e.testProp=function(a){return C([a])},e.testAllProps=E,e.testStyles=v,e}(this,this.document);
SimpleSim = {}; exports = SimpleSim;
(function(exports) {
/** @namespace */
var System = {
name: 'System'
};
/**
* Stores references to all items in the system.
* @private
*/
System._records = {
lookup: {},
list: []
};
/**
* Used to create unique ids.
* @private
*/
System._idCount = 0;
/**
* Holds a transform property based on supportedFeatures.
* @private
*/
System._stylePosition = '';
/**
* Increments idCount and returns the value.
*/
System.getNewId = function() {
this._idCount++;
return this._idCount;
};
/**
* Initializes the system and starts the update loop.
*
* @param {Function} opt_setup= Creates the initial system conditions.
* @param {Object} opt_world= A reference to a DOM element representing the System world.
* @param {Function} opt_supportedFeatures= A map of supported browser features.
*/
System.init = function(opt_setup, opt_world, opt_supportedFeatures) {
var setup = opt_setup || function () {},
world = opt_world || document.body,
supportedFeatures = opt_supportedFeatures || null;
if (supportedFeatures.csstransforms3d) {
this._stylePosition = '-webkit-transform: translate3d(<x>px, <y>px, 0); -moz-transform: translate3d(<x>px, <y>px, 0); -o-transform: translate3d(<x>px, <y>px, 0); -ms-transform: translate3d(<x>px, <y>px, 0);';
} else if (supportedFeatures.csstransforms) {
this._stylePosition = '-webkit-transform: translate(<x>px, <y>px); -moz-transform: translate(<x>px, <y>px); -o-transform: translate(<x>px, <y>px); -ms-transform: translate(<x>px, <y>px);';
} else {
this._stylePosition = 'position: absolute; left: <x>px; top: <y>px;';
}
System._records.list.push(new exports.World(world));
setup.call(this);
this._update();
};
/**
* Adds an object to the system.
*
* @param {Object} opt_options= Object properties.
*/
System.add = function(klass, opt_options) {
var last, records = this._records.list,
recordsLookup = this._records.lookup,
options = opt_options || {};
options.world = records[0];
if (exports[klass]) {
records[records.length] = new exports[klass](options);
} else if (exports.Classes[klass]) {
records[records.length] = new exports.Classes[klass](options);
} else {
throw new Error(klass + ' class does not exist.');
}
last = records.length - 1;
recordsLookup[records[last].id] = records[last].el.parentNode;
records[last].init(options);
return records[last];
};
/**
* Iterates over objects in the system and calls step() and draw().
* @private
*/
System._update = function() {
var i, records = System._records.list, record;
for (i = records.length - 1; i >= 0; i -= 1) {
records[i].step();
}
for (i = records.length - 1; i >= 0; i -= 1) {
records[i].draw();
}
window.requestAnimFrame(System._update);
};
/**
* Updates the corresponding DOM element's style property.
*/
System._draw = function(obj) {
var cssText = exports.System.getCSSText({
x: obj.location.x - (obj.width / 2),
y: obj.location.y - (obj.height / 2),
width: obj.width,
height: obj.height,
color0: obj.color[0],
color1: obj.color[1],
color2: obj.color[2],
visibility: obj.visibility
});
obj.el.style.cssText = cssText;
};
/**
* Concatenates a new cssText string.
*
* @param {Object} props A map of object properties.
*/
System.getCSSText = function(props) {
return this._stylePosition.replace(/<x>/g, props.x).replace(/<y>/g, props.y) + ' width: ' +
props.width + 'px; height: ' + props.height + 'px; background-color: ' +
'rgb(' + props.color0 + ', ' + props.color1 + ', ' + props.color2 + ');' +
'visibility: ' + props.visibility + ';';
};
exports.System = System;
}(exports));
(function(exports) {
/**
* Creates a new World.
*
* @param {Object} el The DOM element representing the world.
* @constructor
*/
function World(el) {
var viewportSize = exports.Utils.getViewportSize();
if (!el || typeof el !== 'object') {
throw new Error('World: A valid DOM object is required for a new World.');
}
this.el = el;
this.el.className = 'world';
this.width = viewportSize.width;
this.height = viewportSize.height;
this.location = new exports.Vector(viewportSize.width / 2, viewportSize.height / 2);
this.gravity = new exports.Vector(0, 0.1);
this.wind = new exports.Vector(0.05, 0);
this.thermal = new exports.Vector(0, -0.025);
this.color = 'transparent';
this.visibility ='visible';
this.cacheVector = new exports.Vector();
}
/**
* Worlds do not have worlds. However, assigning a
* blank object avoid coding extra logic in System._update.
*/
World.prototype.world = {};
/**
* Updates properties.
*/
World.prototype.step = function() {};
/**
* Updates the corresponding DOM element's style property.
*/
World.prototype.draw = function() {
exports.System._draw(this);
};
exports.World = World;
}(exports));
(function(exports) {
/**
* Creates a new Item.
*
* @param {Object} options A map of initial properties.
* @constructor
*/
function Item(options) {
if (!options || !options.world || typeof options.world !== 'object') {
throw new Error('Item: A valid DOM object is required for a new Item.');
}
this.world = options.world;
this.name = options.name || 'Item';
this.id = this.name + exports.System.getNewId();
this.el = document.createElement('div');
this.el.id = this.id;
this.el.className = 'item ' + this.name.toLowerCase();
this.el.style.visibility = 'hidden';
this.world.el.appendChild(this.el);
}
/**
* Initializes the object.
*/
Item.prototype.init = function(opt_options) {
var options = opt_options || {};
this.acceleration = options.acceleration || new exports.Vector();
this.velocity = options.velocity || new exports.Vector();
this.location = options.location || new exports.Vector(this.world.width / 2, this.world.height / 2);
this.width = options.width || 20;
this.height = options.height || 20;
this.mass = (this.width * this.height) * 0.01;
this.color = options.color || [0, 0, 0];
this.visibility = options.visibility || 'visible';
this.checkWorldEdges = options.checkWorldEdges === undefined ? true : options.checkWorldEdges;
};
/**
* Updates properties.
*/
Item.prototype.step = function() {
this.applyForce(this.world.wind);
this.applyForce(this.world.thermal);
this.applyForce(this.world.gravity);
this.velocity.add(this.acceleration);
if (this.checkWorldEdges) {
this._checkWorldEdges();
}
this.location.add(this.velocity);
this.acceleration.mult(0);
};
/**
* Adds a force to this object's acceleration.
*
* @param {Object} force A Vector representing a force to apply.
*/
Item.prototype.applyForce = function(force) {
var vector = this.world.cacheVector;
vector.x = force.x;
vector.y = force.y;
vector.div(this.mass);
this.acceleration.add(vector);
};
/**
* Determines if this object is outside the world bounds.
* @private
*/
Item.prototype._checkWorldEdges = function() {
var world = this.world,
location = this.location,
velocity = this.velocity,
width = this.width,
height = this.height;
if (location.x + width / 2 > world.width) {
location.x = world.width - width / 2;
velocity.x *= -1;
} else if (location.x < width / 2) {
location.x = width / 2;
velocity.x *= -1;
}
if (location.y + height / 2 > world.height) {
location.y = world.height - height / 2;
velocity.y *= -1;
} else if (location.y < height / 2) {
location.y = height / 2;
velocity.y *= -1;
}
};
/**
* Updates the corresponding DOM element's style property.
*/
Item.prototype.draw = function() {
exports.System._draw(this);
};
exports.Item = Item;
}(exports));
(function(exports) {
var Utils = {};
/**
* Determines the size of the browser viewport.
*
* @returns {Object} The current browser viewport width and height.
* @private
*/
Utils.getViewportSize = function() {
var d = {};
if (typeof(window.innerWidth) !== 'undefined') {
d.width = window.innerWidth;
d.height = window.innerHeight;
} else if (typeof(document.documentElement) !== 'undefined' &&
typeof(document.documentElement.clientWidth) !== 'undefined') {
d.width = document.documentElement.clientWidth;
d.height = document.documentElement.clientHeight;
} else if (typeof(document.body) !== 'undefined') {
d.width = document.body.clientWidth;
d.height = document.body.clientHeight;
} else {
d.width = undefined;
d.height = undefined;
}
return d;
};
exports.Utils = Utils;
}(exports));
(function(exports) {
var Classes = {};
exports.Classes = Classes;
}(exports));
(function(exports) {
/**
* Creates a new Vector.
*
* @param {number} [opt_x = 0] The x location.
* @param {number} [opt_y = 0] The y location.
* @constructor
*/
function Vector(opt_x, opt_y) {
var x = opt_x || 0,
y = opt_y || 0;
this.x = x;
this.y = y;
}
/**
* Adds a vector to this vector.
*
* @param {Object} vector The vector to add.
* @returns {Object} This vector.
*/
Vector.prototype.add = function(vector) {
this.x += vector.x;
this.y += vector.y;
return this;
};
/**
* Subtracts a vector from this vector.
*
* @param {Object} vector The vector to subtract.
* @returns {Object} This vector.
*/
Vector.prototype.sub = function(vector) {
this.x -= vector.x;
this.y -= vector.y;
return this;
};
/**
* Multiplies this vector by a passed value.
*
* @param {number} n Vector will be multiplied by this number.
* @returns {Object} This vector.
*/
Vector.prototype.mult = function(n) {
this.x *= n;
this.y *= n;
return this;
};
/**
* Divides this vector by a passed value.
*
* @param {number} n Vector will be divided by this number.
* @returns {Object} This vector.
*/
Vector.prototype.div = function(n) {
this.x = this.x / n;
this.y = this.y / n;
return this;
};
/**
* Calculates the magnitude of this vector.
*
* @returns {number} The vector's magnitude.
*/
Vector.prototype.mag = function() {
return Math.sqrt((this.x * this.x) + (this.y * this.y));
};
/**
* Limits the vector's magnitude.
*
* @param {number} opt_high The upper bound of the vector's magnitude
* @param {number} opt_low The lower bound of the vector's magnitude.
* @returns {Object} This vector.
*/
Vector.prototype.limit = function(opt_high, opt_low) {
var high = opt_high || null,
low = opt_low || null;
if (high && this.mag() > high) {
this.normalize();
this.mult(high);
}
if (low && this.mag() < low) {
this.normalize();
this.mult(low);
}
return this;
};
/**
* Divides a vector by its magnitude to reduce its magnitude to 1.
* Typically used to retrieve the direction of the vector for later manipulation.
*
* @returns {Object} This vector.
*/
Vector.prototype.normalize = function() {
var m = this.mag();
if (m !== 0) {
return this.div(m);
}
};
/**
* Rotates a vector using a passed angle in radians.
*
* @param {number} radians The angle to rotate in radians.
* @returns {Object} This vector.
*/
Vector.prototype.rotate = function(radians) {
var cos = Math.cos(radians),
sin = Math.sin(radians),
x = this.x,
y = this.y;
this.x = x * cos - y * sin;
this.y = x * sin + y * cos;
return this;
};
exports.Vector = Vector;
}(exports));
/**
* RequestAnimationFrame shim layer with setTimeout fallback
* @param {function} callback The function to call.
* @returns {function|Object} An animation frame or a timeout object.
*/
window.requestAnimFrame = (function(callback){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment