"use strict";

import {cos, sin} from "../modules/MathUtils.js";
import V3 from "../modules/V3.js";
let PI = Math.PI;

/** 
 * Funciones auxiliares para la generación de la geometría de figuras 3D.
 * @author Melissa Méndez Servín. 
*/

/**
 * Definición de un plano.
 * @param {Number} width ancho del plano.
 * @param {Number} height alto del plano.
 * @param {Boolean} weldedVertices true si se va a renderizar por vértices compartidas,
 * false si se va a renderizar como caras independientes.
 */
function Plane(width, height, weldedVertices=true){
    this.width = (width || 1)/2;
    this.height = (height || 1)/2;
    this.numVertices = 4;
    this.numIndices = 6;
    this.byIndices = weldedVertices;
    this.texcoords = undefined;
}
Plane.prototype.getVertices = function(){
    return [ -this.width, -this.height, 0, 
              this.width, -this.height, 0,
              this.width,  this.height, 0,
             -this.width,  this.height, 0
        ];
}
Plane.prototype.getFaces = function(){
    return [ 0, 1, 2,
             0, 2, 3];
}
Plane.prototype.getTexCoordinates = function(){
  if(this.texcoords)
    return this.texcoords;
  if(this.byIndices){
    this.texcoords = [0.0, 0.0, 
                      1.0, 0.0,
                      1.0, 1.0,
                      0.0, 1.0];
  }else {
    this.texcoords = [ 0.0, 0.0, 
                       1.0, 0.0,
                       1.0, 1.0,
                       0.0, 0.0,
                       1.0, 1.0,
                       0.0, 1.0 ];
  }
  return this.texcoords;
}
Plane.prototype.setTexcoords = function (new_texcoords) {
  this.texcoords = new_texcoords;
}

/**
 * Definición de un cubo.
 * @param {Number} length largo del cubo.
 * @param {Boolean} weldedVertices true si se va a renderizar por vértices compartidas,
 * false si se va a renderizar como caras independientes.
 */
function Cube(length, weldedVertices = false){
  this.length = (length || 1)/2;
  this.numVertices = 8;
  this.numIndices = 6 * 6;
  this.byIndices = weldedVertices;
  this.flat = true;
}
Cube.prototype.getVertices = function(){
  return [
    -this.length, -this.length,  this.length,
     this.length, -this.length,  this.length,
     this.length,  this.length,  this.length,
    -this.length,  this.length,  this.length,
    -this.length, -this.length, -this.length,
     this.length, -this.length, -this.length,
     this.length,  this.length, -this.length,
    -this.length,  this.length, -this.length
  ];
}
Cube.prototype.getFaces = function(){
  return [ 0, 1, 2, //Front
           2, 3, 0, 
         
           5, 4, 7, //Back
           7, 6, 5,
         
           1, 5, 6, //Right
           6, 2, 1,
         
           4, 0, 3, //Left
           3, 7, 4,
         
           3, 2, 6, //Top
           6, 7, 3,
         
           4, 5, 1, //Bottom
           1, 0, 4
          ];
}
Cube.prototype.getTexCoordinates = function(){
    let texcoords = [];
    for(var i= 0; i< 5; i++){
      texcoords.push( 0.0, 0.0,
                      1.0, 0.0, 
                      1.0, 1.0, 
                      1.0, 1.0, 
                      0.0, 1.0,
                      0.0, 0.0, );
    }
    texcoords.push( 0.0, 1.0,
                    1.0, 1.0, 
                    1.0, 0.0, 
                    1.0, 0.0, 
                    0.0, 0.0,
                    0.0, 1.0 );
  return texcoords;
}

/** 
 * Definición de un prisma rectangular.
 * @param {Number} width ancho de la base del prisma.
 * @param {Number} height alto de la base del prisma.
 * @param {Number} length alto del prisma.
 * @param {Boolean} weldedVertices true si se va a renderizar por vértices compartidas,
 * false si se va a renderizar como caras independientes.
 */
