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); } } }); }); }); });