modules/2d/engrenage.js

import { abs, round } from 'mathjs'
import { colorToLatexOrHTML, ObjetMathalea2D } from '../2dGeneralites.js'
import { degCos, degSin } from '../fonctionsMaths.js'
import { arrondi } from '../outils.js'
/**
 * @author Jean-Claude Lhote (Sébastien Lozano et Sylvain Chambon pour la partie tikz)
 * @param {object} parametres paramètres de l'objet voir ci-dessous
 * @param {number} [parametres.rayon] rayon du disque sans les dents
 * @param {number} [parametres.rayonExt] rayon du disque avec les dents
 * @param {number} [parametres.rayonInt] rayon du trou de l'axe
 * @param {number} [parametres.nbDents] nombre de dents souhaitées
 * @param {xCenter} [parametres.xCenter] abscisse du centre
 * @param {yCenter} [parametres.yCenter] ordonnée du centre
 * @param {string} [parametres.couleur] couleur du tracé
 * @param {string} [parametres.couleurDeRemplissage] couleur du remplissage
 * @param {string} [parametres.couleurDuTrou] couleur du disque intérieur
 * @param {number} [parametres.dureeTour] temps en secondes mis par la roue pour effectuer un tout en SVG
 * @param {number} [parametres.angleStart] angle de départ de la première dent (90 par défaut) utile pour synchroniser deux roues
 * @param {number | null} [parametres.marqueurG] position angulaire en degrés d'un marqueur à gauche si de type number
 * @param {number | null} [parametres.marqueurD] position angulaire en degrés d'un marqueur à droite si de type number
 * @param {string}[parametres.marqueurColorG] couleur du marqueur gauche
 * @param {string} [parametres.marqueurColorD] couleur du marqueur droit
 * @class
 * @classdesc Constructeur de roues d'engrenage animées
 * @returns {Engrenage}
 */