function RectangularPrism(width, height, length, weldedVertices = false){
    this.width  = (width  || 1)/2;
    this.height = (height || 1)/2;
    this.length = (length || 1)/2;
    this.numVertices = 8;
    this.numIndices = 6*6;
    this.byIndices = weldedVertices;
}
RectangularPrism.prototype.getVertices = function() {
    return [
      -this.width, -this.height,  this.length,
       this.width, -this.height,  this.length,
       this.width,  this.height,  this.length,
      -this.width,  this.height,  this.length,
      -this.width, -this.height, -this.length,
       this.width, -this.height, -this.length,
       this.width,  this.height, -this.length,
      -this.width,  this.height, -this.length,
    ];
}
RectangularPrism.prototype.getFaces = function() {
    return [  0, 1, 2, 
              0, 2, 3, 
            
              5, 4, 7,
              5, 7, 6, 
            
              1, 5, 6, 
              1, 6, 2,
            
              4, 0, 3, 
              4, 3, 7,
            
              3, 2, 6, 
              3, 6, 7, 
            
              4, 5, 1, 
              4, 1, 0
    ];
}

/**
 * Definición de un cilindro.
 * @param {Number} radius el radio de la base del cilindro.
 * @param {Number} height la altura de la base del cilindro.
 * @param {Number} Nv el número de vértices que contiene la base del cilindro.
 * @param {Boolean} weldedVertices true si se va a renderizar por vértices compartidas,
 * false si se va a renderizar como caras independientes.
 */
function Cylinder(radius, height, Nv, weldedVertices=true){
    this.radius = (radius || 1);
    this.height = (height || 2)/2;
    this.Nv = Nv || 3;
    this.numVertices = this.Nu * (this.Nv+1);
    this.vertices = undefined;
    this.faces = undefined;
    this.byIndices = weldedVertices;
    this.texcoords = [];
}
Cylinder.prototype.getVertices = function() {
  if(this.vertices != undefined)
    return this.vertices;

  this.vertices = [];

  for (let i=0; i<this.Nv; i++) {
    this.vertices.push(
      this.radius * cos(i*2*PI/this.Nv),
      -this.height,
      this.radius * sin(i*2*PI/this.Nv),

      this.radius * cos(i*2*PI/this.Nv),
      this.height,
      this.radius * sin(i*2*PI/this.Nv),
    );
  }

  this.vertices.push( 0, -this.height, 0);
  this.vertices.push( 0, this.height, 0);

  this.numVertices = this.vertices.length/3;
  return this.vertices;
}
Cylinder.prototype.getFaces = function() {
  if(this.faces != undefined)
    return this.faces;
  
  this.faces = [];
  let edgeVertices = this.numVertices -2;
  for (let i=0; i<edgeVertices; i+=2) {
    this.faces.push(
      i, 
      (i+3)%(edgeVertices),
      (i+2)%(edgeVertices),

      i, 
      (i+1)%(edgeVertices),
      (i+3)%(edgeVertices),
    );
  }
  //Base inferior
  for(let i=0; i < edgeVertices; i+=2){
    this.faces.push(
      i,
      (i+2)%(edgeVertices),
      this.numVertices-2,
    );
  }
  //Base superior
  for(let i=1; i < edgeVertices; i+=2){
    this.faces.push(
      i,
      this.numVertices-1,
      (i+2)%(edgeVertices),
    );
  }
  this.numIndices = this.faces.length;
  return this.faces;
}

/**
 * Definición de una esfera.
 * @param {Number} radius el radio de la esfera.
 * @param {Number} Nu numero de vértices o subdivisiones con respecto al eje x de la esfera.
 * @param {Number} Nv número de vértices o subdivisiones con respecto al eje y de la esfera.
 */
