modules/3d.js

import { point, vecteur, droite, segment, polyline, polygone } from './2d.js'
import { matrix, multiply, norm, cross, dot } from 'mathjs'
import { context } from './context.js'
import { colorToLatexOrHTML } from './2dGeneralites.js'
const math = { matrix: matrix, multiply: multiply, norm: norm, cross: cross, dot: dot }

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%% OBJET PARENT %%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
*/

/*
 * Classe parente de tous les objets de MathALEA2D
 *
 * @author Rémi Angot
 */
let numId = 0
function ObjetMathalea2D () {
  this.positionLabel = 'above'
  this.isVisible = true
  this.color = colorToLatexOrHTML('black')
  this.style = '' // stroke-dasharray="4 3" pour des hachures //stroke-width="2" pour un trait plus épais
  this.styleTikz = ''
  this.epaisseur = 1
  this.opacite = 1
  this.pointilles = false
  this.id = numId
  numId++
  //   mesObjets.push(this);
  context.objets2D.push(this)
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%% OBJETS DE BASE %%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
*/

/**
 * LE POINT
 *
* @author Jean-Claude Lhote
* Point de l'espace défini par ses trois coordonnées (Si deux sont données seulement, le point est dans le plan XY)
* le paramètre visible définit si ce point est placé devant (par défaut) ou derrière une surface. Il sera utilisé pour définir la visibilité des arêtes qui en partent
*/
class Point3d {
  constructor (x, y, z, visible, label, positionLabel) {
    const alpha = context.anglePerspective * Math.PI / 180 // context.anglePerspective peut être changé globalement pour modifier la perspective
    const rapport = context.coeffPerspective // idem pour context.coefficientPerspective qui est la réduction sur l'axe y.
    const MT = math.matrix([[1, rapport * Math.cos(alpha), 0], [0, rapport * Math.sin(alpha), 1]]) // La matrice de projection 3d -> 2d
    this.x = x
    this.y = y
    this.z = z
    this.visible = visible
    this.label = label
    this.typeObjet = 'point3d'
    const V = math.matrix([this.x, this.y, this.z])
    const W = math.multiply(MT, V)
    this.c2d = point(W._data[0], W._data[1], this.label, positionLabel)
  }
}
export function point3d (x, y, z = 0, visible = true, label = '', positionLabel = 'above left') {
  return new Point3d(x, y, z, visible, label, positionLabel)
}

/**
   * LE VECTEUR
   *
   * @author Jean-Claude Lhote
   * le vecteur3d est sans doute l'objet le plus important de cette base d'objets
   * On les utilise dans tous les objets complexes et dans toutes les transformations
   * Ils servent notament à définir la direction des plans.
   *
   * 3 usages : vecteur3d(A,B) ou vecteur3d(x,y,z) ou vecteur3d(math.matrix([x,y,z]))
   * A et B sont deux objets de type Point3d
   * x,y et z sont trois nombres
   * la commande math.matrix([x,y,z]) crée une matrice colonne.
   *
   * L'objet créé est de type Vecteur3d
   * sa propriété p2d est un objet Vecteur (2 dimensions : c'est la projection du vecteur)
   * sa propriété this.representant(A) est le dessin du représentant d'origine A.
   * exemples :
   * let v = vecteur3d(3,5,1) -> définit un vecteur de composantes (3;5;1)
   * let w = vecteur(point3d(0,0,0),point3d(1,1,1)) -> définit un vecteur d'origine O et d'extrémité M(1;1;1)
   * let fleche = w.representant(point3d(5,0,0)) -> fleche est un objet 2d qui représente le vecteur w au point (5;0;0)
   */
class Vecteur3d {
  constructor (...args) {
    const alpha = context.anglePerspective * Math.PI / 180
    const rapport = context.coeffPerspective
    const MT = math.matrix([[1, rapport * Math.cos(alpha), 0], [0, rapport * Math.sin(alpha), 1]]) // ceci est la matrice de projection 3d -> 2d
    if (args.length === 2) {
      this.x = args[1].x - args[0].x
      this.y = args[1].y - args[0].y
      this.z = args[1].z - args[0].z
    } else {
      if (typeof (args[0]) === 'number') {
        this.x = args[0]
        this.y = args[1]
        this.z = args[2]
      } else if (args.length === 1) {
        this.x = args[0]._data[0]
        this.y = args[0]._data[1]
        this.z = args[0]._data[2]
      }
    }
    this.matrice = math.matrix([this.x, this.y, this.z]) // On exporte cette matrice colonne utile pour les calculs vectoriels qui seront effectués par math.js
    this.norme = Math.sqrt(this.x ** 2 + this.y ** 2 + this.z ** 2) // la norme du vecteur
    const W = math.multiply(MT, this.matrice) // voilà comment on obtient les composantes du projeté 2d du vecteur
    this.c2d = vecteur(W._data[0], W._data[1]) // this.c2d est l'objet 2d qui représente l'objet 3d this
    this.representant = function (A) { // méthode pour construire un représentant d'origine A (un point 3d)
      const B = translation3d(A, this)
      return vecteur(A.c2d, B.c2d).representant(A.c2d) // qui retourne un représentant de vecteur 2d (objet dessiné)
    }
  }
}

export function vecteur3d (...args) { // A,B deux Point3d ou x,y,z les composantes du vecteur
  return new Vecteur3d(...args)
}

/**
   * L'ARETE
   * @author Jean-Claude lhote
   * Une telle arête est définie par deux points
   * Si l'un des deux points n'est pas visible (propriété visible à false) alors l'arête aura aussi visible à false
   * sa propriété p2d est un segment en pointillé ou en trait plein suivant sa visibilité.
   */
class Arete3d {
  constructor (point1, point2, color, visible) {
    this.extremite1 = point1
    this.extremite2 = point2
    this.color = color
    if (!point1.visible || !point2.visible || !visible) {
      this.visible = false
    } else {
      this.visible = true
    }
    this.c2d = segment(point1.c2d, point2.c2d, this.color)
    if (!this.visible) {
      this.c2d.pointilles = 2
    } else {
      this.c2d.pointilles = false
    }
  }
}
// l'arête est visible par défaut sauf si p1 ou p2 sont invisibles
export function arete3d (p1, p2, color = 'black', visible = true) {
  return new Arete3d(p1, p2, color, visible)
}

/**
   * LA DROITE
   *
   * @author Jean-claude Lhote
   * Droite de l'espace définie par point et vecteur directeur droite3d(A,v)
   * Droite de l'espace définie par 2 points droite3d(A,B)
   * Les droites servent principalement à définir des axes de rotation dans l'espace
   */
class Droite3d {
  constructor (point3D, vecteur3D) {
    if (vecteur3D.constructor === Vecteur3d) {
      this.directeur = vecteur3D
    } else if (vecteur3D.constructor === Point3d) {
      this.directeur = vecteur3d(point3D, vecteur3D)
    }
    this.origine = point3D
    const M = translation3d(this.origine, this.directeur)
    this.point = M
    this.c2d = droite(this.origine.c2d, M.c2d) // la droite correspndant à la projection de cette droite dans le plan Mathalea2d
    this.c2d.isVisible = false
  }
}

export function droite3d (point3D, vecteur3D) {
  return new Droite3d(point3D, vecteur3D)
}

/**
 * LE DEMI-CERCLE
 *
 *@author Jean-Claude Lhote
 * Le nom est trompeur, il s'agit le plus souvent d'une demi-ellipse représentant un cercle projeté
 * Utilisé pour représenter un cercle dont une moitié est visible mais pas l'autre.
 *
 * normal et rayon sont deux vecteurs 3d
 * normal est un vecteur normal au plan du cercle
 * rayon est le vecteur qui part du centre et qui joint la 1ere extremité visible.
 * cote est soit 'caché' soit 'visible' et déterminera dans quel sens on crée le demi-cercle.
 * Si cote='caché' alors on tourne dans le sens direct et le tracé est en pointillés
 * Si cote='visible' alors on tourne dans le sens indirect et le tracé est plein.
 *
 */
export function demicercle3d (centre, normal, rayon, cote, color, angledepart = context.anglePerspective) {
  let signe; const M = []; const listepoints = []
  if (cote === 'caché') {
    signe = 1
  } else {
    signe = -1
  }
  const d = droite3d(centre, normal)
  M.push(rotation3d(translation3d(centre, rayon), d, angledepart))
  listepoints.push(M[0].c2d)

  for (let i = 1; i < 19; i++) {
    M.push(rotation3d(M[i - 1], d, 10 * signe))
    listepoints.push(M[i].c2d)
  }
  const demiCercle = polyline(listepoints, color)
  if (cote === 'caché') {
    demiCercle.pointilles = 2
    demiCercle.opacite = 0.3
  }
  return demiCercle
}

/**
 * L'ARC
 *
 *@author Mickael Guironnet
 * Le nom est trompeur, il s'agit le plus souvent d'un morceau d'ellipse représentant un arc projeté
 * Utilisé pour représenter un arc dont une moitié est visible mais pas l'autre.
 *
 * normal et rayon sont deux vecteurs 3d
 * normal est un vecteur normal au plan du cercle
 * rayon est le vecteur qui part du centre et qui joint la 1ere extremité visible.
 * cote est soit 'caché' soit 'visible'
 *
 */
export function arc3d (centre, normal, rayon, cote, color, angledepart, angledefin) {
  const M = []; const listepoints = []
  const d = droite3d(centre, normal)
  M.push(rotation3d(translation3d(centre, rayon), d, angledepart))
  listepoints.push(M[0].c2d)

  const nbr = Math.floor((angledefin - angledepart) / 10)
  for (let i = 1; i <= nbr; i++) {
    M.push(rotation3d(M[i - 1], d, 10))
    listepoints.push(M[i].c2d)
  }
  const arc = polyline(listepoints, color)
  if (cote === 'caché') {
    arc.pointilles = 2
    arc.opacite = 0.3
  }
  return arc
}

/**
    * LE CERCLE
    *
    * @author Jean-Claude Lhote
    *
    * C'est la version entière du cercle : soit totalement visible, soit totalement caché.
    * visible est un booléen
    *
    */
export function cercle3d (centre, normal, rayon, visible = true, color = 'black') {
  const M = []; const listepoints = []
  const d = droite3d(centre, normal)
  M.push(rotation3d(translation3d(centre, rayon), d, context.anglePerspective))
  listepoints.push(M[0].c2d)
  for (let i = 1; i < 37; i++) {
    M.push(rotation3d(M[i - 1], d, 10))
    listepoints.push(M[i].c2d)
  }
  const C = polygone(listepoints, color)
  if (!visible) {
    C.pointilles = 2
  }
  return C
}

/**
   * LE POLYGONE
   *
   * @author Jean-Claude Lhote
   * usages : polygone3d([A,B,C,...],color) ou polygone3d(A,B,C...) où A,B,C ... sont des point3d. color='black' par défaut.
   */
class Polygone3d {
  constructor (...args) {
    if (Array.isArray(args[0])) {
      // Si le premier argument est un tableau
      this.listePoints = args[0]
      if (args[1]) {
        this.color = args[1]
      }
    } else {
      this.listePoints = args
      this.color = 'black'
    }
    const segments3d = []; let A; const segments = []
    A = this.listePoints[0]
    for (let i = 1; i < this.listePoints.length; i++) {
      segments3d.push(arete3d(A, this.listePoints[i], this.color))
      segments.push(segments3d[i - 1].c2d)
      A = this.listePoints[i]
    }
    segments3d.push(arete3d(A, this.listePoints[0], this.color))
    segments.push(segments3d[this.listePoints.length - 1].c2d)
    this.aretes = segments3d
    this.c2d = segments
  }
}

export function polygone3d (...args) {
  return new Polygone3d(...args)
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%% OBJETS DE COMPLEXES %%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
*/

/**
   * LA SPHERE
   *
   * @author Jean-Claude Lhote
   * Produit une sphère : choisir un nombre de parallèles impair pour avoir l'équateur. normal défini l'axe Nord-Sud.
   * rayon est le rayon de la sphère. l'équateur est dans le plan xy l'axe Nord-Sud est sur z
   * @param {Point3d} centre
   * @param {Number} rayon
   * @param {Number} nbParalleles
   * @param {Number} nbMeridiens
   * @param {string} color
   */
function Sphere3d (centre, rayon, nbParalleles, nbMeridiens, color) {
  ObjetMathalea2D.call(this, { })
  this.centre = centre
  this.rayon = vecteur3d(rayon, 0, 0)
  this.normal = vecteur3d(0, 0, 1)
  this.color = color
  this.nbMeridiens = nbMeridiens
  this.nbParalleles = nbParalleles
  this.c2d = []; let c1; let c2; let c3; let c4; let C; let D
  const prodvec = vecteur3d(math.cross(this.normal.matrice, this.rayon.matrice))
  const rayon2 = vecteur3d(math.cross(this.rayon.matrice, math.multiply(prodvec.matrice, 1 / math.norm(prodvec.matrice))))
  const R = rayon
  const cote1 = 'caché'
  const cote2 = 'visible'
  for (let k = 0, rayon3; k < 1; k += 1 / (this.nbParalleles + 1)) {
    C = point3d(centre.x, centre.y, centre.z + R * Math.sin(k * Math.PI / 2))
    D = point3d(centre.x, centre.y, centre.z + R * Math.sin(-k * Math.PI / 2))
    rayon3 = vecteur3d(R * Math.cos(k * Math.PI / 2), 0, 0)
    c1 = demicercle3d(C, this.normal, rayon3, cote1, this.color, context.anglePerspective)
    c2 = demicercle3d(C, this.normal, rayon3, cote2, this.color, context.anglePerspective)
    c3 = demicercle3d(D, this.normal, rayon3, cote1, this.color, context.anglePerspective)
    c4 = demicercle3d(D, this.normal, rayon3, cote2, this.color, context.anglePerspective)
    this.c2d.push(c1, c2, c3, c4)
  }
  for (let k = 0, V; k < 181; k += 90 / this.nbMeridiens) {
    V = rotationV3d(prodvec, this.normal, context.anglePerspective + k)
    c1 = demicercle3d(this.centre, V, rayon2, cote2, this.color, 0)
    c2 = demicercle3d(this.centre, V, rayon2, cote1, this.color, 0)
    this.c2d.push(c1, c2)
  }
}
export function sphere3d (centre, rayon, nbParalleles, nbMeridiens, color = 'black') {
  return new Sphere3d(centre, rayon, nbParalleles, nbMeridiens, color)
}

/**
    * LE CONE
    *
    * @author Jean-Claude Lhote
    *
    * centrebase est le centre du disque de base
    * sommet est le sommet du cône
    * normal est un vecteur 3d normal au plan du disque (il détermine avec rayon de quel côté se trouve la partie visible)
    *
    */
function Cone3d (centrebase, sommet, normal, rayon, generatrices = 18) {
  ObjetMathalea2D.call(this, { })
  this.sommet = sommet
  this.centrebase = centrebase
  this.normal = normal
  if (typeof (rayon) === 'number') {
    this.rayon = vecteur3d(rayon, 0, 0)
  } else {
    this.rayon = rayon
  }
  this.c2d = []
  let s, color1, color2
  const prodvec = vecteur3d(math.cross(normal.matrice, this.rayon.matrice))
  const prodscal = math.dot(prodvec.matrice, vecteur3d(0, 1, 0).matrice)
  let cote1, cote2
  if (prodscal > 0) {
    cote1 = 'caché'
    color1 = 'gray'
    cote2 = 'visible'
    color2 = 'black'
  } else {
    cote2 = 'caché'
    cote1 = 'visible'
    color1 = 'black'
    color2 = 'gray'
  }
  const c1 = demicercle3d(this.centrebase, this.normal, this.rayon, cote1, color1)
  const c2 = demicercle3d(this.centrebase, this.normal, this.rayon, cote2, color2)

  for (let i = 0; i < c1.listePoints.length; i++) {
    if (i % generatrices === 0) {
      s = segment(this.sommet.c2d, c1.listePoints[i])
      if (cote1 === 'caché') {
        s.pointilles = 2
        s.color = colorToLatexOrHTML('gray')
      } else {
        s.color = colorToLatexOrHTML('black')
      }
      this.c2d.push(s)
    }
  }
  for (let i = 0; i < c2.listePoints.length; i++) {
    if (i % generatrices === 0) {
      s = segment(this.sommet.c2d, c2.listePoints[i])
      if (cote2 === 'caché') {
        s.pointilles = 2
        s.color = colorToLatexOrHTML('gray')
      } else {
        s.color = colorToLatexOrHTML('black')
      }
      this.c2d.push(s)
    }
  }
  this.c2d.push(c1, c2)
}
export function cone3d (centre, sommet, normal, rayon, generatrices = 18) {
  return new Cone3d(centre, sommet, normal, rayon, generatrices)
}

/**
   * LE CYLINDRE
   *
   * @author Jean-Claude Lhote
   * Crée un cylindre de révolution définit par les centres de ses 2 bases
   * Permet en faisant varier les rayons des deux bases de créer des troncs de cônes
   * @param {Point3d} centrebase1
   * @param {Point3d} centrebase2
   * @param {Vecteur3d} normal
   * @param {Vecteur3d} rayon1
   * @param {Vecteur3d} rayon2
   */
function Cylindre3d (centrebase1, centrebase2, normal, rayon1, rayon2, color) {
  ObjetMathalea2D.call(this, { })
  this.centrebase1 = centrebase1
  this.centrebase2 = centrebase2
  this.normal = normal
  this.rayon1 = rayon1
  this.rayon2 = rayon2
  this.color = color
  this.c2d = []
  let s
  const prodvec = vecteur3d(math.cross(this.normal.matrice, this.rayon1.matrice))
  const prodscal = math.dot(prodvec.matrice, vecteur3d(0, 1, 0).matrice)
  let cote1, cote2
  if (prodscal > 0) {
    cote1 = 'caché'
    cote2 = 'visible'
  } else {
    cote2 = 'caché'
    cote1 = 'visible'
  }
  const c1 = demicercle3d(this.centrebase1, this.normal, this.rayon1, cote1, this.color)
  const c3 = demicercle3d(this.centrebase2, this.normal, this.rayon2, cote1, this.color)
  const c2 = demicercle3d(this.centrebase1, this.normal, this.rayon1, cote2, this.color)
  const c4 = demicercle3d(this.centrebase2, this.normal, this.rayon2, cote2, this.color)
  c3.pointilles = false
  for (let i = 0; i < c1.listePoints.length; i += 2) {
    s = segment(c3.listePoints[i], c1.listePoints[i], this.color)
    if (cote1 === 'caché') {
      s.pointilles = 2
      s.opacite = 0.3
    }
    this.c2d.push(s)
  }
  for (let i = 0; i < c2.listePoints.length; i += 2) {
    s = segment(c4.listePoints[i], c2.listePoints[i], this.color)
    if (cote2 === 'caché') {
      s.pointilles = 2
      s.opacite = 0.3
    }
    this.c2d.push(s)
  }
  this.c2d.push(c1, c2, c3, c4)
}
export function cylindre3d (centrebase1, centrebase2, normal, rayon, rayon2, color = 'black') {
  return new Cylindre3d(centrebase1, centrebase2, normal, rayon, rayon2, color)
}

/**
   * LE PRISME
   *
   * @author Jean-Claude Lhote
   * Crée un prisme à partir du base Polygone3d et d'un vecteur3d d'extrusion (on peut faire des prismes droits ou non droits)
   */
class Prisme3d {
  constructor (base, vecteur, color) {
    ObjetMathalea2D.call(this, { })

    this.color = color
    base.color = colorToLatexOrHTML(color)
    this.base1 = base
    this.base2 = translation3d(base, vecteur)
    this.base2.color = this.base1.color
    this.aretes = []
    this.c2d = []; let s
    for (let i = 0; i < this.base1.listePoints.length; i++) {
      this.c2d.push(this.base1.c2d[i])
    }
    for (let i = 0; i < this.base2.listePoints.length; i++) {
      this.c2d.push(this.base2.c2d[i])
    }
    for (let i = 0; i < this.base1.listePoints.length; i++) {
      s = arete3d(this.base1.listePoints[i], this.base2.listePoints[i], this.color)
      this.c2d.push(s.c2d)
    }
  }
}

export function prisme3d (base, vecteur, color = 'black') {
  return new Prisme3d(base, vecteur, color)
}
/**
   * La pyramide
   *
   * @author Jean-Claude Lhote
   * Crée une pyramide à partir d'une base Polygone3d et d'un sommet
   */
class Pyramide3d {
  constructor (base, sommet, color) {
    ObjetMathalea2D.call(this, { })

    this.color = color
    base.color = colorToLatexOrHTML(color)
    this.base = base
    this.aretes = []
    this.sommet = sommet
    this.c2d = []; let s
    for (let i = 0; i < this.base.listePoints.length; i++) {
      s = this.base.c2d[i]
      if (this.base.listePoints[i].visible) {
        s.pointilles = false
      } else {
        s.pointilles = 2
      }
      this.c2d.push(s)
    }
    for (let i = 0; i < this.base.listePoints.length; i++) {
      s = arete3d(this.base.listePoints[i], this.sommet, this.color, true)
      if (this.base.listePoints[i].visible) {
        s.c2d.pointilles = false
      } else {
        s.c2d.pointilles = 2
      }
      this.c2d.push(s.c2d)
    }
  }
}

export function pyramide3d (base, vecteur, color = 'black') {
  return new Pyramide3d(base, vecteur, color)
}

/**
   * La pyramide tronquée
   *
   * @author Jean-Claude Lhote
   * Crée une pyramide à partir d'une base Polygone3d d'un sommet et d'un coefficient compris entre 0 et 1
   * un coefficient de 0.5 coupera la pyramide à mi-hauteur (valeur par défaut).
   */
class PyramideTronquee3d {
  constructor (base, sommet, coeff = 0.5, color = 'black') {
    ObjetMathalea2D.call(this, { })

    this.color = color
    base.color = colorToLatexOrHTML(color)
    this.base = base
    this.coeff = coeff
    this.aretes = []
    this.sommet = sommet
    this.c2d = []
    const sommetsBase2 = []
    for (let i = 0, pointSection; i < this.base.listePoints.length; i++) {
      pointSection = homothetie3d(sommet, base.listePoints[i], coeff)
      pointSection.visible = true
      sommetsBase2.push(pointSection)
    }
    this.base2 = polygone3d(...sommetsBase2)
    this.c2d.push(...this.base.c2d)
    for (let i = 0; i < base.listePoints.length; i++) {
      this.aretes.push(arete3d(base.listePoints[i], this.base2.listePoints[i], this.color, base.listePoints[i].visible))
      this.c2d.push(this.aretes[i].c2d)
    }
    this.c2d.push(...this.base2.c2d)
  }
}
export function pyramideTronquee3d (base, sommet, coeff = 0.5, color = 'black') {
  return new PyramideTronquee3d(base, sommet, coeff, color)
}
/**
   * LE cube
   * @author Jean-Claude Lhote
   * usage : cube(x,y,z,c,color) construit le cube d'arète c dont le sommet en bas à gauche a les coordonnées x,y,z.
   * le face avant est dans le plan xz
   *
*/
class Cube3d {
  constructor (x, y, z, c, color = 'black', colorAV = 'lightgray', colorTOP = 'white', colorDr = 'darkgray') {
    ObjetMathalea2D.call(this, { })
    const A = point3d(x, y, z)
    const vx = vecteur3d(c, 0, 0)
    const vy = vecteur3d(0, c, 0)
    const vz = vecteur3d(0, 0, c)
    const B = translation3d(A, vx)
    const C = translation3d(B, vz)
    const D = translation3d(A, vz)
    const E = translation3d(A, vy)
    const F = translation3d(E, vx)
    const G = translation3d(F, vz)
    const H = translation3d(D, vy)
    const faceAV = polygone([A.c2d, B.c2d, C.c2d, D.c2d], color)
    const faceDr = polygone([B.c2d, F.c2d, G.c2d, C.c2d], color)
    const faceTOP = polygone([D.c2d, C.c2d, G.c2d, H.c2d], color)
    faceAV.couleurDeRemplissage = colorToLatexOrHTML(colorAV)
    faceTOP.couleurDeRemplissage = colorToLatexOrHTML(colorTOP)
    faceDr.couleurDeRemplissage = colorToLatexOrHTML(colorDr)
    this.c2d = [faceAV, faceDr, faceTOP]
  }
}
export function cube3d (x, y, z, c, color = 'black', colorAV = 'lightgray', colorTOP = 'white', colorDr = 'darkgray') {
  return new Cube3d(x, y, z, c, color, colorAV, colorTOP, colorDr)
}
/**
 * @author Jean-Claude Lhote
 * Créer une barre de l cubes de c de côté à partir du point (x,y,z)
 * La barre est positionnée suivant l'axe x
 */
class Barre3d {
  constructor (x, y, z, c, l, color = 'black') {
    ObjetMathalea2D.call(this, { })
    let B, C, D, E, F, G, H, faceAv, faceTop
    this.c2d = []
    const vx = vecteur3d(c, 0, 0)
    const vy = vecteur3d(0, c, 0)
    const vz = vecteur3d(0, 0, c)
    let A = point3d(x, y, z)

    for (let i = 0; i < l; i++) {
      B = translation3d(A, vx)
      C = translation3d(B, vz)
      D = translation3d(A, vz)
      E = translation3d(A, vy)
      F = translation3d(E, vx)
      G = translation3d(F, vz)
      H = translation3d(D, vy)
      faceAv = polygone([A.c2d, B.c2d, C.c2d, D.c2d], color)
      faceTop = polygone([D.c2d, C.c2d, G.c2d, H.c2d], color)
      faceAv.couleurDeRemplissage = colorToLatexOrHTML('lightgray')
      faceTop.couleurDeRemplissage = colorToLatexOrHTML('white')
      this.c2d.push(faceAv, faceTop)
      A = translation3d(A, vx)
    }
    const faceD = polygone([B.c2d, F.c2d, G.c2d, C.c2d], color)
    faceD.couleurDeRemplissage = colorToLatexOrHTML('darkgray')
    this.c2d.push(faceD)
  }
}

export function barre3d (x, y, z, c, l, color = 'black') {
  return new Barre3d(x, y, z, c, l, color)
}

/**
 * @author Jean-Claude Lhote
 * Crée une plaque de cubes de côtés c de dimensions l suivant x et p suivant y
 */
class Plaque3d {
  constructor (x, y, z, c, l, p, color = 'black') {
    ObjetMathalea2D.call(this, { })
    let A, B, C, D, F, G, H, faceAv, faceTop, faceD
    this.c2d = []
    const vx = vecteur3d(c, 0, 0)
    const vy = vecteur3d(0, c, 0)
    const vz = vecteur3d(0, 0, c)

    for (let i = 0; i < l; i++) {
      for (let j = 0; j < p; j++) {
        A = point3d(x + i * c, y + j * c, z)
        B = translation3d(A, vx)
        C = translation3d(B, vz)
        D = translation3d(A, vz)
        F = translation3d(B, vy)
        G = translation3d(F, vz)
        H = translation3d(D, vy)
        if (j === 0) {
          faceAv = polygone([A.c2d, B.c2d, C.c2d, D.c2d], color)
          faceAv.couleurDeRemplissage = colorToLatexOrHTML('lightgray')
          this.c2d.push(faceAv)
        }
        if (i === l - 1) {
          faceD = polygone([B.c2d, F.c2d, G.c2d, C.c2d], color)
          faceD.couleurDeRemplissage = colorToLatexOrHTML('darkgray')
          this.c2d.push(faceD)
        }
        faceTop = polygone([D.c2d, C.c2d, G.c2d, H.c2d], color)
        faceTop.couleurDeRemplissage = colorToLatexOrHTML('white')
        this.c2d.push(faceTop)
      }
    }
  }
}

export function plaque3d (x, y, z, c, l, p, color = 'black') {
  return new Plaque3d(x, y, z, c, l, p, color)
}

class PaveLPH3d {
  constructor (x, y, z, c, l, p, h, color = 'black') {
    ObjetMathalea2D.call(this, { })
    let A, B, C, D, F, G, H, faceAv, faceTop, faceD
    this.c2d = []
    const vx = vecteur3d(c, 0, 0)
    const vy = vecteur3d(0, c, 0)
    const vz = vecteur3d(0, 0, c)

    for (let i = 0; i < l; i++) {
      for (let j = 0; j < p; j++) {
        for (let k = 0; k < h; k++) {
          A = point3d(x + i * c, y + j * c, z + k * c)
          B = translation3d(A, vx)
          C = translation3d(B, vz)
          D = translation3d(A, vz)
          F = translation3d(B, vy)
          G = translation3d(F, vz)
          H = translation3d(D, vy)
          if (j === 0) {
            faceAv = polygone([A.c2d, B.c2d, C.c2d, D.c2d], color)
            faceAv.couleurDeRemplissage = colorToLatexOrHTML('lightgray')
            this.c2d.push(faceAv)
          }
          if (i === l - 1) {
            faceD = polygone([B.c2d, F.c2d, G.c2d, C.c2d], color)
            faceD.couleurDeRemplissage = colorToLatexOrHTML('darkgray')
            this.c2d.push(faceD)
          }
          if (k === h - 1) {
            faceTop = polygone([D.c2d, C.c2d, G.c2d, H.c2d], color)
            faceTop.couleurDeRemplissage = colorToLatexOrHTML('white')
            this.c2d.push(faceTop)
          }
        }
      }
    }
  }
}
/**
 *
 * @param {number} x coordonnées du sommet en bas à gauche
 * @param {number} y
 * @param {number} z
 * @param {number} c longueur de l'unité
 * @param {number} p profondeur
 * @param {number} l longueur
 * @param {number} h hauteur
 * @param {*} color couleur
 * @returns {object}
 */
export function paveLPH3d (x, y, z, c, l, p, h, color = 'black') {
  return new PaveLPH3d(x, y, z, c, l, p, h, color)
}

/**
 * @author Erwan Duplessis et Jean-Claude Lhote
 * Attention !
 * Cette Classe définit un objet cube dans une représentation en perspective axonométrique paramétrée par alpha et beta
 * et non pas context.anglePerspective (contrairement à l'objet cube3d ci-dessus ou l'objet pave3d ci-dessous)
 * Il ne s'agit pas à proprement parler d'un objet 3d, c'est un objet 2d avec sa méthode svg() et sa méthode tikz()
 * Utilisée par exemple dans 6G43
 */
class Cube {
  constructor (x, y, z, alpha, beta, colorD, colorT, colorG) {
    ObjetMathalea2D.call(this, { })
    this.x = x
    this.y = y
    this.z = z
    this.alpha = alpha
    this.beta = beta
    this.colorD = colorToLatexOrHTML(colorD)
    this.colorG = colorToLatexOrHTML(colorG)
    this.colorT = colorToLatexOrHTML(colorT)

    this.lstPoints = []
    this.lstPolygone = []
    function proj (x, y, z, alpha, beta) {
      const cosa = Math.cos(alpha * Math.PI / 180)
      const sina = Math.sin(alpha * Math.PI / 180)
      const cosb = Math.cos(beta * Math.PI / 180)
      const sinb = Math.sin(beta * Math.PI / 180)
      return point(cosa * x - sina * y, -sina * sinb * x - cosa * sinb * y + cosb * z)
    }

    this.lstPoints.push(proj(this.x, this.y, this.z, this.alpha, this.beta)) // point 0 en bas
    this.lstPoints.push(proj(this.x + 1, this.y, this.z, this.alpha, this.beta)) // point 1
    this.lstPoints.push(proj(this.x + 1, this.y, this.z + 1, this.alpha, this.beta)) // point 2
    this.lstPoints.push(proj(this.x, this.y, this.z + 1, this.alpha, this.beta)) // point 3
    this.lstPoints.push(proj(this.x + 1, this.y + 1, this.z + 1, this.alpha, this.beta)) // point 4
    this.lstPoints.push(proj(this.x, this.y + 1, this.z + 1, this.alpha, this.beta)) // point 5
    this.lstPoints.push(proj(this.x, this.y + 1, this.z, this.alpha, this.beta)) // point 6
    let p
    p = polygone([this.lstPoints[0], this.lstPoints[1], this.lstPoints[2], this.lstPoints[3]], 'black')
    p.opaciteDeRemplissage = 1
    p.couleurDeRemplissage = this.colorD
    this.lstPolygone.push(p)
    p = polygone([this.lstPoints[2], this.lstPoints[4], this.lstPoints[5], this.lstPoints[3]], 'black')
    p.couleurDeRemplissage = this.colorG
    p.opaciteDeRemplissage = 1
    this.lstPolygone.push(p)
    p = polygone([this.lstPoints[3], this.lstPoints[5], this.lstPoints[6], this.lstPoints[0]], 'black')
    p.couleurDeRemplissage = this.colorT
    p.opaciteDeRemplissage = 1
    this.lstPolygone.push(p)
    this.c2d = this.lstPolygone
  }
}
export function cube (x = 0, y = 0, z = 0, alpha = 45, beta = -35, { colorD = 'green', colorT = 'white', colorG = 'gray' } = {}) {
  return new Cube(x, y, z, alpha, beta, colorD, colorG, colorT)
}
/**
   * LE PAVE
   * @author Jean-Claude Lhote
   * usage : pave(A,B,D,E) construit le pavé ABCDEFGH dont les arêtes [AB],[AD] et [AE] délimitent 3 faces adjacentes.
   *
*/
class Pave3d {
  constructor (A, B, D, E, color) {
    ObjetMathalea2D.call(this, { })
    const v1 = vecteur3d(A, B)
    const v2 = vecteur3d(A, E)
    const v3 = vecteur3d(A, D)
    const C = translation3d(D, v1)
    const H = translation3d(D, v2)
    const G = translation3d(C, v2)
    const F = translation3d(B, v2)
    E.visible = false
    this.color = color
    this.base = polygone3d([A, B, F, E])
    this.hauteur = v3
    this.c2d = []
    this.aretes = [arete3d(A, B, color), arete3d(A, D, color), arete3d(A, E, color), arete3d(C, B, color), arete3d(F, B, color), arete3d(C, D, color), arete3d(C, G, color), arete3d(F, G, color), arete3d(F, E, color), arete3d(H, G, color), arete3d(H, E, color), arete3d(H, D, color)]
    for (const arete of this.aretes) {
      this.c2d.push(arete.c2d)
    }
  }
}

export function pave3d (A, B, C, E, color = 'black') {
  return new Pave3d(A, B, C, E, color)
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%% TRANSFORMATIONS%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
*/

/**
   * LA ROTATION VECTORIELLE
   *
   * @author Jean-Claude Lhote
   * Cette rotation se distingue de la rotation d'axe (d) par le fait qu'on tourne autour d'une droite vectorielle
   * Elle sert à faire tourner des vecteurs essentiellement.
   * Si on l'utilise sur un point, alors il tournera autour d'une droite passant par l'origine.
   *
   * @param {*} point3D pour l'instant, cette fonction ne fait tourner qu'un point3d ou un vecteur3d
   * @param {*} vecteur3D vecteur directeur de l'axe de rotation (l'axe passe par l'origine, pour tourner autour d'une droite particulière on utilise rotation3d())
   * @param {*} angle Angle de rotation
   */
export function rotationV3d (point3D, vecteur3D, angle) { // point = ce qu'on fait tourner (Point3d) ; vecteur = directeur de l'axe de rotation [x,y,z] et angle de rotation en degrés
  let V, p2
  const norme = math.norm(vecteur3D.matrice)
  const unitaire = math.multiply(vecteur3D.matrice, 1 / norme)
  const u = unitaire._data[0]; const v = unitaire._data[1]; const w = unitaire._data[2]
  const c = Math.cos(angle * Math.PI / 180); const s = Math.sin(angle * Math.PI / 180)
  const k = 1 - c
  const matrice = math.matrix([[u * u * k + c, u * v * k - w * s, u * w * k + v * s], [u * v * k + w * s, v * v * k + c, v * w * k - u * s], [u * w * k - v * s, v * w * k + u * s, w * w * k + c]])
  if (point3D.constructor === Point3d) {
    V = math.matrix([point3D.x, point3D.y, point3D.z])
    p2 = math.multiply(matrice, V)
    return point3d(p2._data[0], p2._data[1], p2._data[2])
  } else if (point3D.constructor === Vecteur3d) {
    V = point3D
    p2 = math.multiply(matrice, V.matrice)
    return vecteur3d(p2._data[0], p2._data[1], p2._data[2])
  }
}

/**
   * LA ROTATION D'AXE UNE DROITE
   *
   * @author Jean-Claude Lhote
   *
   * @param {Point3d} point3D Pour l'instant on ne fait tourner qu'un point3d
   * Remarque : ça n'a aucun sens de faire tourner un vecteur autour d'une droite particulière, on utilise la rotation vectorielle pour ça.
   * @param {Droite3d} droite3D Axe de rotation
   * @param {Number} angle Angle de rotation
   * @param {string} color couleur du polygone créé. si non précisé la couleur sera celle du polygone argument
   */
export function rotation3d (point3D, droite3D, angle, color) {
  const directeur = droite3D.directeur
  const origine = droite3D.origine
  const p = []
  if (point3D.constructor === Point3d) {
    const V = vecteur3d(origine, point3d(0, 0, 0))
    const W = vecteur3d(point3d(0, 0, 0), origine)
    const M = translation3d(point3D, V)
    const N = rotationV3d(M, directeur, angle)
    return translation3d(N, W)
  } else if (point3D.constructor === Vecteur3d) {
    return rotationV3d(point3D, directeur, angle)
  } else if (point3D.constructor === Polygone3d) {
    for (let i = 0; i < point3D.listePoints.length; i++) {
      p.push(rotation3d(point3D.listePoints[i], droite3D, angle))
    }
    if (typeof (color) !== 'undefined') {
      return polygone3d(p, color)
    } else { return polygone3d(p, point3D.color) }
  }
}

/**
 * @author Jean-Claude Lhote
 * Crée une flèche en arc de cercle pour montrer un sens de rotation autour d'un axe 3d
 * cette flèche est dessinée dans le plan orthogonal à l'axe qui passe par l'origine de l'axe
 * le rayon est ici un vecteur 3d qui permet de fixer le point de départ de la flèche par translation de l'origine de l'axe
 * l'angle définit l'arc formé par la flèche
 * son sens est définit par le vecteur directeur de l'axe (changer le signe de chaque composante de ce vecteur pour changer le sens de rotation)
 */
function SensDeRotation3d (axe, rayon, angle, epaisseur, color) {
  ObjetMathalea2D.call(this, { })
  this.epaisseur = epaisseur
  this.color = color
  this.c2d = []
  let M; let N; let s
  M = translation3d(axe.origine, rayon)
  for (let i = 0; i < angle; i += 5) {
    N = rotation3d(M, axe, 5)
    s = segment(M.c2d, N.c2d, this.color)
    s.epaisseur = this.epaisseur
    this.c2d.push(s)
    M = N
  }
  N = rotation3d(M, axe, 5)
  s = segment(M.c2d, N.c2d, this.color)
  s.epaisseur = this.epaisseur
  this.c2d.push(s)
  const d = droite3d(N, axe.directeur)
  const A = rotation3d(M, d, 30)
  const B = rotation3d(M, d, -30)
  s = segment(N.c2d, A.c2d, this.color)
  s.epaisseur = this.epaisseur
  this.c2d.push(s)
  s = segment(N.c2d, B.c2d, this.color)
  s.epaisseur = this.epaisseur
  this.c2d.push(s)
}
export function sensDeRotation3d (axe, rayon, angle, epaisseur, color) {
  return new SensDeRotation3d(axe, rayon, angle, epaisseur, color)
}

/**
   * LA TRANSLATION
   *
   * @author Jean-Claude Lhote
   * @param {Point3d} point3D Pour l'instant on ne translate qu'un point3d ou un polygone3d
   * @param {Vecteur3d} vecteur3D
   */
export function translation3d (point3D, vecteur3D) {
  if (point3D.constructor === Point3d) {
    const x = point3D.x + vecteur3D.x
    const y = point3D.y + vecteur3D.y
    const z = point3D.z + vecteur3D.z
    return point3d(x, y, z)
  } else if (point3D.constructor === Polygone3d) {
    const p = []
    for (let i = 0; i < point3D.listePoints.length; i++) {
      p.push(translation3d(point3D.listePoints[i], vecteur3D))
    }
    return polygone3d(p, point3D.color)
  }
}

/**
 * L'homothetie
 * @author Jean-Claude Lhote
 * La même chose qu'ne 2d, mais en 3d...
 * Pour les points3d les polygones ou les vecteurs (multiplication scalaire par rapport)
 */
export function homothetie3d (point3D, centre, rapport, color) {
  let V
  const p = []
  if (point3D.constructor === Point3d) {
    V = vecteur3d(centre, point3D)
    V.x *= rapport
    V.y *= rapport
    V.z *= rapport
    return translation3d(centre, V)
  } else if (point3D.constructor === Vecteur3d) {
    V = vecteur3d(point3D.x, point3D.y, point3D.z)
    V.x *= rapport
    V.y *= rapport
    V.z *= rapport
    return V
  } else if (point3D.constructor === Polygone3d) {
    for (let i = 0; i < point3D.listePoints.length; i++) {
      p.push(homothetie3d(point3D.listePoints[i], centre, rapport, color))
    }
    if (typeof (color) !== 'undefined') {
      return polygone3d(p, color)
    } else { return polygone3d(p, point3D.color) }
  }
}
export class CodageAngleDroit3D extends ObjetMathalea2D {
  constructor (A, B, C) {
    super()
    const BA = vecteur3d(B, A)
    const BC = vecteur3d(B, C)
    const k1 = BA.norme
    const k2 = BC.norme
    const M1 = homothetie3d(A, B, 0.5 / k1)
    const M3 = homothetie3d(C, B, 0.5 / k2)
    const BM1 = vecteur3d(B, M1)
    const BM3 = vecteur3d(B, M3)
    const x = B.x + BM1.x + BM3.x
    const y = B.y + BM1.y + BM3.y
    const z = B.z + BM1.z + BM3.z
    const M2 = point3d(x, y, z)
    const M1M2 = arete3d(M1, M2)
    const M2M3 = arete3d(M2, M3)
    this.svg = function (coeff) {
      return M1M2.c2d.svg(coeff) + M2M3.c2d.svg(coeff)
    }
    this.tikz = function () {
      return M1M2.c2d.tikz() + M2M3.c2d.tikz()
    }
  }
}