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 }}