define(["sugar-web/activity/activity"], function (activity) {
|
|
|
|
// Manipulate the DOM only when it is ready.
|
|
requirejs(['domReady!'], function (doc) {
|
|
|
|
// Initialize the activity
|
|
activity.setup();
|
|
|
|
// Initialize cordova
|
|
var useragent = navigator.userAgent.toLowerCase();
|
|
var sensorButton = document.getElementById("sensor-button");
|
|
var gravityButton = document.getElementById("gravity-button");
|
|
var appleButton = document.getElementById("apple-button");
|
|
var runButton = document.getElementById("run-button");
|
|
var readyToWatch = false;
|
|
var sensorMode = true;
|
|
var newtonMode = false;
|
|
var resizeTimer = null;
|
|
if (useragent.indexOf('android') != -1 || useragent.indexOf('iphone') != -1 || useragent.indexOf('ipad') != -1 || useragent.indexOf('ipod') != -1 || useragent.indexOf('mozilla/5.0 (mobile') != -1) {
|
|
document.addEventListener('deviceready', function() {
|
|
readyToWatch = true;
|
|
}, false);
|
|
sensorButton.disabled = false;
|
|
sensorButton.classList.add('active');
|
|
} else {
|
|
sensorButton.disabled = true;
|
|
}
|
|
|
|
// Initialize the world
|
|
var body = document.getElementById("body");
|
|
var innerWidth = body.offsetWidth;
|
|
var innerHeight = body.offsetHeight;
|
|
var toolbarHeight = 55;
|
|
var outerWidth = 0; // Use to determine if items could disappear, could be 300;
|
|
var init = false;
|
|
var gravityMode = 0;
|
|
var currentType = 0;
|
|
var physicsActive = true;
|
|
Physics({ timestep: 6 }, function (world) {
|
|
|
|
// bounds of the window
|
|
var viewWidth = innerWidth
|
|
,viewportBounds = Physics.aabb(0-outerWidth, toolbarHeight, innerWidth+outerWidth, innerHeight)
|
|
,edgeBounce
|
|
,renderer
|
|
;
|
|
|
|
// let's use the pixi renderer
|
|
requirejs(['pixi'], function( PIXI ){
|
|
window.PIXI = PIXI;
|
|
// create a renderer
|
|
renderer = Physics.renderer('pixi', {
|
|
el: 'viewport'
|
|
});
|
|
|
|
// add the renderer
|
|
world.add(renderer);
|
|
// render on each step
|
|
world.on('step', function () {
|
|
world.render();
|
|
if (!init) {
|
|
init = true;
|
|
zoom();
|
|
}
|
|
if (readyToWatch) {
|
|
watchId = navigator.accelerometer.watchAcceleration(accelerationChanged, null, { frequency: 500 });
|
|
readyToWatch = false;
|
|
}
|
|
});
|
|
// add the interaction
|
|
world.add(Physics.behavior('interactive', { el: renderer.container }));
|
|
});
|
|
|
|
// constrain objects to these bounds
|
|
edgeBounce = Physics.behavior('edge-collision-detection', {
|
|
aabb: viewportBounds
|
|
,restitution: 0.2
|
|
,cof: 0.8
|
|
});
|
|
|
|
// resize events
|
|
window.addEventListener('resize', function () {
|
|
if (resizeTimer) {
|
|
clearTimeout(resizeTimer);
|
|
}
|
|
resizerTimer = setTimeout(function() {
|
|
renderer.resize(body.offsetWidth,body.offsetHeight);
|
|
// as of 0.7.0 the renderer will auto resize... so we just take the values from the renderer
|
|
viewportBounds = Physics.aabb(0-outerWidth, toolbarHeight, renderer.width+outerWidth, renderer.height);
|
|
// update the boundaries
|
|
edgeBounce.setAABB(viewportBounds);
|
|
innerWidth = body.offsetWidth;
|
|
innerHeight = body.offsetHeight;
|
|
zoom();
|
|
}, 500);
|
|
|
|
}, true);
|
|
|
|
// handle toolbar buttons
|
|
document.getElementById("box-button").addEventListener('click', function (e) {
|
|
currentType = 1;
|
|
switchToType(currentType);
|
|
}, true);
|
|
document.getElementById("circle-button").addEventListener('click', function (e) {
|
|
currentType = 0;
|
|
switchToType(currentType);
|
|
}, true);
|
|
|
|
document.getElementById("triangle-button").addEventListener('click', function (e) {
|
|
currentType = 2;
|
|
switchToType(currentType);
|
|
}, true);
|
|
|
|
document.getElementById("polygon-button").addEventListener('click', function (e) {
|
|
currentType = 3;
|
|
switchToType(currentType);
|
|
}, true);
|
|
|
|
gravityButton.addEventListener('click', function () {
|
|
setGravity((gravityMode + 1)%8);
|
|
}, true);
|
|
|
|
runButton.addEventListener('click', function () {
|
|
togglePause();
|
|
}, true);
|
|
|
|
document.getElementById("clear-button").addEventListener('click', function () {
|
|
currentType = -1;
|
|
switchToType(currentType);
|
|
}, true);
|
|
|
|
// Handle acceleration and gravity mode
|
|
sensorButton.addEventListener('click', function () {
|
|
sensorMode = !sensorMode;
|
|
if (sensorMode)
|
|
sensorButton.classList.add('active');
|
|
else
|
|
sensorButton.classList.remove('active');
|
|
}, true);
|
|
|
|
appleButton.addEventListener('click', function () {
|
|
newtonMode = !newtonMode;
|
|
if (newtonMode) {
|
|
world.remove(gravity);
|
|
world.add(newton);
|
|
appleButton.classList.add('active');
|
|
gravityButton.disabled = true;
|
|
} else {
|
|
world.remove(newton);
|
|
world.add(gravity);
|
|
appleButton.classList.remove('active');
|
|
gravityButton.disabled = false;
|
|
}
|
|
}, true);
|
|
|
|
function accelerationChanged(acceleration) {
|
|
if (!sensorMode) return;
|
|
if (acceleration.x < -4.5) {
|
|
if (acceleration.y > 4.75)
|
|
setGravity(3);
|
|
else if (acceleration.y < -4.75)
|
|
setGravity(5);
|
|
else
|
|
setGravity(4);
|
|
} else if (acceleration.x <= 4.5 && acceleration.x >= -4.5) {
|
|
if (acceleration.y > 4.75)
|
|
setGravity(2);
|
|
else if (acceleration.y < -4.75)
|
|
setGravity(6);
|
|
} else if (acceleration.x > 4.5) {
|
|
if (acceleration.y > 4.75)
|
|
setGravity(1);
|
|
else if (acceleration.y < -4.75)
|
|
setGravity(7);
|
|
else
|
|
setGravity(0);
|
|
}
|
|
}
|
|
|
|
// Save/Load world
|
|
loadWorld();
|
|
var stopButton = document.getElementById("stop-button");
|
|
stopButton.addEventListener('click', function (event) {
|
|
console.log("writing...");
|
|
saveWorld(function (error) {
|
|
if (error === null) {
|
|
console.log("write done.");
|
|
}
|
|
else {
|
|
console.log("write failed.");
|
|
}
|
|
});
|
|
});
|
|
|
|
// Force resize renderer at startup to avoid glitch margin
|
|
var initialResize = function() {
|
|
if (renderer) {
|
|
renderer.resize(body.offsetWidth,body.offsetHeight);
|
|
} else {
|
|
setTimeout(initialResize, 300);
|
|
}
|
|
};
|
|
setTimeout(initialResize, 300);
|
|
|
|
var colors = [
|
|
['0x268bd2', '0x0d394f']
|
|
,['0xc93b3b', '0x561414']
|
|
,['0xe25e36', '0x79231b']
|
|
,['0x6c71c4', '0x393f6a']
|
|
,['0x58c73c', '0x30641c']
|
|
,['0xcac34c', '0x736a2c']
|
|
];
|
|
|
|
function zoom() {
|
|
if (window.devicePixelRatio == 1) {
|
|
return;
|
|
}
|
|
var canvas = document.getElementById("viewport").children[0];
|
|
var zoom = 1.0 / window.devicePixelRatio;
|
|
canvas.style.zoom = zoom;
|
|
var useragent = navigator.userAgent.toLowerCase();
|
|
if (useragent.indexOf('chrome') == -1) {
|
|
canvas.style.MozTransform = "scale("+zoom+")";
|
|
canvas.style.MozTransformOrigin = "0 0";
|
|
}
|
|
world.wakeUpAll();
|
|
}
|
|
|
|
function random( min, max ){
|
|
return (Math.random() * (max-min) + min)|0;
|
|
}
|
|
|
|
function switchToType(newtype) {
|
|
document.getElementById("box-button").classList.remove('active');
|
|
document.getElementById("circle-button").classList.remove('active');
|
|
document.getElementById("polygon-button").classList.remove('active');
|
|
document.getElementById("triangle-button").classList.remove('active');
|
|
document.getElementById("clear-button").classList.remove('active');
|
|
if (newtype == 0) document.getElementById("circle-button").classList.add('active');
|
|
else if (newtype == 1) document.getElementById("box-button").classList.add('active');
|
|
else if (newtype == 2) document.getElementById("triangle-button").classList.add('active');
|
|
else if (newtype == 3) document.getElementById("polygon-button").classList.add('active');
|
|
else if (newtype == -1) document.getElementById("clear-button").classList.add('active');
|
|
}
|
|
|
|
function dropInBody(type, pos){
|
|
|
|
var body;
|
|
var c;
|
|
|
|
switch (type){
|
|
|
|
// add a circle
|
|
case 0:
|
|
c = colors[random(0, colors.length-1)];
|
|
body = Physics.body('circle', {
|
|
x: pos.x
|
|
,y: pos.y
|
|
,vx: random(-5, 5)/100
|
|
,radius: 40
|
|
,restitution: 0.9
|
|
,styles: {
|
|
fillStyle: c[0]
|
|
,strokeStyle: c[1]
|
|
,lineWidth: 1
|
|
,angleIndicator: c[1]
|
|
}
|
|
});
|
|
break;
|
|
|
|
// add a square
|
|
case 1:
|
|
c = colors[random(0, colors.length-1)];
|
|
var l = random(0, 70);
|
|
body = Physics.body('rectangle', {
|
|
width: 50+l
|
|
,height: 50+l
|
|
,x: pos.x
|
|
,y: pos.y
|
|
,vx: random(-5, 5)/100
|
|
,restitution: 0.9
|
|
,styles: {
|
|
fillStyle: c[0]
|
|
,strokeStyle: c[1]
|
|
,lineWidth: 1
|
|
,angleIndicator: c[1]
|
|
}
|
|
});
|
|
break;
|
|
|
|
|
|
// add a polygon
|
|
case 2:
|
|
case 3:
|
|
var s = (type == 2 ? 3 : random( 5, 10 ));
|
|
c = colors[ random(0, colors.length-1) ];
|
|
body = Physics.body('convex-polygon', {
|
|
vertices: Physics.geometry.regularPolygonVertices( s, 30 )
|
|
,x: pos.x
|
|
,y: pos.y
|
|
,vx: random(-5, 5)/100
|
|
,angle: random( 0, 2 * Math.PI )
|
|
,restitution: 0.9
|
|
,styles: {
|
|
fillStyle: c[0]
|
|
,strokeStyle: c[1]
|
|
,lineWidth: 1
|
|
,angleIndicator: c[1]
|
|
}
|
|
});
|
|
break;
|
|
}
|
|
|
|
body.treatment = "static";
|
|
|
|
world.add( body );
|
|
return body;
|
|
}
|
|
|
|
// Save world to datastore
|
|
function saveWorld(callback) {
|
|
// Build bodies list
|
|
var bodies = world.getBodies();
|
|
var objects = [];
|
|
for(var i = 0 ; i < bodies.length ; i++) {
|
|
var object = serializeObject(bodies[i]);
|
|
objects.push(object);
|
|
}
|
|
|
|
// Save to datastore
|
|
var datastoreObject = activity.getDatastoreObject();
|
|
var jsonData = JSON.stringify({world: objects});
|
|
datastoreObject.setDataAsText(jsonData);
|
|
datastoreObject.save(callback);
|
|
}
|
|
|
|
function serializeObject(body) {
|
|
var object = {};
|
|
object.type = body.geometry.name;
|
|
if (object.type == "circle") {
|
|
object.radius = body.radius;
|
|
} else if (body.geometry.name == "rectangle") {
|
|
object.width = body.view.width;
|
|
object.height = body.view.height;
|
|
} else if (body.geometry.name == "convex-polygon") {
|
|
object.vertices = body.vertices;
|
|
}
|
|
object.restitution = body.restitution;
|
|
object.styles = body.styles;
|
|
object.x = body.view.x;
|
|
object.y = body.view.y;
|
|
return object;
|
|
}
|
|
|
|
// Load world from datastore
|
|
function loadWorld(objects) {
|
|
var datastoreObject = activity.getDatastoreObject();
|
|
datastoreObject.loadAsText(function (error, metadata, data) {
|
|
var data = JSON.parse(data);
|
|
if (data == null)
|
|
return;
|
|
|
|
// Create bodies
|
|
var objects = data.world;
|
|
for(var i = 0 ; i < objects.length ; i++) {
|
|
var newBody = deserializeObject(objects[i]);
|
|
world.add(newBody);
|
|
}
|
|
});
|
|
}
|
|
|
|
function deserializeObject(savedObject) {
|
|
var newOptions = {
|
|
x: savedObject.x,
|
|
y: savedObject.y,
|
|
restitution: savedObject.restitution,
|
|
styles: savedObject.styles
|
|
};
|
|
if (savedObject.angle)
|
|
newOptions.angle = savedObject.angle;
|
|
if (savedObject.type == "circle") {
|
|
newOptions.radius = savedObject.radius;
|
|
} else if (savedObject.type == "rectangle") {
|
|
newOptions.width = savedObject.width;
|
|
newOptions.height = savedObject.height;
|
|
} else if (savedObject.type = "convex-polygon") {
|
|
newOptions.vertices = savedObject.vertices;
|
|
}
|
|
return Physics.body(savedObject.type, newOptions);
|
|
}
|
|
|
|
function setBodiesTreatmentStatic() {
|
|
var bodies = world.getBodies();
|
|
bodies.forEach(function(item, index, array) {
|
|
item.treatment = 'static';
|
|
});
|
|
}
|
|
|
|
function setBodiesTreatmentDynamic() {
|
|
var bodies = world.getBodies();
|
|
bodies.forEach(function(item, index, array) {
|
|
item.treatment = 'dynamic';
|
|
});
|
|
}
|
|
|
|
function togglePause() {
|
|
if (physicsActive) {
|
|
document.getElementById("run-button").classList.remove('running');
|
|
document.getElementById("run-button").setAttribute('title', 'Play');
|
|
setBodiesTreatmentStatic();
|
|
} else {
|
|
document.getElementById("run-button").classList.add('running');
|
|
document.getElementById("run-button").setAttribute('title', 'Pause');
|
|
Physics.util.ticker.start();
|
|
setBodiesTreatmentDynamic();
|
|
}
|
|
physicsActive = !physicsActive;
|
|
}
|
|
|
|
// Change gravity value
|
|
function setGravity(value) {
|
|
if (gravityMode == value) return;
|
|
var acc = {};
|
|
switch(value) {
|
|
case 0:
|
|
acc = { x: 0, y: 0.0004 };
|
|
break;
|
|
case 1:
|
|
acc = { x: 0.0004, y: 0.0004 };
|
|
break;
|
|
case 2:
|
|
acc = { x: 0.0004, y: 0 };
|
|
break;
|
|
case 3:
|
|
acc = { x: 0.0004, y: -0.0004 };
|
|
break;
|
|
case 4:
|
|
acc = { x: 0, y: -0.0004 };
|
|
break;
|
|
case 5:
|
|
acc = { x: -0.0004, y: -0.0004 };
|
|
break;
|
|
case 6:
|
|
acc = { x: -0.0004, y: 0 };
|
|
break;
|
|
case 7:
|
|
acc = { x: -0.0004, y: 0.0004 };
|
|
break;
|
|
}
|
|
var reverse = (window.orientation == -90 ? -1 : 1);
|
|
acc = { x: acc.x * reverse, y: acc.y * reverse };
|
|
document.getElementById("gravity-button").style.backgroundImage = "url(icons/gravity"+(reverse == -1 ? (value+4)%8 : value)+".svg)";
|
|
gravity.setAcceleration(acc);
|
|
world.wakeUpAll();
|
|
gravityMode = value;
|
|
}
|
|
|
|
// add some fun interaction
|
|
var createdBody = null;
|
|
var createdStart = null;
|
|
world.on({
|
|
'interact:poke': function( pos ){
|
|
// create body at a static place
|
|
if (currentType != -1 && pos.y > toolbarHeight) {
|
|
createdBody = dropInBody(currentType, pos);
|
|
createdStart = pos;
|
|
}
|
|
}
|
|
,'interact:move': function( pos ){
|
|
// update size of created body
|
|
if (createdBody != null) {
|
|
// compute new size
|
|
var distx = createdStart.x - pos.x;
|
|
var disty = createdStart.y - pos.y;
|
|
var distance = Math.min(Math.sqrt(Math.abs(distx*distx-disty*disty)),createdStart.y-toolbarHeight);
|
|
if (createdBody.view != null) {
|
|
// Recreate the object with new size
|
|
var object = serializeObject(createdBody);
|
|
if (object.type == "circle") {
|
|
object.radius = Math.max(40, distance);
|
|
} else if (object.type == "rectangle") {
|
|
object.width = object.height = Math.max(50, distance);
|
|
} else if (object.type = "convex-polygon") {
|
|
object.vertices = Physics.geometry.regularPolygonVertices( object.vertices.length, Math.max(30, distance));
|
|
}
|
|
world.removeBody(createdBody);
|
|
var v1 = new Physics.vector(createdStart.x, 0);
|
|
var v2 = new Physics.vector(pos.x-createdStart.x, pos.y-createdStart.y);
|
|
object.angle = -v1.angle(v2);
|
|
createdBody = deserializeObject(object);
|
|
createdBody.treatment = "static";
|
|
world.add(createdBody);
|
|
}
|
|
}
|
|
}
|
|
,'interact:release': function( pos ){
|
|
if (physicsActive) {
|
|
if (createdBody != null) {
|
|
createdBody.treatment = "dynamic";
|
|
}
|
|
world.wakeUpAll();
|
|
}
|
|
createdBody = null;
|
|
}
|
|
,'interact:grab': function ( data ) {
|
|
if (currentType == -1) {
|
|
world.remove(data.body);
|
|
}
|
|
}
|
|
});
|
|
|
|
// add things to the world
|
|
var gravity = Physics.behavior('constant-acceleration');
|
|
var newton = Physics.behavior('newtonian', { strength: .5 });
|
|
world.add([
|
|
gravity
|
|
,Physics.behavior('body-impulse-response')
|
|
,Physics.behavior('body-collision-detection')
|
|
,Physics.behavior('sweep-prune')
|
|
,edgeBounce
|
|
]);
|
|
|
|
// subscribe to ticker to advance the simulation
|
|
Physics.util.ticker.on(function( time ) {
|
|
// next step
|
|
world.step( time );
|
|
|
|
// remove bodies out of
|
|
var bodies = world.getBodies();
|
|
var limit = outerWidth / 2;
|
|
if (limit > 0) {
|
|
for(var i = 0 ; i < bodies.length ; i++) {
|
|
var body = bodies[i];
|
|
if (body.state.pos.x < 0-limit || body.state.pos.x > innerWidth+limit)
|
|
world.remove(body);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
});
|