function Engrenage ({ rayon = 1, rayonExt, rayonInt, nbDents = 12, xCenter = 0, yCenter = 0, couleur = 'black', couleurDeRemplissage = 'black', couleurDuTrou = 'white', dureeTour = 10, angleStart = 90, marqueurG = null, marqueurD = null, marqueurColorG = 'Sienna', marqueurColorD = 'Sienna' } = {}) {
  ObjetMathalea2D.call(this)
  this.rayon = rayon
  this.rayonExt = rayonExt > rayon ? rayonExt : round(rayon * 4 / 3)
  this.rayonInt = rayonInt < rayon ? rayonInt : round(rayon * 3 / 4)
  this.nbDents = nbDents
  this.xCenter = xCenter
  this.yCenter = yCenter
  this.dureeTour = dureeTour
  this.marqueurG = marqueurG
  this.marqueurD = marqueurD
  this.marqueurColorG = marqueurColorG
  this.marqueurColorD = marqueurColorD
  this.color = colorToLatexOrHTML(couleur)
  this.couleurDeRemplissage = colorToLatexOrHTML(couleurDeRemplissage)
  this.couleurDuTrou = colorToLatexOrHTML(couleurDuTrou)
  this.angleStart = angleStart
  this.bordures = [xCenter - rayonExt - 0.2, yCenter - rayonExt - 0.2, xCenter + rayonExt + 0.2, yCenter + rayonExt + 0.2]
  this.svg = function (coeff) {
    const xC = this.xCenter * coeff
    const yC = -this.yCenter * coeff
    const R1 = round(this.rayon * coeff)
    const R2 = round(this.rayonExt * coeff)
    const R0 = round(this.rayonInt * coeff)
    const angle = 360 / this.nbDents
    const r1x = round(R2 - R1)
    const r1y = round(R1 * degSin(0.25 * angle))
    const Ax = round(xC + R1 * degCos(angle * 0.25 + this.angleStart))
    const Ay = round(yC + R1 * degSin(angle * 0.25 + this.angleStart))
    let code = `<g class="roueEngrenage" id=roue${this.id}>
      <path stroke="${this.color[0]}" fill="${this.couleurDeRemplissage[0]}"
        d="M ${Ax},${Ay} `
    for (let i = 0; i < this.nbDents; i++) {
      const Bx = xC + R1 * degCos(angle * (-i - 0.25) + this.angleStart)
      const By = yC + R1 * degSin(angle * (-i - 0.25) + this.angleStart)
      const Cx = xC + R2 * degCos(angle * (-i + 0.125) + this.angleStart)
      const Cy = yC + R2 * degSin(angle * (-i + 0.125) + this.angleStart)
      const Dx = xC + R2 * degCos(angle * (-i - 0.125) + this.angleStart)
      const Dy = yC + R2 * degSin(angle * (-i - 0.125) + this.angleStart)
      const Ex = xC + R1 * degCos(angle * (-i - 0.75) + this.angleStart)
      const Ey = yC + R1 * degSin(angle * (-i - 0.75) + this.angleStart)
      code += `A${r1x},${r1y} ${180 + this.angleStart - i * angle},0 0 ${Cx},${Cy} L${Dx},${Dy} A${r1x},${r1y} ${round(180 + this.angleStart - (i - 0.125) * angle)}, 0, 0 ${Bx},${By} A${R1},${R1} 0, 0, 0 ${Ex},${Ey} `
    }
    code += 'Z"/>'
    if (typeof this.marqueurG === 'number') code += `<circle cx="${round(xC + (R1 - 5) * degCos(this.marqueurG))}" cy="${round(yC + (R1 - 5) * degSin(this.marqueurG))}" r="3" stroke="HotPink" fill="${this.marqueurColorG}" />`
    if (typeof this.marqueurD === 'number') code += `<circle cx="${round(xC + (R1 - 5) * degCos(this.marqueurD))}" cy="${round(yC + (R1 - 5) * degSin(this.marqueurD))}" r="3" stroke="HotPink" fill="${this.marqueurColorD}" />`
    if (this.dureeTour !== 0) {
      code += `<animateTransform
        id="animRoue${this.id}"
        attributeName="transform"
        attributeType="XML"
        type="rotate"
        from="0 ${xC} ${yC}"
        to="${this.dureeTour < 0 ? -360 : 360} ${xC} ${yC}"
        dur="${abs(this.dureeTour)}"
        repeatCount="indefinite"
        />
        </g>
        <circle cx="${xC}" cy="${yC}" r="${R0}" stroke="${this.color[0]}" fill="${this.couleurDuTrou[0]}" />
        <text class="compteurDeTours" id="compteur${this.id}" fill="red" align="middle" dominant-baseline="middle" text-anchor="middle" x="${xC}" y="${yC}">0</text>`
    } else {
      code += `</g>
        <circle cx="${xC}" cy="${yC}" r="${R0}" stroke="${this.color[0]}" fill="${this.couleurDuTrou[0]}" />`
    }
    return code
  }
  this.tikz = function () {
    const R1 = this.rayon
    const R2 = this.rayonExt
    const R0 = this.rayonInt
    let code = `% engrenage de rayon ${this.rayon}, avec ${this.nbDents} dents centré en (${this.xCenter};${this.yCenter})
      \\foreach \\i in {1,2,...,${this.nbDents}}{
                    \\pgfmathparse{360*(\\i-1)/${this.nbDents}}\\let\\angle\\pgfmathresult
                    \\begin{scope}[shift={(${this.xCenter},${this.yCenter})}]
                        \\pgfmathparse{${this.rayon}*cos(${this.angleStart}+90/${this.nbDents})}\\let\\Ax\\pgfmathresult 
                    \\pgfmathparse{${R1}*sin(${this.angleStart}+90/${this.nbDents})}\\let\\Ay\\pgfmathresult
                    \\pgfmathparse{${R1}*cos(${this.angleStart}-90/${this.nbDents})}\\let\\Bx\\pgfmathresult
                    \\pgfmathparse{${R1}*sin(${this.angleStart}-90/${this.nbDents})}\\let\\By\\pgfmathresult
                    \\pgfmathparse{${R2}*cos(${this.angleStart}+45/${this.nbDents})}\\let\\Cx\\pgfmathresult
                    \\pgfmathparse{${R2}*sin(${this.angleStart}+45/${this.nbDents})}\\let\\Cy\\pgfmathresult
                    \\pgfmathparse{${R2}*cos(${this.angleStart}-45/${this.nbDents})}\\let\\Dx\\pgfmathresult
                    \\pgfmathparse{${R2}*sin(${this.angleStart}-45/${this.nbDents})}\\let\\Dy\\pgfmathresult
                    \\pgfmathparse{${this.angleStart}-90/${this.nbDents}}\\let\\a\\pgfmathresult
                    \\pgfmathparse{${this.angleStart}-270/${this.nbDents}}\\let\\b\\pgfmathresult
                    \\fill[${this.couleurDeRemplissage[1]},draw,rotate=\\angle] (0,0) -- (\\Ax,\\Ay) to[bend left=15] (\\Cx,\\Cy) -- (\\Dx,\\Dy) to[bend left=15] (\\Bx,\\By) arc (\\a:\\b:${R1}cm) -- cycle; 
                    \\draw[${this.color[1]},rotate=\\angle] (\\Ax,\\Ay) to[bend left=15] (\\Cx,\\Cy) -- (\\Dx,\\Dy) to[bend left=15] (\\Bx,\\By) arc (\\a:\\b:${R1}cm); 
                    \\end{scope}}
                \\fill[${this.couleurDuTrou[1]},draw=${this.color[1]}] (${this.xCenter},${this.yCenter}) circle (${R0});
    `
    if (typeof this.marqueur === 'number') {
      code += `\\fill[HotPink,draw=black] (${arrondi(this.xCenter + (R1 - 0.2) * degCos(this.marqueur), 2)},${arrondi(this.yCenter + (R1 - 0.2) * degSin(this.marqueur), 2)}) circle (0.1degS);
  `
    }
    return code
  }
}

