<script>


import { nextTick } from 'vue'

import Matter from "matter-js";
import MatterAttractors from "matter-attractors";
// Matter.use(MatterAttractors);
// import polydecomp from "poly-decomp";

import paper from 'paper';

// import { easeInOutQuad, easeInOutQuart } from 'easing-utils';
import Tween from "@tweenjs/tween.js";

import { mapState, mapActions } from 'pinia'
import { ConcernementsStore } from '@/stores/concernements'
import { CommonStore } from '@/stores/common'
import { mdiPictureInPictureBottomRight } from '@mdi/js';

export default {
  inject: ['canvasMap', 'matterEngine'],
  data() {
    return {
      id: null,
      cid: null,
      concernement: null,
      active_revision: null,
      entities: null,
      superposedEntiteID: null,
      // superposedEntitesIDsList: [],
      canvas: null,
      ctx: null,
      pos : {
        x: 0,
        y: 0
      },
      ray: 100,
      time: 0,
      salientPoints: [],
      scale: 1,
      prev_scale: 1,
      //
      details_zoom_scale: 1,
      is_dragging: false,
      //
      opacity: 0,
      tween: null,
      is_visible: true,
      is_open: false,
      is_opening: false,
      is_closing: false,
      is_hover: false,
      //
      body: null,
      body_parts: [],
      constraint: null,
      //
      paper_main_object: {},
      paper_groups: {},
      //
      doleance_transition: false,
      //

    }
  },
  props: ['mapitem'],
  computed: {
    ...mapState(ConcernementsStore,['map_mode',
                                    'concernementsByID',
                                    'allEntitesById',
                                    'allSuperpositions_bycids',
                                    'allSuperpositions_clustered',
                                    'allMapItems_byid',
                                    'opened_concernement',
                                    'opened_entite_id',
                                    'detailsZoomValue']),
    ...mapState(CommonStore,['hover_elmt',
                            'map_item_ray',
                            'cartouch_width',
                            'cartouch_is_opened',
                            'paper_symbol_definitions'])
  },
  created () {
    // console.log('ConcernementMapItem created', this.mapitem);
    // this.id = this.superposition_id ? `${this.cid}___${this.superposition_id}` : this.cid;
    // console.log(`this.mapitem.id`, this.mapitem.id);
    this.id = this.mapitem.id;
    this.concernement = this.mapitem.concernement;
    this.cid = this.concernement.id;
    // console.log('mapitem this.cid', this.cid);
    this.active_revision = this.concernement.active_revision;

    // console.log(`ConcernementsMapItem ${this.id} created`);
    // this.entites = this.concernement.entites
    this.entites = this.concernement.revisions_byid[this.concernement.revision_id].entites;
    

    // disable concernement if less than 3 entite
    if(this.entites.length < 3){
      this.hideShowConcernement(this.concernement.id, false);
    } else{
      // record canvas and ctx for rendering (drawing)
      this.canvas = this.canvasMap.canvas
      this.ctx = this.canvasMap.ctx
      
      // define the ray regarding the width and height of screen
      this.ray = this.map_item_ray;
      // console.log(`this.ray: ${this.ray}`);
      // 
      this.parseEntityPointsValues()
      // this.getSalientPoints()
      this.sailentEntites = this.concernement.sailentEntites = this.getJarvisEnvelopeConvexeEntites(this.entites)

      // define init position of the item
      this.pos = this.getRandomPos();
      //
      this.getSuperposedEntiteID();
      //
      this.initMatterBody()
      //
      this.initPaperObjects()
      //
      if(this.mapitem.clone){
        if(this.map_mode === 'superposition'){
          this.handleMapitemVisibility(true);
        }else{
          this.handleMapitemVisibility(false);
        }
      }
    }

    // this.setConcernementMapItem(this.cid, this);
    // this.setConcernementScale(this.cid, this.scale);
    this.mapitem.scale = this.scale;

    // window.addEventListener('resize', this.onWindowResize.bind(this));
  },
  // mounted() {
  //   console.log(`ConcernementsMapItem ${this.concernement.id} mounted`, this.canvasMap.canvas);
    
  // },
  // beforeUnmount () {
  unmounted () {
    // console.log(`mapitem ${this.id} unmounted`);
    if(this.mapitem.clone) {
      // console.log(`this.paper_main_object ${this.paper_main_object.id}`, this.paper_main_object);
      paper.project.getItem({id:this.paper_main_object.id}).remove();
    };
    if (this.body) {
      Matter.Composite.remove(this.matterEngine.world, this.body);
    }
  },
  watch: {
    // canvasMap (n, o) {
    //   console.log("concernementItem watch canvasMap", o, n);
    // }
    canvasMap: {
      handler (n, o){
        // console.log("concernementItem watch canvasMap.ctx", typeof this.canvas, o, n);
        if (!this.canvas) {
          this.initCanvasMap()
        }
      },
      deep: true
    },
    opened_concernement: {
      handler (n, o) {
        if (!n) { // if there is no opened concernement
          this.is_open = false;
          this.applyShuffleForces();
        } else {
          if(this.cid === n.id && this.id === n.opened_mapitem_id){
            this.is_open = this.mapitem.is_open = true;
          }else{
            this.is_open = this.mapitem.is_open = false;
          }
        }
      },
      deep: true
    },
    is_open: {
      handler (n, o) {
        // console.log('mapItem watch is_open', n, this.is_open);
        if(n){ // opened
          this.openClose(true);
        }else if(o) { // closed if was opened
          this.openClose(false);
        }
      },
      deep: true
    },
    map_mode: {
      handler (n, o) {
        console.log('concernementMapItem watch map_mode', o, n);
        
        // apply a little force to check the map when returning to terrain de vie
        if (n === 'terraindevie' && !this.opened_concernement) {
          this.applyShuffleForces();
        }
        
        if (this.is_open && n !== "terraindevie"){
          console.log('concernementMapItem watch map_mode CALLING setDetailsZoomValue');
          this.setDetailsZoomValue(1);
          // we have to call updateDetailsZoomScale() right now, can't wait for wath f° which will append after the map_item contents redrawing
          this.updateDetailsZoomScale();
        }
        
        // if map_item is open and we change the mad mode, redefined what is displayed in the "boussole"
        if (this.is_open & n !== o) {
          this.setPaperContents();
          this.resetMapItemPosition();
          this.resetMapItemScale();
        }

        // Clones visibility
        if(this.mapitem.clone){
          let is_visible = false;
          if(n === 'superposition'){
            // superposition mapemode all clones are visible
            is_visible = true;
          }else{
            if(this.opened_concernement){
              if(this.mapitem.is_open){
                is_visible = true;
              }else{
                // keep open the clones that are superposed to the opened map_item
                let opened_mapitem_id = this.opened_concernement.opened_mapitem_id;
                if (this.opened_concernement.superposed_mapitem_id_by_mapitem_id && this.opened_concernement.superposed_mapitem_id_by_mapitem_id[opened_mapitem_id]) {
                  if(this.opened_concernement.superposed_mapitem_id_by_mapitem_id[opened_mapitem_id].indexOf(this.mapitem.id) !== -1){
                    is_visible = true;
                  }
                }
              }
            }
          }
          this.handleMapitemVisibility(is_visible);
        }
      },
      deep: true
    },
    hover_elmt: {
      handler (n, o) {
        // console.log(`watch hover_elmt ${this.id}`, o, n);
        if (n && (n.type === 'concernement' || n.type === 'superposition') && n.id === this.id) {
          this.is_hover = true;
        } else {
          this.is_hover = false;
        }
      },
      deep: true
    },
    is_hover: {
      handler (n, o) {
        this.onIsHover(n);
      },
      deep: true
    },
    concernement: {
      handler (n, o){
        // check for active_revision changes
        // do not trigger on first variable filling (if o is null)
        if(this.is_open && o && o.active_revision && n.active_revision && this.map_mode === "terraindevie"){
          // be aware that here o & n active_revision are already the same :( !?
          console.log(`concernementMapItem watch concernement o.active_revision:${o.active_revision}, n.active_revision:${n.active_revision}`);
          this.resetPaperActiveRevision();
        }
        // // check for doleance change
        // if (this.is_open && o && o.opened_doleance && n.opened_doleance && this.map_mode === "doleancer") {
        //   // be aware that here o & n opened_doleance are already the same :( !?
        //   console.log(`concernementMapItem watch concernement o.opened_doleance:${o.opened_doleance.id}, n.opened_doleance:${n.opened_doleance.id}`);
        // }
      },
      deep: true
    },
    // window as been resized
    map_item_ray: {
      handler (n, o) {
        console.log(`map_item_ray updated o: ${o}, n: ${n}`);
        this.ray = n;
        this.updateMatterBodyRay();
        this.updatePaperObjectSize();
      },
      deep: true
    },
    detailsZoomValue: {
      handler (n, o) {
        if (this.is_open && this.map_mode === "terraindevie") {
          console.log('map_item detailsZoomValue watch', n);
          this.updateDetailsZoomScale()
        }
      },
      deep: true
    }
  },
  methods: {
    ...mapActions(CommonStore,['setHoverElmt']),
    ...mapActions(ConcernementsStore,['openCloseConcernements',
                                      'hideShowConcernement',
                                      'setOpenedEntiteId',
                                      'setActiveRevision',
                                      'setBesoinPaperId',
                                      'setOpenedDoleanceField',
                                      'setOpenedRecit',
                                      'setDetailsZoomValue',
                                    ]),
    parseEntityPointsValues (){
      // converts data (menace/maintien, actuel/future, prise) into atcual position x,y
      for (let r = 0; r < this.concernement.revisions.length; r++) { // loop through all revisions
        for (let i = 0; i < this.concernement.revisions[r].entites.length; i++) { // loop through all entite for each revision
          let entite = this.concernement.revisions[r].entites[i]
          // console.log('entite', entite);
          
          entite.display = {
            alpha: null,
            ray: null
          }
          // RAYON
          // https://stackoverflow.com/questions/5731863/mapping-a-numeric-range-onto-another
          // slope = (output_end - output_start) / (input_end - input_start)
          // output = output_start + slope * (input - input_start)
          // from range 0 -> 100 to range 0 -> this.ray
          let init_max = 100
          let slope = this.ray / init_max
          entite.display.ray = slope * (init_max - entite.prise);
          // if (this.concernement.id === 28) {
          //   console.log(`entity prise: ${entite.prise} | ray: ${entite.display.ray}`);
          // }
          

          // ANGLE
          // -90 <= mm <= 90
          if (entite.actuelfuture) {
            // future en haut : 180 <= a <= 360
            // from -90 -> 90 to range 180 -> 360
            entite.display.alpha = entite.menacemaintien + 270
          } else {
            // actuel: en bas : O <= a <= 180
            // from -90 -> 90 to range 180 -> 0
            entite.display.alpha = -1 * entite.menacemaintien + 90
          }

          // POSITION X Y (par rapport au centre du concernement)
          entite.display.pos = {
            x: entite.display.ray * Math.cos(entite.display.alpha * (Math.PI/180)),
            y: entite.display.ray * Math.sin(entite.display.alpha * (Math.PI/180))
          }
          // if (entite.entite) {
          //   this.entites_byid[entite.entite.id].display = entite.display;
          // }
        }
      }
    },
    getSalientPoints_OLD() {
      // debugger
      // console.log(this.entites);
      let arc = 360/30;
      // loop through arcs
      // for (let i = 360/arc; i >= 0 ; i--) {
      for (let i = 0; i <= 360/arc ; i++) {
        // loop through entities to find the farest on the arc
        let max_r = 0;
        let farest = null;
        for (let j = 0; j < this.entites.length; j++) {
          let entite = this.entites[j];
          if(arc*i <= entite.display.alpha && entite.display.alpha <= arc*i+arc) { // if entity is in arc
            if (entite.display.ray > max_r) { //  && entite.display.ray > this.ray/2 // and farest from minimu
              // if entity is farest from precedent one 
              max_r = entite.display.ray;
              // recalcul x & y to get a little padding between entite and contour by increasing ray
              farest = {
                alpha: entite.display.alpha,
                ray: entite.display.ray,
                pos: {
                  x: (entite.display.ray + 3) * Math.cos(entite.display.alpha * (Math.PI/180)),
                  y: (entite.display.ray + 3) * Math.sin(entite.display.alpha * (Math.PI/180))
                }
              };
            }
          }
        }
        if (farest) {
          this.salientPoints.push(farest)
        }
      }
      // console.log(`this.salientPoints ${this.concernement.id}`, this.salientPoints);
    },
    getJarvisEnvelopeConvexeEntites(entites){
      let sailentEntites = [];
      // https://www.geeksforgeeks.org/convex-hull-using-jarvis-algorithm-or-wrapping/
      // find the most left point
      let l, min_x = null;
      for (let i = 0; i < entites.length; i++) {
        let entite = entites[i];
        let x = entite.display.ray * Math.cos(entite.display.alpha * (Math.PI/180));
        if(!min_x || min_x > x){
          l = i;
          min_x = x;
        }
      }
      // Start from leftmost point, keep moving clockwise
      // until reach the start point again.  This loop runs O(h)
      // times where h is number of points in result or output.
      let p = l, q;
      do {
        // console.log(`do while ${this.id}`, p);
        // Add current point to result
        let entite = entites[p];
        sailentEntites.push(entite);

        // Search for a point 'q' such that 
        // orientation(p, q, x) is clockwise 
        // for all points 'x'. The idea is to keep 
        // track of last visited most clockwise
        // point in q. If any point 'i' is more 
        // clockwise than q, then update q.
        q = (p + 1) % entites.length;
        for (let i = 0; i < entites.length; i++) {
          let p_x = (entites[p].display.ray) * Math.cos(entites[p].display.alpha * (Math.PI/180));
          let p_y = (entites[p].display.ray) * Math.sin(entites[p].display.alpha * (Math.PI/180));
          let i_x = (entites[i].display.ray) * Math.cos(entites[i].display.alpha * (Math.PI/180));
          let i_y = (entites[i].display.ray) * Math.sin(entites[i].display.alpha * (Math.PI/180));
          let q_x = (entites[q].display.ray) * Math.cos(entites[q].display.alpha * (Math.PI/180));
          let q_y = (entites[q].display.ray) * Math.sin(entites[q].display.alpha * (Math.PI/180));
          
          let val = (i_y - p_y) * (q_x - i_x) - (i_x - p_x) * (q_y - i_y);
          
          // If i is more clockwise than current q, then update q
          if (val > 0){
            q = i;
          }
        }

        // Now q is the most clockwise with
        // respect to p. Set p as q for next iteration, 
        // so that q is added to result 'hull'
        p = q;
      } while (p != l);

      return sailentEntites;
    },
    getRandomPos(){
      let pad = 200;
      // if (this.concernement.id === 56) {
      //   return {
      //     x: Math.random() > 0.5 ? pad : this.canvas.width - pad,
      //     y: pad + this.ray/2 + Math.random()*(this.canvas.height - this.ray - pad)
      //   };
      // } else {
        return {
          x: pad + this.getGaussianRandom()*(this.canvas.width - pad*2),
          y: pad + this.getGaussianRandom()*(this.canvas.height - pad*2)
        };
      // }
    },
    getGaussianRandom(){
      // INFO https://stackoverflow.com/a/49434653
      let u = 0, v = 0;
      while(u === 0) u = Math.random(); //Converting [0,1) to (0,1)
      while(v === 0) v = Math.random();
      let num = Math.sqrt( -2.0 * Math.log( u ) ) * Math.cos( 2.0 * Math.PI * v );
      num = num / 10.0 + 0.5; // Translate to 0 -> 1
      if (num > 1 || num < 0) return this.getGaussianRandom() // resample between 0 and 1
      return num
    },
    getSuperposedEntiteID(){
      if (this.concernement.has_superpositions) {
        let ids = this.id.match(/(\d+)___(\d+)/i);
        if (ids) {
          let cluster_index = ids[2];
          // console.log(ids, cluster_index);
          for(let cid_eid of this.allSuperpositions_clustered[cluster_index] ){
            if(this.cid === cid_eid.cid){
              this.superposedEntiteID = cid_eid.eid;
              break;
            }
          }
        }
      }
    },
    // MATTER BODY
    initMatterBody (){
      
      // MATTER
      // create the matter body and add it to the engine
      if (!this.body) {
        // console.log('concernementItem creating body');

        // INFO https://github.com/liabru/matter-attractors/issues/8        
        // INFO https://github.com/liabru/matter-attractors/blob/master/index.js
        // INFO https://github.com/liabru/matter-attractors/blob/master/build/matter-attractors.js#L180
        MatterAttractors.Attractors.gravityConstant = -20;

        // Create parts of the body : main big circle & entities
        // INFO map a range of numbers to another range of numbers https://stackoverflow.com/a/46462321
        let entite_range = [3, 100];
        let ray_range = [this.ray*0.8,this.ray*1.2];
        let ray = ray_range[0] + (this.entites.length - entite_range[0]) * (ray_range[1] - ray_range[0]) / (entite_range[1] - entite_range[0]);
        // this.body_parts = [
        //   Matter.Bodies.circle(0, 0, ray)
        // ];
        
        // INFO map a range of numbers to another range of numbers https://stackoverflow.com/a/46462321
        let mass_range = [5,15];
        let mass = mass_range[0] + (this.entites.length - entite_range[0]) * (mass_range[1] - mass_range[0]) / (entite_range[1] - entite_range[0]);

        // create the body
        this.body = Matter.Bodies.circle(0, 0, ray, {
          item_type: 'concernement',
          id: this.id,
          frictionAir: 0,
          mass: mass,
          restitution: 0.06,
          collisionFilter: {
            group: -1,
            category: 0x0002,
            mask: 0x0001
          },
          plugin: {
            attractors: [
              // // there is a built in helper function for Newtonian gravity!
              // // you can find out how it works in index.js
              MatterAttractors.Attractors.gravity
              
              // function(bodyA, bodyB) {
              //   var force = {
              //     x: (bodyA.position.x - bodyB.position.x) * 1e-6,
              //     y: (bodyA.position.y - bodyB.position.y) * 1e-6
              //   }
              //   // apply force to both bodies
              //   Matter.Body.applyForce(bodyA, bodyA.position, force);
              //   Matter.Body.applyForce(bodyB, bodyB.position, Matter.Vector.neg(force));
              // }

              // INFO https://github.com/liabru/matter-attractors/blob/master/build/matter-attractors.js#L192
              // function (bodyA, bodyB){
              //   // use Newton's law of gravitation
              //   var bToA = Matter.Vector.sub(bodyB.position, bodyA.position),
              //       distanceSq = Matter.Vector.magnitudeSquared(bToA) || 0.0001,
              //       normal = Matter.Vector.normalise(bToA),
              //       magnitude = -MatterAttractors.Attractors.gravityConstant * (bodyA.mass * bodyB.mass / distanceSq),
              //       force = Matter.Vector.mult(normal, magnitude);

              //   // to apply forces to both bodies
              //   Matter.Body.applyForce(bodyA, bodyA.position, Matter.Vector.neg(force));
              //   Matter.Body.applyForce(bodyB, bodyB.position, force);
              // }
            ]
          }
        })
        // this.body = Matter.Body.create({
        //   parts: this.body_parts,
        //   item_type: 'concernement',
        //   id: this.id,
        //   frictionAir: 0,
        //   // mass: Math.pow(3, this.entites.length),
        //   // mass: 10,
        //   mass: mass,
        //   restitution: 0.06,
        //   collisionFilter: {
        //     group: -1
        //   },
        //   plugin: {
        //     attractors: [
        //       // // there is a built in helper function for Newtonian gravity!
        //       // // you can find out how it works in index.js
        //       MatterAttractors.Attractors.gravity
              
        //       // function(bodyA, bodyB) {
        //       //   var force = {
        //       //     x: (bodyA.position.x - bodyB.position.x) * 1e-6,
        //       //     y: (bodyA.position.y - bodyB.position.y) * 1e-6
        //       //   }
        //       //   // apply force to both bodies
        //       //   Matter.Body.applyForce(bodyA, bodyA.position, force);
        //       //   Matter.Body.applyForce(bodyB, bodyB.position, Matter.Vector.neg(force));
        //       // }

        //       // INFO https://github.com/liabru/matter-attractors/blob/master/build/matter-attractors.js#L192
        //       // function (bodyA, bodyB){
        //       //   // use Newton's law of gravitation
        //       //   var bToA = Matter.Vector.sub(bodyB.position, bodyA.position),
        //       //       distanceSq = Matter.Vector.magnitudeSquared(bToA) || 0.0001,
        //       //       normal = Matter.Vector.normalise(bToA),
        //       //       magnitude = -MatterAttractors.Attractors.gravityConstant * (bodyA.mass * bodyB.mass / distanceSq),
        //       //       force = Matter.Vector.mult(normal, magnitude);

        //       //   // to apply forces to both bodies
        //       //   Matter.Body.applyForce(bodyA, bodyA.position, Matter.Vector.neg(force));
        //       //   Matter.Body.applyForce(bodyB, bodyB.position, force);
        //       // }
        //     ]
        //   }
        // });
        Matter.Body.setPosition(this.body, this.pos);

        // add init velocity
        // INFO no need of init velocity as each mapitems are repeling the each others
        // this.setInitBodyVelocity()
        
        // console.log('concernementItem mass', this.body.mass);
        Matter.Composite.add(this.matterEngine.world, this.body);
        // console.log('concernement body', this.body);

        // listen for afterUpdate event from Matter.Engine object
        Matter.Events.on(this.matterEngine, "beforeUpdate", this.onBeforeEngineUpdate);
        Matter.Events.on(this.matterEngine, "afterUpdate", this.onAfterEngineUpdate);
      }
    },
    setInitBodyVelocity(){
      let delta = 5;
      Matter.Body.setVelocity(this.body, {
        x: -delta + Math.random()*delta*2,
        y: -delta + Math.random()*delta*2
      });
    },
    updateMatterBodyRay(){
      console.log('updateMatterBodyRay',this.ray, this.body);
      let entite_range = [3, 100];
      let ray_range = [this.ray*0.8,this.ray*1.2];
      let new_ray = ray_range[0] + (this.entites.length - entite_range[0]) * (ray_range[1] - ray_range[0]) / (entite_range[1] - entite_range[0]);
      let old_ray = this.body.circleRadius;
      let scale = new_ray/old_ray;
      Matter.Body.scale(this.body, scale, scale);
      // Matter.Body.set(this.body, 'circleRadius', ray);
      console.log(`this.body circleRadius ${this.body.circleRadius}`);
    },
    // PAPER OBJECTS
    initPaperObjects(){

      // the main paper group containing all paper graphical items for one concernement
      this.paper_main_object = new paper.Group({
        pivot: new paper.Point(this.pos),
        name: `main_${this.id}`,
        cid: this.cid,
        // superposition_id: this.mapitem.superposition_ids[0]
      });

      // fadein intro
      // if(!this.mapitem.clone){
        this.paper_main_object.opacity = (1 + Math.random())*0.001;
      // }

      // the sub items for one concernement
      this.paper_main_object.addChild(this.setPaperContour());

      if (this.concernement.has_superpositions) {
        this.paper_main_object.addChild(this.setPaperEntitesSuperposees());
      }

      if (this.concernement.has_proximites) {
        this.paper_main_object.addChild(this.setPaperEntitesProximite());
      }

      // if (this.concernement.has_puissancedagir) {
      //   this.addNewPaperSymbolInstance('puissanceagir_icon', false, 0.7);
      // }
      // if (this.concernement.has_agissantes) {
      //   this.paper_main_object.addChild(this.setPaperAgissantesIcons());
      // }
      // if (this.concernement.has_doleance) {
      //   this.addNewPaperSymbolInstance('doleance_icon', false, 0.7);
      // }


      this.initPaperEvents()
    },
    async updatePaperObjectSize(){
      await nextTick();
      // INFO we redraw the points with the new ray AND sacle the backgrounds ... would it be simplier to only resize the whole mapitem ???
      // window has been resized


      // if open
      if (this.is_open) {
        // move to the new position
        // INFO we need to change position first for the contours to be redraw in the right place
        // this.resetMapItemPosition();
        this.pos = this.paper_main_object.position = {
          x: (this.canvas.width - this.cartouch_width) / 2,
          y: this.canvas.height / 2
        };
        this.constraint.pointA = this.pos;
        Matter.Body.setPosition(this.body, this.pos);
        
        // reset the paper contents with paper symbols redrew by MapConcernements.vue
        this.setPaperContents();

        // scale
        // let s = this.getOpeningAvailableScale();
        // this.paper_main_object.scale(1 / this.scale);
        // this.paper_main_object.scale(s);
        
      }

      // in any case
      // delet the old contours
      let clearables = ['contours'];
      clearables.forEach(child_name => {
        if (this.paper_main_object.children[child_name]) {
          this.paper_main_object.children[child_name].remove();
        }
      });
      // rebuild the contours with the new ray
      this.parseEntityPointsValues()
      this.sailentEntites = this.concernement.sailentEntites = this.getJarvisEnvelopeConvexeEntites(this.entites)
      // redraw the contours
      this.paper_main_object.addChild(this.setPaperContour());

    },
    updateDetailsZoomScale(){
      console.log(`updateDetailsZoomScale ${this.detailsZoomValue}`);
      // revert to the original scale (by reverting the previous scale)
      this.paper_main_object.scale(1 / this.details_zoom_scale);
      // compute the zoom scale
      this.details_zoom_scale = this.scale * this.detailsZoomValue;
      // then scale again to new scale
      this.paper_main_object.scale(this.details_zoom_scale);
      // // handle superposition scaling
      // this.resetSuperpositionsConstraintsScaling(s);

      // this.prev_scale = this.scale = s;

      // allow to go through walls if zoomed in
      if (this.detailsZoomValue > 1) {
        this.body.collisionFilter.mask = 0x0004;
      } else {
        this.body.collisionFilter.mask = 0x0001;
      }
    },
    /*
    * called by openClose() function
    */
    setPaperContents(){
      console.log('setPaperContents');
      // trigered once opening tween is complete
      // trigered once mapmode changed
      this.clearPaperContents();
      switch(this.map_mode){
        case 'terraindevie':
          this.addNewPaperSymbolInstance('boussole_bg', true);
          this.paper_main_object.addChild(this.setPaperEntites());
          break;
        case 'superposition':
          this.addNewPaperSymbolInstance('boussole_bg', true);
          break;
        case 'proximite':
          this.addNewPaperSymbolInstance('boussole_bg', true);
          this.paper_main_object.addChild(this.setPaperEntitesProximiteReferences());
          break;
        case 'puissancedagir':
          this.addNewPaperSymbolInstance('puissanceagir_bg', true);
          this.paper_main_object.addChild(this.setPaperPuissanceagirBesoins());
          break;
        case 'action':
          this.addNewPaperSymbolInstance('boussole_bg', true);
          this.paper_main_object.addChild(this.setPaperAgissantes());
          break;
        case 'doleancer':
          this.addNewPaperSymbolInstance('doleance_bg');
          this.paper_main_object.addChild(this.setPaperDoleances());
          break;
      }
    },
    resetPaperActiveRevision(){
      console.log('resetPaperActiveRevision');
      
      this.sailentEntites = this.getJarvisEnvelopeConvexeEntites(this.concernement.revisions_byid[this.concernement.active_revision].entites);
      
      // remove contours if already exists
      if (this.paper_main_object.children.contours) {
        this.paper_main_object.children.contours.remove();
      }
      // redraw contours
      this.paper_main_object.addChild(this.setPaperContour());

      // remove entites if already exists
      if (this.paper_main_object.children.entites) {
        this.paper_main_object.children.entites.remove();
      }
      // redraw entites
      this.paper_main_object.addChild(this.setPaperEntites());
    },
    clearPaperContents(){
      let clearable_children = ['boussole_bg', 'entites',
                                'entites_proximites_references',
                                'puissanceagir_bg','puissanceagir_besoins',
                                'agissantes',
                                'doleance_bg', 'doleances'];

      clearable_children.forEach(child_name => {
        if (this.paper_main_object.children[child_name]) {
          this.paper_main_object.children[child_name].remove();
        }
      });
    },
    addNewPaperSymbolInstance(name, back, scale){
      let instance = new paper.SymbolItem(this.paper_symbol_definitions[name]); // , {x:0,y:0}
      instance.name = name;
      instance.position = this.pos;
      let s = scale ? this.scale * scale : this.scale;
      instance.scale(s);
      // instance.locked = true;
      this.paper_main_object.addChild(instance);
      if (back) {
        this.paper_main_object.children[name].sendToBack();
      } else {
        this.paper_main_object.children[name].bringToFront();
      }
    },
    getPaddedRoundedSegments(points, scale){
      // console.log(`setPaperContour ${this.concernement.id}`);
      let getSegmentProps = (b,a,c,d) => {
        const ac  = { x: c.x - a.x, y: c.y - a.y } // get ac vecteur
        const lac = Math.sqrt(Math.pow(ac.x, 2) + Math.pow(ac.y, 2)); // get ac longueur ac
        const ab  = { x: b.x - a.x, y: b.y - a.y } // get ab vecteur
        const lab = Math.sqrt(Math.pow(ab.x, 2) + Math.pow(ab.y, 2)); // get ab longeur
        const vab = { x: ab.x/lab, y: ab.y/lab } // get unit vecteur ab
        const an  = { x: vab.x*lac, y: vab.y*lac } // get an vecteur
        const n   = { x: a.x + an.x, y: a.y+an.y } // get n point
        const m   = { x: (c.x + n.x)/2, y: (c.y + n.y)/2 } // get nc midle point
        const ma  = { x:a.x - m.x, y: a.y - m.y } // get ma vecteur
        const lma = Math.sqrt(Math.pow(ma.x, 2)+Math.pow(ma.y, 2)) // get longeur m->a
        const vma = { x: ma.x/lma, y: ma.y/lma } // get ma vecteur unitaire
        // console.log(`vma x:${vma.x}, y:${vma.y}`);
        const pad = 4; // exterior padding
        // the final padded point
        const pa  = [
          a.x+vma.x*pad,
          a.y+vma.y*pad
        ]

        // handles
        const delta = 0.05 * scale;
        // handle IN
        const hli   = Math.abs(lab)*delta;            // handle longeur
        const vnai  = { x: -vma.y, y: vma.x }     // get the ma normal unit vector IN
        const hai   = [ vnai.x*hli, vnai.y*hli ]; // get the handleIn point
        // handle OUT
        const hlo   = Math.abs(lac)*delta;            // handle longeur
        const vnao  = { x: vma.y, y: -vma.x }     // get the ma normal vector Out
        const hao   = [ vnao.x*hlo, vnao.y*hlo ]; // get the handleOut point

        return {
          point: pa,
          handleIn: hai,
          handleOut: hao
        }
      }
      const first_point = getSegmentProps(
        points[points.length-1],
        points[0],
        points[1]
      );
      let segments = [first_point]; 
      for (let j = 1; j < points.length-1; j++) {
          segments.push(getSegmentProps(
            points[j-1],
            points[j],
            points[j+1]
          ))
      }
      const last_point = getSegmentProps(
        points[points.length-2],
        points[points.length-1],
        points[0]
      );
      segments.push(last_point)
      segments.push(first_point)

      return segments;
    },
    setPaperContour(){
      // console.log('setPaperContour, this.sailentEntites', this.sailentEntites);
      // convert sailent entites to x,y points
      let points = [];
      this.sailentEntites.forEach(entite => {
        points.push({
          x: (entite.display.ray) * Math.cos(entite.display.alpha * (Math.PI/180)),
          y: (entite.display.ray) * Math.sin(entite.display.alpha * (Math.PI/180))
        })
      })
      // convert points to rouded and padded segments props
      let segments = this.getPaddedRoundedSegments(points, this.scale)
      // create "real" Paper Segments from previous segments props
      let paper_segments = [];
      segments.forEach(seg => {
        paper_segments.push(new paper.Segment({
          point: [this.pos.x+seg.point[0]*this.scale, this.pos.y+seg.point[1]*this.scale],
          handleIn: seg.handleIn,
          handleout: seg.handleOut
        }))
      });
      // create the paper path with previous segments
      const contrs = new paper.Path({
        name: 'contours',
        segments: paper_segments,
        fillColor: 'rgba(255,255,255,0.4)',
        // selected: true,
        strokeColor: this.mapitem.clone ? '#f00' : '#fff',
        strokeWidth: 1,
        pivot: new paper.Point(this.pos),
        cid: this.cid
      });
      // return the paper path
      return contrs;
    },
    setPaperEntites(){
      let g = new paper.Group({
        pivot: new paper.Point(this.pos),
        name: 'entites'
      });
      // for (let i = 0; i < this.entites.length; i++) {
      for (let i = 0; i < this.concernement.revisions_byid[this.concernement.active_revision].entites.length; i++) {
        let entite = this.concernement.revisions_byid[this.concernement.active_revision].entites[i];
        // item type
        let item_type = entite.entite ? entite.entite.agissante ? 'entite_action' : 'entite' : 'entite_hidden';

        // use paper symbol
        let symbol_name = item_type;
        switch (item_type) {
          case 'entite':
            if(parseInt(this.opened_entite_id) === entite.entite.id){
              symbol_name = 'entite_hover'; 
            } 
            break;
          case 'entite_action':
            if(parseInt(this.opened_entite_id) === entite.entite.id){
              symbol_name = 'entite_action_hover'; 
            } 
            break;
        }

        // console.log(`item_type: ${item_type}, symbol_name: ${symbol_name}`);

        let instance = new paper.SymbolItem(this.paper_symbol_definitions[symbol_name]);
        instance.name = 'entite';
        instance.position = new paper.Point([this.pos.x + entite.display.pos.x * this.scale, this.pos.y + entite.display.pos.y * this.scale]);
        instance.scale(this.scale);
        instance.fillColor = '#000';
        instance.item_id = entite.entite ? entite.entite.id : null;
        instance.item_type = item_type;
        instance.is_symbol_instance = true;
        g.addChild(instance)
      }
      return g;
    },
    setPaperEntitesSuperposees(){
      // console.log('setPaperSuperpositions, superpositions', this.concernement.superpositions);

      let g = new paper.Group({
        pivot: new paper.Point(this.pos),
        name: 'entites_superposes'
      });
      for (let i = 0; i < this.concernement.revisions_byid[this.concernement.active_revision].entites.length; i++) {
        let entite = this.concernement.revisions_byid[this.concernement.active_revision].entites[i];
        if (entite.entite // check if we have an entite object with all the contents
            && this.superposedEntiteID === entite.entite.id) // check if entite id
          {
          // console.log(`entite ${entite.entite.id}`, entite, entite.entite.superposition);
          // use paper symbol
          let instance = new paper.SymbolItem(this.paper_symbol_definitions['entite']);
          instance.name = 'entite';
          instance.position = new paper.Point([this.pos.x + entite.display.pos.x * this.scale, this.pos.y + entite.display.pos.y * this.scale]);
          // instance.scale(this.scale);
          instance.scale(3);
          instance.fillColor = '#000';
          instance.item_id = entite.entite.id;
          instance.item_type = 'entite_superpose';
          instance.is_symbol_instance = true;
          g.addChild(instance)
          break; // there is only one superposed entite by mapitem
        }
      }
      return g;
    },
    setPaperEntitesProximite(){
      let g = new paper.Group({
        pivot: new paper.Point(this.pos),
        name: 'entites_proximites'
      });
      for (let i = 0; i < this.concernement.revisions_byid[this.concernement.active_revision].entites.length; i++) {
        let entite = this.concernement.revisions_byid[this.concernement.active_revision].entites[i];
        if (entite.entite // check if we have an entite object with all the contents
            && entite.entite.proximite.length ) // check if entite id is in the list builded above
          {
          // console.log(`entite ${entite.entite.id}`, entite, entite.entite.superposition);
          // use paper symbol
          let instance = new paper.SymbolItem(this.paper_symbol_definitions['entite']);
          instance.name = 'entite';
          instance.position = new paper.Point([this.pos.x + entite.display.pos.x * this.scale, this.pos.y + entite.display.pos.y * this.scale]);
          // instance.scale(this.scale);
          instance.scale(3);
          instance.fillColor = '#000';
          instance.item_id = entite.entite.id;
          instance.item_type = 'entite_proximite';
          instance.is_symbol_instance = true;
          g.addChild(instance)
        }
      }
      return g;
    },
    setPaperEntitesProximiteReferences(){
      console.log('setPaperEntitesProximiteReferences')
      let g = new paper.Group({
        pivot: new paper.Point(this.pos),
        name: 'entites_proximites_references'
      });
      // loop through all concernement's entites
      // keeping only those who have proximite
      for (let i = 0; i < this.concernement.revisions_byid[this.concernement.active_revision].entites.length; i++) {
        let entite = this.concernement.revisions_byid[this.concernement.active_revision].entites[i];
        if (entite.entite // check if we have an entite object with all the contents
            && entite.entite.proximite.length ) // check if entite id is in the list builded above
          {
          // console.log(`PROXIMITE entite ${entite.entite.id}`, entite, entite.entite.proximite);
          // create the main entite paper point object
          let entite_pos = {
            x: this.pos.x + entite.display.pos.x * this.scale,
            y: this.pos.y + entite.display.pos.y * this.scale
          }
          // console.log(`entite_pos x:${entite_pos.x}, y:${entite_pos.y}`);
          // use paper symbol
          let instance = new paper.SymbolItem(this.paper_symbol_definitions['entite']);
          instance.name = 'entite';
          instance.position = new paper.Point([entite_pos.x, entite_pos.y]);
          // instance.scale(this.scale);
          instance.scale(this.scale);
          instance.fillColor = '#000';
          instance.item_id = entite.entite.id;
          instance.item_type = 'entite_proximite';
          instance.is_symbol_instance = true;
          g.addChild(instance)

          // create the proximite reference of the main entite
          // a paper group wich contains the ref entite point and the ref concernement's contour
          let ref_g = new paper.Group({
            pivot: new paper.Point(this.pos),
            name: 'ref_entite_proximite'
          });

          let allEntiteRefConcernementContourEntites = [];

          let beta = 360 / entite.entite.proximite.length;
          let ray = 5;
          let e=0;
          // define the right scaling down of the big original concernement contours
          let scaledown = 0.05;

          entite.entite.proximite.forEach(entite_ref => {
            // console.log(`${entite_ref.id}, ${entite_ref.title}`, this.allEntitesById[entite_ref.id]);

            // get the concernement parents of the proximite reference
            let ref_cid = this.allEntitesById[entite_ref.id].cid;
            // console.log('PROXIMITE ref concernement jarvis_envelope_convexe', this.concernementsByID[ref_cid].sailentEntites);
            let ref_concernement = this.concernementsByID[ref_cid];
            
            // create the entite ref paper point
            let ref_instance = new paper.SymbolItem(this.paper_symbol_definitions['entite']);
            ref_instance.name = 'entite_ref';

            let entite_ref_pos = {
              x: entite_pos.x + ray * Math.cos((beta*e) * (Math.PI/180)) * this.scale, // met a l'echelle la position du point de l'entite en proximite
              y: entite_pos.y + ray * Math.sin((beta*e) * (Math.PI/180)) * this.scale, // met a l'echelle la position du point de l'entite en proximite
            }
            // console.log(`entite_ref_pos x:${entite_ref_pos.x}, y:${entite_ref_pos.y}`);
          
            ref_instance.position = new paper.Point(entite_ref_pos);
            // ref_instance.scale(this.scale);
            ref_instance.scale(this.scale*0.75); // met a l'échelle la taille du point et la diminue
            ref_instance.fillColor = '#000';
            ref_instance.item_id = entite_ref.id;
            ref_instance.item_type = 'entite_proximite_reference';
            ref_instance.ref_cid = ref_cid;
            ref_instance.parent_eid = entite.entite.id;
            ref_instance.is_symbol_instance = true;
            ref_g.addChild(ref_instance)

            // create the original concernement contours arround the entite_ref
            
            let points = [];
            ref_concernement.sailentEntites.forEach(ent => {
              points.push({
                x: (ent.display.ray) * Math.cos(ent.display.alpha * (Math.PI/180)) * this.scale, // met a l'echelle la position du point par rapport a l'entite e proximite
                y: (ent.display.ray) * Math.sin(ent.display.alpha * (Math.PI/180)) * this.scale // met a l'echelle la position du point par rapport a l'entite e proximite
              })
              
              // - - //
              // concat all the contours points for general contours arround all contours :/ :( (that's a very clear description)
              let p = {
                x: entite_ref_pos.x - entite_pos.x + (ent.display.ray * Math.cos(ent.display.alpha * (Math.PI/180))) * this.scale,
                y: entite_ref_pos.y - entite_pos.y + (ent.display.ray * Math.sin(ent.display.alpha * (Math.PI/180))) * this.scale
              }
              // console.log(`p x:${p.x}, y:${p.y}`);
                
              allEntiteRefConcernementContourEntites.push({
                display:{
                  ray: Math.sqrt(Math.pow(p.x,2)+Math.pow(p.y,2)),
                  alpha: parseFloat(Math.atan2(p.y,p.x) * (180/Math.PI)).toFixed(2)
                }
              })
              // - - //
            })
            
            // convert points to rouded and padded segments props
            let segments = this.getPaddedRoundedSegments(points, scaledown)
            // create "real" Paper Segments from previous segments props
            let paper_segments = [];
            segments.forEach(seg => {
              let paddedSegPoint = {
                x: entite_ref_pos.x+seg.point[0]*scaledown,
                y: entite_ref_pos.y+seg.point[1]*scaledown
              }
              // console.log('paddedSegPoint', paddedSegPoint);
              paper_segments.push(new paper.Segment({
                point: paddedSegPoint,
                handleIn: seg.handleIn,
                handleout: seg.handleOut
              }))

              // - - //
              // concat all the contours points for general contours arround all contours :/ :( (that's a very clear description)
              // let p = {
              //   x: (entite_ref_pos.x - entite_pos.x + (ent.display.ray * Math.cos(ent.display.alpha * (Math.PI/180)))),
              //   y: (entite_ref_pos.y - entite_pos.y + (ent.display.ray * Math.sin(ent.display.alpha * (Math.PI/180))))
              // }
              // console.log(`p x:${p.x}, y:${p.y}`);
                
              // allEntiteRefConcernementContourEntites.push({
              //   display:{
              //     ray: Math.sqrt(Math.pow(paddedSegPoint.x,2)+Math.pow(paddedSegPoint.y,2)),
              //     alpha: parseFloat(Math.atan2(paddedSegPoint.y,paddedSegPoint.x) * (180/Math.PI)).toFixed(2)
              //   }
              // })
              // - - //
            });
            // create the paper path with previous segments
            const contrs = new paper.Path({
              name: 'contours',
              segments: paper_segments,
              fillColor: 'rgba(255,255,255,0.4)',
              // selected: true,
              strokeColor: '#fff',
              strokeWidth: 1,
              pivot: new paper.Point(this.pos),
              cid: ref_concernement.id
            });
            ref_g.addChild(contrs);
            contrs.sendToBack();
            ref_instance.contour_paper_item_id = contrs.id;
            e++;
          }); // end of loop on proximite for each main entite
          

          // general contour arround proximité
          // console.log('allEntiteRefConcernementContourEntites', allEntiteRefConcernementContourEntites);
          let genContoursEntite = this.getJarvisEnvelopeConvexeEntites(allEntiteRefConcernementContourEntites);
          // console.log('genContoursEntite', genContoursEntite);
          let points = [];
          genContoursEntite.forEach(ent => {
            points.push({
              x: ent.display.ray * Math.cos(ent.display.alpha * (Math.PI/180)),
              y: ent.display.ray * Math.sin(ent.display.alpha * (Math.PI/180))
            })
          })
          // console.log('points', points);

          let gen_scaledown = 0.16;
          // scaledown = 1;
          // convert points to rouded and padded segments props
          let segments = this.getPaddedRoundedSegments(points, gen_scaledown)
          // create "real" Paper Segments from previous segments props
          let paper_segments = [];
          segments.forEach(seg => {
            paper_segments.push(new paper.Segment({
              point: [entite_pos.x+seg.point[0]*gen_scaledown, entite_pos.y+seg.point[1]*gen_scaledown],
              // point: [seg.point[0], seg.point[1]],
              handleIn: seg.handleIn,
              handleout: seg.handleOut
            }))
          });
          // create the paper path with previous segments
          const genContrs = new paper.Path({
            name: 'gen_contours',
            segments: paper_segments,
            fillColor: 'rgba(255,255,255,0.4)',
            // selected: true,
            strokeColor: '#fff',
            strokeWidth: 1,
            pivot: new paper.Point(this.pos),
            // cid: this.cid
          });
          ref_g.addChild(genContrs);
          genContrs.sendToBack();

          g.addChild(ref_g);
          ref_g.sendToBack();
        } // end of loop on entite wich have proximite
      }
      return g;
    },
    setPaperAgissantes(){
      console.log('setPaperAgissantes');
      let g = new paper.Group({
        pivot: new paper.Point(this.pos),
        name: 'agissantes'
      });
      
      for (let i = 0; i < this.concernement.revisions_byid[this.concernement.revision_id].entites.length; i++) {
        let entite = this.concernement.revisions_byid[this.concernement.revision_id].entites[i];
        // console.log('agissantes', entite);
        if (entite.entite && entite.entite.agissante) {
          let instance = new paper.SymbolItem(this.paper_symbol_definitions['entite_action']);
          instance.name = 'entite_action';
          instance.position = new paper.Point([this.pos.x + entite.display.pos.x * this.scale, this.pos.y + entite.display.pos.y * this.scale]);
          instance.fillColor = '#000';
          // instance.scale(0.2);
          instance.scale(this.scale);
          instance.item_id = entite.entite.id;
          instance.item_type = 'entite_action';
          instance.is_symbol_instance = true;
          g.addChild(instance)
        }
      }
      return g;
    },
    // setPaperAgissantesIcons(){
    //   let g = new paper.Group({
    //     pivot: new paper.Point(this.pos),
    //     name: 'agissantes_icons'
    //   });
    //   for (let i = 0; i < this.concernement.revisions_byid[this.concernement.revision_id].entites.length; i++) {
    //     let entite = this.concernement.revisions_byid[this.concernement.active_revision].entites[i];
    //     if (entite.entite && entite.entite.agissante) {
    //       let instance = new paper.SymbolItem(this.paper_symbol_definitions['entite_action_icon']);
    //       instance.name = 'entite_action';
    //       instance.position = new paper.Point([this.pos.x + entite.display.pos.x * this.scale, this.pos.y + entite.display.pos.y * this.scale]);
    //       instance.fillColor = '#000';
    //       // instance.scale(0.2);
    //       instance.scale(this.scale);
    //       instance.item_id = entite.entite.id;
    //       instance.item_type = 'entite_action';
    //       instance.is_symbol_instance = true;
    //       g.addChild(instance)
    //     }
    //   }
    //   return g;
    // },
    setPaperPuissanceagirBesoins(){
      let g = new paper.Group({
        pivot: new paper.Point(this.pos),
        name: 'puissanceagir_besoins'
      });

      let res_fields = ['qui','quoi','ou','avec'];
      let arc = (360 / 16);               // unit arc
      let r = (this.ray * this.scale)/5;  // unit ray
      let br = r - r/3;                   // besoin ray

      for (let i = 0; i < this.concernement.besoins.length; i++) {
        let start_a = arc * i;              // angle depart (for reponses)
        let center_a = start_a + arc/2;     // angle central
        let x = Math.cos(center_a*(Math.PI/180)) * br;
        let y = Math.sin(center_a*(Math.PI/180)) * br;

        // use paper symbol
        let besoin = new paper.SymbolItem(this.paper_symbol_definitions['besoin']);
        besoin.position = new paper.Point([this.pos.x + x, this.pos.y + y]);
        besoin.scale(this.scale);
        besoin.item_id = this.concernement.besoins[i].id;
        besoin.item_cid = this.concernement.id;
        besoin.item_type = 'besoin';
        besoin.is_symbol_instance = true;
        console.log(`paper besoin id:${besoin.id}`);
        this.setBesoinPaperId(besoin.id, this.cid, this.concernement.besoins[i].id);
        g.addChild(besoin)

        let res_arc = arc / (1 + this.concernement.besoins[i].reponses.length); // unit arc for responses depending responses number
        for (let j = 0; j < this.concernement.besoins[i].reponses.length; j++) {
          let res_a = start_a + res_arc * (j+1); // angle for response line
          for (let f = 0; f < res_fields.length; f++) { // loop through all 4 fields, keep only the last one filled
            if( this.concernement.besoins[i].reponses[j][res_fields[f]] // if field filled
              && (f === res_fields.length -1 || !this.concernement.besoins[i].reponses[j][res_fields[f+1]]) // and is last field or last field filled
              ){
              let rr = this.ray * this.scale - r*f - r/2; // reponse field ray
              let rx = Math.cos(res_a*(Math.PI/180)) * rr;
              let ry = Math.sin(res_a*(Math.PI/180)) * rr;

              // use paper symbol
              let reponse = new paper.SymbolItem(this.paper_symbol_definitions['reponse']);
              reponse.position = new paper.Point([this.pos.x + rx, this.pos.y + ry]);
              reponse.scale(this.scale);
              reponse.item_id = this.concernement.besoins[i].reponses[j].id;
              reponse.item_bid = this.concernement.besoins[i].id;
              reponse.item_cid = this.concernement.id;
              reponse.item_type = 'reponse';
              reponse.is_symbol_instance = true;
              this.setBesoinPaperId(reponse.id, this.cid, this.concernement.besoins[i].id, this.concernement.besoins[i].reponses[j].id);
              g.addChild(reponse)
            }
          }
        }
      }

      return g;
    },
    setPaperDoleances(){
      let g = new paper.Group({
        pivot: new paper.Point({x:0,y:0}),
        name: `doleances`
      });
      this.concernement.doleances.forEach((d) => {
        g.addChild(this.setPaperDoleanceSteps(d))
      });
      return g;
    },
    setPaperDoleanceSteps(doleance){
      let g = new paper.Group({
        pivot: new paper.Point({x:0,y:0}),
        name: `doleance_${doleance.id}`,
        doleance_id: doleance.id,
        visible: false
      });
      // let doleance = this.concernement.doleances[0];
      let all_fields = [
        [
          'leprobleme',
          'lenquete',
          {
            fieldname: 'groupesinterets',
            fields: [
                    'groupe_interets',
                    'accorder_interets',
                    'formuler',
                    ] 
          }
        ],
        [
          'entite_addresse_doleance',
          'comment_ennonce_doleance',
          'aqui_addresse_doleance',
          {
            fieldname: 'reception_traitement',
            fields: [
                    'entite_adressee',
                    'doleance_formulee',
                    'traite_doleance',
                    'entite_recoit_doleance',
                    ]
          }
        ],
        [
          'entites_decisionnaires',
          'decision_formule',
          {
            fieldname: 'mise_en_oeuvre_decision',
            fields: [
                    'entite_adresse_decision',
                    'formule_decision',
                    'entite_metenoeuvre_decisio',
                    ]
          }
        ],
        [
          'entite_adresse_application',
          'aqui_adresse_decision',
          'comment_formule_decision',
          {
            fieldname: 'receptions_et_applications',
            fields: [
                    'applique_decision',
                    'formule_decision_applic',
                    'entite_recoit_decision',
                    ]
          },
          'probleme_initial_resolu',
          'oui_nouvelle_situation',
          'non_adresse_doleance',
        ]
      ];
      
      var r = this.ray * this.scale * 0.8;  // ray
      var dr = r/2;                         // demi ray
      // var pcr = 2*this.scale;               // petits cercle rayon

      // https://fr.wikipedia.org/wiki/Trigonom%C3%A9trie#/media/Fichier:Unit_circle_angles_color.svg
      // https://fr.wikipedia.org/wiki/Identit%C3%A9_trigonom%C3%A9trique_pythagoricienne#Preuve_utilisant_le_cercle_unit%C3%A9
      // radians = degrees * (pi/180)
      // degrees = radians * (180/pi)
      // Points for 45° axes
      let m = Math.sin(45*(Math.PI/180)) * r;   // x = y for rayon
      let n = Math.sin(45*(Math.PI/180)) * r/2; // x = y for demi rayon

      let o = Math.cos(22.5*(Math.PI/180)) * r; // x @ 22.5° for rayon
      let p = Math.sin(22.5*(Math.PI/180)) * r; // y @ 22.5° for rayon
      let o_d = Math.cos(22.5*(Math.PI/180)) * r/2; // x @ 22.5° for demi rayon
      let p_d = Math.sin(22.5*(Math.PI/180)) * r/2; // y @ 22.5° for demi rayon

      let step_Style = {
          strokeColor: '#fff',
          strokeWidth: 2,
          fillColor: "rgba(255, 255, 255, 0.3)"
      }

      // let fontsize = 4;
      
      //
      // CAMENBERT STATIQUES
      //
      // l'enquete
      //
      // camenbert
      if (doleance.lenquete) {
        let cam = new paper.Path({...step_Style, ...{
            item_type: 'doleance_step',
            item_id: `lenquete`,
            did: doleance.id, 
            field: 'lenquete',
            field_index: null
          }
        });
        cam.add({x: this.pos.x , y: this.pos.y + dr});
        cam.lineTo({x: this.pos.x, y: this.pos.y + r});
        cam.arcTo({x: this.pos.x - p, y: this.pos.y + o}, {x: this.pos.x - m, y: this.pos.y + m});
        cam.lineTo({x: this.pos.x - n, y: this.pos.y + n});
        cam.arcTo({x: this.pos.x - p_d, y: this.pos.y + o_d}, {x: this.pos.x , y: this.pos.y + dr});
        // texte
        // enquete.addChild(new paper.PointText({
          //   point: {
            //     x:this.pos.x + Math.sin(22.5*(Math.PI/180)) * r*0.75,
            //     y:this.pos.y + Math.cos(22.5*(Math.PI/180)) * r*0.75
            //   },
            //   content: doleance['lenquete'],
            //   fontSize: fontsize,
        //   fillColor: '#fff',
        //   justification: 'center',
        //   locked: true
        // }));
        g.addChild(cam);
      }
      
      
      //
      // probleme_initial_resolu
      //
      // camenbert
      if(doleance.probleme_initial_resolu === 0 || doleance.probleme_initial_resolu === 1){
        let rescam = new paper.Path({...step_Style, ...{
            closed: true,
            item_type: 'doleance_step',
            item_id: `probleme_initial_resolu`,
            did: doleance.id,
            field: 'probleme_initial_resolu',
            field_index: null
          }
        });
        rescam.add({x: this.pos.x + n, y: this.pos.y + n});
        rescam.lineTo({x: this.pos.x + m , y: this.pos.y + m});
        rescam.arcTo({x: this.pos.x + p, y: this.pos.y + o}, {x: this.pos.x, y: this.pos.y + r});
        rescam.lineTo({x: this.pos.x, y: this.pos.y + dr});
        rescam.arcTo({x: this.pos.x + p_d, y: this.pos.y + o_d}, {x: this.pos.x + n, y: this.pos.y + n});
        // texte
        // resolution.addChild(new paper.PointText({
          //   point: {
            //     x:this.pos.x - Math.sin(22.5*(Math.PI/180)) * r*0.75,
            //     y:this.pos.y + Math.cos(22.5*(Math.PI/180)) * r*0.75
            //   },
            //   content: doleance['probleme_initial_resolu'] ? doleance['oui_nouvelle_situation'] : doleance['non_adresse_doleance'],
            //   fontSize: fontsize,
            //   fillColor: '#fff',
            //   justification: 'center'
            // }));
        g.addChild(rescam)
      }
      
      
      //
      // MULTIPLE FIELDS
      //
      let multiple_fields = [
        { field_name: 'groupesinterets',            arc: 45, decalage: -45 },
        { field_name: 'reception_traitement',       arc: 90, decalage: -90 },
        { field_name: 'mise_en_oeuvre_decision',    arc: 90, decalage: -180 },
        { field_name: 'receptions_et_applications', arc: 45, decalage: 90 }
      ]

      multiple_fields.forEach((mf, j) => {
        for (let i = 0, l = doleance[mf.field_name].length, a = mf.arc/l; i < l; i++) {
          // let gi = new paper.Group({
          //   item_type: 'doleance_step',
          //   item_id: 'lenquete',
          //   item_field: mf.field_name,
          //   item_index: i
          // });
          //camenbert
          let x1=  this.pos.x + Math.sin((mf.decalage- a*i)*(Math.PI/180)) * dr,
              y1=  this.pos.y + Math.cos((mf.decalage- a*i)*(Math.PI/180)) * dr;
          let x2=  this.pos.x + Math.sin((mf.decalage- a*i)*(Math.PI/180)) * r,
              y2=  this.pos.y + Math.cos((mf.decalage- a*i)*(Math.PI/180)) * r;
          let x3=  this.pos.x + Math.sin((mf.decalage- a*(i+1))*(Math.PI/180)) * r,
              y3=  this.pos.y + Math.cos((mf.decalage- a*(i+1))*(Math.PI/180)) * r;
          let x3t= this.pos.x + Math.sin((mf.decalage- a*(i+0.5))*(Math.PI/180)) * r,
              y3t= this.pos.y + Math.cos((mf.decalage- a*(i+0.5))*(Math.PI/180)) * r;
          let x4=  this.pos.x + Math.sin((mf.decalage- a*(i+1))*(Math.PI/180)) * dr,
              y4=  this.pos.y + Math.cos((mf.decalage- a*(i+1))*(Math.PI/180)) * dr;
          let x4t= this.pos.x + Math.sin((mf.decalage- a*(i+0.5))*(Math.PI/180)) * dr,
              y4t= this.pos.y + Math.cos((mf.decalage- a*(i+0.5))*(Math.PI/180)) * dr;
          let x5=  this.pos.x + Math.sin((mf.decalage- a*(i+0.5))*(Math.PI/180)) * r*0.75,
              y5=  this.pos.y + Math.cos((mf.decalage- a*(i+0.5))*(Math.PI/180)) * r*0.75;
          let p = new paper.Path({...step_Style, ...{
              item_type: 'doleance_step',
              item_id: `${mf.field_name}-${i}`,
              did: doleance.id,
              field: mf.field_name,
              field_index: i
            }
          });
          p.add([x1,y1]);
          p.lineTo([x2,y2]);
          p.arcTo([x3t,y3t], [x3,y3]);
          p.lineTo([x4,y4]);
          p.arcTo([x4t,y4t], [x1,y1]);
          g.addChild(p);
          // gi.addChild(p);
          // // text
          // gi.addChild(new paper.PointText({
          //   point: {x:x5,y:y5},
          //   content: `${j}-${i}`,
          //   fontSize: fontsize,
          //   fillColor: '#fff',
          //   justification: 'center'
          // }))

          // g.addChild(gi)
        }
      });


      // POINTS CARDINAUX
      let cardinal_Style = {
          strokeColor: '#fff',
          strokeWidth: 2,
          fillColor: "rgba(255, 255, 255, 0.9)"
      }
      // leprobleme
      if (doleance.leprobleme) {
        g.addChild(new paper.Path.Circle({
          center: [this.pos.x, this.pos.y + r],
          radius: 3 * this.scale,
          style: cardinal_Style,
          item_type: 'doleance_step',
          item_id: `leprobleme`,
          did: doleance.id,
          field: 'leprobleme'
        }));
      }
      

      // adresse de la doleance
      if (doleance.entite_addresse_doleance || doleance.aqui_addresse_doleance || doleance.comment_ennonce_doleance) {
        g.addChild(new paper.Path.Circle({
          center: [this.pos.x - r, this.pos.y],
          radius: 3 * this.scale,
          style: cardinal_Style,
          item_type: 'doleance_step',
          item_id: `adresse_de_la_doleance`,
          did: doleance.id,
          field: 'adresse_de_la_doleance'
        }));
      }
      
      
      // decision
      if (doleance.entites_decisionnaires || doleance.decision_formule) {
        g.addChild(new paper.Path.Circle({
          center: [this.pos.x, this.pos.y - r],
          radius: 3 * this.scale,
          style: cardinal_Style,
          item_type: 'doleance_step',
          item_id: `decision`,
          did: doleance.id,
          field: 'decision'
        }));
      }

      // adresse_de_la_decision
      if (doleance.entite_adresse_application || doleance.aqui_adresse_decision || doleance.comment_formule_decision) {
        g.addChild(new paper.Path.Circle({
          center: [this.pos.x + r, this.pos.y],
          radius: 3 * this.scale,
          style: cardinal_Style,
          item_type: 'doleance_step',
          item_id: `adresse_de_la_decision`,
          did: doleance.id,
          field: 'adresse_de_la_decision'
        }));
      }
      

      return g;
    },
    // mapitem visibility
    handleMapitemVisibility(state){
      this.is_visible = state;
      this.paper_main_object.visible = this.is_visible;
    },
    // PAPER VISIBILITY
    handlePaperVisibilityOnBeforeOpen(){
      // // agissantes
      // if (this.concernement.has_agissantes && this.map_mode === "action") {
      //   if (!this.is_open) {
      //     this.paper_main_object.children.agissantes_icons.visible = true;
      //   } else {
      //     this.paper_main_object.children.agissantes_icons.visible = false;
      //   }
      // }

      // superposition
      // scale down superposed entites on open
      let entites_superposes = this.paper_main_object.children['entites_superposes'];
      if(entites_superposes){
        for(let paper_item of entites_superposes.children) {
          paper_item.scale(0.25)
        }
      }
      // proximite
      // scale down proximite entites on open
      let entites_proximites = this.paper_main_object.children['entites_proximites'];
      if(entites_proximites){
        for(let paper_item of entites_proximites.children) {
          paper_item.scale(0.25)
        }
      }
    },
    handlePaperVisibilityOnOpened(){},
    handlePaperVisibilityOnBeforeClose(){
      // superposition
      // scale up superposed entites on open
      let entites_superposes = this.paper_main_object.children['entites_superposes'];
      if(entites_superposes){
        for(let paper_item of entites_superposes.children) {
          paper_item.scale(4)
        }
      }
      // proximites
      // scale up proximites entites on open
      let entites_proximites = this.paper_main_object.children['entites_proximites'];
      if(entites_proximites){
        for(let paper_item of entites_proximites.children) {
          paper_item.scale(4)
        }
      }
    },
    handlePaperVisibilityOnClosed(){
      // // agissantes
      // if (this.concernement.has_agissantes && this.map_mode === "action") {
      //   if (!this.is_open) {
      //     this.paper_main_object.children.agissantes_icons.visible = true;
      //   } else {
      //     this.paper_main_object.children.agissantes_icons.visible = false;
      //   }
      // }
    },
    handlePaperVisibilityOnAfterEnginUpdate(){
      // contours focused
      if (!this.isFocused()){
        this.paper_main_object.children['contours'].fillColor = "rgba(255,255,255,0.1)"; // this.mapitem.clone ? "rgba(255,0,0,0.1)" : "rgba(255,255,255,0.1)"; // 
      }else{
        this.paper_main_object.children['contours'].fillColor = "rgba(255,255,255,0.4)"; // this.mapitem.clone ? "rgba(255,0,0,0.4)" : "rgba(255,255,255,0.4)"; // 
        if (this.is_hover) {
          this.paper_main_object.children['contours'].strokeColor = "#01ffe2";
          this.paper_main_object.children['contours'].strokeWidth = 2;


        }else{
          this.paper_main_object.children['contours'].strokeColor = "#fff";
          this.paper_main_object.children['contours'].strokeWidth = 1;
        }
      }

      // contours visibility
      if (!this.is_open 
        || (this.is_open && this.map_mode !== "puissancedagir" && this.map_mode !== "doleancer")) {
        this.paper_main_object.children['contours'].visible = true;
      } else {
        this.paper_main_object.children['contours'].visible = false;
      }

      // proximite
      if (this.concernement.has_proximites) {
        if (this.map_mode === "proximite") {
          this.paper_main_object.children.entites_proximites.visible = true;
        }else{
          this.paper_main_object.children.entites_proximites.visible = false;
        }
      }

      // superposition
      if (this.concernement.has_superpositions) {
        if (this.map_mode === "superposition") {
          this.paper_main_object.children.entites_superposes.visible = true;
          // highlight of entite part of opened superposition
          let group = this.paper_main_object.children['entites_superposes'];
          if(group){
            if (this.$route.query.superposition_id) {
              let superposition_id = this.$route.query.superposition_id;
              if(superposition_id){
                // let ids = superposition_id.match(/(\d+)_(\d+)__(\d+)_(\d+)/i)
                // group.children.forEach((item) => {
                //   if ( (this.cid === parseInt(ids[1]) && item.item_id === parseInt(ids[2])) 
                //     || (this.cid === parseInt(ids[3]) && item.item_id === parseInt(ids[4])) ) {
                //       item.definition = this.paper_symbol_definitions.entite_hover;
                //       if(!item.focused){
                //         item.scale(2)
                //         item.focused = true;
                //       }
                //   } else if(!this.is_hover) {
                //     item.definition = this.paper_symbol_definitions.entite;
                //     if (item.focused) {
                //       item.scale(0.5)
                //       item.focused = false;
                //     }
                //   }
                // })
              }
            }else{
              group.children.forEach((item) => {
                if(!this.is_hover) {
                  item.definition = this.paper_symbol_definitions.entite;
                  if (item.focused) {
                    item.scale(0.5)
                    item.focused = false;
                  }
                }
              })
            }
          }
        } else {
            this.paper_main_object.children.entites_superposes.visible = false;
        }
      }

      // // puissance d'agir     
      // if (this.concernement.has_puissancedagir) {
      //   if (this.map_mode === "puissancedagir") {
      //     if (!this.is_open) {
      //       this.paper_main_object.children.puissanceagir_icon.visible = true; // if not opened and has_puissancedagir draw the puissance d'agir icone
      //     } else {
      //       this.paper_main_object.children.puissanceagir_icon.visible = false;
      //     }
      //   } else {
      //     this.paper_main_object.children.puissanceagir_icon.visible = false;
      //   }
      // }
      
      // agissantes
      // if (this.concernement.has_agissantes) {
      //   if (this.map_mode !== "action") {
      //     this.paper_main_object.children.agissantes_icons.visible = false;
      //   } else if(!this.is_open && !this.is_closing){
      //     this.paper_main_object.children.agissantes_icons.visible = true;
      //   }
      // }

      // doleance     
      if (this.concernement.has_doleance) {
        if (this.map_mode === "doleancer") {
          if (!this.is_open) {
            // this.paper_main_object.children.doleance_icon.visible = true;
          } else {
            // this.paper_main_object.children.doleance_icon.visible = false;
            // display the right (opened) doleance
            let from = null
            let to = this.concernement.opened_doleance.id;
            
            this.concernement.doleances.forEach((d) => {
              // this.paper_main_object.children.doleances.children[`doleance_${d.id}`].visible = d.id === this.concernement.opened_doleance.id;
              if (this.paper_main_object.children.doleances.children[`doleance_${d.id}`].visible) {
                from = d.id
              }
            })
            // console.log(`opened_doleance from:${from}, to:${to}`);
            if (from && from !== to) {
              this.initDoleanceTransition(from, to);
            } else if(!this.doleance_transition) {
              this.concernement.doleances.forEach((d) => {
                this.paper_main_object.children.doleances.children[`doleance_${d.id}`].visible = d.id === this.concernement.opened_doleance.id;
            })
            }
          }
        } else {
          // this.paper_main_object.children.doleance_icon.visible = false;
          
        }
      }
      
      // focus on opened entite
      if (this.is_open && (this.map_mode === 'terraindevie' || this.map_mode === 'action')) {
        let group = this.map_mode === 'terraindevie' ? this.paper_main_object.children['entites'] : this.paper_main_object.children['agissantes'];
        if(group){
          group.children.forEach((item) => {
            if (this.opened_entite_id && item.item_id === parseInt(this.opened_entite_id) && item.item_type !== 'entite_hidden') {
              item.definition = item.item_type === 'entite' ? this.paper_symbol_definitions.entite_hover : this.paper_symbol_definitions.entite_action_hover;
            } else {
              if ((!this.hover_elmt || item.item_id !== this.hover_elmt.id) && item.item_type !== 'entite_hidden') {
                item.definition = item.item_type === 'entite' ? this.paper_symbol_definitions.entite : this.paper_symbol_definitions.entite_action;
              }
            }
          })
        }
      }
      

    },
    initDoleanceTransition(from, to){
      if (!this.doleance_transition) {
        this.doleance_transition = true
        // console.log(`initDoleanceTransition from:${from}, to:${to}`);
        let from_pg = this.paper_main_object.children.doleances.children[`doleance_${from}`]
        from_pg.visible = true;
        from_pg.opacity = 1;
        let to_pg = this.paper_main_object.children.doleances.children[`doleance_${to}`]
        to_pg.visible = true;
        to_pg.opacity = 0;
        let bg = this.paper_main_object.children.doleance_bg;
        
        // // fade out the current visible cercle politique then fade in the new one
        new Tween.Tween({o: 1})
          .to({
            o: 0
          }, 600)
          .onUpdate((obj) => {
            console.log(`tween update. o=${obj.o}`);
            from_pg.opacity = bg.opacity = obj.o;
          })
          .onComplete((obj) => {
            new Tween.Tween({o: 0})
              .to({
                o: 1
              }, 900)
              .onUpdate((objb) => {
                to_pg.opacity = bg.opacity = objb.o;
              })
              .onComplete((obj) => {
                from_pg.visible = false;
                from_pg.opacity = 1;
                to_pg.opacity = 1;
                this.doleance_transition = false
              })
              .delay(500)
              .easing(Tween.Easing.Quadratic.InOut)
              .start();
          })
          .easing(Tween.Easing.Quadratic.Out)
          .start();
        
        
      }
    },
    onIsHover(){
      // handle layer z-index
      if (this.is_hover) {
        this.paper_main_object.bringToFront();
      } else {
        // mapitem can be hover while an other mapitem is open if it is superposed (superposition) to the currently opened mapitem
        // in this case don't send it back
        if (!this.opened_concernement) { 
          this.paper_main_object.sendToBack();
        }
      }
      // hightlight superposed entites on focus
      let entites_superposes = this.paper_main_object.children['entites_superposes'];
      // console.log('entites_superposes.children', entites_superposes.children);
      if(entites_superposes){
        for(let paper_item of entites_superposes.children) {
          if (this.is_hover) {
            paper_item.definition = this.paper_symbol_definitions.entite_hover;
            // paper_item.scale(2)
          } else {
            paper_item.definition = this.paper_symbol_definitions.entite;
            // paper_item.scale(0.5)
          }
        }
      }

    },
    /*
    * PAPER EVENTS
    */
    initPaperEvents(){

      this.paper_main_object.onMouseLeave = function(event){
        if (!this.opened_concernement && this.isFocused()) { // only if no concernement is opened
          this.resetHoverElmt();
          document.body.style.cursor = "auto";
        }
      }.bind(this);

      this.paper_main_object.onMouseMove = function(event){
        console.log(`onmousemove ${this.id}`, this.cartouch_is_opened);

        // prevent hover map item mouse event if cartouch is opened
        if (this.cartouch_is_opened && this.map_mode !== "superposition") return;
        
        if (!this.is_open) {
          if (!this.opened_concernement) { // only if no concernement is opened and is this focused
            if(this.isFocused()){
              if (this.map_mode === 'superposition') {
                // get the superposed entite
                this.setHoverElmt({
                  type: 'superposition',
                  id: this.id,
                  cid: this.cid,
                  eid: this.superposedEntiteID
                });
              } else{
                this.setHoverElmt({
                  type: 'concernement',
                  id: this.id,
                  cid: this.cid
                });
              }
              document.body.style.cursor = "pointer";
            }

          } else if (this.map_mode === 'superposition'){
            // if this mapitem is superposed to the opened mapitem
            if(this.opened_concernement.all_superposed_concernements_id.indexOf(this.cid) >= 0){
              this.setHoverElmt({
                type: 'superposition',
                id: this.id,
                cid: this.cid,
                eid: this.superposedEntiteID
              });
              document.body.style.cursor = "pointer";
            }
          }

        } else {
          
          // reset all proximity contrs stroke to white
          if (this.paper_main_object.children['entites_proximites_references']) {
            this.paper_main_object.children['entites_proximites_references'].getItems({name:'contours'}).forEach((contr) => {
              contr.strokeColor = '#fff';
            })
          }

          // lets define some options regarding the map_mode
          let paper_group_tohit;
          switch (this.map_mode) {
            case "terraindevie":
              paper_group_tohit = this.paper_main_object.children['entites'];
              break;
            case "proximite":
              paper_group_tohit = this.paper_main_object.children['entites_proximites_references'];
              break;
            case "superposition":
              paper_group_tohit = this.paper_main_object.children['entites_superposes'];
              break;
            case "action":
              paper_group_tohit = this.paper_main_object.children['agissantes'];
              break;
            case "puissancedagir":
              paper_group_tohit = this.paper_main_object.children['puissanceagir_besoins'];
              break;
            case "doleancer":
              if(this.paper_main_object.children['doleances']){
                paper_group_tohit = this.paper_main_object.children['doleances'].children[`doleance_${this.concernement.opened_doleance.id}`];
              }
              break;
          }

          let result = paper_group_tohit ? paper_group_tohit.hitTest(event.point) : null;
          // console.log('move result', result);
          if (result && result.item.item_id) {
            // console.log('move has result', result);
            let new_hover_elmt = {
              paper_id: result.item.id,
              type: result.item.item_type,
              id: result.item.item_id
            }; 
            switch (result.item.item_type) {
              case "entite_proximite_reference":
                new_hover_elmt.ref_cid = result.item.ref_cid;
                new_hover_elmt.parent_eid = result.item.parent_eid;

                // set the mouse overed contrs to blue
                // contour_paper_item_id is recorded when creating the full paper objects in setPaperEntitesProximiteReferences()
                let contrs_paper_id = result.item.contour_paper_item_id; 
                let contrs_paper_obj = result.item.parent.getItem({id: contrs_paper_id});
                contrs_paper_obj.strokeColor = "#01ffe2";
                break;
              case "besoin":
                new_hover_elmt.no_popup = true;
                break;
              case "reponse":
                new_hover_elmt.bid = result.item.item_bid;
                new_hover_elmt.cid = result.item.item_cid;
                new_hover_elmt.no_popup = true;
                break;
            }
            if (!this.hover_elmt || new_hover_elmt.paper_id !== this.hover_elmt.paper_id) {
              // console.log(`before setHoverElmt ${this.id}`);
              this.setHoverElmt(new_hover_elmt);
            }
            document.body.style.cursor = "pointer";
          } else {
            // console.log('move no result');
            this.resetHoverElmt();
            document.body.style.cursor = "auto";
          }
        }
      }.bind(this);
      
      this.paper_main_object.onClick = async function(event){
        console.log('paper concernement onClick');
        
        // prevent hover map item mouse event if cartouch is opened
        if (this.cartouch_is_opened) return;
        
        if (!this.is_open) { // si ce concernement n'est pas ouvet
          console.log('mapitem is NOT opened');
          if (!this.opened_concernement) { // si aucun concernement n'est ouvert
            console.log(`Open me ${this.id}`);
            // push route (keep the hash for map_mode)
            // wait for routing to be finished before opening the mapItem
            let params = {cid: parseInt(this.cid)}
            
            let query = {mapitemid: this.id};
            if (this.map_mode === "superposition") {
              params.eid = this.superposedEntiteID
              query.superposition_cluster_index = this.mapitem.superposition_cluster_index;
            }
            await this.$router.push({
              name: 'concernement',
              params: params,
              query: query,
              hash: `#${this.map_mode}`
            });
            // open/close all concernements
            this.openCloseConcernements(this.cid, this.id)
            // reset the mousehover
            this.resetHoverElmt();
          }  else if (this.map_mode === 'superposition'){ // si un concernement est ouvert et qu'on est en mode superposition
            // if this mapitem is superposed to the opened mapitem
            if(this.opened_concernement.all_superposed_concernements_id.indexOf(this.cid) >= 0){
              // push route (keep the hash for map_mode)
              // wait for routing to be finished before opening the mapItem
              console.log('click on superposed this.mapitem.superposition_cluster_index', this.mapitem.superposition_cluster_index);
              await this.$router.push({
                name: 'concernement',
                params: {cid: this.opened_concernement.cid, eid: this.$route.params.eid},
                query: {
                  mapitemid: this.opened_concernement.opened_mapitem_id,
                  superposition_cluster_index: this.mapitem.superposition_cluster_index,
                  superposed_cid: this.cid
                },
                hash: `#${this.map_mode}`
              });
            }
          }
        } else { // si ce concernement est ouvert
          console.log('mapitem is opened');

          // do not click if we were dragging
          if (this.is_dragging) {
            this.is_dragging = false;
            return;
          }

          // lets define some options regarding the map_mode
          let group_to_hit = null;
          switch (this.map_mode) {
            case "terraindevie":
              group_to_hit = this.paper_main_object.children['entites'];
              break;
            case "proximite":
              group_to_hit = this.paper_main_object.children['entites_proximites_references'];
              break;
            case "superposition":
              group_to_hit = this.paper_main_object.children['entites_superposes'];
              break;
            case "action":
              group_to_hit = this.paper_main_object.children['agissantes'];
              break;
            case "doleancer":
              group_to_hit = this.paper_main_object.children['doleances'].children[`doleance_${this.concernement.opened_doleance.id}`];
              break;
          }
          if (group_to_hit) {
            let result = group_to_hit.hitTest(event.point);
            console.log('click result', result);
            if (result) {
              switch (this.map_mode) {
                case "terraindevie":
                case "superposition":
                case "action":
                  // we have clicked on an entite
                  this.$router.push({
                    name: 'concernement',
                    params: {cid: this.cid, eid: result.item.item_id},
                    query: {
                      mapitemid: this.id,
                      // superposition_id: this.mapitem.superposition_ids[0]
                    },
                    hash: `#${this.map_mode}`,
                  });    
                  break;
                case "proximite":
                  switch (result.item.item_type) {
                    case "entite_proximite":
                      this.$router.push({
                        name: 'concernement',
                        params: {cid: this.cid, eid: result.item.item_id},
                        hash: `#${this.map_mode}`,
                      });
                      break;
                    case "entite_proximite_reference":
                      this.$router.push({
                        name: 'concernement',
                        params: {cid: this.cid, eid: result.item.parent_eid},
                        query: {
                          proximite_cid: result.item.ref_cid,
                          proximite_eid: result.item.item_id
                        },
                        hash: `#${this.map_mode}`,
                      });
                      break;
                  }
                  
                case "doleancer":
                  this.setOpenedDoleanceField(this.cid, result.item.did, result.item.field, result.item.field_index);
                  break;
              }
              
            } else {
              // otherwise we close the entite and come back to the concernement
              this.$router.push({
                name: 'concernement',
                hash: `#${this.map_mode}`,
                params: {id: this.cid, mapitemid: this.id}
              });

              // reset the mousehover
              this.resetHoverElmt();
            }
          }
        }
      }.bind(this);

      /*
      * works i correlation with updateDetailsZoomScale()
      */
      this.paper_main_object.onMouseDrag = async function(event){
        if (this.is_open && this.map_mode === 'terraindevie') {
          event.stopPropagation();
          event.preventDefault();
          
          console.log('paper concernement onMouseDrag', event.delta);
          if (this.detailsZoomValue > 1) {
            let x = this.pos.x + event.delta.x;
            let y = this.pos.y + event.delta.y;

            this.pos = this.paper_main_object.position = this.constraint.pointA = {x: x, y: y};
            Matter.Body.setPosition(this.body, this.pos);
            
            this.is_dragging = true;
          }
          event.stop();
          return false;
        }
      }.bind(this);
    },
    resetHoverElmt(){
      // console.log('resetHoverElmt');
      setTimeout(()=>{
        this.setHoverElmt(null);
      }, 100);
    },
    // OPEN / CLOSE (with tween)
    getOpeningAvailableScale(){
      // calcul opened size regarding window size and surounding contents
      let header = document.querySelector('header#header');
      let header_height = header.clientHeight;
      let map_nav = document.querySelector('nav#map-nav');
      let map_nav_height = map_nav.clientHeight;
      let s_h = (this.canvas.height - header_height - map_nav_height) / (this.ray*2*1.15);
      let s_w = (this.canvas.width - this.cartouch_width) / (this.ray*2*1.7);
      return Math.min(s_h, s_w)
    },
    openClose(open) { // async 
      // await nextTick(); // not working
      console.log(`ConcernementsMapItem ${this.id} openClose: ${open}`);
      if (this.tween) {
        this.tween.stop();
      }
      if (open) {
        this.is_opening = true;
        // create the paper objects to display (like entite, besoin, etc)
        this.setPaperContents();
        this.handlePaperVisibilityOnBeforeOpen();

        let s = this.getOpeningAvailableScale();
        
        // create once the opening tweening
        this.tween = new Tween.Tween({s: this.scale, x: this.pos.x, y: this.pos.y, o: 0})
          .to({
            s: s,
            x: (this.canvas.width - this.cartouch_width) / 2,
            y: this.canvas.height / 2,
            o: 0.8
          }, 800)
          .onUpdate((obj) => {
            // https://github.com/liabru/matter-js/issues/986#issuecomment-812488873
            // revert to the original size (by reverting the previous scale)
            // Matter.Body.scale(this.body, 1 / this.scale, 1 / this.scale)
            this.paper_main_object.scale(1 / this.scale);
            // then scale again to new scale
            // Matter.Body.scale(this.body, obj.s, obj.s)
            this.paper_main_object.scale(obj.s);

            // update superposition constraints points positions
            this.resetSuperpositionsConstraintsScaling(obj.s);

            // record new scale
            this.prev_scale = this.scale;
            this.scale = this.mapitem.scale = obj.s;
            this.details_zoom_scale = this.scale * this.detailsZoomValue;
            this.opacity = obj.o;
            // console.log('tween update obj.s', obj.s);
            this.pos = {x:obj.x, y:obj.y};
            Matter.Body.setPosition(this.body, this.pos);
          })
          .onComplete((obj) => {
            // console.log('tween complete obj.s', obj.s);
            // record tween one last time
            this.prev_scale = this.scale = obj.s;
            this.details_zoom_scale = this.scale * this.detailsZoomValue;
            this.mapitem.scale = this.scale;
            this.opacity = obj.o;
            this.pos = {x:obj.x, y:obj.y};
            Matter.Body.setPosition(this.body, this.pos);
            
            this.handlePaperVisibilityOnOpened();

            // fix the concernement position with a constraint
            this.constraint = Matter.Constraint.create({
              pointA: this.pos,
              bodyB: this.body,
              stiffness: 1,
              damping: 0,
              length: 0
            });
            Matter.Composite.add(this.matterEngine.world, [this.body, this.constraint]);
            
            // paper bring to front
            // this.paper_main_object.bringToFront();
            this.paper_main_object.sendToBack();

            this.is_opening = false;
          });
          // recreate the matter engine event to get it a the end of the events stack
          Matter.Events.off(this.matterEngine, "afterUpdate", this.onAfterEngineUpdate);
          Matter.Events.on(this.matterEngine, "afterUpdate", this.onAfterEngineUpdate);
      } else {
        // CLOSING
        this.is_closing = true;

        if(this.constraint){
          Matter.Composite.remove(this.matterEngine.world, this.constraint);
        }

        // this.concernement.active_revision = this.concernement.revision_id;
        this.setActiveRevision(this.cid, this.concernement.revision_id);//this.concernement.revisions[this.concernement.revisions.length -1].revision_id);
        this.resetPaperActiveRevision();

        this.handlePaperVisibilityOnBeforeClose();

        this.setOpenedEntiteId(null);

        // this.setOpenedRecit(null);

        // create once the closing tween
        this.tween = new Tween.Tween({s: this.scale, o: 1})
          .to({s: 1, o: 0}, 500)
          .onUpdate((obj) => {
            // https://github.com/liabru/matter-js/issues/986#issuecomment-812488873
            // revert to the original size (by reverting the previous scale)
            // INFO we dont need to sacle the matter body AND it may fix the montgolfière bug
            // TODO find another way to increase the mass
            // Matter.Body.scale(this.body, 1 / this.scale, 1 / this.scale)
            this.paper_main_object.scale(1 / this.scale);
            // then scale again to new scale
            // Matter.Body.scale(this.body, obj.s, obj.s)
            this.paper_main_object.scale(obj.s);
            
            // update superposition constraints points positions
            if (this.concernement.superposition_constraints_id 
              && this.concernement.superposition_constraints_id[this.id] 
              && this.concernement.superposition_constraints_id[this.id].length)
            {
              // get all the constraints of the world
              let all_constrains = Matter.Composite.allConstraints(this.matterEngine.world);
              // loop through all constraint ids recorded in the concernement
              // get only the constraint of this concernement mapitem
              for(let constraint_id of this.concernement.superposition_constraints_id[this.id]) {
                // get the right constraint object from matter
                let constraint;
                for (const c of all_constrains) {
                 if (c.id === constraint_id) {
                  constraint = c;
                  break;
                 } 
                }  
                if (constraint) {
                  // tween the constraint point
                  let ab = this.cid === constraint.concernementA.id ? 'A' : 'B';
                  let point = constraint[`point${ab}`];
                  // revert to the original point
                  let oripoint = Matter.Vector.create(point.x / this.scale, point.y / this.scale)
                  // newpoint from original point
                  let newpoint = Matter.Vector.create(oripoint.x * obj.s, oripoint.y * obj.s)
                  constraint[`point${ab}`] = newpoint;
                }
              }
            }

            // record new scale
            this.prev_scale = this.scale;
            this.scale = obj.s;
            this.mapitem.scale = this.scale;
            this.details_zoom_scale = this.scale * this.detailsZoomValue;
            this.opacity = obj.o;
          })
          .onComplete((obj) => {
            this.prev_scale = this.scale = 1;
            this.mapitem.scale = this.scale;
            this.details_zoom_scale = this.scale * this.detailsZoomValue;
            this.handlePaperVisibilityOnClosed();
            this.clearPaperContents();
            // reset the zoom value
            this.setDetailsZoomValue(1);
            this.is_closing = false;
          });
      }
      this.tween.easing(Tween.Easing.Quadratic.InOut).start();
    },
    resetSuperpositionsConstraintsScaling(s){
      if (this.concernement.superposition_constraints_id 
        && this.concernement.superposition_constraints_id[this.id] 
        && this.concernement.superposition_constraints_id[this.id].length)
      {
        // get all the constraints of the world
        let all_constrains = Matter.Composite.allConstraints(this.matterEngine.world);
        // loop through all constraint ids recorded in the concernement
        // get only the constraint of this concernement mapitem
        for(let constraint_id of this.concernement.superposition_constraints_id[this.id]) {
          // get the right constraint object from matter
          let constraint;
          for (const c of all_constrains) {
            if (c.id === constraint_id) {
            constraint = c;
            break;
            } 
          }  
          if (constraint) {
            // tween the constraint point
            let ab = this.cid === constraint.concernementA.id ? 'A' : 'B';
            let point = constraint[`point${ab}`];
            // revert to the original point
            let oripoint = Matter.Vector.create(point.x / this.scale, point.y / this.scale)
            // newpoint from original point
            let newpoint = Matter.Vector.create(oripoint.x * s, oripoint.y * s)
            constraint[`point${ab}`] = newpoint;
          }
        }
      }
    },
    async resetMapItemPosition(){
      await nextTick();
      console.log('resetMapItemPosition');
      this.pos = this.paper_main_object.position = {
        x: (this.canvas.width - this.cartouch_width) / 2,
        y: this.canvas.height / 2
      };
      this.constraint.pointA = this.pos;
      console.log('resetMapItemPosition', this.cartouch_width, this.pos);
      Matter.Body.setPosition(this.body, this.pos);
    },
    async resetMapItemScale(){
      await nextTick();
      console.log('resetMapItemScale');
      let s = this.getOpeningAvailableScale();
      // revert to the original scale (by reverting the previous scale)
      this.paper_main_object.scale(1 / this.scale);
      // then scale again to new scale
      this.paper_main_object.scale(s);
      // TODO handle superposition scaling
      this.resetSuperpositionsConstraintsScaling(s);
      // record the new scale
      this.prev_scale = this.scale = s;
      this.details_zoom_scale = this.scale * this.detailsZoomValue;
    },
    // ENGINE UPDATE
    onBeforeEngineUpdate (event) {

      // update the opening/closing tweening
      // if (this.tween) {
      //   this.tween.update();
      // }
      Tween.update();

      if (this.opened_concernement) {
        // console.log("try to push aside", this.opened_concernement.opened_mapitem_id, this.id, this.opened_concernement.superposed_mapitem_id_by_mapitem_id[this.opened_concernement.opened_mapitem_id]);
        // console.log(this.id, this.opened_concernement.superposed_mapitem_id_by_mapitem_id[this.opened_concernement.opened_mapitem_id].indexOf(this.id));
        if (this.opened_concernement.opened_mapitem_id !== this.id) {
          // push all aside mapitems not superposed the the opened mapitem
          let pushaside = true;

          // BUT NOT push aside mapitems superposed to this item
          // opened_mapitem <-- superposed_mapitems
          if (this.map_mode === 'superposition') {
            if (this.opened_concernement.superposed_mapitem_id_by_mapitem_id
                && this.opened_concernement.superposed_mapitem_id_by_mapitem_id[this.opened_concernement.opened_mapitem_id])
              { // if opened mapitem has superposed mapitems
              let superposed_ids = this.opened_concernement.superposed_mapitem_id_by_mapitem_id[this.opened_concernement.opened_mapitem_id]
              if (superposed_ids.indexOf(this.id) >= 0) { //this mapitem is directly superposed to the opened mapitem
                pushaside = false;
              }
            }
          }

          if (pushaside) {
            this.pushAside()
          }
        }
      } else if (!this.is_hover){
        if (this.map_mode === 'proximite' 
          || this.map_mode === 'superposition'
          || this.map_mode === 'puissancedagir'
          || this.map_mode === 'action'
          || this.map_mode === 'doleancer'){ // apply focus forces : move unfocused on the sides and focused on the center
          this.applyFocusForces(); //
        } else {
          this.applyNormalForces(); // if terrain de vie apply force to gently reject items from the sides and so create a continuasly mvmt
        }
      } else {
        // stop movement if is mouse hover
        Matter.Body.setVelocity(this.body, {x:0, y:0})
      }
      
      // reset all matter rotation forces otherwise items will spin when colide 
      Matter.Body.setAngle(this.body, 0);
      Matter.Body.setAngularSpeed(this.body, 0);
    },
    applyFocusForces(){
      if(!this.isFocused()) {
        this.pushAside()
      }else{
        this.bringToCenter()
      }
    },
    isFocused(){
      return this.map_mode === 'terraindevie'
          || (this.map_mode === 'proximite' && this.concernement.has_proximites)
          || (this.map_mode === 'superposition' && this.concernement.has_superpositions)
          || (this.map_mode === 'action' && this.concernement.has_agissantes)
          || (this.map_mode === 'puissancedagir' && this.concernement.has_puissancedagir)
          || (this.map_mode === 'doleancer' && this.concernement.has_doleance);
    },
    pushAside(){
      // console.log('pushAside');
      // apply a force in direction of one side or an other depending of the start position
      // the force is exponentialy proportional to the distance from the side
      // INFO logarithmic force : https://stackoverflow.com/questions/846221/logarithmic-slider/846249#846249 
      // TODO cartouch width should change regarding actual cartouch is opened or not (and how many cartouch are opened) 
      let pseudo_center_x = this.opened_concernement 
        ? (this.canvas.width - this.cartouch_width) / 2
        : this.canvas.width / 2;
      // get the direction to the closest side
      let dir = this.map_mode === 'superposition' 
          && this.concernement.superposition_constraints_id
          && this.concernement.superposition_constraints_id[this.id]
          && this.concernement.superposition_constraints_id[this.id].length // go to the right if has superposition constraint applied
        ? 1 // to the right
        : this.pos.x > pseudo_center_x // else
          ? 1   // to the right 
          : -1; // to the left
      
      if (this.map_mode === 'doleancer') {
        dir = 1;
      }
      // max and min item position
      let minp = 0;
      let maxp = dir < 0 
        ? pseudo_center_x 
        : this.canvas.width - pseudo_center_x;
      // max and min force
      let minf = 0;
      let maxf = 6;
      // scale factor
      let scale = (maxf-minf) / (maxp-minp);
      // get the inversed distance
      let dist = dir < 0 
        ? this.pos.x 
        : this.canvas.width - this.pos.x; // get the distance from the side
      // // calculate the force
      // let x_force = Math.pow(dist/700,100) * dir;
      // calculate the logarithmic force
      let x_force = Math.exp(minf + scale*(dist-minp)) * dir;

      let ori_pos = {x:pseudo_center_x, y:this.body.position.y};

      Matter.Body.applyForce(
        this.body, 
        ori_pos, 
        {
          x: x_force,
          y: 0
        }
      );

      this.body.frictionAir = 0.01;
    },
    bringToCenter(){
      // bring to the centre
      // apply a force in direction of one side or an other depending of the start position
      // the force is exponentialy inversed proportional to the distance from the side
      // INFO logarithmic force : https://stackoverflow.com/questions/846221/logarithmic-slider/846249#846249 
      // TODO cartouch width should change regarding actual cartouch is opened or not (and how many cartouch are opened) 
      let pseudo_center_x = this.opened_concernement 
        ? (this.canvas.width - this.cartouch_width) / 2
        : this.canvas.width / 2;
      let dir = this.pos.x > pseudo_center_x 
        ? -1 // to left 
        : 1; // to right
      // get the inversed distance from the side
      let dist = dir < 0 
        ? this.pos.x - pseudo_center_x 
        : pseudo_center_x - this.pos.x; 

      if (dist > this.canvas.width/6) { // apply decreasing forces and increasing friction air only out of center to let the items float in the center zone
        // max & min item position
        let minp = 0;
        let maxp = dir < 0
          ? this.canvas.width - pseudo_center_x
          : pseudo_center_x;
        
        // X force decreasing near the center
        // max and min force
        let xminf = 0;
        let xmaxf = 1;// + Math.random()*4;
        // scale factor
        let xscale = (xmaxf-xminf) / (maxp-minp);
        let x_force = Math.exp(xminf + xscale*(dist-minp)) * dir;
        let ori_pos = dir < 0 
          ? {x:this.canvas.width, y:this.body.position.y} 
          : {x:0, y:this.body.position.y};
        Matter.Body.applyForce(
          this.body, 
          ori_pos, 
          {
            x: x_force,
            y: 0
          }
        );
        
        // Friction air fa (increasing near the center)
        let fa_minf = 0;
        let fa_maxf = 20;
        // scale factor
        let fa_scale = (fa_maxf-fa_minf) / (maxp-minp);
        let fa = Math.exp(fa_minf + fa_scale*(dist-minp)*-1);
        this.body.frictionAir = fa;
        
        // console.log(`bringToCenter dist:${dist}, x_force:${x_force}, friction air:${fa}`);        
      } else { // in the ceneter zone let items floats freely
        let fa = 0.01;
        let velocity = Matter.Body.getVelocity(this.body);
        if (velocity.x) {
          let velocityx = Math.abs(velocity.x);
          // map a range of numbers to another range of numbers
          // INFO https://stackoverflow.com/a/46462321
          let velocity_range = [50, 1000];
          let fa_range = [0,3];
          fa = fa_range[0] + (velocityx - velocity_range[0]) * (fa_range[1] - fa_range[0]) / (velocity_range[1] - velocity_range[0]);
          // console.log(`bringToCenter velocityx:${velocityx}, fa:${fa}, velocity`, velocity);
        } else {
          console.warn(`bringToCenter no velocity`, velocity);
        }

        this.body.frictionAir = fa;
      }

    },
    applyShuffleForces() {
      // console.log('applyShuffleForces');
      // var dist, dir, x_velocity;
      // dir = this.pos.x > this.canvas.width/2 ? -1 : 1; // get the direction to the centre
      // dist = (dir < 0 ? this.pos.x - this.canvas.width/2 : this.canvas.width/2 - this.pos.x); // get the distance from the side
      // x_velocity = Math.pow(dist/650,10) * dir;
      // TODO cartouch width should change regarding actual cartouch is opened or not (and how many cartouch are opened) 
      let pseudo_center_x = this.opened_concernement 
        ? (this.canvas.width - this.cartouch_width) / 2
        : this.canvas.width / 2;
      // get the direction to the centre
      let dir = this.pos.x > pseudo_center_x 
        ? -1 // to left 
        : 1; // to right
      // get the inversed distance from the side
      let dist = dir < 0 
        ? this.pos.x - pseudo_center_x 
        : pseudo_center_x - this.pos.x;

      dist = dist > 1000 ? 1000 : dist;
      
      if (dist > this.canvas.width/6) { // apply decreasing forces and increasing friction air only out of center to let the items float in the center zone
        // max & min item position
        let minp = 0;
        let maxp = dir < 0
          ? this.canvas.width - pseudo_center_x
          : pseudo_center_x;

        // X max and min force
        let xminf = 0;
        // let xmaxf = 4 + Math.random()*3;
        let xmaxf = 2 + Math.random()*2;
        // scale factor
        let xscale = (xmaxf-xminf) / (maxp-minp);
        
        let x_velocity = Math.exp(xminf + xscale*(dist-minp))*dir;

        // let y_velocity = 30 + Math.random() * 50;
        // y_velocity = Math.random() > 0.5 ? -y_velocity : y_velocity;
        // X max and min force
        let yminf = 1;
        // let ymaxf = 2 + Math.random()*4;
        let ymaxf = 4 + Math.random()*2;
        // scale factor
        let yscale = (ymaxf-yminf) / (maxp-minp);
        
        let y_velocity = Math.exp(yminf + yscale*(dist-minp)) * (Math.random() > 0.5 ? 1 : -1);       // let ori_pos = dir < 0 

        //   ? {x:this.canvas.width, y:this.body.position.y} 
        //   : {x:0, y:this.body.position.y};
        // let x_force = Math.pow(dist/800,10) * dir;
        // console.log(`applyShuffleForces dir:${dir}, maxp:${maxp}, dist:${dist}, x_velocity:${x_velocity}, y_velocity:${y_velocity}`);
        Matter.Body.setVelocity(this.body, {x: x_velocity, y: y_velocity});
      } else { // if in center zone real shuffle velocity
        // x_velocity = -50 + Math.random()*100;
        // y_velocity = -50 + Math.random()*100;
        // this.setInitBodyVelocity();
      }
      
      this.body.frictionAir = 0.01;
    },
    applyNormalForces () {
      // bring gently to the centre
      // apply a force in direction of one side or an other depending of the start position
      // the force is exponentialy inversed proportional to the distance from the side
      // INFO logarithmic force : https://stackoverflow.com/questions/846221/logarithmic-slider/846249#846249 
      // cartouch width should change regarding actual cartouch is opened or not (and how many cartouch are opened) 
      
      // TODO force is proportional to window size

      // X
      let pseudo_center_x = this.opened_concernement 
        ? (this.canvas.width - this.cartouch_width) / 2
        : this.canvas.width / 2;
      let xdir = this.pos.x > pseudo_center_x 
        ? -1 // to left 
        : 1; // to right
      // get the inversed distance from the side
      let xdist = xdir < 0 
        ? this.pos.x - pseudo_center_x 
        : pseudo_center_x - this.pos.x; 

      // Y
      let pseudo_center_y = this.canvas.height / 2;
      let ydir = this.pos.y > pseudo_center_y 
        ? -1 // to top
        : 1; // to bottom
      // get the inversed distance from the side
      let ydist = ydir < 0 
        ? this.pos.y - pseudo_center_y 
        : pseudo_center_y - this.pos.y; 

      let k = 4;
      if (xdist > this.canvas.width/k || ydist > this.canvas.height/k) { // apply decreasing forces and increasing friction air only out of center to let the items float in the center zone
        // max and min force
        let minf = 0;
        let maxf = 0.1;

        // X
        // max & min item position
        let xminp = 0;
        let xmaxp = xdir < 0
          ? this.canvas.width - pseudo_center_x
          : pseudo_center_x;
        // X force decrsinsing near the center
        // scale factor
        let xscale = (maxf-minf) / (xmaxp-xminp);
        let x_force = Math.exp(minf + xscale*(xdist-xminp)) * xdir;
        let xori_pos = xdir < 0 
          ? {x:this.canvas.width, y:this.body.position.y} 
          : {x:0, y:this.body.position.y};
        Matter.Body.applyForce(
          this.body, 
          xori_pos, 
          {
            x: x_force,
            y: 0
          }
        );

        // Y
        // max & min item position
        let yminp = 0;
        let ymaxp = ydir < 0
          ? this.canvas.height - pseudo_center_y
          : pseudo_center_y;
        // X force decrsinsing near the center
        // scale factor
        let yscale = (maxf-minf) / (ymaxp-yminp);
        let y_force = Math.exp(minf + yscale*(ydist-xminp)) * ydir;
        let yori_pos = ydir < 0 
          ? {x:this.body.position.x, y:this.canvas.height} 
          : {x:this.body.position.x, y:0};
        Matter.Body.applyForce(
          this.body, 
          yori_pos, 
          {
            x: 0,
            y: y_force
          }
        );
        
        // // Friction air fa (increasing near the center)
        // let fa_minf = 0;
        // let fa_maxf = 6;
        // // scale factor
        // let fa_scale = (fa_maxf-fa_minf) / (maxp-minp);
        // let fa = Math.exp(fa_minf + fa_scale*(dist-minp)*-1);
        // this.body.frictionAir = fa;
        
        // console.log(`bringToCenter dist:${xdist}, x_force:${x_force}`);        
      } else { // in the ceneter zone let items floats freely
        // let velocity = Matter.Body.getVelocity(this.body);
        // let velocityx = Math.abs(velocity.x);
        // // map a range of numbers to another range of numbers
        // // INFO https://stackoverflow.com/a/46462321
        // let velocity_range = [50, 1000];
        // let fa_range = [0,3];
        // let fa = fa_range[0] + (velocityx - velocity_range[0]) * (fa_range[1] - fa_range[0]) / (velocity_range[1] - velocity_range[0]);
        // console.log(`bringToCenter velocity.x:${velocity.x}, fa:${fa}`);

        // this.body.frictionAir = fa;
        this.body.frictionAir = 0.1;
      }

    },
    checkOverflow() {
      // respawn element if not opened and outside screen
      if (!this.is_open) {
        let pad = 1;
        let circleray = this.body.circleRadius+pad*1.1;
        let respawn = false;
        if(this.pos.x <= pad){                    this.pos.x = circleray;                       respawn=true;}
        if(this.pos.x >= this.canvas.width-pad){  this.pos.x = this.canvas.width - circleray;   respawn=true;}
        if(this.pos.y <= pad){                    this.pos.y = circleray;                       respawn=true;}
        if(this.pos.y >= this.canvas.height-pad){ this.pos.y = this.canvas.height - circleray;  respawn=true;}
        if (respawn) {
          console.log('respawning');
          // this.pos = respawn_pos;
          Matter.Body.setPosition(this.body, {x:this.pos.x, y:this.pos.y});
          // this.setInitBodyVelocity();
          Matter.Body.setVelocity(this.body, {x:0,y:0});
        }
      }
      
    },
    onAfterEngineUpdate (event) {
      this.checkOverflow();

      this.paper_main_object.position = this.pos = this.body.position;

      // fadein intro
      // if(!this.mapitem.clone){
        if (this.paper_main_object.opacity < 9) {
          this.paper_main_object.opacity = this.paper_main_object.opacity * 1.1;
        } else{
          this.paper_main_object.opacity = 1;
        }
      // }

      this.handlePaperVisibilityOnAfterEnginUpdate()
      

      // DEBUG
      // this.debugDrawMatterBodyCircle();
      // END DEBUG
      
    },
    debugDrawMatterBodyCircle(){
      if (this.paper_main_object.children['debug_circle']) {
        this.paper_main_object.children['debug_circle'].remove();
      }

      let debugcircle = new paper.Path.Circle({
        center: this.pos,
        // radius: this.ray,
        radius: this.body.circleRadius,
        strokeColor: '#f00',
        strokeWidth: 1,
        name: 'debug_circle'
      })
      this.paper_main_object.addChild(debugcircle)
    }
  },
  render() {
    // console.log('render()', this.ctx);
  },
}

</script>