ConcernementMapItem.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456
  1. <script>
  2. // https://brm.io/matter-js/docs/classes/Engine.html
  3. // import {
  4. // // Engine,
  5. // // Render,
  6. // // World,
  7. // Bodies,
  8. // Body,
  9. // Events,
  10. // Composite,
  11. // // Composites,
  12. // // Constraint,
  13. // // Vertices,
  14. // // Mouse,
  15. // // MouseConstraint,
  16. // // Query,
  17. // // Common
  18. // } from "matter-js";
  19. import Matter from "matter-js";
  20. import MatterAttractors from "matter-attractors";
  21. // Matter.use(MatterAttractors);
  22. // import { easeInOutQuad, easeInOutQuart } from 'easing-utils';
  23. import Tween from "@tweenjs/tween.js";
  24. import { mapState, mapActions } from 'pinia'
  25. import { ConcernementsStore } from '@/stores/concernements'
  26. export default {
  27. inject: ['canvasMap', 'matterEngine'],
  28. data() {
  29. return {
  30. entities: null,
  31. // concernement: null,
  32. canvas: null,
  33. ctx: null,
  34. pos : {
  35. x: 0,
  36. y: 0
  37. },
  38. ray: 60,
  39. time: 0,
  40. salientPoints: [],
  41. scale: 1,
  42. opacity: 0,
  43. // anim: null,
  44. tween: null,
  45. // matter
  46. body: null,
  47. constraint: null
  48. }
  49. },
  50. props: ['concernement', 'opened'],
  51. computed: {
  52. ...mapState(ConcernementsStore,['concernementsByID'])
  53. },
  54. created () {
  55. // console.log("ConcernementsMapItem concernement", this.canvasMap, this.matterEngine);
  56. this.entites = this.concernement.entites
  57. this.entites_byid = this.concernement.entites_byid
  58. this.parsePoints()
  59. this.getSalientPoints()
  60. },
  61. mounted() {
  62. console.log('concernementItem mounted', typeof this.canvasMap.canvas);
  63. if (this.canvasMap) {
  64. this.initCanvasMap()
  65. }
  66. },
  67. watch: {
  68. // canvasMap (n, o) {
  69. // console.log("concernementItem watch canvasMap", o, n);
  70. // }
  71. canvasMap: {
  72. handler (n, o){
  73. // console.log("concernementItem watch canvasMap.ctx", typeof this.canvas, o, n);
  74. if (!this.canvas) {
  75. this.initCanvasMap()
  76. }
  77. },
  78. deep: true
  79. },
  80. opened: {
  81. handler (n, o) {
  82. if(n){ // opened
  83. this.openClose(true);
  84. }else{ // closed
  85. this.openClose(false)
  86. }
  87. },
  88. deep: true
  89. }
  90. },
  91. methods: {
  92. initCanvasMap (){
  93. // console.log('initCanvasMap');
  94. // record canvas and ctx for rendering (drawing)
  95. this.canvas = this.canvasMap.canvas
  96. this.ctx = this.canvasMap.ctx
  97. // define init position of the item
  98. this.pos = this.getRandomPos();
  99. //
  100. this.initMatterBody()
  101. },
  102. initMatterBody (){
  103. // MATTER
  104. // create the matter body and add it to the engine
  105. if (!this.body) {
  106. // console.log('concernementItem creating body');
  107. // https://github.com/liabru/matter-attractors/issues/8
  108. // https://github.com/liabru/matter-attractors/blob/master/index.js
  109. // MatterAttractors.Attractors.gravityConstant = -5;
  110. // Create parts of the body : main big circle & entities
  111. var parts = [
  112. Matter.Bodies.circle(0, 0, this.ray, {
  113. item_type: 'concernement',
  114. id: this.concernement.id,
  115. })
  116. ];
  117. for (let i = 0; i < this.entites.length; i++) {
  118. // parts.push(Matter.Bodies.circle(this.pos.x+this.entites[i].display.pos.x, this.pos.y+this.entites[i].display.pos.y, 15, {
  119. // item_type: 'entite',
  120. // id: this.entites[i].id
  121. // }))
  122. parts.push(Matter.Bodies.circle(this.entites[i].display.pos.x, this.entites[i].display.pos.y, 2, {
  123. item_type: 'entite',
  124. id: this.entites[i].entite.id,
  125. isSensor: true
  126. }))
  127. }
  128. // create the body
  129. this.body = Matter.Body.create({
  130. parts: parts,
  131. item_type: 'concernement',
  132. id: this.concernement.id,
  133. frictionAir: 0,
  134. // mass: Math.pow(3, this.entites.length),
  135. mass: 10,
  136. restitution: 0.4,
  137. collisionFilter: {
  138. group: -1
  139. },
  140. plugin: {
  141. attractors: [
  142. // there is a built in helper function for Newtonian gravity!
  143. // you can find out how it works in index.js
  144. MatterAttractors.Attractors.gravity
  145. ]
  146. }
  147. });
  148. Matter.Body.setPosition(this.body, this.pos);
  149. // add init velocity
  150. let delta = 10;
  151. Matter.Body.setVelocity(this.body, {
  152. x: -delta + Math.random()*delta*2,
  153. y: -delta + Math.random()*delta*2
  154. });
  155. // console.log('concernementItem mass', this.body.mass);
  156. Matter.Composite.add(this.matterEngine.world, this.body);
  157. // console.log('concernement body', this.body);
  158. // // listen for animate event dispatched from parent
  159. // this.canvas.addEventListener('animate', this.animate)
  160. // listen for afterUpdate event from Matter.Engine object
  161. Matter.Events.on(this.matterEngine, "beforeUpdate", this.onBeforeEngineUpdate);
  162. Matter.Events.on(this.matterEngine, "afterUpdate", this.onAfterEngineUpdate);
  163. }
  164. },
  165. getRandomPos(){
  166. return {
  167. x: this.ray/2 + Math.random()*(this.canvas.width - this.ray),
  168. y: this.ray/2 + Math.random()*(this.canvas.height - this.ray)
  169. };
  170. },
  171. parsePoints (){
  172. // converts data (menace/maintien, actuel/future, prise) into atcual position x,y
  173. for (let i = 0; i < this.entites.length; i++) {
  174. let entite = this.entites[i]
  175. // console.log('entite', entite);
  176. this.entites[i].display = {
  177. alpha: null,
  178. ray: null
  179. }
  180. // RAYON
  181. // https://stackoverflow.com/questions/5731863/mapping-a-numeric-range-onto-another
  182. // slope = (output_end - output_start) / (input_end - input_start)
  183. // output = output_start + slope * (input - input_start)
  184. // from range 0 -> 100 to range 0 -> this.ray
  185. let init_max = 100
  186. let slope = this.ray / init_max
  187. this.entites[i].display.ray = slope * (init_max - entite.prise);
  188. // if (this.concernement.id === 28) {
  189. // console.log(`entity prise: ${entite.prise} | ray: ${this.entites[i].display.ray}`);
  190. // }
  191. // ANGLE
  192. // -90 <= mm <= 90
  193. if (entite.actuelfuture) {
  194. // future en haut : 180 <= a <= 360
  195. // from -90 -> 90 to range 180 -> 360
  196. this.entites[i].display.alpha = entite.menacemaintien + 270
  197. } else {
  198. // actuel: en bas : O <= a <= 180
  199. // from -90 -> 90 to range 180 -> 0
  200. this.entites[i].display.alpha = -1 * entite.menacemaintien + 90
  201. }
  202. // POSITION X Y (par rapport au centre de l'entite)
  203. this.entites[i].display.pos = {
  204. x: this.entites[i].display.ray * Math.cos(this.entites[i].display.alpha * (Math.PI/180)),
  205. y: this.entites[i].display.ray * Math.sin(this.entites[i].display.alpha * (Math.PI/180))
  206. }
  207. this.entites_byid[entite.entite.id].display = this.entites[i].display;
  208. }
  209. },
  210. getSalientPoints () {
  211. // debugger
  212. // console.log(this.entites);
  213. let arc = 360/10;
  214. // loop through arcs
  215. for (let i = 0; i <= 360/arc; i++) {
  216. // loop through entities to find the farest on the arc
  217. let max_r = 0;
  218. let farest = null;
  219. for (let j = 0; j < this.entites.length; j++) {
  220. let entite = this.entites[j];
  221. if(arc*i <= entite.display.alpha && entite.display.alpha <= arc*i+arc) {
  222. // if entity is in arc
  223. if (entite.display.ray > max_r) { // && entite.display.ray > this.ray/2 // and farest from minimu
  224. // if entity is farest from precedent one
  225. max_r = entite.display.ray;
  226. farest = entite;
  227. }
  228. }
  229. }
  230. if (farest) {
  231. this.salientPoints.push(farest.display)
  232. }
  233. }
  234. // console.log(`this.salientPoints ${this.concernement.id}`, this.salientPoints);
  235. },
  236. // onBeforeEngineUpdate (event) {
  237. // if (this.opened) {
  238. // // Matter.Body.setPosition(this.body, this.pos);
  239. // }
  240. // },
  241. openClose(open) {
  242. if (this.tween) {
  243. this.tween.stop();
  244. }
  245. if (open) {
  246. // opening tweening
  247. this.tween = new Tween.Tween({s: this.scale, x: this.pos.x, y: this.pos.y, o: 0})
  248. .to({
  249. s: 7,
  250. x: (this.canvas.width - 450) / 2,
  251. y: this.canvas.height / 2,
  252. o: 0.8
  253. }, 800)
  254. .onUpdate((obj) => {
  255. // https://github.com/liabru/matter-js/issues/986#issuecomment-812488873
  256. // revert to the original size (by reverting the previous scale)
  257. Matter.Body.scale(this.body, 1 / this.scale, 1 / this.scale)
  258. // then scale again to new scale
  259. Matter.Body.scale(this.body, obj.s, obj.s)
  260. // record new scale
  261. this.scale = obj.s;
  262. this.opacity = obj.o;
  263. Matter.Body.setPosition(this.body, {x:obj.x, y:obj.y});
  264. this.pos = {x:obj.x, y:obj.y};
  265. })
  266. .onComplete((obj) => {
  267. this.constraint = Matter.Constraint.create({
  268. pointA: this.pos,
  269. bodyB: this.body,
  270. stiffness: 1,
  271. damping: 0,
  272. length: 0
  273. });
  274. Matter.Composite.add(this.matterEngine.world, [this.body, this.constraint]);
  275. });
  276. } else {
  277. // closing
  278. if(this.constraint){
  279. Matter.Composite.remove(this.matterEngine.world, this.constraint);
  280. }
  281. this.tween = new Tween.Tween({s: this.scale, o: 1})
  282. .to({s: 1, o: 0}, 500)
  283. .onUpdate((obj) => {
  284. // https://github.com/liabru/matter-js/issues/986#issuecomment-812488873
  285. // revert to the original size (by reverting the previous scale)
  286. Matter.Body.scale(this.body, 1 / this.scale, 1 / this.scale)
  287. // then scale again to new scale
  288. Matter.Body.scale(this.body, obj.s, obj.s)
  289. // record new scale
  290. this.scale = obj.s;
  291. this.opacity = obj.o;
  292. });
  293. }
  294. this.tween.easing(Tween.Easing.Quadratic.InOut).start();
  295. },
  296. onBeforeEngineUpdate (event) {
  297. if (this.tween) {
  298. this.tween.update();
  299. }
  300. },
  301. onAfterEngineUpdate (event) {
  302. // respawn element if outside screen
  303. if(this.pos.x < 0
  304. || this.pos.x > this.canvas.width
  305. || this.pos.y < 0
  306. || this.pos.y > this.canvas.height){
  307. this.pos = this.getRandomPos()
  308. Matter.Body.setPosition(this.body, {x:this.pos.x, y:this.pos.y});
  309. }
  310. this.draw()
  311. },
  312. draw() {
  313. if (!this.ctx) return;
  314. this.pos = this.body.position;
  315. // this.ctx.clearRect(
  316. // this.pos.x - this.ray*this.scale, this.pos.y - this.ray*this.scale,
  317. // this.ray*this.scale*2, this.ray*this.scale*2
  318. // );
  319. if (this.opened) {
  320. // BOUSSOLE
  321. // exterieur circle
  322. this.ctx.beginPath();
  323. this.ctx.lineWidth = 2;
  324. this.ctx.strokeStyle = `rgba(255,255,255,${this.opacity})`;
  325. // external circle is %8 less than max ray = (*0.92)
  326. this.ctx.arc(this.pos.x, this.pos.y, this.ray*this.scale*0.92, 0, 2 * Math.PI, false);
  327. // this.ctx.stroke();
  328. // interieur circle
  329. this.ctx.arc(this.pos.x, this.pos.y, this.ray/2*this.scale, 0, 2 * Math.PI, false);
  330. // this.ctx.stroke();
  331. // axes
  332. // vertical
  333. this.ctx.moveTo(this.pos.x, this.pos.y - this.ray*this.scale);
  334. this.ctx.lineTo(this.pos.x, this.pos.y + this.ray*this.scale);
  335. // horizontal
  336. this.ctx.moveTo(this.pos.x - this.ray*this.scale, this.pos.y);
  337. this.ctx.lineTo(this.pos.x + this.ray*this.scale, this.pos.y);
  338. // this.ctx.stroke();
  339. // fleches
  340. // haute
  341. this.ctx.moveTo(this.pos.x - (8*this.scale), this.pos.y - this.ray*this.scale*0.92 + (8*this.scale));
  342. this.ctx.lineTo(this.pos.x, this.pos.y - this.ray*this.scale*0.92);
  343. this.ctx.lineTo(this.pos.x + (8*this.scale), this.pos.y - this.ray*this.scale*0.92 + (8*this.scale));
  344. // milieu
  345. this.ctx.moveTo(this.pos.x - (8*this.scale), this.pos.y + (8*this.scale));
  346. this.ctx.lineTo(this.pos.x, this.pos.y);
  347. this.ctx.lineTo(this.pos.x + (8*this.scale), this.pos.y + (8*this.scale));
  348. this.ctx.stroke();
  349. // MOINS - PLUS
  350. this.ctx.beginPath();
  351. this.ctx.lineWidth = 8;
  352. this.ctx.strokeStyle = `rgba(255,255,255,${this.opacity})`;;
  353. // PLUS
  354. // horizontal
  355. this.ctx.moveTo(this.pos.x + this.ray*this.scale - (5 * this.scale), this.pos.y - this.ray*this.scale);
  356. this.ctx.lineTo(this.pos.x + this.ray*this.scale + (5 * this.scale), this.pos.y - this.ray*this.scale);
  357. // vertical
  358. this.ctx.moveTo(this.pos.x + this.ray*this.scale, this.pos.y - this.ray*this.scale - (5 * this.scale));
  359. this.ctx.lineTo(this.pos.x + this.ray*this.scale, this.pos.y - this.ray*this.scale + (5 * this.scale));
  360. // MOINS
  361. // horizontal
  362. this.ctx.moveTo(this.pos.x - this.ray*this.scale - (5 * this.scale), this.pos.y - this.ray*this.scale);
  363. this.ctx.lineTo(this.pos.x - this.ray*this.scale + (5 * this.scale), this.pos.y - this.ray*this.scale);
  364. // vertical
  365. this.ctx.stroke();
  366. }
  367. // contours
  368. if (this.salientPoints.length > 3) {
  369. this.ctx.beginPath();
  370. this.ctx.lineWidth = 1;
  371. this.ctx.strokeStyle = "#000";
  372. this.ctx.fillStyle = "rgba(255,255,255,0.8)";
  373. this.ctx.moveTo(this.pos.x+this.salientPoints[0].pos.x*this.scale*1.15, this.pos.y+this.salientPoints[0].pos.y*this.scale*1.15)
  374. for (let j = 1; j < this.salientPoints.length; j++) {
  375. this.ctx.lineTo(this.pos.x+this.salientPoints[j].pos.x*this.scale*1.15, this.pos.y+this.salientPoints[j].pos.y*this.scale*1.15)
  376. }
  377. this.ctx.lineTo(this.pos.x+this.salientPoints[0].pos.x*this.scale*1.15, this.pos.y+this.salientPoints[0].pos.y*this.scale*1.15)
  378. this.ctx.fill();
  379. this.ctx.stroke();
  380. }
  381. if (this.opened) {
  382. // place all entities points
  383. for (let i = 0; i < this.entites.length; i++) {
  384. let entite = this.entites[i];
  385. // console.log('entite', entite);
  386. this.ctx.beginPath();
  387. this.ctx.arc(this.pos.x+entite.display.pos.x*this.scale, this.pos.y+entite.display.pos.y*this.scale, 5, 0, 2 * Math.PI, false);
  388. this.ctx.strokeStyle = "#F00";
  389. this.ctx.stroke();
  390. }
  391. // OR
  392. for (let i = 0; i < this.body.parts.length; i++) {
  393. // let entite = this.entites[i];
  394. if (this.body.parts[i].item_type === 'entity') {
  395. let part = this.body.parts[i];
  396. // console.log('part', part);
  397. // console.log(`part pos x:${part.position.x} y:${part.position.y} || entity pos x:${this.pos.x+this.entites_byid[part.id].display.pos.x*this.scale} y:${this.pos.y+this.entites_byid[part.id].display.pos.y*this.scale}`);
  398. this.ctx.beginPath();
  399. // this.ctx.arc(this.pos.x+entite.display.pos.x*this.scale, this.pos.y+entite.display.pos.y*this.scale, 2, 0, 2 * Math.PI, false);
  400. this.ctx.arc(this.body.parts[i].position.x, this.body.parts[i].position.y, 2*this.scale, 0, 2 * Math.PI, false);
  401. this.ctx.strokeStyle = "#000";
  402. this.ctx.stroke();
  403. }
  404. }
  405. }
  406. // concernement id @center
  407. this.ctx.beginPath();
  408. // this.ctx.arc(this.pos.x, this.pos.y, 4, 0, 2 * Math.PI, false);
  409. this.ctx.fillStyle = "#000";
  410. this.ctx.fillText(this.concernement.id, this.pos.x, this.pos.y)
  411. this.ctx.fill();
  412. }
  413. },
  414. render() {
  415. // console.log('render()', this.ctx);
  416. },
  417. }
  418. </script>