/**
   * @author Jean-Claude Lhote (Sébastien Lozano et Sylvain Chambon pour la partie tikz)
   * @param {object} parametres paramètres de l'objet voir ci-dessous
   * @param {number} [parametres.rayon] rayon du disque sans les dents
   * @param {number} [parametres.rayonExt] rayon du disque avec les dents
   * @param {number} [parametres.rayonInt] rayon du trou de l'axe
   * @param {number} [parametres.nbDents] nombre de dents souhaitées
   * @param {xCenter} [parametres.xCenter] abscisse du centre
   * @param {yCenter} [parametres.yCenter] ordonnée du centre
   * @param {string} [parametres.couleur] couleur du tracé
   * @param {string} [parametres.couleurDeRemplissage] couleur du remplissage
   * @param {string} [parametres.couleurDuTrou] couleur du disque intérieur
 * @param {number} [parametres.dureeTour] temps en secondes mis par la roue pour effectuer un tout en SVG
 * @param {number} [parametres.angleStart] angle de départ de la première dent (90 par défaut) utile pour synchroniser deux roues
 * @param {number | null} [parametres.marqueurG] position angulaire en degrés d'un marqueur à gauche si de type number
 * @param {number | null} [parametres.marqueurD] position angulaire en degrés d'un marqueur à droite si de type number
 * @param {string}[parametres.marqueurColorG] couleur du marqueur gauche
 * @param {string} [parametres.marqueurColorD] couleur du marqueur droit  * @returns {Engrenage}
   */
function engrenage ({ rayon = 1, rayonExt, rayonInt, nbDents = 12, xCenter = 0, yCenter = 0, couleur = 'black', couleurDeRemplissage = 'black', couleurDuTrou = 'white', dureeTour = 10, angleStart = 90, marqueurG = null, marqueurD = null, marqueurColorG = 'Sienna', marqueurColorD = 'Sienna' } = {}) {
  return new Engrenage({ rayon, rayonExt, rayonInt, nbDents, xCenter, yCenter, couleur, couleurDeRemplissage, couleurDuTrou, dureeTour, angleStart, marqueurG, marqueurD, marqueurColorG, marqueurColorD })
}

/**
 * @ param {object} parametres
 * @param {number} [parametres.dureeTourBase = 0] temps en secondes por que la première roue fasse un tour. La vitesse des autres roues est règlée sur celle-ci
 * @param {number} [parametres.module = 0.5] paramètre dimensionnel générateur. La valeur
 * @param {boolean} [parametres.marqueurs = false] Si true, des marqueurs seront ajoutés pour visualiser la synchronisation des roues dentées
 * @param {...number} nbDents les nombres de dents des roues de gauche à droite
 * @returns {Engrenage[]}
 */
export function engrenages ({ dureeTourBase = 0, module = 0.5, marqueurs = false } = {}, ...nbDents) {
  const roues = []
  let x = 0; const y = 0
  let d1, d2
  let dureeTour = dureeTourBase
  let angleStart = 0
  let marqueurG, marqueurD
  let marqueurColorG, marqueurColorD
  for (let i = 0; i < nbDents.length - 1; i++) {
    d1 = module * nbDents[i]
    d2 = module * nbDents[i + 1]
    const entraxe = (d1 + d2) / 2 // Formule trouvée sur carrefour-numerique.cite-sciences.fr et réduite pour un angle de pression de 20° en ajoutant du jeux.
    if (marqueurs) {
      if (i === 0) {
        marqueurG = null
        marqueurD = 0
        marqueurColorD = 'Sienna'
      } else if (i % 2 === 1) {
        marqueurG = 180
        marqueurD = 0
        marqueurColorG = 'Sienna'
        marqueurColorD = 'blue'
      } else {
        marqueurG = 180
        marqueurD = 0
        marqueurColorG = 'blue'
        marqueurColorD = 'Sienna'
      }
    }
    roues.push(engrenage({ marqueurG, marqueurD, marqueurColorD, marqueurColorG, dureeTour, angleStart, rayonInt: module * 2, rayon: d1 * 0.5 - 0.625 * module, rayonExt: d1 * 0.5 + module / 2, nbDents: nbDents[i], xCenter: x, yCenter: y, couleurDeRemplissage: 'green', couleur: 'black', couleurDuTrou: 'white' }))
    x += entraxe
    if (angleStart === 0) {
      if (nbDents[i + 1] % 2 === 0) {
        angleStart = 180 / nbDents[i + 1]
      } else {
        angleStart = 0
      }
    } else {
      if (nbDents[i + 1] % 2 === 0) {
        angleStart = 0
      } else {
        angleStart = 180 / nbDents[i + 1]
      }
    }
    dureeTour = -dureeTour * d2 / d1
  }
  roues.push(engrenage({ marqueurG: 180, marqueurD: null, marqueurColorG: marqueurColorD, dureeTour, angleStart, rayonInt: module * 2, rayon: d2 * 0.5 - 0.625 * module, rayonExt: d2 * 0.5 + module / 2, nbDents: nbDents[nbDents.length - 1], xCenter: x, yCenter: y, couleurDeRemplissage: 'green', couleur: 'black', couleurDuTrou: 'white' }))
  return roues
}