function Sphere(radius, Nu, Nv, byIndices=true){
    this.radius = (radius || 1);
    this.Nu = Nu || 3;
    this.Nv = Nv || 2;
    this.numVertices = this.Nu * this.Nv;
    this.vertices = undefined;
    this.faces = undefined;
    this.byIndices = byIndices;
    this.texcoords = [];
}
Sphere.prototype.getVertices = function(){
  if(this.vertices != undefined)
    return this.vertices;

  this.vertices = [];
  let phi;
  let theta;
  let x, y, z;

  this.vertices.push(0, this.radius, 0);
  this.texcoords.push(0,1);
  for (let i=1; i<this.Nu; i++) {
    phi = (i*PI)/this.Nu;

    for (let j=0; j<this.Nv; j++) {
      theta = (j*2*PI)/this.Nv;
      
      x = this.radius * sin(phi) * cos(theta);
      y = this.radius * cos(phi);
      z = this.radius * sin(phi) * sin(theta);

      this.vertices.push(x, y, z);
      this.texcoords.push(theta, phi);
    }
  }
  this.texcoords.push(0,0);
  this.vertices.push(0, -this.radius, 0);
  this.numVertices = this.vertices.length/3;
  return this.vertices;
}
Sphere.prototype.getFaces = function() {
  if(this.faces != undefined)
    return this.faces;

  this.faces = [];

  for (let i=0; i<this.Nv; i++) {
    this.faces.push(
      0, 
      ((i+1)%this.Nv)+1, 
      (i%this.Nv)+1
    );
  }

  for (let i=1; i<this.Nu-1; i++) {
    for (let j=0; j<this.Nv; j++) {
      this.faces.push(
        j+1 +(i-1)*this.Nv,
        (j+1)%this.Nv +1 +(i-1)*this.Nv,
        (j+1)%this.Nv +1 +i*this.Nv,

        j+1 +(i-1)*this.Nv,
        (j+1)%this.Nv +1 +i*this.Nv,
        j+1 +i*this.Nv
      );
    }
  }

  for (let i=0; i<this.Nv; i++) {
    this.faces.push(
      this.numVertices-1, 
      this.numVertices-1-this.Nv +i, 
      this.numVertices-1-this.Nv +((i+1)%this.Nv),
    );
  }
  
  this.numIndices = this.faces.length;

  return this.faces;
}
Sphere.prototype.getTexCoordinates = function(){
  return this.texcoords;
}
Sphere.prototype.getTangents = function(){
  this.tangents = [ ];
  for (let i=1; i<this.Nu; i++) {
    phi = (i*PI)/this.Nu;

    for (let j=0; j<this.Nv; j++) {
      theta = (j*2*PI)/this.Nv;
      
      x = -this.radius * sin(phi) * cos(theta);
      y = this.radius * cos(phi);
      z = this.radius * sin(phi) * sin(theta);

      this.vertices.push(x, y, z);
      this.texcoords.push(theta, phi);
    }
  }
}
/**
 * Definición de una esfera unitaria.
 * @param {Number} Nv el número de subdivisiones de la circuferencia en ambos ejes.
 */
function UnitSphere(Nv, byIndices=true){
  this.Nv = Nv || 3;
  this.numVertices = undefined;
  this.vertices = undefined;
  this.faces = undefined;
  this.byIndices = byIndices;
}
UnitSphere.prototype.getVertices = function(){
  if(this.vertices != undefined)
    return this.vertices;

  this.vertices = [];
  var i, ai, si, ci;
  var j, aj, sj, cj;
  
  for (j = 0; j <=  this.Nv; j++) 
  {
    aj = j * PI / this.Nv;
    sj = sin(aj);
    cj = cos(aj);
    for (i = 0; i <= this.Nv; i++) 
    {
      ai = i * 2 * PI / this.Nv;
      si = sin(ai);
      ci = cos(ai);
      this.vertices.push(si * sj);  // X
      this.vertices.push(cj);       // Y
      this.vertices.push(ci * sj);  // Z
    }
  }
  this.numVertices = this.vertices.length/3;
  return this.vertices;
}

UnitSphere.prototype.getFaces = function() {
  if(this.faces != undefined)
    return this.faces;
  
  this.faces = [];
  let p1, p2;
  for (var j = 0; j < this.Nv; j++)
  {
    for (var i = 0; i < this.Nv; i++)
    {
      p1 = j * (this.Nv+1) + i;
      p2 = p1 + (this.Nv+1);
      this.faces.push(p1);
      this.faces.push(p2);
      this.faces.push(p1 + 1);
      this.faces.push(p1 + 1);
      this.faces.push(p2);
      this.faces.push(p2 + 1);
    }
  }

  this.numIndices = this.faces.length;

  return this.faces;
}

/**
 * Definición de un cono.
 * @param {Number} radius el radio del cono.
 * @param {Number} height la altura del cono.
 * @param {Number} Nv el número de subdivisiones de la circunferencia base del cono.
 * @param {Boolean} weldedVertices true si se va a renderizar por vértices compartidas,
 * false si se va a renderizar como caras independientes.
 * @param {Boolean} coneFlip true si el cono tiene la base arriba, false en caso contrario.
 */
