not really known
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

542 lines
15 KiB

  1. define(["sugar-web/activity/activity"], function (activity) {
  2. // Manipulate the DOM only when it is ready.
  3. requirejs(['domReady!'], function (doc) {
  4. // Initialize the activity
  5. activity.setup();
  6. // Initialize cordova
  7. var useragent = navigator.userAgent.toLowerCase();
  8. var sensorButton = document.getElementById("sensor-button");
  9. var gravityButton = document.getElementById("gravity-button");
  10. var appleButton = document.getElementById("apple-button");
  11. var runButton = document.getElementById("run-button");
  12. var readyToWatch = false;
  13. var sensorMode = true;
  14. var newtonMode = false;
  15. var resizeTimer = null;
  16. if (useragent.indexOf('android') != -1 || useragent.indexOf('iphone') != -1 || useragent.indexOf('ipad') != -1 || useragent.indexOf('ipod') != -1 || useragent.indexOf('mozilla/5.0 (mobile') != -1) {
  17. document.addEventListener('deviceready', function() {
  18. readyToWatch = true;
  19. }, false);
  20. sensorButton.disabled = false;
  21. sensorButton.classList.add('active');
  22. } else {
  23. sensorButton.disabled = true;
  24. }
  25. // Initialize the world
  26. var body = document.getElementById("body");
  27. var innerWidth = body.offsetWidth;
  28. var innerHeight = body.offsetHeight;
  29. var toolbarHeight = 55;
  30. var outerWidth = 0; // Use to determine if items could disappear, could be 300;
  31. var init = false;
  32. var gravityMode = 0;
  33. var currentType = 0;
  34. var physicsActive = true;
  35. Physics({ timestep: 6 }, function (world) {
  36. // bounds of the window
  37. var viewWidth = innerWidth
  38. ,viewportBounds = Physics.aabb(0-outerWidth, toolbarHeight, innerWidth+outerWidth, innerHeight)
  39. ,edgeBounce
  40. ,renderer
  41. ;
  42. // let's use the pixi renderer
  43. requirejs(['pixi'], function( PIXI ){
  44. window.PIXI = PIXI;
  45. // create a renderer
  46. renderer = Physics.renderer('pixi', {
  47. el: 'viewport'
  48. });
  49. // add the renderer
  50. world.add(renderer);
  51. // render on each step
  52. world.on('step', function () {
  53. world.render();
  54. if (!init) {
  55. init = true;
  56. zoom();
  57. }
  58. if (readyToWatch) {
  59. watchId = navigator.accelerometer.watchAcceleration(accelerationChanged, null, { frequency: 500 });
  60. readyToWatch = false;
  61. }
  62. });
  63. // add the interaction
  64. world.add(Physics.behavior('interactive', { el: renderer.container }));
  65. });
  66. // constrain objects to these bounds
  67. edgeBounce = Physics.behavior('edge-collision-detection', {
  68. aabb: viewportBounds
  69. ,restitution: 0.2
  70. ,cof: 0.8
  71. });
  72. // resize events
  73. window.addEventListener('resize', function () {
  74. if (resizeTimer) {
  75. clearTimeout(resizeTimer);
  76. }
  77. resizerTimer = setTimeout(function() {
  78. renderer.resize(body.offsetWidth,body.offsetHeight);
  79. // as of 0.7.0 the renderer will auto resize... so we just take the values from the renderer
  80. viewportBounds = Physics.aabb(0-outerWidth, toolbarHeight, renderer.width+outerWidth, renderer.height);
  81. // update the boundaries
  82. edgeBounce.setAABB(viewportBounds);
  83. innerWidth = body.offsetWidth;
  84. innerHeight = body.offsetHeight;
  85. zoom();
  86. }, 500);
  87. }, true);
  88. // handle toolbar buttons
  89. document.getElementById("box-button").addEventListener('click', function (e) {
  90. currentType = 1;
  91. switchToType(currentType);
  92. }, true);
  93. document.getElementById("circle-button").addEventListener('click', function (e) {
  94. currentType = 0;
  95. switchToType(currentType);
  96. }, true);
  97. document.getElementById("triangle-button").addEventListener('click', function (e) {
  98. currentType = 2;
  99. switchToType(currentType);
  100. }, true);
  101. document.getElementById("polygon-button").addEventListener('click', function (e) {
  102. currentType = 3;
  103. switchToType(currentType);
  104. }, true);
  105. gravityButton.addEventListener('click', function () {
  106. setGravity((gravityMode + 1)%8);
  107. }, true);
  108. runButton.addEventListener('click', function () {
  109. togglePause();
  110. }, true);
  111. document.getElementById("clear-button").addEventListener('click', function () {
  112. currentType = -1;
  113. switchToType(currentType);
  114. }, true);
  115. // Handle acceleration and gravity mode
  116. sensorButton.addEventListener('click', function () {
  117. sensorMode = !sensorMode;
  118. if (sensorMode)
  119. sensorButton.classList.add('active');
  120. else
  121. sensorButton.classList.remove('active');
  122. }, true);
  123. appleButton.addEventListener('click', function () {
  124. newtonMode = !newtonMode;
  125. if (newtonMode) {
  126. world.remove(gravity);
  127. world.add(newton);
  128. appleButton.classList.add('active');
  129. gravityButton.disabled = true;
  130. } else {
  131. world.remove(newton);
  132. world.add(gravity);
  133. appleButton.classList.remove('active');
  134. gravityButton.disabled = false;
  135. }
  136. }, true);
  137. function accelerationChanged(acceleration) {
  138. if (!sensorMode) return;
  139. if (acceleration.x < -4.5) {
  140. if (acceleration.y > 4.75)
  141. setGravity(3);
  142. else if (acceleration.y < -4.75)
  143. setGravity(5);
  144. else
  145. setGravity(4);
  146. } else if (acceleration.x <= 4.5 && acceleration.x >= -4.5) {
  147. if (acceleration.y > 4.75)
  148. setGravity(2);
  149. else if (acceleration.y < -4.75)
  150. setGravity(6);
  151. } else if (acceleration.x > 4.5) {
  152. if (acceleration.y > 4.75)
  153. setGravity(1);
  154. else if (acceleration.y < -4.75)
  155. setGravity(7);
  156. else
  157. setGravity(0);
  158. }
  159. }
  160. // Save/Load world
  161. loadWorld();
  162. var stopButton = document.getElementById("stop-button");
  163. stopButton.addEventListener('click', function (event) {
  164. console.log("writing...");
  165. saveWorld(function (error) {
  166. if (error === null) {
  167. console.log("write done.");
  168. }
  169. else {
  170. console.log("write failed.");
  171. }
  172. });
  173. });
  174. // Force resize renderer at startup to avoid glitch margin
  175. var initialResize = function() {
  176. if (renderer) {
  177. renderer.resize(body.offsetWidth,body.offsetHeight);
  178. } else {
  179. setTimeout(initialResize, 300);
  180. }
  181. };
  182. setTimeout(initialResize, 300);
  183. var colors = [
  184. ['0x268bd2', '0x0d394f']
  185. ,['0xc93b3b', '0x561414']
  186. ,['0xe25e36', '0x79231b']
  187. ,['0x6c71c4', '0x393f6a']
  188. ,['0x58c73c', '0x30641c']
  189. ,['0xcac34c', '0x736a2c']
  190. ];
  191. function zoom() {
  192. if (window.devicePixelRatio == 1) {
  193. return;
  194. }
  195. var canvas = document.getElementById("viewport").children[0];
  196. var zoom = 1.0 / window.devicePixelRatio;
  197. canvas.style.zoom = zoom;
  198. var useragent = navigator.userAgent.toLowerCase();
  199. if (useragent.indexOf('chrome') == -1) {
  200. canvas.style.MozTransform = "scale("+zoom+")";
  201. canvas.style.MozTransformOrigin = "0 0";
  202. }
  203. world.wakeUpAll();
  204. }
  205. function random( min, max ){
  206. return (Math.random() * (max-min) + min)|0;
  207. }
  208. function switchToType(newtype) {
  209. document.getElementById("box-button").classList.remove('active');
  210. document.getElementById("circle-button").classList.remove('active');
  211. document.getElementById("polygon-button").classList.remove('active');
  212. document.getElementById("triangle-button").classList.remove('active');
  213. document.getElementById("clear-button").classList.remove('active');
  214. if (newtype == 0) document.getElementById("circle-button").classList.add('active');
  215. else if (newtype == 1) document.getElementById("box-button").classList.add('active');
  216. else if (newtype == 2) document.getElementById("triangle-button").classList.add('active');
  217. else if (newtype == 3) document.getElementById("polygon-button").classList.add('active');
  218. else if (newtype == -1) document.getElementById("clear-button").classList.add('active');
  219. }
  220. function dropInBody(type, pos){
  221. var body;
  222. var c;
  223. switch (type){
  224. // add a circle
  225. case 0:
  226. c = colors[random(0, colors.length-1)];
  227. body = Physics.body('circle', {
  228. x: pos.x
  229. ,y: pos.y
  230. ,vx: random(-5, 5)/100
  231. ,radius: 40
  232. ,restitution: 0.9
  233. ,styles: {
  234. fillStyle: c[0]
  235. ,strokeStyle: c[1]
  236. ,lineWidth: 1
  237. ,angleIndicator: c[1]
  238. }
  239. });
  240. break;
  241. // add a square
  242. case 1:
  243. c = colors[random(0, colors.length-1)];
  244. var l = random(0, 70);
  245. body = Physics.body('rectangle', {
  246. width: 50+l
  247. ,height: 50+l
  248. ,x: pos.x
  249. ,y: pos.y
  250. ,vx: random(-5, 5)/100
  251. ,restitution: 0.9
  252. ,styles: {
  253. fillStyle: c[0]
  254. ,strokeStyle: c[1]
  255. ,lineWidth: 1
  256. ,angleIndicator: c[1]
  257. }
  258. });
  259. break;
  260. // add a polygon
  261. case 2:
  262. case 3:
  263. var s = (type == 2 ? 3 : random( 5, 10 ));
  264. c = colors[ random(0, colors.length-1) ];
  265. body = Physics.body('convex-polygon', {
  266. vertices: Physics.geometry.regularPolygonVertices( s, 30 )
  267. ,x: pos.x
  268. ,y: pos.y
  269. ,vx: random(-5, 5)/100
  270. ,angle: random( 0, 2 * Math.PI )
  271. ,restitution: 0.9
  272. ,styles: {
  273. fillStyle: c[0]
  274. ,strokeStyle: c[1]
  275. ,lineWidth: 1
  276. ,angleIndicator: c[1]
  277. }
  278. });
  279. break;
  280. }
  281. body.treatment = "static";
  282. world.add( body );
  283. return body;
  284. }
  285. // Save world to datastore
  286. function saveWorld(callback) {
  287. // Build bodies list
  288. var bodies = world.getBodies();
  289. var objects = [];
  290. for(var i = 0 ; i < bodies.length ; i++) {
  291. var object = serializeObject(bodies[i]);
  292. objects.push(object);
  293. }
  294. // Save to datastore
  295. var datastoreObject = activity.getDatastoreObject();
  296. var jsonData = JSON.stringify({world: objects});
  297. datastoreObject.setDataAsText(jsonData);
  298. datastoreObject.save(callback);
  299. }
  300. function serializeObject(body) {
  301. var object = {};
  302. object.type = body.geometry.name;
  303. if (object.type == "circle") {
  304. object.radius = body.radius;
  305. } else if (body.geometry.name == "rectangle") {
  306. object.width = body.view.width;
  307. object.height = body.view.height;
  308. } else if (body.geometry.name == "convex-polygon") {
  309. object.vertices = body.vertices;
  310. }
  311. object.restitution = body.restitution;
  312. object.styles = body.styles;
  313. object.x = body.view.x;
  314. object.y = body.view.y;
  315. return object;
  316. }
  317. // Load world from datastore
  318. function loadWorld(objects) {
  319. var datastoreObject = activity.getDatastoreObject();
  320. datastoreObject.loadAsText(function (error, metadata, data) {
  321. var data = JSON.parse(data);
  322. if (data == null)
  323. return;
  324. // Create bodies
  325. var objects = data.world;
  326. for(var i = 0 ; i < objects.length ; i++) {
  327. var newBody = deserializeObject(objects[i]);
  328. world.add(newBody);
  329. }
  330. });
  331. }
  332. function deserializeObject(savedObject) {
  333. var newOptions = {
  334. x: savedObject.x,
  335. y: savedObject.y,
  336. restitution: savedObject.restitution,
  337. styles: savedObject.styles
  338. };
  339. if (savedObject.angle)
  340. newOptions.angle = savedObject.angle;
  341. if (savedObject.type == "circle") {
  342. newOptions.radius = savedObject.radius;
  343. } else if (savedObject.type == "rectangle") {
  344. newOptions.width = savedObject.width;
  345. newOptions.height = savedObject.height;
  346. } else if (savedObject.type = "convex-polygon") {
  347. newOptions.vertices = savedObject.vertices;
  348. }
  349. return Physics.body(savedObject.type, newOptions);
  350. }
  351. function setBodiesTreatmentStatic() {
  352. var bodies = world.getBodies();
  353. bodies.forEach(function(item, index, array) {
  354. item.treatment = 'static';
  355. });
  356. }
  357. function setBodiesTreatmentDynamic() {
  358. var bodies = world.getBodies();
  359. bodies.forEach(function(item, index, array) {
  360. item.treatment = 'dynamic';
  361. });
  362. }
  363. function togglePause() {
  364. if (physicsActive) {
  365. document.getElementById("run-button").classList.remove('running');
  366. document.getElementById("run-button").setAttribute('title', 'Play');
  367. setBodiesTreatmentStatic();
  368. } else {
  369. document.getElementById("run-button").classList.add('running');
  370. document.getElementById("run-button").setAttribute('title', 'Pause');
  371. Physics.util.ticker.start();
  372. setBodiesTreatmentDynamic();
  373. }
  374. physicsActive = !physicsActive;
  375. }
  376. // Change gravity value
  377. function setGravity(value) {
  378. if (gravityMode == value) return;
  379. var acc = {};
  380. switch(value) {
  381. case 0:
  382. acc = { x: 0, y: 0.0004 };
  383. break;
  384. case 1:
  385. acc = { x: 0.0004, y: 0.0004 };
  386. break;
  387. case 2:
  388. acc = { x: 0.0004, y: 0 };
  389. break;
  390. case 3:
  391. acc = { x: 0.0004, y: -0.0004 };
  392. break;
  393. case 4:
  394. acc = { x: 0, y: -0.0004 };
  395. break;
  396. case 5:
  397. acc = { x: -0.0004, y: -0.0004 };
  398. break;
  399. case 6:
  400. acc = { x: -0.0004, y: 0 };
  401. break;
  402. case 7:
  403. acc = { x: -0.0004, y: 0.0004 };
  404. break;
  405. }
  406. var reverse = (window.orientation == -90 ? -1 : 1);
  407. acc = { x: acc.x * reverse, y: acc.y * reverse };
  408. document.getElementById("gravity-button").style.backgroundImage = "url(icons/gravity"+(reverse == -1 ? (value+4)%8 : value)+".svg)";
  409. gravity.setAcceleration(acc);
  410. world.wakeUpAll();
  411. gravityMode = value;
  412. }
  413. // add some fun interaction
  414. var createdBody = null;
  415. var createdStart = null;
  416. world.on({
  417. 'interact:poke': function( pos ){
  418. // create body at a static place
  419. if (currentType != -1 && pos.y > toolbarHeight) {
  420. createdBody = dropInBody(currentType, pos);
  421. createdStart = pos;
  422. }
  423. }
  424. ,'interact:move': function( pos ){
  425. // update size of created body
  426. if (createdBody != null) {
  427. // compute new size
  428. var distx = createdStart.x - pos.x;
  429. var disty = createdStart.y - pos.y;
  430. var distance = Math.min(Math.sqrt(Math.abs(distx*distx-disty*disty)),createdStart.y-toolbarHeight);
  431. if (createdBody.view != null) {
  432. // Recreate the object with new size
  433. var object = serializeObject(createdBody);
  434. if (object.type == "circle") {
  435. object.radius = Math.max(40, distance);
  436. } else if (object.type == "rectangle") {
  437. object.width = object.height = Math.max(50, distance);
  438. } else if (object.type = "convex-polygon") {
  439. object.vertices = Physics.geometry.regularPolygonVertices( object.vertices.length, Math.max(30, distance));
  440. }
  441. world.removeBody(createdBody);
  442. var v1 = new Physics.vector(createdStart.x, 0);
  443. var v2 = new Physics.vector(pos.x-createdStart.x, pos.y-createdStart.y);
  444. object.angle = -v1.angle(v2);
  445. createdBody = deserializeObject(object);
  446. createdBody.treatment = "static";
  447. world.add(createdBody);
  448. }
  449. }
  450. }
  451. ,'interact:release': function( pos ){
  452. if (physicsActive) {
  453. if (createdBody != null) {
  454. createdBody.treatment = "dynamic";
  455. }
  456. world.wakeUpAll();
  457. }
  458. createdBody = null;
  459. }
  460. ,'interact:grab': function ( data ) {
  461. if (currentType == -1) {
  462. world.remove(data.body);
  463. }
  464. }
  465. });
  466. // add things to the world
  467. var gravity = Physics.behavior('constant-acceleration');
  468. var newton = Physics.behavior('newtonian', { strength: .5 });
  469. world.add([
  470. gravity
  471. ,Physics.behavior('body-impulse-response')
  472. ,Physics.behavior('body-collision-detection')
  473. ,Physics.behavior('sweep-prune')
  474. ,edgeBounce
  475. ]);
  476. // subscribe to ticker to advance the simulation
  477. Physics.util.ticker.on(function( time ) {
  478. // next step
  479. world.step( time );
  480. // remove bodies out of
  481. var bodies = world.getBodies();
  482. var limit = outerWidth / 2;
  483. if (limit > 0) {
  484. for(var i = 0 ; i < bodies.length ; i++) {
  485. var body = bodies[i];
  486. if (body.state.pos.x < 0-limit || body.state.pos.x > innerWidth+limit)
  487. world.remove(body);
  488. }
  489. }
  490. });
  491. });
  492. });
  493. });