- 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.
Last active
March 3, 2018 00:56
-
-
Save foldi/5867007 to your computer and use it in GitHub Desktop.
SimpleSim step 8
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 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=["­",'<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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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