function Cone(radius, height, Nv, weldedVertices=true, coneFlip=false){
  this.radius = (radius || 1);
  this.height = (height || 2)/2;
  this.Nv = Nv || 3;
  this.numVertices = this.Nv + 2;
  this.vertices = undefined;
  this.faces = undefined;
  this.byIndices = weldedVertices;
  this.coneFlip = coneFlip;
}
Cone.prototype.getVertices = function(){
  if(this.vertices != undefined)
    return this.vertices;
  let base_height = (this.coneFlip) ? this.height : -this.height;
  let tip_height = (this.coneFlip) ? -this.height : this.height;
  this.vertices = [];
  for (let i=0; i<this.Nv; i++) {
    this.vertices.push(
      this.radius * Math.cos(i*2*Math.PI/this.Nv),
      base_height,
      this.radius * Math.sin(i*2*Math.PI/this.Nv),
      );
    }
  this.vertices.push(0, base_height, 0,);//centro base
  this.vertices.push(0, tip_height, 0);//pico o punta
  
  this.numVertices = this.vertices.length/3;

  return this.vertices;
}
Cone.prototype.getFaces = function() {
  if(this.faces != undefined)
    return this.faces;

  this.faces = [];
  let edgeVertices = this.numVertices -2;
  //Base
  if(this.coneFlip){
    for(let i=0; i<edgeVertices; i++)
        this.faces.push(
          i,
          edgeVertices,
          (i+1)%(edgeVertices),
        );  
  }else{
    for(let i=0; i<edgeVertices; i++)
    this.faces.push(
      i,
      (i+1)%(edgeVertices),
      edgeVertices,
    );  
  }
  //Pirámide
  if(this.coneFlip){
    for(let i=0; i<edgeVertices; i++) 
      this.faces.push(
        i, 
        (i+1)%(edgeVertices),
        this.numVertices-1,
      );
  }else{
    for(let i=0; i<edgeVertices; i++) 
      this.faces.push(
        i, 
        this.numVertices-1,
        (i+1)%(edgeVertices),
      );
  }
  this.numIndices = this.faces.length;
  return this.faces;
}
/**
 * Devuelve una lista de vértices "desoldados", separando/repitiendo 
 * los vértices de las caras compartidas.
 * @param {Array} vertices 
 * @param {Array} faces 
 */
function getDisconnectedVertices(welded_vertices, faces){
  let vertices = [];
  for (let i=0; i<faces.length; i++) {
    vertices.push(
      welded_vertices[faces[i]*3],
      welded_vertices[faces[i]*3 +1],
      welded_vertices[faces[i]*3 +2]
    );
   }
  return vertices;
}
/**
 * Devuelve una lista de vértices "desoldados", separando/repitiendo 
 * los vértices de las caras compartidas.
 * @param {Array} vertices 
 * @param {Array} faces 
 */
 function getDisconnectedTexcoords(welded_texcoords, faces){
  let texcoords = [];
  for (let i=0; i<faces.length; i++) {
    texcoords.push(
      welded_texcoords[faces[i]*2],
      welded_texcoords[faces[i]*2 +1]
    );
   }
  return texcoords;
}
/**
 * Obtiene la lista de normales por vértices, definidos de acuerdo a las 
 * caras.
 * @param {Array} faces 
 * @param {Array} vertices 
 * @returns 
 */
function getNormalsFromIndices(faces, vertices) {
  let normals = new Array(vertices.length).fill(0);
  let i0, i1, i2;
  let n;
  for (var i = 0; i<faces.length; i+=3) {
    i0 = faces[i+0]*3;
    i1 = faces[i+1]*3;
    i2 = faces[i+2]*3;

    var v0 = new V3(vertices[i0], vertices[i0 + 1], vertices[i0 + 2]);
    var v1 = new V3(vertices[i1], vertices[i1 + 1], vertices[i1 + 2]);
    var v2 = new V3(vertices[i2], vertices[i2 + 1], vertices[i2 + 2]);
    n = v0.sub(v1).cross(v0.sub(v2));
    
    var tmp = new V3(normals[i0], normals[i0+1], normals[i0+2]);
    tmp = tmp.add(n);
    normals[i0+0] = tmp.x;
    normals[i0+1] = tmp.y;
    normals[i0+2] = tmp.z;

    tmp = new V3(normals[i1], normals[i1+1], normals[i1+2]);
    tmp = tmp.add(n);
    normals[i1+0] = tmp.x;
    normals[i1+1] = tmp.y;
    normals[i1+2] = tmp.z;

    tmp = new V3(normals[i2], normals[i2+1], normals[i2+2]);
    tmp = tmp.add(n);
    normals[i2+0] = tmp.x;
    normals[i2+1] = tmp.y;
    normals[i2+2] = tmp.z;
  }

  for (let i=0; i<normals.length; i+=3) {
    tmp = new V3(normals[i], normals[i+1], normals[i+2]);
    tmp = tmp.normalize();
    normals[i  ] = tmp.x;
    normals[i+1] = tmp.y;
    normals[i+2] = tmp.z;
  }

  return normals;
}
/**
 * Devuelve la lista de normales considerando que los vértices 
 * describen una malla de caras independientes.
 * @param {Array} vertices 
 * @returns el arreglo de las normales.
 */
