2dLutin.js

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%% LES LUTINS %%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
*/

import { angleModulo, point } from './2d.js'
import { colorToLatexOrHTML, ObjetMathalea2D } from './2dGeneralites.js'
import { context } from './context.js'
import { radians } from './fonctionsMaths.js'

/**
 * Renvoie la mesure d'angle (entre -180° et 180°) dans le cercle trigonométrique à partir d'une mesure d'angle donnée en degrés, qu'utilise Scratch.
 * Parce que le 0 angulaire de Scratch est dirigé vers le Nord et qu'il croît dans le sens indirect
 * @param {number} x Angle Scratch
 * @example x=angleScratchTo2d(0) // x=90
 * @example x=angleScratchTo2d(90) // x=0
 * @example x=angleScratchTo2d(-90) // x=180
 * @example x=angleScratchTo2d(-120) // x=-150
 * @return {angleModulo}
 */
// JSDOC Validee par EE Juin 2022
export function angleScratchTo2d (x) {
  const angle2d = 90 - x
  return angleModulo(angle2d)
}

function ObjetLutin () {
  ObjetMathalea2D.call(this, { })
  this.x = 0
  this.y = 0
  this.xMin = 0
  this.xMax = 0
  this.yMin = 0
  this.yMax = 0
  this.xSVG = function (coeff) {
    return this.x * coeff
  }
  this.ySVG = function (coeff) {
    return -this.y * coeff
  }
  this.orientation = 0
  this.historiquePositions = []
  this.crayonBaisse = false
  this.isVisible = true
  this.costume = ''
  this.listeTraces = [] // [[x0,y0,x1,y1,style]...]
  this.color = colorToLatexOrHTML('black')
  this.epaisseur = 2
  this.pointilles = ''
  this.opacite = 1
  this.style = ''
  this.animation = ''
  this.svg = function (coeff) {
    let code = ''
    for (const trace of this.listeTraces) {
      const A = point(trace[0], trace[1])
      const B = point(trace[2], trace[3])
      const color = colorToLatexOrHTML(trace[4])
      const epaisseur = trace[5]
      const pointilles = trace[6]
      const opacite = trace[7]
      let style = ''
      if (epaisseur !== 1) {
        style += ` stroke-width="${epaisseur}" `
      }
      if (pointilles) {
        style += ' stroke-dasharray="4 3" '
      }
      if (opacite !== 1) {
        style += ` stroke-opacity="${opacite}" `
      }
      code += `\n\t<line x1="${A.xSVG(coeff)}" y1="${A.ySVG(
          coeff
        )}" x2="${B.xSVG(coeff)}" y2="${B.ySVG(coeff)}" stroke="${color[0]}" ${style}  />`
    }
    if (this.isVisible && this.animation !== '') {
      code += '\n <g>' + this.animation + '</g>'
    }
    return code
  }
  this.tikz = function () {
    let code = ''
    for (const trace of this.listeTraces) {
      const A = point(trace[0], trace[1])
      const B = point(trace[2], trace[3])
      const color = colorToLatexOrHTML(trace[4])
      const epaisseur = trace[5]
      const pointilles = trace[6]
      const opacite = trace[7]
      let optionsDraw = []
      const tableauOptions = []
      if (color[1].length > 1 && color[1] !== 'black') {
        tableauOptions.push(`color =${color[1]}`)
      }
      if ((!isNaN(epaisseur)) && epaisseur !== 1) {
        tableauOptions.push(`line width = ${epaisseur}`)
      }
      if ((!isNaN(opacite)) && opacite !== 1) {
        tableauOptions.push(`opacity = ${opacite}`)
      }
      if (pointilles) {
        tableauOptions.push('dashed')
      }
      if (tableauOptions.length > 0) {
        optionsDraw = '[' + tableauOptions.join(',') + ']'
      }
      code += `\n\t\\draw${optionsDraw} (${A.x},${A.y})--(${B.x},${B.y});`
    };
    return code
  }
}
/**
   * Crée une nouvelle instance de l'objet lutin
   * @param  {...any} args En fait, il n'y a pas d'argument... il faudra les renseigner après la création de l'objet.
   * Voire l'objet lutin pour la liste de ses attributs (lutin.x, lutin.y, lutin.orientation, ...)
   * @returns {object} Instance d'un lutin
   */
export function creerLutin (...args) {
  return new ObjetLutin(...args)
}

/**
   * Fait avancer le lutin de d unités de lutin dans la direction de son orientation
   * @param {number} d Nombre d'unités choisi pour avancer
   * @param {ObjetLutin} lutin Lutin
   * @example avance(5, lutin) // Fait avancer le lutin de 5 unités
   * @author Jean-Claude Lhote
   */
