A procedurally generated and interactive animation created with PixiJS

proximity.js 42KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154
  1. /* Sorry, this code is pretty damn ugly. I spend 8 hours 5 days a week refactoring code, I just don't feel like making
  2. * it better right now. Maybe I'll have enough caffiene in my veins and nothing better to do sometime to put this all
  3. * into ES2015 modules or something.
  4. */
  5. // global vars
  6. var renderer;
  7. var stage;
  8. var screenWidth;
  9. var screenHeight;
  10. var counter;
  11. var totalScreenPixels;
  12. var connectionDistance;
  13. var pointShiftDistance;
  14. var polygon;
  15. var startPoints;
  16. var polygonPoints;
  17. var lastLoop;
  18. var thisLoop;
  19. var fps;
  20. var fpsGraphic;
  21. var scrollDelta;
  22. var pointShiftBiasX;
  23. var pointShiftBiasY;
  24. var numPoints;
  25. // global non-configurable vars (modifying these might break stuff)
  26. var click = null;
  27. var hover = null;
  28. var lastHover = null;
  29. var clickEnd = false;
  30. var sprites = [];
  31. var reset = false;
  32. // global configurable vars
  33. var resolution = 1; // scaling for PIXI renderer
  34. var debug = false; // toggles drawing extra indicators for debugging
  35. var fpsEnabled = debug; // toggles the FPS counter
  36. var cycleDuration = 60; // length of a point's "cycle": number of frames it takes for it to travel to its chosen destination
  37. var connectionLimit = 10; // maximum number of lines drawn from one point to others within connection distance
  38. // colorShiftAmt = 80; // disabled for now
  39. var disconnectedColorShiftAmt = 10; // when a point is alone (not connected), shift RGB values by this amount every tick
  40. var allTweeningFns = [ // array of all possible tweening functions, these are defined below
  41. linearTweening,
  42. easeInSine,
  43. easeOutSine,
  44. easeInOutSine,
  45. easeInQuad,
  46. easeOutQuad,
  47. easeInOutQuad,
  48. easeInCubic,
  49. easeOutCubic,
  50. easeInOutCubic,
  51. easeInExpo,
  52. easeOutExpo,
  53. easeInOutExpo,
  54. easeInCirc,
  55. easeOutCirc,
  56. easeInOutCirc,
  57. easeOutBounce,
  58. easeInBounce,
  59. easeInOutBounce,
  60. easeInElastic,
  61. easeOutElastic,
  62. easeInOutElastic,
  63. easeInBack,
  64. easeOutBack,
  65. easeInOutBack
  66. ];
  67. // sets of tweening functions that I think look good with points randomly choose from them
  68. var tweeningSets = { // numbers refer to indicies into the allTweeningsFns array above
  69. linear: [0],
  70. meandering: [1, 2, 3, 4, 5, 6, 7, 8, 9],
  71. snappy: [10, 11, 12, 13, 14, 15],
  72. bouncy: [16, 17, 18],
  73. elastic: [19, 20, 21],
  74. back: [24]
  75. };
  76. var tweeningFns = tweeningSets.back; // the actual set of tweening functions points will randomly choose from
  77. // click effect related config vars
  78. var clickPullRateStart = 0.01; // initial value for the ratio of a point's distance from the click position to travel in one cycle
  79. var clickPullRateInc = 0.005; // amount to increase clickPullRate every tick that a click is held
  80. var clickPullRateMax = 0.5; // maximum value of clickPullRate
  81. var clickPullRate = clickPullRateStart;
  82. var clickMaxDistStart = 50; // initial value for the effect radius of a click: points this distance from click position will be pulled (overridden if small screen size)
  83. var clickMaxDistInc = 2; // amount to increase clickMaxDist every tick that a click is held
  84. var clickMaxDistMax = 5000; // maximum value of clickMaxDist
  85. var clickMaxDist = clickMaxDistStart;
  86. var clickInertiaStart = -0.7; // initial value of the ratio of point's origin distance from the click position to be added to point's new target
  87. var clickInertia = clickInertiaStart;
  88. var clickTweeningFnStart = null; // initial value of the specific tweening function to assign to points in effect radius (null will not change functions)
  89. var clickTweeningFn = clickTweeningFnStart;
  90. var clickColorShiftAmt = disconnectedColorShiftAmt * 3; // amount of RGB color value to shift for each point in effect radius
  91. var clickPullRateEnd = -0.5; // value of clickPullRate during tick after end of click (for "rebound" effect)
  92. var clickInertiaEnd = 0.3; // value of clickInertia during tick after end of click
  93. var clickTweeningFnEnd = 12; // value of clickTweeningFn during tick after end of click (number refers to index into allTweeningsFns)
  94. // hover effect related config vars
  95. var hoverPushRate = -0.05; // ratio of a point's distance from the hover position to travel in one cycle
  96. var hoverInertia = 0.8; // ratio of a point's origin distance from the click position to be added to point's new target
  97. var hoverMaxDistStart = 75; // initial value for the effect radius of a hover: points this distance from hover position will be pushed (overridden if small screen size)
  98. var hoverMaxDistMax = 1000; // maximum value of hoverMaxDist
  99. var hoverMaxDist = hoverMaxDistStart;
  100. var hoverTweeningFn = 5; // specific tweening function to assign to points in effect radius
  101. var zRange = 50; // maximum value for the range of possible z coords for a point
  102. var nodeImg = 'img/node.png'; // image file location for representing every point
  103. var nodeImgRes = 100; // resolution of nodeImg file in pixels (aspect ratio should be square)
  104. // var minNodeDiameter = 3; // minimum pixel size of point on canvas when z coord is 0, maximum is this value plus zRange
  105. var nodeSize = 3; // with z coord ignored, this value is used for scaling the drawing for each node
  106. var drawNodes = true; // whether to display circles at each point's current position
  107. var drawLines = true; // whether to display lines connecting points if they are in connection distance
  108. var lineSize = 1; // thickness in pixels of drawn lines between points
  109. /* TWEENING FUNCTIONS */
  110. // These are modified versions of the jquery easing functions:
  111. // https://github.com/danro/jquery-easing/blob/master/jquery.easing.js
  112. // See license in LICENSE-3RD-PARTY.txt
  113. /* eslint-disable no-unused-vars */
  114. function linearTweening (t, b, c, d) {
  115. // t = current time
  116. // b = start value
  117. // c = change in value
  118. // d = duration
  119. return ((c * t) / d) + b;
  120. }
  121. function easeOutBounce (t, b, c, d) {
  122. if ((t /= d) < (1 / 2.75)) {
  123. return c * (7.5625 * t * t) + b;
  124. } else if (t < (2 / 2.75)) {
  125. return c * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75) + b;
  126. } else if (t < (2.5 / 2.75)) {
  127. return c * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375) + b;
  128. } else {
  129. return c * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375) + b;
  130. }
  131. }
  132. function easeInBounce (t, b, c, d) {
  133. return c - easeOutBounce(d - t, 0, c, d) + b;
  134. }
  135. function easeInOutBounce (t, b, c, d) {
  136. if (t < d / 2) return easeInBounce(t * 2, 0, c, d) * 0.5 + b;
  137. return easeOutBounce(t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b;
  138. }
  139. function easeInSine (t, b, c, d) {
  140. return -c * Math.cos(t / d * (Math.PI / 2)) + c + b;
  141. }
  142. function easeOutSine (t, b, c, d) {
  143. return c * Math.sin(t / d * (Math.PI / 2)) + b;
  144. }
  145. function easeInOutSine (t, b, c, d) {
  146. return -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b;
  147. }
  148. function easeInQuad (t, b, c, d) {
  149. return c * (t /= d) * t + b;
  150. }
  151. function easeOutQuad (t, b, c, d) {
  152. return -c * (t /= d) * (t - 2) + b;
  153. }
  154. function easeInOutQuad (t, b, c, d) {
  155. if ((t /= d / 2) < 1) return c / 2 * t * t + b;
  156. return -c / 2 * ((--t) * (t - 2) - 1) + b;
  157. }
  158. function easeInCubic (t, b, c, d) {
  159. return c * (t /= d) * t * t + b;
  160. }
  161. function easeOutCubic (t, b, c, d) {
  162. return c * ((t = t / d - 1) * t * t + 1) + b;
  163. }
  164. function easeInOutCubic (t, b, c, d) {
  165. if ((t /= d / 2) < 1) return c / 2 * t * t * t + b;
  166. return c / 2 * ((t -= 2) * t * t + 2) + b;
  167. }
  168. function easeInExpo (t, b, c, d) {
  169. return (t === 0) ? b : c * Math.pow(2, 10 * (t / d - 1)) + b;
  170. }
  171. function easeOutExpo (t, b, c, d) {
  172. return (t === d) ? b + c : c * (-Math.pow(2, -10 * t / d) + 1) + b;
  173. }
  174. function easeInOutExpo (t, b, c, d) {
  175. if (t === 0) return b;
  176. if (t === d) return b + c;
  177. if ((t /= d / 2) < 1) return c / 2 * Math.pow(2, 10 * (t - 1)) + b;
  178. return c / 2 * (-Math.pow(2, -10 * --t) + 2) + b;
  179. }
  180. function easeInElastic (t, b, c, d) {
  181. var s = 1.70158; var p = 0; var a = c;
  182. if (t === 0) return b; if ((t /= d) === 1) return b + c; if (!p) p = d * 0.3;
  183. if (a < Math.abs(c)) { a = c; s = p / 4; } else s = p / (2 * Math.PI) * Math.asin(c / a);
  184. return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b;
  185. }
  186. function easeOutElastic (t, b, c, d) {
  187. var s = 1.70158; var p = 0; var a = c;
  188. if (t === 0) return b; if ((t /= d) === 1) return b + c; if (!p) p = d * 0.3;
  189. if (a < Math.abs(c)) { a = c; s = p / 4; } else s = p / (2 * Math.PI) * Math.asin(c / a);
  190. return a * Math.pow(2, -10 * t) * Math.sin((t * d - s) * (2 * Math.PI) / p) + c + b;
  191. }
  192. function easeInOutElastic (t, b, c, d) {
  193. var s = 1.70158; var p = 0; var a = c;
  194. if (t === 0) return b; if ((t /= d / 2) === 2) return b + c; if (!p) p = d * (0.3 * 1.5);
  195. if (a < Math.abs(c)) { a = c; s = p / 4; } else s = p / (2 * Math.PI) * Math.asin(c / a);
  196. if (t < 1) return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b;
  197. return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p) * 0.5 + c + b;
  198. }
  199. function easeInBack (t, b, c, d, s) {
  200. if (s === undefined) s = 1.70158;
  201. return c * (t /= d) * t * ((s + 1) * t - s) + b;
  202. }
  203. function easeOutBack (t, b, c, d, s) {
  204. if (s === undefined) s = 1.70158;
  205. return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b;
  206. }
  207. function easeInOutBack (t, b, c, d, s) {
  208. if (s === undefined) s = 1.70158;
  209. if ((t /= d / 2) < 1) return c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b;
  210. return c / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b;
  211. }
  212. function easeInCirc (t, b, c, d) {
  213. return -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b;
  214. }
  215. function easeOutCirc (t, b, c, d) {
  216. return c * Math.sqrt(1 - (t = t / d - 1) * t) + b;
  217. }
  218. function easeInOutCirc (t, b, c, d) {
  219. if ((t /= d / 2) < 1) return -c / 2 * (Math.sqrt(1 - t * t) - 1) + b;
  220. return c / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1) + b;
  221. }
  222. /* eslint-enable no-unused-vars */
  223. /* TOGGLE FUNCTIONS */
  224. function toggleHelp () {
  225. var help, controls;
  226. help = document.getElementById('help');
  227. controls = document.getElementById('controls');
  228. if (help.style.display === 'none') {
  229. help.style.display = 'block';
  230. // hide controls if open (only want one panel open at a time)
  231. if (controls.style.display === 'block') {
  232. controls.style.display = 'none';
  233. }
  234. } else {
  235. help.style.display = 'none';
  236. }
  237. }
  238. function toggleControls () {
  239. var help, controls;
  240. help = document.getElementById('help');
  241. controls = document.getElementById('controls');
  242. if (controls.style.display === 'none') {
  243. controls.style.display = 'block';
  244. // hide help if open (only want one panel open at a time)
  245. if (help.style.display === 'block') {
  246. help.style.display = 'none';
  247. }
  248. } else {
  249. controls.style.display = 'none';
  250. }
  251. }
  252. function toggleFPS () {
  253. var fpsCheckbox = document.getElementsByName('fpsCounterToggle')[0];
  254. if (fpsEnabled) {
  255. stage.removeChild(fpsGraphic);
  256. fpsEnabled = false;
  257. } else {
  258. stage.addChild(fpsGraphic);
  259. fpsEnabled = true;
  260. lastLoop = new Date();
  261. }
  262. fpsCheckbox.checked = fpsEnabled;
  263. }
  264. function toggleDebug () {
  265. var fpsCheckbox = document.getElementsByName('fpsCounterToggle')[0];
  266. var debugCheckbox = document.getElementsByName('debugToggle')[0];
  267. if (debug) {
  268. if (fpsEnabled) {
  269. stage.removeChild(fpsGraphic);
  270. }
  271. debug = false;
  272. fpsEnabled = debug;
  273. } else {
  274. if (!fpsEnabled) {
  275. stage.addChild(fpsGraphic);
  276. }
  277. debug = true;
  278. fpsEnabled = debug;
  279. lastLoop = new Date();
  280. }
  281. fpsCheckbox.checked = fpsEnabled;
  282. debugCheckbox.checked = debug;
  283. }
  284. function toggleNodes () {
  285. nodesCheckbox = document.getElementsByName('nodesToggle')[0];
  286. if (drawNodes) {
  287. for (i = 0; i < sprites.length; i++) {
  288. sprites[i].visible = false;
  289. }
  290. drawNodes = false;
  291. } else {
  292. for (i = 0; i < sprites.length; i++) {
  293. sprites[i].visible = true;
  294. }
  295. drawNodes = true;
  296. }
  297. nodesCheckbox.checked = drawNodes;
  298. }
  299. function toggleLines () {
  300. linesCheckbox = document.getElementsByName('linesToggle')[0];
  301. drawLines = !drawLines;
  302. linesCheckbox.checked = drawLines;
  303. }
  304. /* UTILITY FUNCTIONS */
  305. function randomInt (min, max) {
  306. // inclusive of min and max
  307. return Math.floor(Math.random() * (max - min + 1)) + min;
  308. }
  309. // from: http://stackoverflow.com/a/5624139
  310. // modified to return integer literal
  311. function rgbToHex (color) {
  312. return parseInt(((1 << 24) + (color.r << 16) + (color.g << 8) + color.b).toString(16).slice(1), 16);
  313. }
  314. /* Choose a random RGB color */
  315. function randomColor () {
  316. return {
  317. r: Math.floor(Math.random() * (255 + 1)),
  318. g: Math.floor(Math.random() * (255 + 1)),
  319. b: Math.floor(Math.random() * (255 + 1))
  320. };
  321. }
  322. /* Find the average of two RGB colors and return one RGB of that color */
  323. function averageColor (color1, color2) {
  324. return {
  325. r: Math.round((color1.r + color2.r) / 2),
  326. g: Math.round((color1.g + color2.g) / 2),
  327. b: Math.round((color1.b + color2.b) / 2)
  328. };
  329. }
  330. /*
  331. * Find the average of two RGB colors where color1 is weighted against color2 by the given weight.
  332. * weight is a decimal between 0 and 1.
  333. */
  334. function weightedAverageColor (color1, color2, weight) {
  335. return {
  336. r: Math.round(((color1.r * (2 * weight)) + (color2.r * (2 * (1 - weight)))) / 2),
  337. g: Math.round(((color1.g * (2 * weight)) + (color2.g * (2 * (1 - weight)))) / 2),
  338. b: Math.round(((color1.b * (2 * weight)) + (color2.b * (2 * (1 - weight)))) / 2)
  339. };
  340. }
  341. /* Darken the color by a factor of 0 to 1, where 1 is black and 0 is white */
  342. function shadeColor (color, shadeFactor) {
  343. return {
  344. r: Math.round(color.r * (1 - shadeFactor)),
  345. g: Math.round(color.g * (1 - shadeFactor)),
  346. b: Math.round(color.b * (1 - shadeFactor))
  347. };
  348. }
  349. /* Given a color component (red, green, or blue int), randomly shift by configurable amount */
  350. function shiftColorComponent (component, maxShiftAmt) {
  351. var shiftAmt = randomInt(maxShiftAmt * -1, maxShiftAmt);
  352. var newComponent = component + shiftAmt;
  353. if ((newComponent < 0) || (newComponent > 255)) {
  354. newComponent = component - shiftAmt;
  355. }
  356. return newComponent;
  357. }
  358. /* Randomly shift a RGB color by a configurable amount and return new RGB color */
  359. function shiftColor (color, maxShiftAmt) {
  360. return {
  361. r: shiftColorComponent(color.r, maxShiftAmt),
  362. g: shiftColorComponent(color.g, maxShiftAmt),
  363. b: shiftColorComponent(color.b, maxShiftAmt)
  364. };
  365. }
  366. /* from: https://stackoverflow.com/a/17130415 */
  367. function getMousePos (evt, res) {
  368. var canvas = document.getElementsByTagName('canvas')[0];
  369. if (canvas !== undefined) {
  370. var rect = canvas.getBoundingClientRect();
  371. return {
  372. x: Math.round((evt.clientX - rect.left) / (rect.right - rect.left) * canvas.width / res),
  373. y: Math.round((evt.clientY - rect.top) / (rect.bottom - rect.top) * canvas.height / res)
  374. };
  375. }
  376. }
  377. function distance (point1, point2) {
  378. var a = point1[0] - point2[0];
  379. var b = point1[1] - point2[1];
  380. return Math.sqrt(a * a + b * b);
  381. }
  382. function distancePos (point1, point2) {
  383. // TODO: refactor distance and distancePos to the same function
  384. var a = point1.x - point2.x;
  385. var b = point1.y - point2.y;
  386. return Math.sqrt(a * a + b * b);
  387. }
  388. // eslint-disable-next-line no-unused-vars
  389. function shiftPointCounter (original, maxShiftAmt) {
  390. var shiftAmt = randomInt(maxShiftAmt * -1, 0);
  391. var newCounter = original + shiftAmt;
  392. if (newCounter < 0) {
  393. newCounter = cycleDuration + shiftAmt;
  394. }
  395. return newCounter;
  396. }
  397. function relativeCounter (counter, targetStart) {
  398. /* Return current progress of point in its cycle. AKA. what count would be if cycleDuration == targetStart */
  399. var relCounter = counter - targetStart;
  400. if (relCounter < 0) {
  401. return cycleDuration + relCounter;
  402. }
  403. return relCounter;
  404. }
  405. function createSprite () {
  406. return new window.PIXI.Sprite(
  407. window.PIXI.loader.resources[nodeImg].texture
  408. );
  409. }
  410. function clearSprites () {
  411. if (sprites.length > 0) {
  412. // need to clear out old sprites
  413. for (i = 0; i < sprites.length; i++) {
  414. stage.removeChild(sprites[i]);
  415. }
  416. sprites = [];
  417. }
  418. }
  419. /* POINT OPERATION FUNCTIONS */
  420. function getRandomPoints (numPoints, maxX, maxY, maxZ, tweeningFns) {
  421. var i, x, y, z, color, cycleStart, easingFn, sprite;
  422. var points = [];
  423. for (i = 0; i < numPoints; i++) {
  424. x = randomInt(0, maxX - 1);
  425. y = randomInt(0, maxY - 1);
  426. // z = randomInt(0, maxZ - 1); // TODO: do something with the 3rd dimension
  427. z = 0; // turns out that 3D is hard and I am a weak 2D boy
  428. cycleStart = randomInt(0, cycleDuration - 1);
  429. color = randomColor();
  430. easingFn = tweeningFns[Math.floor(Math.random() * tweeningFns.length)];
  431. // save PIXI Sprite for each point in array
  432. sprite = createSprite();
  433. if (!drawNodes) sprite.visible = false;
  434. sprites.push(sprite);
  435. stage.addChild(sprite);
  436. points[i] = [x, y, z, cycleStart, color, easingFn];
  437. }
  438. return points;
  439. }
  440. function addOrRemovePoints (numPoints, points) {
  441. /* Given new value numPoints, remove or add new random points to match new count. */
  442. var deletedSprites, newPoints;
  443. if (points.target.length > numPoints) {
  444. points.original.splice(numPoints - 1);
  445. points.target.splice(numPoints - 1);
  446. points.tweened.splice(numPoints - 1);
  447. deletedSprites = sprites.splice(numPoints - 1);
  448. for (var i = 0; i < deletedSprites.length; i++) {
  449. stage.removeChild(deletedSprites[i]);
  450. }
  451. } else if (points.target.length < numPoints) {
  452. newPoints = getRandomPoints(numPoints - points.target.length, screenWidth, screenHeight, zRange, tweeningFns);
  453. points.original = points.original.concat(newPoints);
  454. points.target = points.target.concat(JSON.parse(JSON.stringify(newPoints)));
  455. points.tweened = points.tweened.concat(JSON.parse(JSON.stringify(newPoints)));
  456. }
  457. return points;
  458. }
  459. function shiftPoints (points, maxShiftAmt, counter, tweeningFns) {
  460. var i, shiftX, shiftY, candidateX, candidateY;
  461. for (i = 0; i < points.original.length; i++) {
  462. if (points.target[i][3] >= cycleDuration) {
  463. // cycleDuration was reduced and now this point's cycle is out of bounds. Randomly pick a new valid one.
  464. points.target[i][3] = randomInt(0, cycleDuration - 1);
  465. }
  466. if (points.target[i][3] === counter) {
  467. points.original[i] = points.target[i].slice();
  468. shiftX = randomInt(maxShiftAmt * -1, maxShiftAmt);
  469. shiftY = randomInt(maxShiftAmt * -1, maxShiftAmt);
  470. if (((shiftX < 0) && (pointShiftBiasX === 1)) || ((shiftX > 0) && (pointShiftBiasX === -1))) {
  471. shiftX = shiftX * -1;
  472. }
  473. if (((shiftY < 0) && (pointShiftBiasY === 1)) || ((shiftY > 0) && (pointShiftBiasY === -1))) {
  474. shiftY = shiftY * -1;
  475. }
  476. candidateX = points.original[i][0] + shiftX;
  477. candidateY = points.original[i][1] + shiftY;
  478. if ((candidateX > screenWidth) || (candidateX < 0)) {
  479. candidateX = points.original[i][0] - shiftX;
  480. }
  481. if ((candidateY > screenHeight) || (candidateY < 0)) {
  482. candidateY = points.original[i][1] - shiftY;
  483. }
  484. points.target[i][0] = candidateX;
  485. points.target[i][1] = candidateY;
  486. points.target[i][5] = tweeningFns[Math.floor(Math.random() * tweeningFns.length)];
  487. // FIXME: buggy, makes points jump around too fast
  488. // points.target[i][3] = shiftPointCounter(points.original[i][3], maxShiftAmt);
  489. }
  490. }
  491. // clear pointShiftBiases now that they have been "used"
  492. pointShiftBiasX = 0;
  493. pointShiftBiasY = 0;
  494. return points;
  495. }
  496. function pullPoints (points, clickPos, pullRate, inertia, maxDist, counter, resetPoints, tweeningFn) {
  497. var targetXDiff, targetYDiff, originXDiff, originYDiff;
  498. for (var i = 0; i < points.target.length; i++) {
  499. targetXDiff = clickPos.x - points.target[i][0];
  500. targetYDiff = clickPos.y - points.target[i][1];
  501. originXDiff = clickPos.x - points.original[i][0];
  502. originYDiff = clickPos.y - points.original[i][1];
  503. if (Math.sqrt((targetXDiff * targetXDiff) + (targetYDiff * targetYDiff)) <= maxDist) {
  504. // point is within effect radius
  505. if (resetPoints) {
  506. // Good for changing directions, reset the points original positions to their current positions
  507. points.original[i][0] = points.tweened[i][0];
  508. points.original[i][1] = points.tweened[i][1];
  509. }
  510. points.target[i][0] += Math.round((targetXDiff + (inertia * originXDiff)) * pullRate); // pull X
  511. points.target[i][1] += Math.round((targetYDiff + (inertia * originYDiff)) * pullRate); // pull Y
  512. // shift the color of each point in effect radius by some configurable amount
  513. points.target[i][4] = shiftColor(points.original[i][4], clickColorShiftAmt);
  514. if (tweeningFn !== null) {
  515. // Also switch the tweening function for all affected points for additional effect
  516. // The tweening function will be re-assigned at the start of the point's next cycle
  517. points.target[i][4] = tweeningFn;
  518. }
  519. if (debug) {
  520. points.target[i][6] = true; // marks this point as affected
  521. }
  522. // If this point's cycle is near it's end, bump it up some ticks to make the animation smoother
  523. if (relativeCounter(points.target[i][3]) > Math.roundcycleDuration - 10) {
  524. points.target[i][3] = (points.target[i][3] + Math.round(cycleDuration / 2)) % cycleDuration;
  525. }
  526. } else {
  527. if (debug) {
  528. points.target[i][6] = false; // marks this point as unaffected
  529. }
  530. }
  531. }
  532. }
  533. function clearAffectedPoints (points) {
  534. for (var i = 0; i < points.target.length; i++) {
  535. points.target[i][6] = false;
  536. }
  537. }
  538. function redistributeCycles (points, oldCycleDuration, cycleDuration) {
  539. /* Given old and new cycleDuration, re-assign points' cycle starts that expand/compress to fit the new range in a
  540. * way that ensures the current progress of the point in its cycle is around the same percentage (so that the point
  541. * does not jump erratically back or forward in it's current trajectory).
  542. */
  543. // FIXME: if cycleDuration goes to 1 all points' cycles will be compressed to about the same value, and when
  544. // cycleDuration goes back up, the values will remain the same, making the points appear to dance in sync.
  545. var progress;
  546. for (var i = 0; i < points.original.length; i++) {
  547. progress = points.target[i][3] / oldCycleDuration;
  548. points.target[i][3] = Math.round(progress * cycleDuration);
  549. }
  550. return points;
  551. }
  552. function randomizeCycles (points, cycleDuration) {
  553. /* Assigns every point a new random cycle start */
  554. for (var i = 0; i < points.original.length; i++) {
  555. points.target[i][3] = randomInt(0, cycleDuration - 1);
  556. }
  557. return points;
  558. }
  559. function synchronizeCycles (points, cycleDuration) {
  560. /* Assigns every point the same cycle start (0) */
  561. for (var i = 0; i < points.original.length; i++) {
  562. points.target[i][3] = 0;
  563. }
  564. return points;
  565. }
  566. /* DRAW FUNCTIONS */
  567. function drawPolygon (polygon, points, counter, tweeningFns) {
  568. var i, j, easingFn, relativeCount, avgColor, shadedColor, connectionCount, dist, connectivity, scale, nodeDiameter;
  569. // calculate vectors
  570. for (i = 0; i < points.original.length; i++) {
  571. easingFn = allTweeningFns[points.target[i][5]];
  572. relativeCount = relativeCounter(counter, points.target[i][3]);
  573. points.tweened[i][0] = easingFn(relativeCount, points.original[i][0], points.target[i][0] - points.original[i][0], cycleDuration);
  574. points.tweened[i][1] = easingFn(relativeCount, points.original[i][1], points.target[i][1] - points.original[i][1], cycleDuration);
  575. if (debug) {
  576. // draw vector trajectories
  577. if (points.target[i][6]) {
  578. polygon.lineStyle(1, 0x008b8b, 1); // draw path different color if it is under effect of click/hover
  579. } else {
  580. polygon.lineStyle(1, 0x191970, 1);
  581. }
  582. polygon.moveTo(points.tweened[i][0], points.tweened[i][1]);
  583. for (j = relativeCount; j < cycleDuration; j++) {
  584. polygon.lineTo(
  585. easingFn(j, points.original[i][0], points.target[i][0] - points.original[i][0], cycleDuration),
  586. easingFn(j, points.original[i][1], points.target[i][1] - points.original[i][1], cycleDuration)
  587. );
  588. }
  589. }
  590. }
  591. // draw lines
  592. for (i = 0; i < points.original.length; i++) {
  593. connectionCount = 0;
  594. for (j = i + 1; j < points.original.length; j++) {
  595. // TODO pick the N (connectionLimit) closest connections instead of the first N that occur sequentially.
  596. if (connectionCount >= connectionLimit) break;
  597. dist = distance(points.tweened[i], points.tweened[j]);
  598. connectivity = dist / connectionDistance;
  599. if ((j !== i) && (dist <= connectionDistance)) {
  600. // find average color of both points
  601. if ((points.tweened[i][3] === counter) || (points.tweened[j][3] === counter)) {
  602. // avgColor = shiftColor(avgColor, Math.round(colorShiftAmt * (1 - connectivity)));
  603. points.tweened[i][4] = weightedAverageColor(points.tweened[i][4], points.tweened[j][4], connectivity);
  604. points.tweened[j][4] = weightedAverageColor(points.tweened[j][4], points.tweened[i][4], connectivity);
  605. }
  606. avgColor = averageColor(points.tweened[i][4], points.tweened[j][4]);
  607. shadedColor = shadeColor(avgColor, connectivity);
  608. if (drawLines) {
  609. polygon.lineStyle(lineSize, rgbToHex(shadedColor), 1);
  610. polygon.moveTo(points.tweened[i][0], points.tweened[i][1]);
  611. polygon.lineTo(points.tweened[j][0], points.tweened[j][1]);
  612. }
  613. connectionCount = connectionCount + 1;
  614. }
  615. }
  616. if (connectionCount === 0) {
  617. points.tweened[i][4] = shiftColor(points.tweened[i][4], disconnectedColorShiftAmt);
  618. }
  619. if (drawNodes) {
  620. // draw nodes
  621. nodeDiameter = nodeSize;
  622. scale = nodeDiameter / nodeImgRes;
  623. sprites[i].scale.x = scale;
  624. sprites[i].scale.y = scale;
  625. sprites[i].x = points.tweened[i][0] - (nodeDiameter / 2);
  626. sprites[i].y = points.tweened[i][1] - (nodeDiameter / 2);
  627. sprites[i].tint = rgbToHex(points.tweened[i][4]);
  628. }
  629. }
  630. }
  631. /* MAIN LOOP */
  632. function loop () {
  633. screenWidth = window.innerWidth;
  634. screenHeight = window.innerHeight;
  635. renderer.resize(screenWidth, screenHeight);
  636. polygon.clear();
  637. if (reset === true) {
  638. var newPoints;
  639. clearSprites();
  640. newPoints = getRandomPoints(numPoints, screenWidth, screenHeight, zRange, tweeningFns);
  641. polygonPoints = {
  642. original: newPoints,
  643. target: JSON.parse(JSON.stringify(newPoints)),
  644. tweened: JSON.parse(JSON.stringify(newPoints))
  645. };
  646. reset = false;
  647. }
  648. if (click !== null) {
  649. if (clickEnd) {
  650. // apply "rebound" effects
  651. clickPullRate = clickPullRateEnd;
  652. clickInertia = clickInertiaEnd;
  653. clickTweeningFn = clickTweeningFnEnd;
  654. if (debug) {
  655. // draw debug click effect radius red color when clickEnd == true
  656. polygon.lineStyle(1, 0xDC143C, 1);
  657. polygon.drawCircle(click.x, click.y, clickMaxDist);
  658. }
  659. } else {
  660. if (debug) {
  661. // draw click effect radius blue when debug is on
  662. polygon.lineStyle(1, 0x483D8B, 1);
  663. polygon.drawCircle(click.x, click.y, clickMaxDist);
  664. }
  665. }
  666. // a pointer event is occuring and needs to affect the points in effect radius
  667. pullPoints(polygonPoints, click, clickPullRate, clickInertia, clickMaxDist, counter, clickEnd, clickTweeningFn);
  668. // slightly increase effect amount for next loop if click is still occuring
  669. if (clickMaxDist <= clickMaxDistMax) {
  670. clickMaxDist += clickMaxDistInc;
  671. }
  672. if (clickPullRate <= clickPullRateMax) {
  673. clickPullRate += clickPullRateInc;
  674. }
  675. if (clickEnd) {
  676. // done with rebound effect, re-initialize everything to prepare for next click
  677. click = null;
  678. clickEnd = false;
  679. clickMaxDist = clickMaxDistStart;
  680. clickPullRate = clickPullRateStart;
  681. clickInertia = clickInertiaStart;
  682. clickTweeningFn = clickTweeningFnStart;
  683. clearAffectedPoints(polygonPoints);
  684. }
  685. } else if (hover !== null) {
  686. if (lastHover !== null) {
  687. // hover effect radius grows bigger the faster the mouse moves
  688. hoverMaxDist += Math.min(Math.round(distancePos(hover, lastHover)), hoverMaxDistMax);
  689. }
  690. if (debug) {
  691. // draw hover effect radius yellow when debug is on
  692. polygon.lineStyle(1, 0xBDB76B, 1);
  693. polygon.drawCircle(hover.x, hover.y, hoverMaxDist);
  694. }
  695. // a hover event is occuring and needs to affect the points in effect radius
  696. pullPoints(polygonPoints, hover, hoverPushRate, hoverInertia, hoverMaxDist, counter, false, hoverTweeningFn);
  697. hoverMaxDist = hoverMaxDistStart;
  698. lastHover = hover;
  699. }
  700. // TODO: it would be cool to fill in triangles
  701. // polygon.beginFill(0x00FF00);
  702. drawPolygon(polygon, polygonPoints, counter, tweeningFns);
  703. // polygon.endFill();
  704. counter += 1;
  705. counter = counter % cycleDuration;
  706. if (counter === 0 && fpsEnabled) {
  707. thisLoop = new Date();
  708. fps = Math.round((1000 / (thisLoop - lastLoop)) * cycleDuration);
  709. fpsGraphic.setText(fps.toString());
  710. lastLoop = thisLoop;
  711. }
  712. // points that have reached the end of their cycles need new targets
  713. polygonPoints = shiftPoints(polygonPoints, pointShiftDistance, counter, tweeningFns);
  714. // If user scrolled, modify cycleDuration by amount scrolled
  715. if (scrollDelta !== 0) {
  716. var oldCycleDuration = cycleDuration;
  717. cycleDuration = Math.round(cycleDuration + scrollDelta);
  718. if (cycleDuration < 1) {
  719. cycleDuration = 1;
  720. }
  721. scrollDelta = 0;
  722. polygonPoints = redistributeCycles(polygonPoints, oldCycleDuration, cycleDuration);
  723. // Update control inputs
  724. var timeRange = document.getElementsByName('timeRange')[0];
  725. var timeInput = document.getElementsByName('timeInput')[0];
  726. timeRange.value = cycleDuration;
  727. timeInput.value = cycleDuration;
  728. }
  729. // Tell the `renderer` to `render` the `stage`
  730. renderer.render(stage);
  731. window.requestAnimationFrame(loop);
  732. }
  733. function registerEventHandlers() {
  734. var tweeningInputs, debugCheckbox, fpsCheckbox, nodeCheckbox, linesCheckbox;
  735. tweeningInputs = document.getElementsByName('tweening');
  736. debugCheckbox = document.getElementsByName('debugToggle')[0];
  737. fpsCheckbox = document.getElementsByName('fpsCounterToggle')[0];
  738. nodesCheckbox = document.getElementsByName('nodesToggle')[0];
  739. linesCheckbox = document.getElementsByName('linesToggle')[0];
  740. /* MOUSE AND TOUCH EVENTS */
  741. window.addEventListener('wheel', function (e) {
  742. if (e.target.tagName !== 'CANVAS') return;
  743. scrollDelta = scrollDelta + e.deltaY;
  744. });
  745. window.addEventListener('touchstart', function (e) {
  746. if (e.target.tagName !== 'CANVAS') return;
  747. e.target.focus();
  748. click = getMousePos(e.changedTouches[0], resolution);
  749. clickEnd = false;
  750. });
  751. window.addEventListener('touchmove', function (e) {
  752. if (e.target.tagName !== 'CANVAS') return;
  753. if (click !== null) {
  754. click = getMousePos(e.changedTouches[0], resolution);
  755. }
  756. });
  757. window.addEventListener('touchend', function (e) {
  758. clickEnd = true;
  759. });
  760. window.addEventListener('touchcancel', function (e) {
  761. clickEnd = true;
  762. });
  763. window.addEventListener('mousedown', function (e) {
  764. if (e.target.tagName !== 'CANVAS') return;
  765. e.target.focus();
  766. click = getMousePos(e, resolution);
  767. clickEnd = false;
  768. });
  769. window.addEventListener('mousemove', function (e) {
  770. if (e.target.tagName !== 'CANVAS') return;
  771. var pos = getMousePos(e, resolution);
  772. if (click !== null) {
  773. click = pos;
  774. }
  775. hover = pos;
  776. });
  777. window.addEventListener('mouseup', function (e) {
  778. clickEnd = true;
  779. hover = null;
  780. lastHover = null;
  781. });
  782. window.addEventListener('mouseleave', function (e) {
  783. clickEnd = true;
  784. hover = null;
  785. lastHover = null;
  786. clearAffectedPoints(polygonPoints);
  787. });
  788. document.addEventListener('mouseleave', function (e) {
  789. clickEnd = true;
  790. hover = null;
  791. lastHover = null;
  792. clearAffectedPoints(polygonPoints);
  793. });
  794. /* KEYBOARD EVENTS */
  795. window.addEventListener('keydown', function (e) {
  796. var i;
  797. if (e.target.tagName === 'INPUT') return;
  798. if (e.keyCode === 37) { // left
  799. pointShiftBiasX = -1;
  800. } else if (e.keyCode === 38) { // up
  801. pointShiftBiasY = -1;
  802. } else if (e.keyCode === 39) { // right
  803. pointShiftBiasX = 1;
  804. } else if (e.keyCode === 40) { // down
  805. pointShiftBiasY = 1;
  806. } else if (e.keyCode === 49) { // 1
  807. tweeningFns = tweeningSets.linear;
  808. tweeningInputs[0].checked = true;
  809. } else if (e.keyCode === 50) { // 2
  810. tweeningFns = tweeningSets.meandering;
  811. tweeningInputs[1].checked = true;
  812. } else if (e.keyCode === 51) { // 3
  813. tweeningFns = tweeningSets.snappy;
  814. tweeningInputs[2].checked = true;
  815. } else if (e.keyCode === 52) { // 4
  816. tweeningFns = tweeningSets.bouncy;
  817. tweeningInputs[3].checked = true;
  818. } else if (e.keyCode === 53) { // 5
  819. tweeningFns = tweeningSets.elastic;
  820. tweeningInputs[4].checked = true;
  821. } else if (e.keyCode === 54) { // 6
  822. tweeningFns = tweeningSets.back;
  823. tweeningInputs[5].checked = true;
  824. } else if (e.keyCode === 70) { // f
  825. toggleFPS();
  826. } else if (e.keyCode === 68) { // d
  827. toggleDebug();
  828. } else if (e.keyCode === 78) { // n
  829. toggleNodes();
  830. } else if (e.keyCode === 76) { // l
  831. toggleLines();
  832. } else if (e.keyCode === 191) { // ?
  833. toggleHelp();
  834. }
  835. });
  836. /* BUTTON & INPUT EVENTS */
  837. document.getElementById('toggle-help').addEventListener('click', function () {
  838. toggleHelp();
  839. }, false);
  840. document.getElementById('toggle-controls').addEventListener('click', function () {
  841. toggleControls();
  842. }, false);
  843. document.getElementById('close-help').addEventListener('click', function () {
  844. toggleHelp();
  845. }, false);
  846. document.getElementById('close-controls').addEventListener('click', function () {
  847. toggleControls();
  848. }, false);
  849. document.getElementById('synchronize-cycles').addEventListener('click', function () {
  850. synchronizeCycles(polygonPoints, cycleDuration);
  851. }, false);
  852. document.getElementById('randomize-cycles').addEventListener('click', function () {
  853. randomizeCycles(polygonPoints, cycleDuration);
  854. }, false);
  855. document.getElementById('reset').addEventListener('click', function () {
  856. reset = true;
  857. }, false);
  858. var connectDistRange = document.getElementsByName('connectDistRange')[0];
  859. connectDistRange.value = connectionDistance;
  860. connectDistRange.addEventListener('input', function (e) {
  861. connectionDistance = parseInt(this.value, 10);
  862. });
  863. var connectDistInput = document.getElementsByName('connectDistInput')[0];
  864. connectDistInput.value = connectionDistance;
  865. connectDistInput.addEventListener('input', function (e) {
  866. connectionDistance = parseInt(this.value, 10);
  867. });
  868. var connectLimitRange = document.getElementsByName('connectLimitRange')[0];
  869. connectLimitRange.value = connectionLimit;
  870. connectLimitRange.addEventListener('input', function (e) {
  871. connectionLimit = parseInt(this.value, 10);
  872. });
  873. var connectLimitInput = document.getElementsByName('connectLimitInput')[0];
  874. connectLimitInput.value = connectionLimit;
  875. connectLimitInput.addEventListener('input', function (e) {
  876. connectionLimit = parseInt(this.value, 10);
  877. });
  878. var pointsNumRange = document.getElementsByName('pointsNumRange')[0];
  879. pointsNumRange.value = numPoints;
  880. pointsNumRange.addEventListener('input', function (e) {
  881. numPoints = parseInt(this.value, 10);
  882. polygonPoints = addOrRemovePoints(numPoints, polygonPoints);
  883. });
  884. var pointsNumInput = document.getElementsByName('pointsNumInput')[0];
  885. pointsNumInput.value = numPoints;
  886. pointsNumInput.addEventListener('input', function (e) {
  887. numPoints = parseInt(this.value, 10);
  888. polygonPoints = addOrRemovePoints(numPoints, polygonPoints);
  889. });
  890. var maxTravelRange = document.getElementsByName('maxTravelRange')[0];
  891. maxTravelRange.value = pointShiftDistance;
  892. maxTravelRange.addEventListener('input', function (e) {
  893. pointShiftDistance = parseInt(this.value, 10);
  894. });
  895. var maxTravelInput = document.getElementsByName('maxTravelInput')[0];
  896. maxTravelInput.value = pointShiftDistance;
  897. maxTravelInput.addEventListener('input', function (e) {
  898. pointShiftDistance = parseInt(this.value, 10);
  899. });
  900. var timeRange = document.getElementsByName('timeRange')[0];
  901. timeRange.value = cycleDuration;
  902. timeRange.addEventListener('input', function (e) {
  903. var oldCycleDuration = cycleDuration;
  904. cycleDuration = parseInt(this.value, 10);
  905. polygonPoints = redistributeCycles(polygonPoints, oldCycleDuration, cycleDuration);
  906. });
  907. var timeInput = document.getElementsByName('timeInput')[0];
  908. timeInput.value = cycleDuration;
  909. timeInput.addEventListener('input', function (e) {
  910. var oldCycleDuration = cycleDuration;
  911. if (this.value === '' || this.value === '0') return;
  912. cycleDuration = parseInt(this.value, 10);
  913. polygonPoints = redistributeCycles(polygonPoints, oldCycleDuration, cycleDuration);
  914. });
  915. var i;
  916. for (i = 0; i < tweeningInputs.length; i++) {
  917. tweeningInputs[i].addEventListener('change', function (e) {
  918. tweeningFns = tweeningSets[this.value];
  919. });
  920. }
  921. var pointSizeRange = document.getElementsByName('pointSizeRange')[0];
  922. pointSizeRange.value = nodeSize;
  923. pointSizeRange.addEventListener('input', function (e) {
  924. nodeSize = parseInt(this.value, 10);
  925. });
  926. var pointSizeInput = document.getElementsByName('pointSizeInput')[0];
  927. pointSizeInput.value = nodeSize;
  928. pointSizeInput.addEventListener('input', function (e) {
  929. nodeSize = parseInt(this.value, 10);
  930. });
  931. var lineSizeRange = document.getElementsByName('lineSizeRange')[0];
  932. lineSizeRange.value = lineSize;
  933. lineSizeRange.addEventListener('input', function (e) {
  934. lineSize = parseInt(this.value, 10);
  935. });
  936. var lineSizeInput = document.getElementsByName('lineSizeInput')[0];
  937. lineSizeInput.value = lineSize;
  938. lineSizeInput.addEventListener('input', function (e) {
  939. lineSize = parseInt(this.value, 10);
  940. });
  941. var colorShiftRange = document.getElementsByName('colorShiftRange')[0];
  942. colorShiftRange.value = disconnectedColorShiftAmt;
  943. colorShiftRange.addEventListener('input', function (e) {
  944. disconnectedColorShiftAmt = parseInt(this.value, 10);
  945. });
  946. var colorShiftInput = document.getElementsByName('colorShiftInput')[0];
  947. colorShiftInput.value = disconnectedColorShiftAmt;
  948. colorShiftInput.addEventListener('input', function (e) {
  949. disconnectedColorShiftAmt = parseInt(this.value, 10);
  950. });
  951. debugCheckbox.addEventListener('change', function (e) {
  952. toggleDebug();
  953. });
  954. fpsCheckbox.addEventListener('change', function (e) {
  955. toggleFPS();
  956. });
  957. nodesCheckbox.addEventListener('change', function (e) {
  958. toggleNodes();
  959. });
  960. linesCheckbox.addEventListener('change', function (e) {
  961. toggleLines();
  962. });
  963. }
  964. function loopStart () {
  965. screenWidth = window.innerWidth;
  966. screenHeight = window.innerHeight;
  967. // Create the renderer
  968. renderer = window.PIXI.autoDetectRenderer(screenWidth, screenHeight, {antialias: true, resolution: resolution});
  969. // Add the canvas to the HTML document
  970. document.body.appendChild(renderer.view);
  971. // Create a container object called the `stage`
  972. stage = new window.PIXI.Container();
  973. renderer.view.style.position = 'absolute';
  974. renderer.view.style.display = 'block';
  975. renderer.autoResize = true;
  976. counter = 0;
  977. totalScreenPixels = screenWidth + screenHeight;
  978. connectionDistance = Math.min(Math.round(totalScreenPixels / 16), 75);
  979. pointShiftDistance = Math.round(totalScreenPixels / 45);
  980. clickMaxDistStart = Math.min(Math.round(totalScreenPixels / 20), clickMaxDistStart);
  981. hoverMaxDistStart = Math.min(Math.round(totalScreenPixels / 16), hoverMaxDistStart);
  982. polygon = new window.PIXI.Graphics();
  983. stage.addChild(polygon);
  984. numPoints = Math.round(totalScreenPixels / 6);
  985. startPoints = getRandomPoints(numPoints, screenWidth, screenHeight, zRange, tweeningFns);
  986. polygonPoints = {
  987. original: startPoints,
  988. target: JSON.parse(JSON.stringify(startPoints)),
  989. tweened: JSON.parse(JSON.stringify(startPoints))
  990. };
  991. fpsGraphic = new window.PIXI.Text('0', {font: '25px monospace', fill: 'yellow'});
  992. fpsGraphic.anchor = new window.PIXI.Point(1, 0);
  993. fpsGraphic.x = screenWidth - 1;
  994. fpsGraphic.y = 0;
  995. if (fpsEnabled) {
  996. stage.addChild(fpsGraphic);
  997. }
  998. lastLoop = new Date();
  999. scrollDelta = 0;
  1000. // Try to fix bug where click initializes to a bogus value
  1001. click = null;
  1002. hover = null;
  1003. registerEventHandlers();
  1004. window.requestAnimationFrame(loop);
  1005. }
  1006. // Use PIXI loader to load image and then start animation
  1007. window.PIXI.loader
  1008. .add(nodeImg)
  1009. .load(loopStart);