function getNormalsFromVertices(vertices) {
  let normals = [];
  let n;
  var v0, v1, v2;
  for (let i=0; i<vertices.length/3; i+=3) {
    v0 = new V3(vertices[3*i], vertices[(3*i)+1], vertices[(3*i)+2]);
    v1 = new V3(vertices[(3*(i+1))], vertices[(3*(i+1))+1], vertices[(3*(i+1))+2]);
    v2 = new V3(vertices[(3*(i+2))], vertices[(3*(i+2))+1], vertices[(3*(i+2))+2]);
    n =  v1.sub(v0).cross(v2.sub(v0)).normalize();
    normals.push(
      n.x, n.y, n.z, 
      n.x, n.y, n.z, 
      n.x, n.y, n.z
    );
  }
  return normals;
}
/**
 * Devuelve un arreglo con las coordenadas de textura dada una lista de vértices.
 * @param {Array} vertices 
 * @returns arreglo de las coordenadas de textura.
 */
function getTexCoordinates(vertices){
  let texcoords = [];
  var vi;
  var u,v;
  let PI = Math.PI;
  for (let i=0; i<vertices.length/3; i+=1) {
    vi = new V3(vertices[3*i], vertices[(3*i)+1], vertices[(3*i)+2]);
    vi = vi.normalize();
    u = 0.5 + Math.atan(vi.x, vi.z)/(2*PI);
    v = 0.5 + Math.asin(vi.y)/PI;

    texcoords.push(u,v);
  }
  return texcoords;
}
/**
 * Devuelve un arreglo con las coordenadas cilíndricas de textura dada una lista de vértices.
 * @param {Array} vertices 
 * @returns arreglo de las coordenadas de textura.
 */
function getCylindricTexCoordinates(vertices){
  let texcoords = [];
  var vi;
  var u,v;
  let PI = Math.PI;
  for (let i=0; i<vertices.length/3; i+=1) {
    vi = new V3(vertices[3*i], vertices[(3*i)+1], vertices[(3*i)+2]);
    vi = vi.normalize();
    u = 0.5 + Math.atan(vi.x, vi.z)/(2*PI);
    v = 0.5 + vi.y/3.14159;
    texcoords.push(u,v);
  }
  return texcoords;
}
/**
 * Definición de puntos de un Parche de Bézier.
 * @param {Number} m grado del parche en dirección de u (y)
 * @param {Number} n grado del parche en dirección de v (x)
 * @param {Boolean} plane true si va a ser un parche plano, false 
 * se generan las posiciones de los puntos de control pseudo aleatorias.
 */
function PatchCP(m,n,plane=false){
  this.numVertices = m*n;
  this.numIndices = 6;
  this.byIndices = false;
  this.m = m || 4;
  this.n = n || 4;
  this.vertices = undefined;
  this.faces = undefined;
  this.plane = plane;
}
PatchCP.prototype.setDim = function(m,n){ 
    this.m = m; this.n = n; 
}
PatchCP.prototype.getVertices = function(){
  if(this.vertices != undefined)
    return this.vertices;
  let vertices = [];
  let plane = this.plane;
  function random(){
    if(plane)
      return 0;
    let sign = (Math.random < .5) ? -1 : 1;
    return sign * Math.random();
  }
  for(var z = -this.m/2; z < this.m/2; z++){
    for(var x = -this.n/2; x < this.n/2; x++){
      vertices.push(x+random(), 0+random(), z+random());
    }
  }
  this.vertices = vertices;
  return vertices;
}
PatchCP.prototype.getFaces = function(){
  let faces = [];
  var k;
  let m = this.m -1;
  let n = this.n -1;
  for(var i = 0; i < m; i++){
    for(var j = 0; j < n; j++){
      k = i*this.n + j;
      faces.push(k, k+1, k+n+2, k+n+1);
    }
  } 
  this.faces = faces;
  this.numIndices = faces.length;
  return faces;
}
/**
 * Definición de la superficie de un Parche de Bézier dado su arreglo de 
 * puntos de control.
 * @param {Number} m grado del parche en dirección de u (y).
 * @param {Number} n grado del parche en dirección de v (x).
 * @param {Array} control_points arreglo de los puntos con los control del parche.
 * @param {Boolean} quads true si se va a representar con líneas (caras cuadradas),
 * false en otro caso (caras triangulares).
 */