// JSDOC Validee par EE Juin 2022
export function avance (d, lutin = context.lutin) { // A faire avec pointSurCercle pour tenir compte de l'orientation
  const xdepart = lutin.x
  const ydepart = lutin.y
  lutin.x = lutin.x + d / context.unitesLutinParCm * Math.cos(radians(lutin.orientation))
  lutin.y = lutin.y + d / context.unitesLutinParCm * Math.sin(radians(lutin.orientation))
  lutin.historiquePositions.push([lutin.x, lutin.y])
  if (lutin.crayonBaisse) {
    lutin.listeTraces.push([xdepart, ydepart, lutin.x, lutin.y, lutin.color, lutin.epaisseur, lutin.pointilles, lutin.opacite])
  }
  lutin.xMin = Math.min(lutin.xMin, lutin.x)
  lutin.yMin = Math.min(lutin.yMin, lutin.y)
  lutin.xMax = Math.max(lutin.xMax, lutin.x)
  lutin.yMax = Math.max(lutin.yMax, lutin.y)
}

/**
   * Fait entrer le lutin dans le mode "trace"
   * @param {ObjetLutin} lutin
   * @example baisseCrayon(lutin) // Met lutin en mode "trace"
   */
export function baisseCrayon (lutin = context.lutin) {
  lutin.crayonBaisse = true
}
/**
   * Fait sortir le lutin du mode "trace"
   * @param {ObjetLutin} lutin
   * @example leveCrayon(lutin) // Sort lutin du mode "trace"
   */
// JSDOC Validee par EE Juin 2022
export function leveCrayon (lutin = context.lutin) {
  lutin.crayonBaisse = false
}
/**
   * Fixe l'orientation du lutin à a degrés (au sens Mathalea2d=trigo)
   * Voire la fonction angleScratchTo2d(angle_scratch) pour la conversion
   * @param {number} a
   * @param {ObjetLutin} lutin
   */
export function orienter (a, lutin = context.lutin) {
  lutin.orientation = angleModulo(a)
}
/**
   * Fait tourner de a degrés le lutin dans le sens direct
   * @param {number} a
   * @param {ObjetLutin} lutin
   */
export function tournerG (a, lutin = context.lutin) {
  lutin.orientation = angleModulo(lutin.orientation + a)
}
/**
   * Fait tourner de a degrés le lutin dans le sens indirect
   * @param {number} a
   * @param {ObjetLutin} lutin
   */
export function tournerD (a, lutin = context.lutin) {
  lutin.orientation = angleModulo(lutin.orientation - a)
}
/**
   * Déplace le lutin de sa position courante à (x;y)
   * @param {number} x Nouvelle abscisse
   * @param {number} y Nouvelle ordonnée
   * @param {ObjetLutin} lutin Lutin
   * @example allerA(10,-5,lutin) // Le lutin prend pour coordonnées (10 ; -5).
   */
// JSDOC Validee par EE Juin 2022
export function allerA (x, y, lutin = context.lutin) {
  const xdepart = lutin.x
  const ydepart = lutin.y
  lutin.x = x / context.unitesLutinParCm
  lutin.y = y / context.unitesLutinParCm
  lutin.historiquePositions.push([lutin.x, lutin.y])
  if (lutin.crayonBaisse) {
    lutin.listeTraces.push([xdepart, ydepart, lutin.x, lutin.y, lutin.color, lutin.epaisseur, lutin.pointilles, lutin.opacite])
  }
  lutin.xMin = Math.min(lutin.xMin, lutin.x)
  lutin.yMin = Math.min(lutin.yMin, lutin.y)
  lutin.xMax = Math.max(lutin.xMax, lutin.x)
  lutin.yMax = Math.max(lutin.yMax, lutin.y)
}
/**
   * Change en x à l'abscisse du lutin
   * @param {number} x Nouvelle abscisse
   * @param {ObjetLutin} lutin Lutin
   * @example mettrexA(10,lutin) // L'abscisse de lutin devient 10.
   */
export function mettrexA (x, lutin = context.lutin) {
  const xdepart = lutin.x
  lutin.x = x / context.unitesLutinParCm
  lutin.historiquePositions.push([lutin.x, lutin.y])
  if (lutin.crayonBaisse) {
    lutin.listeTraces.push([xdepart, lutin.y, lutin.x, lutin.y, lutin.color, lutin.epaisseur, lutin.pointilles, lutin.opacite])
  }
  lutin.xMin = Math.min(lutin.xMin, lutin.x)
  lutin.xMax = Math.max(lutin.xMax, lutin.x)
}
/**
   * change en y l'ordonnée du lutin
   * @param {number} y Nouvelle ordonnée
   * @param {ObjetLutin} lutin Lutin
   * @example mettreyA(10,lutin) // L'ordonnée de lutin devient 10.
   */
