All pastes #3410084 Raw Edit

Anonymous

public unlisted javascript v1 · immutable
#3410084 ·published 2016-03-24 13:17 UTC
rendered paste body
// Subdivision nach Loop// Die Daten liegen in einer Half Edge Data Structure (HEDS)"use strict";/** * Knoten einer Baumstruktur  * Abstrakte Klasse fr die Klassen Node und Geometry */class ASpatial {}/** * Abstrakte Klasse  * Blatt einer Baumstruktur  */class AGeometry extends ASpatial {   constructor() {      super();      // private Eigenschaften:      var material = null;            /**       * Im COLLADA kann man einer Geometry optionell ein Material zuweisen.       * @param null|Material        * @throws string       */      this.setMaterial = function(_material) {         if(_material === null || _material === undefined) {            material = null;         }         else if(_material instanceof Material) {            material = _material;         }         else throw "material must be of type null or Material";      };      /**       * @return null|Material        */      this.getMaterial = function() {         return material;      };      // vorlufig nur hier - ohne Inhalt:      this.updateWorldMatrix = function() {      };   }   /**    * @param Node _parent     * @throws string     */   set parent(_parent) {      //this.setParent(_parent);   }   /**    * @return null|Material     */   get material() {      return this.getMaterial();   }   /**    * @param null|Material     * @throws string    */   set material(_material) {      this.setMaterial(_material);   }}/** * Abstrakte Klasse fr DrawArraysGeometry und DrawElementsGeometry */class ADrawGeometry extends AGeometry {   /**    * @param array _vertices     * @return array    * @throws string     */   static checkVertices(_vertices) {      if(Object.prototype.toString.call(_vertices) !== '[object Array]')         throw "vertices must be an array";      let count = _vertices.length;      for(let i = 0; i < count; ++i) {         if(!(_vertices[i] instanceof Vertex))            throw "vertices must be of type Vertex";      }      return _vertices;   }   /**    * @return array vom Typ Vertex     */   get vertices() {      return this.getVertices();   }}/** * Vertices mit Indizes  */class DrawElementsGeometry extends ADrawGeometry {   /**    * @param array _vertices vom Typ Vertex     * @param array _indices vom Typ integer >= 0    * @throws string    */   constructor(_vertices, _indices) {           super();            // private Eigenschaften:      var vertices;      var indices;            /**       * @param array _vertices vom Typ Vertex        * @param array _indices vom Typ integer        * @throws string       */      this.setElements = function(_vertices, _indices) {         vertices = ADrawGeometry.checkVertices(_vertices);         indices = DrawElementsGeometry.checkIndices(_indices);      };          /**       * @return array vom Typ Vertex       */      this.getVertices = function() {         return vertices;      };            /**       * @return array vom Typ int        */      this.getIndices = function() {         return indices;      };      /**       * @return string        */      this.toString = function() {         let string = "";         let count = vertices.length;         for(let i = 0; i < count; ++i) {            string += vertices[i].toString() + "\n";         }         count = indices.length;         for(let i = 0; i < count; ++i) {            string += indices[i] + " ";         }         return string;      };            this.setElements(_vertices, _indices);   }   /**    * @param array _indices vom Typ integer >= 0    * @return array     * @throws string    */   static checkIndices(_indices) {      if(Object.prototype.toString.call(_indices) !== '[object Array]')         throw "indices must be an array";      let count = _indices.length;      for(let i = 0; i < count; ++i) {         let index = _indices[i];         if(!Number.isInteger(index) || index < 0)             throw "indices must be of type integer >= 0";      }      return _indices;   }   /**    * @return array vom Typ integer     */   get indices() {      return this.getIndices();   }   /**    * Visitor-Pattern    * @param AVisitor _visitor    * @throws string    */   accept(_visitor) {      if(!(_visitor instanceof AVisitor))         throw "visitor must be of type AVisitor";      _visitor.visitDrawElementsGeometry(this);   }}class Icosahedron extends DrawElementsGeometry {   constructor() {      let t = (1 + Math.sqrt(5)) / 2;      let vertices = [         new Vertex(-1,  t,  0),         new Vertex( 1,  t,  0),         new Vertex(-1, -t,  0),         new Vertex( 1, -t,  0),         new Vertex( 0, -1,  t),         new Vertex( 0,  1,  t),         new Vertex( 0, -1, -t),         new Vertex( 0,  1, -t),         new Vertex( t,  0, -1),         new Vertex( t,  0,  1),         new Vertex(-t,  0, -1),         new Vertex(-t,  0,  1)      ];      let indices = [         0, 11, 5,         0, 5, 1,         0, 1, 7,         0, 7, 10,         0, 10, 11,         1, 5, 9,         5, 11, 4,         11, 10, 2,         10, 7, 6,         7, 1, 8,         3, 9, 4,         3, 4, 2,         3, 2, 6,         3, 6, 8,         3, 8, 9,         4, 9, 5,         2, 4, 11,         6, 2, 10,         8, 6, 7,         9, 8, 1      ];            super(vertices, indices);   }}class Sphere extends DrawElementsGeometry {   /**    * @param int _resolution > 0 Bei 1 erhlt man ein Oktaeder.    * @throws string    */   constructor(_resolution) {      if(!Number.isInteger(_resolution) || _resolution < 1)         throw "resolution must be of type integer > 0";      let countI = Math.pow(2, _resolution + 1); // Anzahl Lngengrade      let countJ = Math.pow(2, _resolution) - 1; // Anzahl Breitengrade            /**       * @param number _deg Winkel in Grad       * @return number Winkel in Radiant       */      var deg2Rad = function(_deg) {         return _deg / 180 * Math.PI;      }      let vertices = [];      let alpha = Math.PI / Math.pow(2, _resolution); // Winkel Schrittweite            for(let i = 0; i < countI; ++i) { // fr kappa2         // Jedes i steht fr einen bestimmten Lngengrad (0 Maridian bei count/2)         let radKappa2 = -Math.PI + i * alpha; // [-PI, +PI[                   // Punkte eines bestimmten Lngengrads (von unten nach oben):         for(let j = 0; j < countJ; ++j) { // fr kappa1, Punkte fr einen bestimmten Lngengrad            // Jedes j steht fr einen bestimmten Breitengrad. Die Pole wren bei j == 0 (-PI/2 Sdpol) bzw. j == count (+PI/2 Nordpol)            let radKappa1 = -Math.PI / 2 + (j + 1) * alpha; // ]-PI/2, +PI/2[                           let cosKappa1 = Math.cos(radKappa1);                              let z = 0.5 * cosKappa1 * Math.cos(radKappa2);            let x = 0.5 * cosKappa1 * Math.sin(radKappa2);            let y = 0.5 * Math.sin(radKappa1);                       let vertex = new Vertex(x, y, z);             vertex.addNormal(vertex.asVector());            vertices.push(vertex);         }      }        let indices = [];      // Je Lngengrad hat man count/2 - 1 Vertizes, exklusive der beiden Pol-Vertizes      if(_resolution > 1) {         // Dreiecke zwischen ersten bis zum letzten Lngengrad:         for(let lon = 0; lon < countI - 1; ++lon) {            for(let lat = 0; lat < countJ - 1; ++lat) {               // Viereck:               let index0 = lon * countJ + lat;               let index1 = index0 + countJ;               let index2 = index0 + 1;               let index3 = index1 + 1;               // erstes Dreieck:               indices.push(index0);               indices.push(index1);               indices.push(index2);               // zweites Dreieck:               indices.push(index1);               indices.push(index3);               indices.push(index2);            }         }         // Dreiecke zwischen letzten und ersten Lngengrad:         for(let lat = 0; lat < countJ - 1; ++lat) {            // Viereck:            let index0 = (countI - 1) * countJ + lat;            let index1 = lat;            let index2 = index0 + 1;            let index3 = index1 + 1;            // erstes Dreieck:            indices.push(index0);            indices.push(index1);            indices.push(index2);            // zweites Dreieck:            indices.push(index1);            indices.push(index3);            indices.push(index2);         }      }            // Sdpol:      let index1 = vertices.length;      let vertexSouth = new Vertex(0, -0.5, 0);      vertexSouth.addNormal(vertexSouth.asVector().normalize());      vertices.push(vertexSouth);      for(let lon = 0; lon < countI - 1; ++lon) {         indices.push(lon * countJ);         indices.push(index1);         indices.push((lon + 1) * countJ);      }      indices.push((countI - 1) * countJ); // index0      indices.push(index1);      indices.push(0); // index2            // Nordpol:      let index2 = vertices.length;      let vertexNorth = new Vertex(0, 0.5, 0)      vertexNorth.addNormal(vertexNorth.asVector().normalize());      vertices.push(vertexNorth);      for(let lon = 0; lon < countI - 1; ++lon) {         indices.push(lon * countJ + countJ - 1); // index0         indices.push((lon + 1) * countJ + countJ - 1); // index1         indices.push(index2);      }      indices.push((countI - 1) * countJ + countJ - 1); // index0      indices.push(countJ - 1); // index1      indices.push(index2);            super(vertices, indices);   }}class Color {   /**    * @param number _r    * @param number _g     * @param number _b     * @param number _a    * @throws string    */   constructor(_r, _g, _b, _a) {      // private Methoden:      /**       * @param number _v [0, 1]       * @return number        * @throws string       */      var check = function(_v) {         if(isNaN(_v))            throw "value must be number";         if(_v < 0 || _v > 1)            throw "value out of range";         return _v;      };            // private Eigenschaften:      var r = check(_r);      var g = check(_g);      var b = check(_b);      var a = _a === undefined ? 1 : check(_a);            // ffentliche Methoden:      /**       * @return number [0, 1]       */      this.getR = function() {         return r;      };      /**       * @return number [0, 1]       */      this.getG = function() {         return g;      };      /**       * @return number [0, 1]       */      this.getB = function() {         return b;      };      /**       * @return number [0, 1]       */      this.getA = function() {         return a;      };      /**       * @param number _r [0, 1]       * @throws string       */      this.setR = function(_r) {         r = check(_r);      };      /**       * @param number _g [0, 1]       * @throws string       */      this.setG = function(_g) {         g = check(_g);      };      /**       * @param number _b [0, 1]       * @throws string       */      this.setB = function(_b) {         b = check(_b);      };      /**       * @param number _a [0, 1]       * @throws string       */      this.setA = function(_a) {         a = check(_a);      };      /**       * @return string        */      this.toString = function() {         return r + " " + g + " " + b + " " + a;      };   }   /**    * @return number [0, 1]    */   get r() {      return this.getR();   }   /**    * @return number [0, 1]    */   get g() {      return this.getG();   }   /**    * @return number [0, 1]    */   get b() {      return this.getB();   }   /**    * @return number [0, 1]    */   get a() {      return this.getA();   }   /**    * @param number _r [0, 1]    * @throws string    */   set r(_r) {      this.setR(_r);   }   /**    * @param number _g [0, 1]    * @throws string    */   set g(_g) {      this.setG(_g);   }   /**    * @param number _b [0, 1]    * @throws string    */   set b(_b) {      this.setB(_b);   }   /**    * @param number _a [0, 1]    * @throws string    */   set a(_a) {      this.setA(_a);   }}/** * Abstrakte Klasse fr alle Arten von Lichtquellen. */class ALight extends AGeometry {   constructor(_color) {      super();            // private Eigenschaften:      var color;            /**       * Die Farbe lsst sich im Laufe einer Animation verndern.       * An einem Sommertag ist die Sonne morgens rtlich, tagsber im Winter eher wei.       * @param Color _color        * @throws string       */      this.setColor = function(_color) {         if(!(_color instanceof Color))            throw "color must be of type Color";         color = _color;      };      /**       * @return Color        */      this.getColor = function() {         return color;      };            this.setColor(_color);   }   /**    * @param Color _color     * @throws string    */   set color(_color) {      return this.setColor(_color);   }   /**    * @return Color     */   get color() {      return this.getColor();   }}/** * Sonne (sehr weit entfernte Lichtquelle) * Default-Richtung: (0, 0, -1) * Die Richtung kann entweder hier angegeben oder im scene-Graph mittels * Matrizen transformiert werden. */class DirectionalLight extends ALight {   /**    * @param Vector _direction Licht-Vektor, Einheitsvektor    */   constructor(_color, _direction) {      super(_color);            // private Eigenschaften:      var direction;            /**       * Die Richtung lsst sich im Laufe einer Animation verndern.       * @param null|Vector _direction Licht-Vektor, Einheitsvektor       * @throws string       */      this.setDirection = function(_direction) {         if(_direction === null || _direction === undefined) {            direction = new Vector(0, 0, -1);         }         else if(_direction instanceof Vector) {            direction = _direction.normalize();         }         else throw "direction must be null or of type Vector";      };      /**       * @return Vector        */      this.getDirection = function() {         return direction;       };      /**       * @return string       */      this.toString = function() {         return this.color.toString() + "\n" +                 this.direction.toString();      };            this.setDirection(_direction);   }   /**    * @param Vector _direction     * @throws string     */   set direction(_direction) {      return this.setDirection(_direction);   }   /**    * @return Vector     */   get direction() {      return this.getDirection();   }   /**    * Visitor-Pattern    * @param AVisitor _visitor    * @throws string    */   accept(_visitor) {      if(!(_visitor instanceof AVisitor))         throw "visitor must be of type AVisitor";      _visitor.visitDirectionalLight(this);   }}//// initWebGL//// Initialize WebGL, returning the GL context or null if// WebGL isn't available or could not be initialized.//function webGL(canvas) {  let gl = null;  try {    gl = canvas.getContext("experimental-webgl");  }  catch(e) {  }  // If we don't have a GL context, give up now  if (!gl) {    alert("Unable to initialize WebGL. Your browser may not support it.");  }  return gl;}//// getShader//// Loads a shader program by scouring the current document,// looking for a script with the specified ID.//function getShader(gl, id) {  var shaderScript = document.getElementById(id);  // Didn't find an element with the specified ID; abort.  if (!shaderScript) {    return null;  }  // Walk through the source element's children, building the  // shader source string.  var theSource = "";  var currentChild = shaderScript.firstChild;  while(currentChild) {    if (currentChild.nodeType == 3) {      theSource += currentChild.textContent;    }    currentChild = currentChild.nextSibling;  }  // Now figure out what type of shader script we have,  // based on its MIME type.  var shader;  if (shaderScript.type == "x-shader/x-fragment") {    shader = gl.createShader(gl.FRAGMENT_SHADER);  } else if (shaderScript.type == "x-shader/x-vertex") {    shader = gl.createShader(gl.VERTEX_SHADER);  } else {    return null;  // Unknown shader type  }  // Send the source to the shader object  gl.shaderSource(shader, theSource);  // Compile the shader program  gl.compileShader(shader);  // See if it compiled successfully  if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {    alert("An error occurred compiling the shaders: " + gl.getShaderInfoLog(shader));    return null;  }  return shader;}class AMatrix {}class Matrix3 extends AMatrix {   constructor(_values) {      super();            // private Eigenschaften:      if(Object.prototype.toString.call(_values) !== '[object Array]')         throw "values must be an array";      let count = _values.length;      if(count != 3)         throw "values must have 3 rows";      for(let i = 0; i < count; ++i) {         if(Object.prototype.toString.call(_values[i]) !== '[object Array]')            throw "a row must be an array";         if(_values[i].length != count)            throw "Matrix must be square"         for(let j = 0; j < count; ++j) {            if(isNaN(_values[i][j]))               throw "value must be a number";         }      }      var values = _values;            /**       * @return Matrix        */      this.transpose = function() {         let count = values.length;         let transposeValues = [];         for(let i = 0; i < count; ++i) {            transposeValues.push([]);         }                  for(let i = 0; i < count; ++i) { // Zeilen            for(let j = 0; j < count; ++j) { // Spalten               transposeValues[j][i] = values[i][j];            }         }         return new Matrix3(transposeValues);      };      /**       * @param Vector _vector       * @return Vector              * @throws string        */      this.multiplyVector = function(_vector) {         if(!(_vector instanceof Vector))            throw "vector must be of type Vector";         return new Vector(            values[0][0] * _vector.x + values[0][1] * _vector.y + values[0][2] * _vector.z,            values[1][0] * _vector.x + values[1][1] * _vector.y + values[1][2] * _vector.z,            values[2][0] * _vector.x + values[2][1] * _vector.y + values[2][2] * _vector.z         );      };      /**        * @return array        */      this.getValues = function() {         return values;      };      /**       * @return string       */      this.toString = function() {         return "| " + values[0][0] + " " + values[0][1] + " " + values[0][2] + " |\n" +                "| " + values[1][0] + " " + values[1][1] + " " + values[1][2] + " |\n" +                "| " + values[2][0] + " " + values[2][1] + " " + values[2][2] + " |\n";      };   }   get values() {      return this.getValues();   }}      /** * 4x4-Matrix  */class Matrix extends AMatrix {   /**    * @param array _values 2-dimensionales Array, number    * @throws string    */   constructor(_values) {      super();            // private Eigenschaften:      if(Object.prototype.toString.call(_values) !== '[object Array]')         throw "values must be an array";      let count = _values.length;      if(count != 4)         throw "values must have 4 rows";      for(let i = 0; i < count; ++i) {         if(Object.prototype.toString.call(_values[i]) !== '[object Array]')            throw "a row must be an array";         if(_values[i].length != count)            throw "Matrix must be square"         for(let j = 0; j < count; ++j) {            if(isNaN(_values[i][j]))               throw "value must be a number";         }      }      var values = _values;      /**       * Gibt einen Teilbereich der Matrixwerte zurck.       * _row und _col definieren jene Zeile und Spalte, die von dem Matrixwerten       * ausgenommen werden sollen.       * @param array _values Matrix-Werte       * @param int _row        * @param int _col        * @return array       */      var sub = function(_values, _row, _col) {         let count = _values.length;         let subValues = [];         for(let i = 0, subI = 0; i < count; ++i) {            if(i != _row) {               subValues[subI] = [];                              for(let j = 0, subJ = 0; j < count; ++j) {                  if(j != _col) {                     subValues[subI][subJ] = _values[i][j];                     ++subJ;                  }               }                              ++subI;            }         }         return subValues;      };      /**       * @param array _values Matrix-Werte       * @return number       */      var det = function(_values) {         let count = _values.length;         if(count == 1) { // Abbruchbedingung            return _values[0][0];         }                  let sum = 0;         for(let j = 0; j < count; ++j) {            sum += _values[0][j] * Math.pow(-1, j) * det(sub(_values, 0, j)); // rekursiver Aufruf         }         return sum;      };      /**       * Matrix invertieren       * @return Matrix        */      this.inverse = function() {         // Invertieren nach Cramer-Regel.                  let count = values.length;         let inverseValues = [];         for(let i = 0; i < count; ++i) {            inverseValues[i] = [];         }                  // adjoint Matrix:         for(let i = 0; i < count; ++i) {            for(let j = 0; j < count; ++j) {               inverseValues[j][i] = Math.pow(-1, i + j) * det(sub(values, i, j));               // Berechnung gleich transponiert in inverseValues eingetragen, darum [j][i]            }         }                  // Determinante:         let d = 0;         for(let k = 0; k < count; ++k) {            d += values[0][k] * inverseValues[k][0];         }         for(let i = 0; i < count; ++i) {            for(let j = 0; j < count; ++j) {               inverseValues[i][j] /= d;            }         }                  return new Matrix(inverseValues);      };      /**       * @param Vertex _vertex       * @return Vertex              * @throws string        */      this.multiplyVertex = function(_vertex) {         if(!(_vertex instanceof Vertex))            throw "v must be of type Vertex";         return new Vertex(            values[0][0] * _vertex.x + values[0][1] * _vertex.y + values[0][2] * _vertex.z + values[0][3] * _vertex.w,            values[1][0] * _vertex.x + values[1][1] * _vertex.y + values[1][2] * _vertex.z + values[1][3] * _vertex.w,            values[2][0] * _vertex.x + values[2][1] * _vertex.y + values[2][2] * _vertex.z + values[2][3] * _vertex.w,            values[3][0] * _vertex.x + values[3][1] * _vertex.y + values[3][2] * _vertex.z + values[3][3] * _vertex.w         );      };      /**       * @param Matrix _m        * @return Matrix        * @throws string        */      this.multiplyMatrix = function(_other) {         if(!(_other instanceof Matrix))            throw "other must be of type Matrix";         let v = _other.values;         return new Matrix([[                  values[0][0] * v[0][0] + values[0][1] * v[1][0] + values[0][2] * v[2][0] + values[0][3] * v[3][0],                  values[0][0] * v[0][1] + values[0][1] * v[1][1] + values[0][2] * v[2][1] + values[0][3] * v[3][1],                  values[0][0] * v[0][2] + values[0][1] * v[1][2] + values[0][2] * v[2][2] + values[0][3] * v[3][2],                  values[0][0] * v[0][3] + values[0][1] * v[1][3] + values[0][2] * v[2][3] + values[0][3] * v[3][3]               ], [                  values[1][0] * v[0][0] + values[1][1] * v[1][0] + values[1][2] * v[2][0] + values[1][3] * v[3][0],                  values[1][0] * v[0][1] + values[1][1] * v[1][1] + values[1][2] * v[2][1] + values[1][3] * v[3][1],                  values[1][0] * v[0][2] + values[1][1] * v[1][2] + values[1][2] * v[2][2] + values[1][3] * v[3][2],                  values[1][0] * v[0][3] + values[1][1] * v[1][3] + values[1][2] * v[2][3] + values[1][3] * v[3][3]               ], [                  values[2][0] * v[0][0] + values[2][1] * v[1][0] + values[2][2] * v[2][0] + values[2][3] * v[3][0],                  values[2][0] * v[0][1] + values[2][1] * v[1][1] + values[2][2] * v[2][1] + values[2][3] * v[3][1],                  values[2][0] * v[0][2] + values[2][1] * v[1][2] + values[2][2] * v[2][2] + values[2][3] * v[3][2],                  values[2][0] * v[0][3] + values[2][1] * v[1][3] + values[2][2] * v[2][3] + values[2][3] * v[3][3]               ], [                  values[3][0] * v[0][0] + values[3][1] * v[1][0] + values[3][2] * v[2][0] + values[3][3] * v[3][0],                  values[3][0] * v[0][1] + values[3][1] * v[1][1] + values[3][2] * v[2][1] + values[3][3] * v[3][1],                  values[3][0] * v[0][2] + values[3][1] * v[1][2] + values[3][2] * v[2][2] + values[3][3] * v[3][2],                  values[3][0] * v[0][3] + values[3][1] * v[1][3] + values[3][2] * v[2][3] + values[3][3] * v[3][3]               ]            ]         );      };      /**        * @return array        */      this.getValues = function() {         return values;      };      /**       * @return Matrix3        */      this.toMatrix3 = function() {         let values3 = [];         for(let i = 0; i < 3; ++i) { // Zeilen            values3[i] = [];            for(let j = 0; j < 3; ++j) { // Spalten               values3[i][j] = values[i][j];            }         }         return new Matrix3(values3);      };      /**       * @return string       */      this.toString = function() {         return "| " + values[0][0] + " " + values[0][1] + " " + values[0][2] + " " + values[0][3] + " |\n" +                "| " + values[1][0] + " " + values[1][1] + " " + values[1][2] + " " + values[1][3] + " |\n" +                "| " + values[2][0] + " " + values[2][1] + " " + values[2][2] + " " + values[2][3] + " |\n" +                "| " + values[3][0] + " " + values[3][1] + " " + values[3][2] + " " + values[3][3] + " |";      };   }   /**    * @return array     */   get values() {      return this.getValues();   }   /**    * @return Matrix    */   static identity() {      return new Matrix([            [1, 0, 0, 0],            [0, 1, 0, 0],            [0, 0, 1, 0],            [0, 0, 0, 1]         ]      );   }   /**    * @param number _x Rotationswinkel in Radiant um x-Achse    * @param number _y Rotationswinkel in Radiant um y-Achse    * @param number _z Rotationswinkel in Radiant um z-Achse    * @return Matrix    */   static rotation(_x, _y, _z) {      let sX = Math.sin(_x);      let cX = Math.cos(_x);      let sY = Math.sin(_y);      let cY = Math.cos(_y);      let sZ = Math.sin(_z);      let cZ = Math.cos(_z);      return new Matrix([            [cY * cZ, -cX * sZ + sX * sY * cZ,  sX * sZ + cX * sY * cZ,  0],            [cY * sZ, cX * cZ + sX * sY * sZ,   -sX * cZ + cX * sY * sZ, 0],            [-sY,     sX * cY,                  cX * cY,                 0],            [0,       0,                        0,                       1]         ]      );   }   /**    * @param number _x auf x-Achse verschieben    * @param number _y auf y-Achse verschieben    * @param number _z auf z-Achse verschieben    * @return Matrix     */   static translation(_x, _y, _z) {      return new Matrix([            [1, 0, 0, _x],            [0, 1, 0, _y],            [0, 0, 1, _z],            [0, 0, 0, 1]         ]      );   }}class JitterCamera {   /**    * @param Vertex _position homogenisiert; Position der Kamera    * @param Vertex _at homogenisiert; das Ziel, worauf die Kamera blickt    * @param Vector _up    * @param number _fov in Radiant > 0 vertikaler Blickwinkel    * @param int _width viewport-Breite in Pixel, integer > 0     * @param int _height viewport-Hhe in Pixel, integer > 0     * @param number _near near-Plane > 0     * @param number _far far-Plane > near-Plane     * @param number _jitterX Kamera-Versatz in x-Richtung     * @param number _jitterY Kamera-Versatz in y-Richtung    * @param number _focus > 0 auf welchen negativen z-Ebene fokusiert wird     * @throws string    */   constructor(_position, _at, _up, _fov, _width, _height, _near, _far, _jitterX, _jitterY, _focus) {      // ================ view-Parameter ===================      if(!(_position instanceof Vertex))         throw "position must be of type Vertex";      if(!(_at instanceof Vertex))         throw "at must be of type Vertex";      if(!(_up instanceof Vector))         throw "up must be of type Vector";            var position = _position;      var at = _at;      var up = _up;            var w = position.minus(at).normalize(); // camera-z-axis       var u = up.cross(w).normalize();        // camera-x-axis       var v = w.cross(u).normalize();         // camera-y-axis         // ================ view-frustum-Parameter ===================      if(isNaN(_fov) || _fov <= 0)         throw "fov must be of type number > 0";      if(isNaN(_width) || _width <= 0)         throw "width must be of type number > 0";      if(isNaN(_height) || _height <= 0)         throw "width must be of type number > 0";      if(isNaN(_near) || _near <= 0)         throw "near must be of type number > 0";            let top = _near * Math.tan(_fov / 2); // die halbe Hhe bei der near-plane      let bottom = -top;      let right = top * _width / _height; // die halbe Breite bei der near-plane (_widht / _height entspricht aspectRatio)      let left = -right;      // ================ jitter-Parameter ===================      if(         _jitterX !== null && _jitterX !== undefined ||         _jitterY !== null && _jitterY !== undefined ||         _focus !== null && _focus !== undefined      ) {         if(_focus === null || _focus === undefined || isNaN(_focus) || _focus <= 0)            throw "focus must be of type number > 0";                  let getDiff = function(_near, _jitter, _focus) {            if(_jitter === null || _jitter === undefined) {               return 0;            }                           if(isNaN(_jitter))               throw "jitter must be of type number";                           return _jitter * _near / _focus;         };                  let diffX = getDiff(_near, _jitterX, _focus);         let diffY = getDiff(_near, _jitterY, _focus);                  // Bewegt sich die Kamera im view-space nach rechts (x wird grer), wird right kleiner und left betragsmig grer (also noch negativer).         top -= diffY;         bottom -= diffY;         right -= diffX;         left -= diffX;       }      else {         _jitterX = 0;         _jitterY = 0;      }         // ================ jitter-view-Transformation ================      let positionVector = position.asVector();            // Coord-Conversion     * Translation              = view-Transformationsmatrix      // | u.x  u.y  u.z  0 |   | 1  0  0  -position.x |   | u.x  u.y  u.z  -(u dot position) |      // | v.x  v.y  v.z  0 | * | 0  1  0  -position.y | = | v.x  v.y  v.z  -(v dot position) |      // | w.x  w.y  w.z  0 |   | 0  0  1  -position.z |   | w.x  w.y  w.z  -(w dot position) |      // | 0    0    0    1 |   | 0  0  0  1           |   | 0    0    0    1                 |            // Jitter-Translation       view-Transformationsmatrix           = jittered view-Transformationmatrix      // | 1, 0, 0, -jitterX |   | u.x  u.y  u.z  -(u dot position) |   | u.x  u.y  u.z  -(u dot position)-jitterX |      // | 0, 1, 0, -jitterY | * | v.x  v.y  v.z  -(v dot position) | = | v.x  v.y  v.z  -(v dot position)-jitterY |       // | 0, 0, 1, 0        |   | w.x  w.y  w.z  -(w dot position) |   | w.x  w.y  w.z  -(w dot position)         |      // | 0, 0, 0, 1        |   | 0    0    0    1                 |   | 0    0    0    1                         |            var viewMatrix = new Matrix([            [u.x, u.y, u.z, -u.dot(positionVector)-_jitterX],            [v.x, v.y, v.z, -v.dot(positionVector)-_jitterY],            [w.x, w.y, w.z, -w.dot(positionVector)],            [0,   0,   0,   1]         ]      );         // ================ projection-Transformation (unter Bercksichtigung der jitter-Parameter) ===================      var projectionMatrix = new Matrix([            [2 * _near / (right - left), 0,                          (right + left) / (right - left), 0],            [0,                          2 * _near / (top - bottom), (top + bottom) / (top - bottom), 0],            [0,                          0,                          (_near + _far) / (_near - _far), 2 * _near * _far / (_near - _far)],            [0,                          0,                          -1,                              0]         ]      );      /**       * @return Vertex       */      this.getPosition = function() {         return position;      };      /**       * @return Vertex       */      this.getAt = function() {         return at;      };      /**       * @return Vector       */      this.getUp = function() {         return up;      };      /**       * @return Vector       */      this.getU = function() {         return u;      };      /**       * @return Vector       */      this.getV = function() {         return v;      };      /**       * @return Vector       */      this.getW = function() {         return w;      };      /**       * @return Matrix        */      this.getViewMatrix = function() {         return viewMatrix;      };      /**       * @return Matrix        */      this.getProjectionMatrix = function() {         return projectionMatrix;      };      /**       * @return string        */      this.toString = function() {         return p.toString() + "\n" +                 at.toString() + "\n" +                up.toString();      };   }   /**    * @return Vertex    */   get position() {      return this.getPosition();   }   /**    * @return Vertex    */   get at() {      return this.getAt();   }   /**    * @return Vector    */   get up() {      return this.getUp();   }   /**    * @return Vector    */   get u() {      return this.getU();   }   /**    * @return Vector    */   get v() {      return this.getV();   }   /**    * @return Vector    */   get w() {      return this.getW();   }   /**    * @return Matrix     */   get viewMatrix() {      return this.getViewMatrix();   }   /**    * @return Matrix     */   get projectionMatrix() {      return this.getProjectionMatrix();   }   /**    * Visitor-Pattern    * @param AVisitor _visitor    * @throws string    */   accept(_visitor) {      if(!(_visitor instanceof AVisitor))         throw "visitor must be of type AVisitor";      _visitor.visitCamera(this);   }}class HalfEdge {   /**    * @param int _id     * @param string _name wird nur zum Debuggen verwendet, um zwischen alte und neue Kanten unterscheiden zu knnen    */   constructor(_id) {       if(isNaN(_id) || Number(_id) !== parseInt(_id) || _id < 0)          throw "id must be of type int >= 0";      // private Eigenschaften:      var id = _id;      var vertexIndex = null; // von welchem Vertex diese Kante ausgeht      var face = null;      var nextHalfEdge = null;      var twinHalfEdge = null;            /**       * @param int _vertexIndex >= 0       * @throws string       */      this.setVertexIndex = function(_vertexIndex) {         if(isNaN(_vertexIndex) || Number(_vertexIndex) !== parseInt(_vertexIndex) || _vertexIndex < 0)             throw "vertexIndex must be of type int >= 0";                  vertexIndex = _vertexIndex;      };      /**       * @return null|int >= 0       */      this.getVertexIndex = function() {         return vertexIndex;      };      /**       * @param Face _face       * @throws string       */      this.setFace = function(_face) {         if(!(_face instanceof Face))            throw "face must be of type Face";                  face = _face;      };      /**       * @return null|Face       */      this.getFace = function() {         return face;      };      /**       * @param HalfEdge _nextHalfEdge       * @throws string       */      this.setNextHalfEdge = function(_nextHalfEdge) {         if(!(_nextHalfEdge instanceof HalfEdge))            throw "nextHalfEdge must be of type HalfEdge";                  nextHalfEdge = _nextHalfEdge;      };      /**       * @return null|HalfEdge       */      this.getNextHalfEdge = function() {         return nextHalfEdge;      };      /**       * @param HalfEdge _twinHalfEdge       * @throws string       */      this.setTwinHalfEdge = function(_twinHalfEdge) {         if(!(_twinHalfEdge instanceof HalfEdge))            throw "twinHalfEdge must be of type HalfEdge";                  twinHalfEdge = _twinHalfEdge;      };      /**       * @return null|HalfEdge       */      this.getTwinHalfEdge = function() {         return twinHalfEdge;      };      /**       * @param int _id fr die Kopie       * @return HalfEdge       * @throws string       */      this.clone = function(_id) {         if(_id === undefined) {            _id = id;         }         else if(isNaN(_id) || Number(_id) !== parseInt(_id) || _id < 0)             throw "id must be of type int >= 0";                  let halfEdge = new HalfEdge(_id);          halfEdge.vertexIndex = vertexIndex;         halfEdge.face = face;         halfEdge.nextHalfEdge = nextHalfEdge;         halfEdge.twinHalfEdge = twinHalfEdge;         return halfEdge;      };      /**       * @return int >= 0       */      this.getId = function() {         return id;      };      /**       * @return string       */      this.toString = function() {         return "Kanten-ID: " + id + "\n" +                 "face-ID: " + (face === null ? "null" : face.id) + "\n" +                "Vertex-Index: " + (vertexIndex === null ? "null" : vertexIndex) + "\n" +                "next-ID: " + (nextHalfEdge === null ? "null" : nextHalfEdge.id) + "\n" +                "twin-ID: " + (twinHalfEdge === null ? "null" : twinHalfEdge.id);      };   }   /**    * @return int >= 0    */   get id() {      return this.getId();   }   /**    * @param int _vertexIndex >= 0    * @throws string    */   set vertexIndex(_vertexIndex) {      this.setVertexIndex(_vertexIndex);   }   /**    * @return null|int >= 0    */   get vertexIndex() {      return this.getVertexIndex();   }   /**    * @param Face _face     * @throws string    */   set face(_face) {      this.setFace(_face);   }   /**    * @return null|Face     */   get face() {      return this.getFace();   }   /**    * @param HalfEdge _nextHalfEdge    * @throws string    */   set nextHalfEdge(_nextHalfEdge) {      this.setNextHalfEdge(_nextHalfEdge);   }   /**    * @return null|HalfEdge    */   get nextHalfEdge() {      return this.getNextHalfEdge();   }   /**    * @param HalfEdge _twinHalfEdge    * @throws string    */   set twinHalfEdge(_twinHalfEdge) {      this.setTwinHalfEdge(_twinHalfEdge);   }   /**    * @return null|HalfEdge    */   get twinHalfEdge() {      return this.getTwinHalfEdge();   }}class Face {   /**    * @param int _id >= 0    */   constructor(_id) {       if(isNaN(_id) || Number(_id) !== parseInt(_id) || _id < 0)          throw "id must be of type int >= 0";      var id = _id;            // private Eigenschaften:      var halfEdge = null;            /**       * @param HalfEdge _halfEdge       * @throws string       */      this.setHalfEdge = function(_halfEdge) {         if(!(_halfEdge instanceof HalfEdge))            throw "halfEdge must be of type HalfEdge";                  halfEdge = _halfEdge;      };      /**       * @return null|HalfEdge       */      this.getHalfEdge = function() {         return halfEdge;      };      /**       * @return int >= 0       */      this.getId = function() {         return id;      };      /**       * @param int _id >= 0 fr die Kopie       * @return Face       * @throws string       */      this.clone = function(_id) {         if(_id === undefined) {            _id = id;         }         else if(isNaN(_id) || Number(_id) !== parseInt(_id) || _id < 0)             throw "id must be of type int >= 0";                  let face = new Face(_id);          face.halfEdge = halfEdge;         return face;      };      /**       * @return string       */      this.toString = function() {         return "face-ID: " + id + "\n" +                 "Kante-ID: " + (halfEdge === null ? "null" : halfEdge.id);      };   }   /**    * @return int >= 0    */   get id() {      return this.getId();   }   /**    * @param HalfEdge _halfEdge    * @throws string    */   set halfEdge(_halfEdge) {      this.setHalfEdge(_halfEdge);   }   /**    * @return null|HalfEdge    */   get halfEdge() {      return this.getHalfEdge();   }}/** * Abstrakte Klasse fr die Klassen Vector und Vertex. */class AVector {   /**    * @param number _x     * @param number _y    * @param number _z    * @throws string    */   constructor(_x, _y, _z) {            // private Eigenschaften:      var x = AVector.check(_x);      var y = AVector.check(_y);      var z = AVector.check(_z);            // ffentliche Methoden:      /**       * @return number        */      this.getX = function() {         return x;      };      /**       * @return number        */      this.getY = function() {         return y;      };      /**       * @return number        */      this.getZ = function() {         return z;      };      /**       * @param number _x       * @throws string       */      this.setX = function(_x) {         x = AVector.check(_x);      };      /**       * @param number _y       * @throws string       */      this.setY = function(_y) {         y = AVector.check(_y);      };      /**       * @param number _z       * @throws string       */      this.setZ = function(_z) {         z = AVector.check(_z);      };   }   /**    * @param number _v     * @throws string    */   static check(_v) {      if(isNaN(_v))         throw "value must be number";      return _v;   }   // setter/getter:   /**    * @param number _x    * @throws string    */   set x(_x) {      this.setX(_x);   }   /**    * @param number _y    * @throws string    */   set y(_y) {      this.setY(_y);   }   /**    * @param number _z    * @throws string    */   set z(_z) {      this.setZ(_z);   }   /**    * @return number     */   get x() {      return this.getX();   }   /**    * @return number     */   get y() {      return this.getY();   }   /**    * @return number     */   get z() {      return this.getZ();   }}/** * Ein Vektor besteht aus den Komponenten x, y und z. * Ein Vektor ist eine Richtungsangabe. */class Vector extends AVector {   /**    * @param number _x     * @param number _y     * @param number _z     * @throws string    */   constructor(_x, _y, _z) {      super(_x, _y, _z);            /**       * Vorzeichen umdrehen        * @return Vector       */      this.negative = function() {         return new Vector(            -this.x,            -this.y,            -this.z         );      };      /**       * @return number        */      this.length = function() {          return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);      };      /**       * @param boolean _self optionell, ob dieser Vektor selbst normalisiert werden soll       * @return Vector        * @throws string       */      this.normalize = function(_self) {          if(_self === undefined)            _self = false;         else if(typeof(_self) !== "boolean")            throw "self must be of type boolean";                  let length = this.length();         if(length == 0)            throw "0-vector cant be normalized";                  if(_self) {            this.x /= length;            this.y /= length;            this.z /= length;         }         else return new Vector(            this.x / length,            this.y / length,            this.z / length         );      };      /**       * @param Vector _other        * @return number        * @throws string        */      this.dot = function(_other) {         if(!(_other instanceof Vector))            throw "other must be of type Vector";                  return this.x * _other.x + this.y * _other.y + this.z * _other.z;      };      /**       * Cross-Produkt zwischen 2 Vectoren.       * @param Vector _other       * @return Vector       * @throws string       */      this.cross = function(_other) {          if(!(_other instanceof Vector))            throw "other must be of type Vector";                  return new Vector(            this.y * _other.z - this.z * _other.y,            this.z * _other.x - this.x * _other.z,            this.x * _other.y - this.y * _other.x         );      };      /**       * Vektor + Vektor = Vektor        * @param Vector _other        * @param boolean _self optionell, ob dieser Vertex selbst homogenisiert werden soll       * @return Vector        * @throws string       */      this.add = function(_other, _self) {         if(_self === undefined)            _self = false;         else if(typeof(_self) !== "boolean")            throw "self must be of type boolean";         if(_self) {            this.x += _other.x;            this.y += _other.y;            this.z += _other.z;         }         else {            return new Vector(               this.x + _other.x,               this.y + _other.y,               this.z + _other.z            );         }      };      /**       * @return array        */      this.asArray = function() {         return [            this.x,            this.y,            this.z         ];      };      /**       * @return string       */      this.toString = function() {         var round = function(_v, _f) {            return Math.round(_v * _f) / _f;         }         let f = 1000;         return "(" + round(this.x, f) + ", " + round(this.y, f) + ", " + round(this.z, f) + ")";      };      /**       * nur zum Testen!!!!       */      this.toVertex = function() {         return new Vertex(            this.x,            this.y,            this.z         );      };   }}/** * Ein Vertex besteht aus den Komponenten x, y, z und w. * Ein Vertex ist eine Positionsangabe. */class Vertex extends AVector {   /**    * @param number _x     * @param number _y     * @param number _z     * @param number _w    * @throws string    */   constructor(_x, _y, _z, _w) {       super(_x, _y, _z);            // private Eigenschaften:      var w = _w === undefined ? 1 : AVector.check(_w); // darf die homogene Komponente auch negativ werden?      var normal = new Vector(0, 0, 0); // Normale auf Vertex            // ffentliche Methoden:      /**       * @return number        */      this.getW = function() {         return w;      };      /**       * @param number _w       * @throws string       */      this.setW = function(_w) {         w = AVector.check(_w);      };            // private Eigenschaften:      var outgoingHalfEdge = null;      // Jeder Vertex verweist auf eine seiner ausgehenden Kanten.            /**       * @param boolean _self optionell, ob dieser Vertex selbst homogenisiert werden soll       * @return Vertex       * @throws string       */      this.homogenize = function(_self) {          if(_self === undefined)            _self = false;         else if(typeof(_self) !== "boolean")            throw "self must be of type boolean";                  if(this.w == 0)            throw "a vector with w = 0 cant be homogenized";         if(_self) {            this.x /= this.w;            this.y /= this.w;            this.z /= this.w;         }         else return new Vertex(            this.x / this.w,            this.y / this.w,            this.z / this.w         );      };            /**       * Ein Vertex minus einem Vertex ergibt einen Vektor.       * @param Vertex _other       * @return Vector       * @throws string       */      this.minus = function(_other) {          if(!(_other instanceof Vertex))            throw "other must be of type Vertex";         let v0 = this.w == 1 ? this : this.homogenize();         let v1 = _other.w == 1 ? _other : _other.homogenize();         return new Vector(            v0.x - v1.x,            v0.y - v1.y,            v0.z - v1.z         );      };            /**       * Vektor + Vektor = Vektor        * @param Vertex _other        * @return Vertex        * @throws string       */      this.add = function(_other) {         if(!(_other instanceof Vertex))            throw "other must be of type Vertex";         let v0 = w == 1 ? this : this.homogenize();         let v1 = _other.w == 1 ? _other : _other.homogenize();         return new Vertex(            v0.x + v1.x,            v0.y + v1.y,            v0.z + v1.z         );      };      /**       * @param number _f       * @return Vertex        * @throws string       */      this.multiply = function(_f) {         if(isNaN(_f))            throw "f must be of type number";         let v0 = w == 1 ? this : this.homogenize();         return new Vertex(            v0.x * _f,            v0.y * _f,            v0.z * _f         );      };      /**       * Fr Phong-Shading:       * Jede Flche muss seine Normale an ihre Vertices bergeben.       * @param Vector _normal Normale jenes Dreiecks, dessen Teil dieser Vertex ist        */      this.addNormal = function(_normal) {         normal.add(_normal, true);      };      /**       * Fr Phong-Shading:       * Nachdem alle addNormal-Befehle ausgefhrt wurde, stellt die Vertex-Normale        * noch keinen Einheitsvektor dar. Das Normalisieren wird hier erledigt.       */      this.getNormal = function() {         return normal.normalize();      };      /**       * @param HalfEdge _outgoingHalfEdge        * @throws string       */      this.setOutgoingHalfEdge = function(_outgoingHalfEdge) {         if(!(_outgoingHalfEdge instanceof HalfEdge))            throw "outgoingHalfEdge must be of type HalfEdge";                  outgoingHalfEdge = _outgoingHalfEdge;      };      /**       * @return null|HalfEdge       */      this.getOutgoingHalfEdge = function() {         return outgoingHalfEdge;      };            /**       * Liefert den Vertex als Vektor zurck.       * Dabei wird der Vertex automatisch homogenisiert.       * @return Vector        * @throws string       */      this.asVector = function() {         let v = this.w == 1 ? this : this.homogenize();         return new Vector(            v.x,            v.y,            v.z         );      };            /**       * @return array        */      this.asArray = function() {         return [            this.x,            this.y,            this.z,            this.w         ];      };      /**       * @return Vertex       * @throws string       */      this.clone = function() {                let vertex = new Vertex(this.x, this.y, this.z, w);          vertex.outgoingHalfEdge = outgoingHalfEdge;         return vertex;      };      /**       * @return string       */      this.toString = function() {         let round = function(v) {            return Math.round(v * 100) / 100;         };         return "(" + round(this.x) + ", " + round(this.y) + ", " + round(this.z) + ")\n" +                "ausgehende Kante-ID: " + (outgoingHalfEdge === null ? "null" : outgoingHalfEdge.id);      };   }   /**    * @return number     */   get w() {      return this.getW();   }   /**    * @param number _w    * @throws string    */   set w(_w) {      this.setW(_w);   }   /**    * @param HalfEdge _outgoingHalfEdge    * @throws string    */   set outgoingHalfEdge(_outgoingHalfEdge) {      this.setOutgoingHalfEdge(_outgoingHalfEdge);   }   /**    * @return null|HalfEdge    */   get outgoingHalfEdge() {      return this.getOutgoingHalfEdge();   }   /**    * @return Vector    */   get normal() {      return this.getNormal();   }}class HalfEdgeDataStructure {   constructor() {      // private Eigenschaften:      var vertices = null;  // indiziertes Array vom Typ Vertex      var faces = null;     // indiziertes Array vom Typ Face      var halfEdges = null; // indiziertes Array vom Typ HalfEdge            /**       * Vertizes setzen.       * @param array _vertices vom Typ Vertex       * @throws string       */      var setVertices = function(_vertices) {         if(Object.prototype.toString.call(_vertices) !== '[object Array]')            throw "vertices must be an array";                  vertices = [];         let count = _vertices.length;         for(let i = 0; i < count; ++i) {            if(!(_vertices[i] instanceof Vertex))               throw "vertices must be an array of type Vertex";         }         vertices = _vertices;      };      /**       * Aus den Vertex- und Index-Listen die half-edge Datenstruktur aufbauen.       * Fllt die privaten Eigenschaften halfEdges und faces auf.       * @param array _vertices vom Typ Vertex       * @param array _indices vom Typ int >= 0       * @throws string       */      this.createByIndices = function(_vertices, _indices) {         /**          * Indizes prfen.          * @param array _indices vom Typ int >= 0          * @throws string          */         let checkIndices = function(_indices) {            if(Object.prototype.toString.call(_indices) !== '[object Array]')               throw "indices must be an array";                        let count = _indices.length;            for(let i = 0; i < count; ++i) {               let index = _indices[i];               if(isNaN(index) || Number(index) !== parseInt(index) || index < 0)                   throw "indices must be an array of type int >= 0";            }         };                  setVertices(_vertices);         checkIndices(_indices);                  // Je Index 1 Kante anlegen:         halfEdges = [];         let count = _indices.length;         for(let i = 0; i < count; ++i) {            halfEdges[i] = new HalfEdge(i, "orig");         }                  faces = [];         let indexMap = []; // assoziatives Array, key vom Typ string, value vom Typ int         let size = 3; // je Dreieck 3 Vertizes/Kanten         for(let i = 0; i < count; i += size) {            // Dreieck            let face = new Face(i / size, "orig");                        // Verweis vom face auf eine seiner Kanten;            face.halfEdge = halfEdges[i];                        for(let j = 0; j < size; ++j) {               // Dreiecks-Vertex/-Kante                              // Indizes fr Index-Array:               let currIndex = i + j;               let nextIndex = i + (j + 1) % size;               let prevIndex = i + (j + size - 1) % size;                                       // Indizes der zur Kante zugehrigen Vertizes:               halfEdges[currIndex].vertexIndex = _indices[currIndex]; // Index Start-Vertex               let endVertexIndex = _indices[nextIndex];               // Index Ziel-Vertex                              // Verweis auf face:               halfEdges[currIndex].face = face;                              // Innerhalb des Dreiecks Verweise auf die vorhergehende und nachfolgende Kanten:               halfEdges[currIndex].nextHalfEdge = halfEdges[nextIndex];               // Die zugewiesenen Kanten sind ev. noch gar nicht vollstndig definiert.               // Dann passiert das in den folgenden Schleifendurchlufen.                              // Verweis des Vertex auf eine seiner anliegenden Kanten:               if(!vertices[_indices[currIndex]].outgoingHalfEdge) {                  vertices[_indices[currIndex]].outgoingHalfEdge = halfEdges[currIndex];               }                              // Verweis der Kanten auf ihre Zwillinge:               let key =                   Math.min(halfEdges[currIndex].vertexIndex, endVertexIndex) +                  "#" +                  Math.max(halfEdges[currIndex].vertexIndex, endVertexIndex);               if(indexMap[key] === undefined) {                  indexMap[key] = currIndex;               }               else {                  halfEdges[currIndex].twinHalfEdge = halfEdges[indexMap[key]];                  halfEdges[indexMap[key]].twinHalfEdge = halfEdges[currIndex];               }            }                        // face in Liste aufnehmen:            faces.push(face);         }      };            /**       * Aus den Daten einer half-edge Datenstruktur eine neue Datenstruktur aufbauen.       * @param array _vertices vom Typ Vertex       * @param array _halfEdges vom Typ HalfEdge       * @param array _faces vom Typ Face       * @throws string       */      this.createByHalfEdges = function(_vertices, _halfEdges, _faces) {         /**          * @param array _halfEdges vom Typ HalfEdge          * @throws string          */         let setHalfEdges = function(_halfEdges) {            if(Object.prototype.toString.call(_halfEdges) !== '[object Array]')               throw "halfEdges must be an array";                        halfEdges = [];            let count = _halfEdges.length;            for(let i = 0; i < count; ++i) {               if(!(_halfEdges[i] instanceof HalfEdge))                  throw "halfEdges must be an array of type HalfEdge";            }            halfEdges = _halfEdges;         };                  /**          * @param array _faces vom Typ Face          * @throws string          */         let setFaces = function(_faces) {            if(Object.prototype.toString.call(_faces) !== '[object Array]')               throw "faces must be an array";                        faces = [];            let count = _faces.length;            for(let i = 0; i < count; ++i) {               if(!(_faces[i] instanceof Face))                  throw "faces must be an array of type Face";            }            faces = _faces;         };                  setVertices(_vertices);         setHalfEdges(_halfEdges);         setFaces(_faces);      };            /**       * Beim subdivide immer ausschlielich immer nur von den alten Daten ausgegangen.       * Sowohl beim neu Erzeugen vom Vertizes, als auch beim Neupositionieren.       * Es werden auch nur die alten Vertizes neu positioniert.       */      this.subdivide = function() {         /**          * @param array _vertices vom Typ Vertex          *    Je Kante kommt ein neuer Vertex hinzu.          *    _vertices mssen also die Vertizes der neuen Datenstruktur sein.          * @param array _halfEdges vom Typ HalfEdge          *    Hier kommen je face-split 2 Kanten hinzu. Jene Kanten, die die resultierenden 2 faces trennen.          *    _halfEdges mssen daher die Kanten der neuen Datenstruktur sein.          * @param array _faces vom Typ Face          *    Aus jedem Dreieck ergeben sich 4 Dreiecke.          *    Das ursprngliche Dreieck bleibt erhalten, ist dann nur kleiner.          *    _faces mssen daher die Flchen der neuen Datenstruktur sein.          */         let refine = function(_vertices, _halfEdges, _faces) {            /**             * Alle alten Kanten durchlaufen und unterteilen.             * @param array _vertices vom Typ Vertex             *    Je Kante kommt ein neuer Vertex hinzu.             *    _vertices mssen also die Vertizes der neuen Datenstruktur sein.             * @param array _halfEdges vom Typ HalfEdge             *    Je Kante kommen 2 neue Kanten hinzu.             *    _halfEdges mssen also die Kanten der neuen Datenstruktur sein.             * @throws string             */            let splitEdges = function(_vertices, _halfEdges) {               /**                * Auf der bergebenen Kante einen neuen Vertex erzeugen.                *            3/8 [1]                *           / | \                *          /  |  \                *    [2] 1/8  x  1/8 [3]    x ... neuer Vertex                *          \  |  /                *           \ | /                *            3/8 [0]                * Zur Berechnung des neuen Vertex drfen nur die alten Nachbar-Vertizes herangezogen werden.                * @param HalfEdge _halfEdge muss eine Kante der alten Datenstruktur sein                * @return Vertex                 * @throws string                */               let createVertex = function(_halfEdge) {                  // Zum Berechnen des neuen Vertex werden 4 Vertizes bentigt:                  let quadVertices = [];                                    // alte Flche links der alten Kante _halfEdge:                  let halfEdge = _halfEdge;                  do {                     let vertexIndex = halfEdge.vertexIndex;                     quadVertices.push(vertices[vertexIndex]);                     halfEdge = halfEdge.nextHalfEdge;                  }                  while(halfEdge != _halfEdge);                  if(quadVertices.length != 3)                     throw "count of vertices must be 3";                  // alte Flche rechts der alten Kante _halfEdge:                  let twinHalfEdge = _halfEdge.twinHalfEdge;                  halfEdge = twinHalfEdge.nextHalfEdge.nextHalfEdge;                  do {                     let vertexIndex = halfEdge.vertexIndex;                     quadVertices.push(vertices[vertexIndex]);                     halfEdge = halfEdge.nextHalfEdge;                  }                  while(halfEdge != twinHalfEdge);                  if(quadVertices.length != 4)                     throw "count of vertices must be 4";                                                      let vertex = quadVertices[0].multiply(3 / 8);                  vertex = vertex.add(quadVertices[1].multiply(3 / 8));                  vertex = vertex.add(quadVertices[2].multiply(1 / 8));                  vertex = vertex.add(quadVertices[3].multiply(1 / 8));                       return vertex;               };                           /**                * Teilt eine Kante mit einem mittels createVertex neuberechneten Vertex.                * aus:                *    x---------->x                 * wird:                *    x---->x---->x                *      alt   neu                * und fr den Zwilling:                * aus:                *    x<----------x                 * wird:                *    x<----x<----x                *      neu   alt                * @param array _halfEdges                 *    Je Kante kommen 2 neue Kanten hinzu.                *    _halfEdges mssen also die Kanten der neuen Datenstruktur sein.                * @param HalfEdge _halfEdge                 *    Die Kante, die geteilt werden soll.                *    _halfEdge muss eine Kante der neuen Datenstruktur sein.                * @param Vertex _vertex dieser neue Vertex wird in die Kante (und dessen Zwilling) eingefgt                * @param int _vertexIndex Index von _vertex fr neue Datenstruktur                */               let splitEdge = function(_halfEdges, _halfEdge, _vertex, _vertexIndex) {                           // Indizes fr neue Kanten:                  let index0 = _halfEdges.length;                   let index1 = index0 + 1;                                    // neue Kante:                  let newHalfEdge = _halfEdge.clone(index0, "clone"); // bernimmt face und nextHalfEdge                  newHalfEdge.vertexIndex = _vertexIndex; // geht vom neuen Index aus                  _halfEdges[index0] = newHalfEdge;                                    // Verweis auf neue Kante:                  _halfEdge.nextHalfEdge = newHalfEdge;                                    // der neue Vertex verweist auf die neue Kante:                  _vertex.outgoingHalfEdge = newHalfEdge;                                    // der Zwilling muss ebenfalls unterteilt werden:                  let twinHalfEdge = _halfEdge.twinHalfEdge;                  let newTwinHalfEdge = twinHalfEdge.clone(index1, "clone"); // bernimmt face und nextHalfEdge                  newTwinHalfEdge.vertexIndex = _vertexIndex; // geht vom neuen Index aus                  _halfEdges[index1] = newTwinHalfEdge;                  // nderung am alten Zwilling:                                   twinHalfEdge.nextHalfEdge = newTwinHalfEdge;                                                      // Zwillingsbeziehungen definieren:                  twinHalfEdge.twinHalfEdge = newHalfEdge;                  _halfEdge.twinHalfEdge = newTwinHalfEdge;               };                           if(Object.prototype.toString.call(_halfEdges) !== '[object Array]')                  throw "halfEdges must be an array";                              let count = halfEdges.length; // Anzahl der Kanten der alten Datenstruktur               let countVertices = vertices.length; // aktueller Stand Anzahl Vertizes               for(let i = 0; i < count; ++i) {                  // Wird eine halfEdge geteilt, bekommt die neu hinzugefgte nextHalfEdge einen vertexIndex >= countHalfEdges.                  // Daran erkennt man, ob eine Kante bereits geteilt wurde. Ist das der Fall, darf sie nicht nochmals geteilt werden.                   if(_halfEdges[i].nextHalfEdge.vertexIndex < countVertices) {                     // Neuen Vertex berechnen:                     let vertex = createVertex(halfEdges[i]); // eine Kante der alten Datenstruktur bergeben                                          // Kante splitten:                     splitEdge(                        _halfEdges, // die Kanten der neuen Datenstruktur                        _halfEdges[i], // eine Kante der neuen Datenstruktur                        vertex,                         _vertices.length // Index fr den neuen Vertex, _vertices mssen also die Vertizes der neuen Datenstruktur sein                     );                                          // Vertex in Liste aufnehmen:                     _vertices.push(vertex);                  }               }            };                     /**             * Flche teilen.             * Es sind bereits alle Vertizes zum Teilen vorhanden.              * Beispiel:             *    Beim ersten Aufruf der Funktion liegt ein Dreieck mit 6 Kanten und 6 Vertizes vor.             *          face verweist auf eine alte Kante (Ausgangspunkt fr den ersten Split)             *            |             *           alt neu             *          x---x---x     _halfEdge ist eine alte Kante der Flche. nextHalfEdge ist eine neue Kante.              *       neu \     / alt  Alte und neue Kanten wechseln sich immer ab.             *            x   x             *         alt \ / neu             *              x             *    Nach dem Split:             *          x---x---x     Das rechte, obere Dreieck ist nun eine neue Flche.             *           \   \ /                *            x   x             *             \ /              *              x             *    Beim zweiten Aufruf muss nur noch die linke, untere Flche betrachtet werden.             *          x---x                  *           \   \ <----- nun ist diese Kante Ausgangspunkt fr den Split               *            x   x             *             \ /              *              x             *    Nach dem Split:             *          x---x                  *           \   \              *            x---x       Das untere Dreieck ist nun eine neue Flche.             *             \ /              *              x             *    Beim dritten (und letzten) Aufruf muss nur noch die obere Flche betrachtet werden.             *           Nun ist diese Kante wieder Ausgangspunkt fr diesen Split.             *            |             *          x---x                  *           \   \              *            x---x                *    Nach dem Split:             *          x---x         Das rechte, untere Dreieck ist nun eine neue Flche.             *           \ / \              *            x---x                * @param array _faces             *    Hier kommt eine neue Flche hinzu.             *    _faces mssen daher die Flchen der neuen Datenstruktur sein.             * @param HalfEdge _halfEdge              *    Eine Kante jener Flche, die geteilt werden soll.             *    _halfEdge muss eine Kante der neuen Datenstruktur sein.             * @param array _halfEdges              *    Hier kommen 2 Kanten hinzu. Jene Kanten, die die resultierenden 2 faces trennen.             *    _halfEdges mssen daher die Kanten der neuen Datenstruktur sein.             */            let refineFace = function(_faces, _halfEdge, _halfEdges) {               let halfEdge1 = _halfEdge;               let halfEdge2 = halfEdge1.nextHalfEdge;               let halfEdge3 = halfEdge2.nextHalfEdge;               let halfEdge4 = halfEdge3.nextHalfEdge;               // Indizes:               let halfEdgeIndex0 = _halfEdges.length;               let halfEdgeIndex1 = halfEdgeIndex0 + 1;                              // neue Kante:               let newHalfEdge = halfEdge2.clone(halfEdgeIndex0, "clone"); // bernimmt face und vertexIndex               newHalfEdge.nextHalfEdge = halfEdge4;               _halfEdges[halfEdgeIndex0] = newHalfEdge;                              // Verweis auf neue Kante:               halfEdge1.nextHalfEdge = newHalfEdge;                              // neue Kante (Zwilling):               let newTwinHalfEdge = halfEdge4.clone(halfEdgeIndex1, "clone"); // bernimmt vertexIndex               newTwinHalfEdge.nextHalfEdge = halfEdge2;               _halfEdges[halfEdgeIndex1] = newTwinHalfEdge;                              // Verweis auf neuen Zwilling:               halfEdge3.nextHalfEdge = newTwinHalfEdge;                              // Zwillingsbeziehungen definieren:               newHalfEdge.twinHalfEdge = newTwinHalfEdge;               newTwinHalfEdge.twinHalfEdge = newHalfEdge;                              // neue Flche:               let face = new Face(_faces.length, "clone");               face.halfEdge = halfEdge2;               _faces.push(face);                              // Kanten der neuen Flche zuordnen:               let halfEdge = halfEdge2;               do {                  halfEdge.face = face;                  halfEdge = halfEdge.nextHalfEdge;               }               while(halfEdge != halfEdge2);            };            // Alle Kanten teilen:            splitEdges(_vertices, _halfEdges); // vertices und halfEdges mssen die Daten der neuen Datenstruktur sein.            // Alle faces durchlaufen und teilen:            let countFaces = _faces.length;            for(let i = 0; i < countFaces; ++i) {               let halfEdge = _faces[i].halfEdge;               refineFace(_faces, halfEdge, _halfEdges);              // alle Parameter               refineFace(_faces, halfEdge.nextHalfEdge, _halfEdges); // mssen die Daten der               refineFace(_faces, halfEdge, _halfEdges);              // neuen Datenstruktur sein.            }         };                  /**          * Die Positionen der neuen Vertizes "weichzeichnen".          * @param array _vertices in der neuen Datenstruktur wird hier aufgefllt          */         let smooth = function(_vertices) {            /**             * Position eines Vertex anpassen. Hier werden nur die alten Vertizes neu positioniert.             * Zur Berechnung werden nur die Nachbar-Vertizes der alten Datenstruktur herangezogen.             * @param Vertex _vertex zentraler Vertex aus der alten Datenstruktur, dessen Position neu berechnet werden soll             * @return Vertex Neuer Vertex mit angepasster Position fr die neue Datenstruktur             * @throws string             */            let smoothVertex = function(_vertex, _newVertexId) {                  if(!(_vertex instanceof Vertex))                  throw "vertex must be of type Vertex";                              /**                * Gibt alle Indizes der zu einem Vertex benachbarten Vertizes aus der alten Datenstruktur zurck.                 * Dieser Algorithmus funktioniert nur bei geschlossenen Objekten.                * @param Vertex _vertex                * @throws string                */               let neighbourVertexIndices = function(_vertex) {                  if(!(_vertex instanceof Vertex))                     throw "vertex must be of type Vertex";                                    let indices = [];                  let firstHalfEdge = _vertex.outgoingHalfEdge; // in der alten Datenstruktur die vom Vertex ausgehende Kante                                    let halfEdge = firstHalfEdge;                  do {                     let twinHalfEdge = halfEdge.twinHalfEdge;                     indices.push(twinHalfEdge.vertexIndex);                     halfEdge = twinHalfEdge.nextHalfEdge;                  }                  while(halfEdge != firstHalfEdge);                  return indices;               };               // Alle Nachbar-Vertizes aus der alten Datenstruktur auslesen:               let neighbourIndices = neighbourVertexIndices(_vertex);                              let count = neighbourIndices.length;               if(count < 3)                  throw "In a solid object each vertex must have at least 3 neighbour-vertices.";                              // Alle Nachbar-Vertizes aufsummieren:               let vertex = new Vertex(0, 0, 0);                for(let i = 0; i < count; ++i) {                  let neighbourVertex = vertices[neighbourIndices[i]];                        vertex = vertex.add(neighbourVertex);               }                              // Berechnung von beta nach der Variante von Warren und Weimer:               //let beta = 3 / (count * (count + 2));               //if(beta <= 0 || beta >= 1)               //   throw "calculation error; beta must be ]0, 1[";                              // Berechnung von beta nach Loop:               let beta = 1 / count * (5 / 8 - Math.pow(3 + 2 * Math.cos(2 * Math.PI / count), 2) / 64);               if(beta <= 0 || beta >= 1)                  throw "calculation error; beta must be ]0, 1[";                              // Gewichtung der Vertizes:               vertex = vertex.multiply(beta);               vertex = _vertex.multiply(1 - count * beta).add(vertex);                              return vertex;             };            // Alle alten Vertices durchlaufen und neu positionieren.            let count = vertices.length;                        for(let i = 0; i < count; ++i) {                              // Neuer Vertex mit neuer Position:               let vertex = smoothVertex(vertices[i], _vertices.length);               vertex.outgoingHalfEdge = _vertices[i].outgoingHalfEdge;               _vertices[i] = vertex;            }         };                  // Vertizes kopieren:         let newVertices = [];         let count = vertices.length;         for(let i = 0; i < count; ++i) {            newVertices[i] = vertices[i].clone(i, "clone");         }                  // Kanten kopieren:         let newHalfEdges = [];         count = halfEdges.length;         for(let i = 0; i < count; ++i) {            newHalfEdges[i] = halfEdges[i].clone(i, "clone");         }                  // Flchen kopieren:         let newFaces = [];         count = faces.length;         for(let i = 0; i < count; ++i) {            newFaces[i] = faces[i].clone(i, "clone");         }                  // Beziehungen Vertizes:         count = vertices.length;         for(let i = 0; i < count; ++i) {            // Verweis auf die ausgehende Kante:            let outgoingHalfEdge = vertices[i].outgoingHalfEdge;            if(!outgoingHalfEdge)               throw "each vertex must have an outgoing halfEdge";            newVertices[i].outgoingHalfEdge = newHalfEdges[outgoingHalfEdge.id];         }                  // Beziehungen Kanten:         count = halfEdges.length;         for(let i = 0; i < count; ++i) {            let halfEdge = halfEdges[i];                        // Verweis auf nchste Kante:            let nextHalfEdge = halfEdge.nextHalfEdge;            if(!nextHalfEdge)               throw "each halfEdge must have a next halfEdge";            newHalfEdges[i].nextHalfEdge = newHalfEdges[nextHalfEdge.id];                        // Verweis auf Zwilling:            let twinHalfEdge = halfEdge.twinHalfEdge;            if(!twinHalfEdge)               throw "each halfEdge must have a twin halfEdge";            newHalfEdges[i].twinHalfEdge = newHalfEdges[twinHalfEdge.id];                        // Verweis auf Flche:            let face = halfEdge.face;            if(!face)               throw "each halfEdge must have a face";            newHalfEdges[i].face = newFaces[face.id];         }          // Beziehungen Flchen:         count = faces.length;         for(let i = 0; i < count; ++i) {            // Verweis auf eine Kante:            let halfEdge = faces[i].halfEdge;            if(!halfEdge)               throw "each face must have a halfEdge";            newFaces[i].halfEdge = newHalfEdges[halfEdge.id];         }         // Flchen der neuen Datenstruktur teilen:         refine(newVertices, newHalfEdges, newFaces); // alle Parameter mssen die Daten der neuen Datenstruktur sein         // Die Vertizes der neuen Datenstruktur "weichzeichnen":         let dataStructure = new HalfEdgeDataStructure();         smooth(newVertices); // mssen die Vertizes der neuen Datenstruktur sein                dataStructure.createByHalfEdges(newVertices, newHalfEdges, newFaces);                          return dataStructure;      };            /**       * @return null|array vom Typ Vertex       */      this.getVertices = function() {         return vertices;      };            /**       * Je Flche (Dreieck) 3 Vertizes. Je Vertex x, y, z und w.       * @return array 2-dim Array vom Typ Vertex       */      this.getFaceVertices = function() {         let returnFaces = [];                  // alle Flchen durchlaufen:         let countFaces = faces.length;         for(let i = 0; i < countFaces; ++i) {            let returnVertices = [];                        let face = faces[i];            let firstHalfEdge = face.halfEdge; // die erste Kante der Flche            let halfEdge = firstHalfEdge;            do {               // Vertex-Position:               let vertex = vertices[halfEdge.vertexIndex];               returnVertices.push(vertex);                              halfEdge = halfEdge.nextHalfEdge;            }            while(halfEdge != firstHalfEdge);                        returnFaces.push(returnVertices);         }                  return returnFaces;      };   }   /**    * @return null|array vom Typ Vertex    */   get vertices() {      return this.getVertices();   }}function start() {   let z;   let vertices;   let indices;   if(false) {      // Tetraeder (Dreieckspyramide):      z = Math.tan(30 * Math.PI / 180); // 0.577      vertices = [         // Grundflche:         new Vertex(0, 0, 0),          new Vertex(2, 0, 0),          new Vertex(1, 0, Math.sqrt(3)), // (1, 0, 1.732)         // oben:         new Vertex(1, Math.sqrt(2), z) // (1, 1.414, 0.577)      ];      indices = [ // faces                 halfEdges             next  twin         0, 1, 2,     // [0] ... unten         [0] ... von 0 nach 1  [1]   [11]                      //                       [1] ... von 1 nach 2  [2]   [5]                      //                       [2] ... von 2 nach 0  [0]   [6]         1, 3, 2,     // [1] ... rechts vorne  [3] ... von 1 nach 3  [4]   [10]                      //                       [4] ... von 3 nach 2  [5]   [7]                      //                       [5] ... von 2 nach 1  [3]   [1]         0, 2, 3,     // [2] ... links vorne   [6] ... von 0 nach 2  [7]   [2]                      //                       [7] ... von 2 nach 3  [8]   [4]                      //                       [8] ... von 3 nach 0  [6]   [9]         0, 3, 1      // [3] ... hinten        [9] ... von 0 nach 3  [10]  [8]                      //                       [10] .. von 3 nach 1  [11]  [3]                      //                       [11] .. von 1 nach 0  [9]   [0]      ];   }   else {      //let s = new Sphere(2);      //vertices = s.vertices;      //indices = s.indices;      //z = 0;            let s = new Icosahedron();      vertices = s.vertices;      indices = s.indices;      z = 0;   }      // half-edge Datenstruktur anlegen:   let object = new HalfEdgeDataStructure();   object.createByIndices(vertices, indices);      // subdivisions:   object = object.subdivide();   object = object.subdivide();   object = object.subdivide();      let faceVertices = object.getFaceVertices();         /**    * @param array _faces    * @return array    */   let getNormals = function(_faces) {      let normals = [];            // Alle Dreiecke durchlaufen:      let count = _faces.length;      for(let i = 0; i < count; ++i) {         let vertices = _faces[i]; // Dreieck mit 3 Vertizes         let v01 = vertices[1].minus(vertices[0]).normalize();         let v02 = vertices[2].minus(vertices[0]).normalize();                           // Normale fr die 3 Vertizes:         let normal = v01.cross(v02);         normal = normal.normalize();         normals.push([normal, normal, normal]);      }            return normals;   };      let faceNormals = getNormals(faceVertices); // Vektoren      /**    * @param array _faces    * @return array    */   let flatten = function(_faces) {      let webglValues = [];            let countI = _faces.length;      for(let i = 0; i < countI; ++i) {         let values = _faces[i];                  let countJ = values.length; // Vertex oder Vector         for(let j = 0; j < countJ; ++j) {            webglValues = webglValues.concat(values[j].asArray());         }      }            return webglValues;   }      let webGlVertices = flatten(faceVertices);   let webGlNormals = flatten(faceNormals);      /**    * Matrix-Werte fr WebGL aufbereiten.    * @param AMatrix _matrix     * @return array     * @throws string    */   let flattenMatrix = function(_matrix) {      if(!(_matrix instanceof AMatrix))         throw "matrix must be of type AMatrix";            // Indizes fr WebGL fr 4x4-Matrix:      // | 0 4 8  12 |      // | 1 5 9  13 |      // | 2 6 10 14 |      // | 3 7 11 15 |      // Fr 3x3-Matrix:      // | 0 3 6 |      // | 1 4 7 |      // | 2 5 8 |            let webglValues = [];            let values = _matrix.values;      let count = values.length;      for(let j = 0; j < count; ++j) { // Spalten         for(let i = 0; i < count; ++i) { // Zeilen            webglValues.push(values[i][j]);         }      }            return webglValues;   };         let canvas = document.getElementById("glcanvas");   let gl = webGL(canvas);      // Initialize the GL context   if (gl) {      gl.clearColor(0.0, 0.0, 0.0, 1.0);  // Clear to black, fully opaque      gl.clearDepth(1.0);                 // Clear everything      gl.enable(gl.DEPTH_TEST);           // Enable depth testing      gl.depthFunc(gl.LEQUAL);            // Near things obscure far things           // Initialize the shaders; this is where all the lighting for the      // vertices and so forth is established.      // Create the shader program      let shaderProgram = gl.createProgram();      gl.attachShader(shaderProgram, getShader(gl, "shader-vs"));      gl.attachShader(shaderProgram, getShader(gl, "shader-fs"));      gl.linkProgram(shaderProgram);      if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {         alert("Unable to initialize the shader program.");      }      gl.useProgram(shaderProgram);                  // von oben nach unten blicken:      let camera = new JitterCamera(         new Vertex(0, 0, 4.5), // Position          new Vertex(0, 0, 0), // wohin die Kamera ausgerichtet ist         new Vector(0, 1, 0), // Kamera-up-Vektor         45 * Math.PI / 180, // fov in aus yz-Ebene         canvas.width,          canvas.height,          0.1, // near-plane         20 // far-plane      );            let mvMatrix = camera.viewMatrix; // fr die model-Matrix wird eine identity gewhlt      let mvNMatrix = mvMatrix.inverse().toMatrix3().transpose();      let mvpMatrix = camera.projectionMatrix.multiplyMatrix(mvMatrix);         // Positionen Vertices:      let vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "a_position");      gl.enableVertexAttribArray(vertexPositionAttribute);      let verticesBuffer = gl.createBuffer();      gl.bindBuffer(gl.ARRAY_BUFFER, verticesBuffer);      gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(webGlVertices), gl.STATIC_DRAW);      gl.vertexAttribPointer(vertexPositionAttribute, 4, gl.FLOAT, false, 0, 0); // x, y, z und w            // Vertex-Normale:      let mVNAttribute = gl.getAttribLocation(shaderProgram, "a_normal"); // Vertex-Normale im model-space       gl.enableVertexAttribArray(mVNAttribute);      let verticesNormalBuffer = gl.createBuffer();      gl.bindBuffer(gl.ARRAY_BUFFER, verticesNormalBuffer);      gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(webGlNormals), gl.STATIC_DRAW);       gl.vertexAttribPointer(mVNAttribute, 3, gl.FLOAT, false, 0, 0); // x, y und z               // Uniforms:      let mvpMatrixUniform = gl.getUniformLocation(shaderProgram, "mvpMatrix");      gl.uniformMatrix4fv(mvpMatrixUniform, false, new Float32Array(flattenMatrix(mvpMatrix)));      let mvNMatrixUniform = gl.getUniformLocation(shaderProgram, "mvNMatrix");      gl.uniformMatrix3fv(mvNMatrixUniform, false, new Float32Array(flattenMatrix(mvNMatrix)));       // Licht vInvLUniform:      let vInvLUniform = gl.getUniformLocation(shaderProgram, "vNegativeL");      let vMatrix3 = mvMatrix.toMatrix3(); // links oberen 3x3 Elemente der view-Matrix      let directionLight = new DirectionalLight(new Color(1, 1, 1), (new Vector(0.2, 0, -1)).normalize());      let vNegativeL = vMatrix3.multiplyVector(directionLight.direction.negative()).normalize();      gl.uniform3f(vInvLUniform, vNegativeL.x, vNegativeL.y, vNegativeL.z);         // Clear the canvas before we start drawing on it.      gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);           gl.drawArrays(gl.TRIANGLES, 0, faceVertices.length * 3); // Anzahl Vertices = Anzahl faces * 3   }}