function Patch(m, n, control_points, quads=false){
  this.numVertices = m*n;
  this.numIndices = 6;
  this.byIndices = false;
  this.m = m;
  this.n = n;
  this.RATIO = 10;
  this.control_points = control_points;
  this.vertices = undefined;
  this.faces = undefined;
  this.quads = quads;
}
Patch.prototype.setDim = function(m,n){ 
  this.m = m; this.n = n; 
}
Patch.prototype.getVertices = function(){
  let RATIO = 1/this.RATIO;
  let vertices = [];
  var p_uv;
  for(var u = 0; u <= 1; u += RATIO){
      for(var v = 0; v <= 1; v += RATIO){
        p_uv = evaluatePatch(u,v, this.m, this.n, this.control_points);
        vertices.push(p_uv.x, p_uv.y, p_uv.z);
      }
  }
  this.vertices = vertices;
  return vertices;
}
function evaluatePatch(u,v, m, n, control_points){
  let newton = [[1], [1,1], [1,2,1], [1,3,3,1], [1,4,6,4,1]];
  var p_ij, b_m, b_n, ij;
  var p_uv = new V3(0,0,0);
  for(var i = 0; i < m; i++){
    b_m = newton[m-1][i] * (u**i) * ((1-u)**(m-i-1));
    for(var j= 0; j < n; j++){
      b_n = newton[n-1][j] * (v**j) * ((1-v)**(n-j-1));
      ij = ((i*n)+j)*3;
      p_ij = new V3(control_points[ij], control_points[ij+1], control_points[ij+2]);
      p_uv = p_uv.add(p_ij.scale(b_n*b_m));
     // console.log(v,u, b_m, b_n, p_uv,ij/3)
    }
  }
  return p_uv;
}
Patch.prototype.getFaces = function(){
  let faces = [];
  var k;
  for(var i = 0; i < this.RATIO; i++){
    for(var j = 0; j < this.RATIO; j++){
      k = i*(this.RATIO+1) + j;
      if(this.quads)
        faces.push(k, k+1, k+this.RATIO+2, k+this.RATIO+1); //QUADS
      else{
        faces.push( k+this.RATIO+1, k+this.RATIO+2, k+1,
                    k+1, k, k+this.RATIO+1);  
      }  
    }
  }
  this.faces = faces;
  this.numIndices = faces.length;
  return faces;
}
/**
 * Definición de un cubo para su representación con líneas (caras cuadradas). 
 * @param {Number} length 
 */
function QuadCube(length){
  this.length = (length || 1)/2;
  this.numVertices = 8;
  this.numIndices = 6 * 6;
}
QuadCube.prototype.getVertices = function(){
  return [
    -this.length, -this.length,  this.length,
     this.length, -this.length,  this.length,
     this.length,  this.length,  this.length,
    -this.length,  this.length,  this.length,
    -this.length, -this.length, -this.length,
     this.length, -this.length, -this.length,
     this.length,  this.length, -this.length,
    -this.length,  this.length, -this.length
  ];
}
QuadCube.prototype.getFaces = function(){
  return [ 0, 1, 2, 3, //Front
           1, 5, 6, 2, //Right
           4, 0, 3, 7, //Left
           4, 5, 6, 7, //Back
           0, 1, 5, 4, //Bottom
           3, 2, 6, 7  // Up
           ];
}
export{
    Plane,
    Cube,
    RectangularPrism, 
    Cylinder,
    Sphere, UnitSphere,
    Cone, 
    PatchCP, Patch,
    QuadCube, 
    getDisconnectedVertices,
    getNormalsFromVertices,
    getNormalsFromIndices,
    getTexCoordinates,
    getDisconnectedTexcoords,
    getCylindricTexCoordinates,
}