export function mettreyA (y, lutin = context.lutin) {
  const ydepart = lutin.y
  lutin.y = y / context.unitesLutinParCm
  lutin.historiquePositions.push([lutin.x, lutin.y])
  if (lutin.crayonBaisse) {
    lutin.listeTraces.push([lutin.x, ydepart, lutin.x, lutin.y, lutin.color, lutin.epaisseur, lutin.pointilles, lutin.opacite])
  }
  lutin.yMin = Math.min(lutin.yMin, lutin.y)
  lutin.yMax = Math.max(lutin.yMax, lutin.y)
}
/**
   * Ajoute x à l'abscisse du lutin
   * @param {number} x Valeur à ajouter à l'abscisse
   * @param {ObjetLutin} lutin Lutin
   * @example ajouterAx(10,lutin) // L'abscisse de lutin est augmentée de 10.
   */
// JSDOC Non Validee EE Juin 2022 (impossible à tester car non utilisée)
export function ajouterAx (x, lutin = context.lutin) {
  const xdepart = lutin.x
  lutin.x += x / context.unitesLutinParCm
  lutin.historiquePositions.push([lutin.x, lutin.y])
  if (lutin.crayonBaisse) {
    lutin.listeTraces.push([xdepart, lutin.y, lutin.x, lutin.y, lutin.color, lutin.epaisseur, lutin.pointilles, lutin.opacite])
  }
  lutin.xMin = Math.min(lutin.xMin, lutin.x)
  lutin.xMax = Math.max(lutin.xMax, lutin.x)
}
/**
   * Ajoute y à l'ordonnée du lutin
   * @param {number} y Valeur à ajouter à l'ordonnée
   * @param {ObjetLutin} lutin Lutin
   * @example ajouterAy(10,lutin) // L'ordonnée de lutin est augmentée de 10.
   */
// JSDOC Non Validee EE Juin 2022 (impossible à tester car non utilisée)
export function ajouterAy (y, lutin = context.lutin) {
  const ydepart = lutin.y
  lutin.y += y / context.unitesLutinParCm
  lutin.historiquePositions.push([lutin.x, lutin.y])
  if (lutin.crayonBaisse) {
    lutin.listeTraces.push([lutin.x, ydepart, lutin.x, lutin.y, lutin.color, lutin.epaisseur, lutin.pointilles, lutin.opacite])
  }
  lutin.yMin = Math.min(lutin.yMin, lutin.y)
  lutin.yMax = Math.max(lutin.yMax, lutin.y)
}

/**
   * Fait "vibrer" le lutin, tempo fois autour de sa position courante
   * @param {number} tempo Nombre de vibrations
   * @param {ObjetLutin} lutin Lutin
   * @example attendre(5, lutin) // Fait "vibrer" 5 fois le lutin
   * @author Jean-Claude Lhote
   */
// JSDOC Validee par EE Juin 2022
export function attendre (tempo, lutin = context.lutin) {
  const x = lutin.x; const y = lutin.y
  lutin.listeTraces.push([x, y, x + 0.08, y, lutin.color, lutin.epaisseur, lutin.pointilles, lutin.opacite])
  for (let i = 0; i < tempo; i++) {
    lutin.listeTraces.push([x + 0.08, y, x + 0.08, y + 0.08, lutin.color, lutin.epaisseur, lutin.pointilles, lutin.opacite])
    lutin.listeTraces.push([x + 0.08, y + 0.08, x - 0.08, y + 0.08, lutin.color, lutin.epaisseur, lutin.pointilles, lutin.opacite])
    lutin.listeTraces.push([x + 0.08, y + 0.08, x - 0.08, y + 0.08, lutin.color, lutin.epaisseur, lutin.pointilles, lutin.opacite])
    lutin.listeTraces.push([x - 0.08, y + 0.08, x - 0.08, y - 0.08, lutin.color, lutin.epaisseur, lutin.pointilles, lutin.opacite])
    lutin.listeTraces.push([x - 0.08, y - 0.08, x + 0.08, y - 0.08, lutin.color, lutin.epaisseur, lutin.pointilles, lutin.opacite])
    lutin.listeTraces.push([x + 0.08, y - 0.08, x + 0.08, y, lutin.color, lutin.epaisseur, lutin.pointilles, lutin.opacite])
  }
  lutin.listeTraces.push([x + 0.03, y, x, y, lutin.color, lutin.epaisseur, lutin.pointilles, lutin.opacite])
}

/**
 * fork de https://javascript.developpez.com/actu/94357/JavaScript-moins-Realiser-une-copie-parfaite-d-objet/
 * Ne fonctionne pas complètement : ne copie pas les méthodes svg et tikz...
 * @param {ObjetMathalea2D} originalObject
 * @returns {object} copie de cet objet.
 */
export function clone (obj) {
  if (obj === null || typeof obj !== 'object') return obj
  if (obj instanceof Array) {
    const copy = []
    for (let i = 0, len = obj.length; i < len; i++) {
      copy[i] = clone(obj[i])
    }
    return copy
  }
  if (obj instanceof Object) {
    const copy = {}
    for (const attr in obj) {
      if (Object.prototype.hasOwnProperty.call(obj, attr)) copy[attr] = clone(obj[attr])
    }
    return copy
  }
  throw new Error('Unable to copy obj this object.')
}