WaBis

walter.bislins.ch

Datei: JSG3D: jsg3d.js

Inhalt der Datei: javascript/jsg3d/src/jsg3d.js
// jsg3d.js, (C) Walter Bislin, walter.bislins.ch, August 2013
//
// description and download:
//  http://walter.bislins.ch/doku/jsg3d
//
// dependecies:
//  x.js
//  jsg.js
//
// History:
//  27.05.2016 new functions BezierCurveOnPlane, BezierCurveToOnPlane, SplineCurveOnPlane
//  24.05.2016 some functions also accept JsgVect2 instead of x, y
/////////////////////////////////
// JsgPolygon and JsgPolygonList iterator

function JsgPolyListIter( polys ) {
  // polys: JsgPolygon or JsgPolygonList
  polys = xDefObjOrNull( polys, null );
  this.Reset( polys );
}

JsgPolyListIter.prototype.Reset = function( polys ) {
  // polys: JsgPolygon or JsgPolygonList or undefined
  this.CurrPolyIx = -1;
  this.CurrPointIx = -1;
  if (JsgPolygonList.Ok(polys)) {
    this.Poly = null;
    this.PolyList = polys;
  } else if (JsgPolygon.Ok(polys)) {
    this.Poly = polys;
    this.PolyList = null;
  }
  if (this.PolyList && this.PolyList.Size > 0) {
    this.CurrPolyIx = 0;
    this.Poly = polys.PolyList[0];
  }
  return this;
}

JsgPolyListIter.prototype.GetNextPoint = function( p ) {
  // p: JsgVect3
  // returns false if no more points found
  var poly = this.Poly;
  if (!poly) return false;
  this.CurrPointIx++;
  if (this.PolyList) {
    if (this.CurrPointIx >= poly.Size) {
      this.CurrPolyIx++;
      if (this.CurrPolyIx >= this.PolyList.Size) return false;
      this.Poly = this.PolyList.PolyList[this.CurrPolyIx];
      this.CurrPointIx = -1;
      return this.GetNextPoint( p );
    }
  } else {
    if (this.CurrPointIx >= poly.Size) return false;
  }
  var i = this.CurrPointIx;
  JsgVect3.Set( p, poly.X[i], poly.Y[i], poly.Z[i] );
  return true;
}

JsgPolyListIter.prototype.Back = function() {
  // requires this.CurrPointIx >= 0
  this.CurrPointIx--;
}

/////////////////////////////////
// Vectors and Matrices
// Note: JsgVect3 is also a JsgVect2. Functions that accept a JsgVect2 also accept a JsgVect3 (but not in the reverse!)

var JsgVect3 = {

  New: function( x, y, z ) {
    return [ x, y, z ];
  },

  Null: function() {
    return [ 0, 0, 0 ];
  },

  Ok: function( vec ) {
    return xArray(vec) && (vec.length == 3);
  },

  Reset: function( vec ) {
    vec[0] = 0; vec[1] = 0; vec[2] = 0;
    return vec;
  },

  Set: function( vec, x, y, z ) {
    vec[0] = x; vec[1] = y; vec[2] = z;
    return vec;
  },

  Copy: function( src ) {
    return [ src[0], src[1], src[2] ];
  },

  CopyTo: function( src, dest ) {
    dest[0] = src[0]; dest[1] = src[1]; dest[2] = src[2];
    return dest;
  },

  FromAngle: function( aHAng, aVAng, aDist ) {
    // angles are in degree. returns a vector of length aDist and direction
    // direction defined by the angles.
    // aXAng: number as degrees
    // aDist: number
    // return: JsgVect3
    aVAng *= Math.PI / 180;
    aHAng *= Math.PI / 180;
    var z = aDist * Math.sin( aVAng );
    var a = aDist * Math.cos( aVAng );
    var x = a * Math.cos( aHAng );
    var y = a * Math.sin( aHAng );
    return [x,y,z];
  },

  Scale: function( v, s ) {
    return [ v[0]*s, v[1]*s, v[2]*s ];
  },

  ScaleTo: function( v, s ) {
    v[0] *= s; v[1] *= s; v[2] *= s;
    return v;
  },

  Length2: function( v ) {
    return v[0]*v[0] + v[1]*v[1] + v[2]*v[2];
  },

  Length: function( v ) {
    return Math.sqrt( v[0]*v[0] + v[1]*v[1] + v[2]*v[2] );
  },

  Norm: function( v ) {
    var s = this.Length( v );
    if (s == 0.0) {
      return [ v[0], v[1], v[2] ];
    } else {
      return [ v[0]/s, v[1]/s, v[2]/s ];
    }
  },

  NormTo: function( v ) {
    var s = this.Length( v );
    if (s != 0.0) this.ScaleTo( v, 1/s );
    return v;
  },

  Add: function( a, b ) {
    return [ a[0]+b[0], a[1]+b[1], a[2]+b[2] ];
  },

  AddTo: function( a, b ) {
    a[0] += b[0]; a[1] += b[1]; a[2] += b[2];
    return a;
  },

  Sub: function( a, b ) {
    return [ a[0]-b[0], a[1]-b[1], a[2]-b[2] ];
  },

  SubFrom: function( a, b ) {
    a[0] -= b[0]; a[1] -= b[1]; a[2] -= b[2];
    return a;
  },

  ScalarProd: function( a, b ) {
    return a[0]*b[0] + a[1]*b[1] + a[2]*b[2];
  },

  Mult: function( a, b ) {
    return [
      a[1] * b[2] - a[2] * b[1],
      a[2] * b[0] - a[0] * b[2],
      a[0] * b[1] - a[1] * b[0]
    ];
  },

  MultTo: function( v, a, b ) {
    v[0] = a[1] * b[2] - a[2] * b[1];
    v[1] = a[2] * b[0] - a[0] * b[2];
    v[2] = a[0] * b[1] - a[1] * b[0];
    return v;
  },

};

///////////////////

var JsgVect3List = {

  Ok: function( obj ) {
    if (!xArray(obj)) return false;
    if (obj.length == 0) return true;
    return JsgVect3.Ok(obj[0]);
  },

  ToPoly2D: function( vl, poly ) {
    // vl: array of JsgVect3
    // poly: JsgPolygon or undefined
    // returns: poly or new JsgPolygon
    poly = poly || new JsgPolygon();
    poly.Reset();
    var len = vl.length;
    for (var i = 0; i < len; i++) {
      var v = vl[i];
      poly.AddPoint( v[0], v[1] );
    }
    return poly;
  },

};

///////////////////

var JsgVect3Grid = {

  Ok: function( obj ) {
    if (!xArray(obj)) return false;
    if (obj.length == 0) return true;
    return JsgVect3List.Ok(obj[0]);
  },

};

//////////////////

var JsgVect3InterGrid = {

  Ok: function( obj ) {
    return JsgVect3Grid.Ok( obj );
  },

};

//////////////////

var JsgMat3 = {

  Zero: function( ) {
    return [ [ 0, 0, 0, 0 ], [ 0, 0, 0, 0 ], [ 0, 0, 0, 0 ] ];
  },

  Unit: function( ) {
    return [ [ 1, 0, 0, 0 ], [ 0, 1, 0, 0 ], [ 0, 0, 1, 0 ] ];
  },

  FromVect: function( v1, v2, v3 ) {
    return [
      [ v1[0], v1[1], v1[2], 0 ],
      [ v2[0], v2[1], v2[2], 0 ],
      [ v3[0], v3[1], v3[2], 0 ]
    ];
  },

  Moving: function( x, y, z, m ) {
    var r = [ [ 1, 0, 0, x ], [ 0, 1, 0, y ], [ 0, 0, 1, z ] ];
    if (m) r = this.Mult( r, m );
    return r;
  },

  Scaling: function( sx, sy, sz, m ) {
    var r = [ [ sx, 0, 0, 0 ], [ 0, sy, 0, 0 ], [ 0, 0, sz, 0 ] ];
    if (m) r = this.Mult( r, m );
    return r;
  },

  RotatingX: function( aAngle, m ) {
    var c = Math.cos( aAngle ), s = Math.sin( aAngle );
    var r = [ [ 1, 0, 0, 0 ], [ 0, c, -s, 0 ], [ 0, s, c, 0 ] ];
    if (m) r = this.Mult( r, m );
    return r;
  },

  RotatingY: function( aAngle, m ) {
    var c = Math.cos( aAngle ), s = Math.sin( aAngle );
    var r = [ [ c, 0, s, 0 ], [ 0, 1, 0, 0 ], [ -s, 0, c, 0 ] ];
    if (m) r = this.Mult( r, m );
    return r;
  },

  RotatingZ: function( aAngle, m ) {
    var c = Math.cos( aAngle ), s = Math.sin( aAngle );
    var r = [ [ c, -s, 0, 0 ], [ s, c, 0, 0 ], [ 0, 0, 1, 0 ] ];
    if (m) r = this.Mult( r, m );
    return r;
  },

  Copy: function( m ) {
    return [ [m[0][0],m[0][1],m[0][2],m[0][3]], [m[1][0],m[1][1],m[1][2],m[1][3]], [m[2][0],m[2][1],m[2][2],m[2][3]] ];
  },

  CopyTo: function( src, dest ) {
    for (var row = 0; row < 3; row++) {
      for (var col = 0; col < 4; col++) {
        dest[row][col] = src[row][col];
      }
    }
    return dest;
  },

  Mult: function( a, b ) {
    return this.MultTo( a, b, this.Zero() );
  },

  MultTo: function( a, b, to ) {
    for (var i = 0; i < 3; i++) {
      for (var j = 0; j < 3; j++) {
        to[i][j] = a[i][0] * b[0][j] + a[i][1] * b[1][j] + a[i][2] * b[2][j];
      }
      to[i][3] = a[i][0] * b[0][3] + a[i][1] * b[1][3] + a[i][2] * b[2][3] + a[i][3];
    }
    return to;
  },

  Trans: function( m, v ) {
    return this.TransTo( m, v, JsgVect3.Null() );
  },

  TransTo: function( m, v, to ) {
    // m: JsgMat3
    // v: JsgVect3
    // to: JsgVect3 or undefined
    // return to or v
    // if to is undefined result is stored in v (v = to allowed)
    to = to || v;
    return this.TransXYZTo( m, v[0], v[1], v[2], to )
  },

  TransXYZTo: function( m, x, y, z, to ) {
    // m: JsgMat3
    // x,y,z: number
    // to: JsgVect3
    // stores result of m * v in to
    to[0] = m[0][0] * x + m[0][1] * y + m[0][2] * z + m[0][3];
    to[1] = m[1][0] * x + m[1][1] * y + m[1][2] * z + m[1][3];
    to[2] = m[2][0] * x + m[2][1] * y + m[2][2] * z + m[2][3];
    return to;
  },

  TransList: function( m, vl ) {
    // vl: array[] of JsgVect3
    var rl = [], len = vl.length;
    for (var i = 0; i < len; i++) { rl.push( this.Trans( m, vl[i] ) ); }
    return rl;
  },

  TransGrid: function( m, vg ) {
    // vg: array[i][j] of JsgVect3
    var rg = [], len = vg.length;
    for (var i = 0; i < len; i++) { rg.push( this.TransList( m, vg[i] ) ); }
    return rg;
  },

};

////////////////////////////////////
// JsgCamera

function JsgCamera( ) {
  // Note: primary properties are CamViewCenter, CamPos, CamUp, ScreenSize, SceneSize
  // Computed from these: ViewCenterDist, ViewDir, CamTrans, ScreenDist

  this.CamViewCenter = JsgVect3.Null();
  this.CamPos = JsgVect3.New( 100, 0, 0 );
  this.CamUp = JsgVect3.New( 0, 0, 1 );
  this.ViewDir = JsgVect3.New( -1, 0, 0 );

  // some props to hold temp data
  this.WorkPoint  = JsgVect3.Null();
  this.WorkPoint2 = JsgVect3.Null();
  this.ResultPoly = new JsgPolygon( true, 'JsgCamera.ResultPoly' );

  this.Reset();
}

JsgCamera.prototype.Reset = function() {
  this.Aperture = 1;
  this.Zoom = 1;
  this.SceneSize = 2;
  this.ScreenSize = 1;
  this.ObjectZExtend = 0;
  this.HighResolution = '';
  JsgVect3.Reset( this.CamViewCenter );
  JsgVect3.Set( this.CamPos, 100, 0, 0 );
  JsgVect3.Set( this.CamUp, 0, 0, 1 );
  this.Update();
}

JsgCamera.prototype.Update = function() {
  // is called after each camera position and view change to compute/normalize internal data
  var vVect = JsgVect3.Sub( this.CamViewCenter, this.CamPos );
  this.ViewCenterDist = JsgVect3.Length( vVect );
  if (this.ViewCenterDist == 0) {
    this.CamPos = JsgVect3.Add( this.CamViewCenter, [1,0,0] );
    this.ViewCenterDist = 1;
    vVect = [-1,0,0];
  }
  this.ViewDir = JsgVect3.Norm( vVect );
  var xVect = JsgVect3.Scale( this.ViewDir, -1 );
  var zVect = JsgVect3.Norm( this.CamUp );
  if (JsgVect3.Length2(zVect) == 0) { zVect = [0,0,1]; }
  var yVect = JsgVect3.Norm( JsgVect3.Mult( zVect, xVect ) );
  if (JsgVect3.Length2(yVect) == 0) {
    this.CamUp = JsgMat3.Trans( JsgMat3.FromVect([0,0,1],[0,0,0],[1,1,0]), this.CamUp );
    this.Update();
    return;
  }
  zVect = JsgVect3.Norm( JsgVect3.Mult( xVect, yVect ) );
  this.CamTrans = JsgMat3.FromVect( xVect, yVect, zVect );
  this.ScreenDist = 1;
  if (this.SceneSize != 0) {
    this.ScreenDist = (this.ScreenSize / this.SceneSize) * (this.ViewCenterDist - this.ObjectZExtend);
  }
}

JsgCamera.prototype.Set = function( aParams ) {
  if (!xObj(aParams)) return;

  if (xNum(aParams.SceneSize)) this.SceneSize = aParams.SceneSize;
  if (xNum(aParams.ScreenSize)) this.ScreenSize = aParams.ScreenSize;
  if (xNum(aParams.ObjectZExtend)) this.ObjectZExtend = aParams.ObjectZExtend;
  if (xNum(aParams.Zoom)) this.Zoom = aParams.Zoom;
  if (xNum(aParams.Aperture)) this.Aperture = aParams.Aperture;
  if (xStr(aParams.HighResolution)) this.HighResolution = aParams.HighResolution;
  if (JsgVect3.Ok(aParams.CamViewCenter)) this.CamViewCenter = aParams.CamViewCenter;
  if (JsgVect3.Ok(aParams.CamUp)) this.CamUp = aParams.CamUp;
  if (JsgVect3.Ok(aParams.CamPos)) this.CamPos = aParams.CamPos;

  this.SetAngle( aParams.CamHAng, aParams.CamVAng, aParams.CamDist );
  this.Update();
}

JsgCamera.prototype.SetAngle = function( aHAng, aVAng, aDist ) {
  // private function
  if (xNum(aHAng) || xNum(aVAng)) {
    var hAng = xDefNum( aHAng, 0 );
    var vAng = xDefNum( aVAng, 0 );
    var vVect = JsgVect3.Sub( this.CamViewCenter, this.CamPos );
    var dist = JsgVect3.Length( vVect );
    if (xNum(aDist)) dist = aDist;
    this.CamPos = JsgVect3.Add( this.CamViewCenter, JsgVect3.FromAngle( hAng, vAng, dist ) );
  }
}

JsgCamera.prototype.Save = function( aParams ) {
  var par = xDefObj( aParams, {} );
  par.SceneSize = this.SceneSize;
  par.ScreenSize = this.ScreenSize;
  par.ObjectZExtend = this.ObjectZExtend;
  par.CamViewCenter = this.CamViewCenter;
  par.CamUp = this.CamUp;
  par.CamPos = this.CamPos;
  par.Zoom = this.Zoom;
  par.Aperture = this.Aperture;
  par.HighResolution = this.HighResolution;
  return par;
}

JsgCamera.prototype.SetScale = function( aSceneSize, aScreenSize, aObjectZExtend, aZoom ) {
  if (xNum(aSceneSize)) this.SceneSize = aSceneSize;
  if (xNum(aScreenSize)) this.ScreenSize = aScreenSize;
  if (xNum(aObjectZExtend)) this.ObjectZExtend = aObjectZExtend;
  if (xNum(aZoom)) this.Zoom = aZoom;
  this.Update();
}

JsgCamera.prototype.SetPos = function( aPos, aViewCenter, aUp ) {
  if (JsgVect3.Ok(aPos)) this.CamPos = aPos;
  if (JsgVect3.Ok(aViewCenter)) this.CamViewCenter = aViewCenter;
  if (JsgVect3.Ok(aUp)) this.CamUp = aUp;
  this.Update();
}

JsgCamera.prototype.SetView = function( aViewCenter, aHAng, aVAng, aDist, aUp ) {
  // Angles are in degree!
  if (JsgVect3.Ok(aViewCenter)) this.CamViewCenter = aViewCenter;
  if (JsgVect3.Ok(aUp)) this.CamUp = aUp;
  this.SetAngle( aHAng, aVAng, aDist );
  this.Update();
}

JsgCamera.prototype.SetZoom = function( aZoom ) {
  this.Zoom = xDefNum( aZoom, 1 );
  // this.UpdateCamera(); not needet!
}

JsgCamera.prototype.SetAperture = function( aAperture ) {
  this.Aperture = xDefNum( aAperture, 1 );
  // this.UpdateCamera(); not needet!
}

JsgCamera.prototype.SetHighResolution = function( aMode ) {
  // aMode: string ('on', 'off', ''), '' -> default see this.HighResolution
  this.HighResolution = xDefStr( aMode, '' );
  // this.UpdateCamera(); not needet!
}

JsgCamera.prototype.TransPoly = function( poly, transPolys ) {
  // poly: JsgPolygon(3D)
  // transPolys: JsgPolygon or JsgPolygonList or undefined
  // if transPolys is undefined then this.ResultPoly is used
  // returns transPolys or this.ResultPoly
  // applies camera perspective transformation on poly
  transPolys = transPolys || this.ResultPoly.Reset();
  var p = this.WorkPoint;
  var xs = poly.X;
  var ys = poly.Y;
  var zs = poly.Z;
  var f = this.ScreenDist;
  var zoom = this.Zoom;
  var size = poly.Size;
  for (var i = 0; i < size; i++) {
    JsgVect3.Set( p, xs[i], ys[i], zs[i] );
    JsgVect3.SubFrom( p, this.CamPos );
    JsgMat3.TransTo( this.CamTrans, p );
    var x = p[1];
    var y = p[2];
    var z = -p[0];
    if (z != 0) {
      x *= f / z * zoom;
      y *= f / z * zoom;
    }
    transPolys.AddPoint( x, y, z );
  }
  return transPolys;
}

JsgCamera.prototype.Trans = function( v ) {
  // v: JsgVect3
  // returns new JsgVect3
  return this.TransTo( v[0], v[1], v[2], JsgVect3.Copy(v) );
}

JsgCamera.prototype.TransTo = function( x, y, z, v ) {
  // v: JsgVect3
  // returns v or this.WorkPoint
  v = v || this.WorkPoint;
  JsgVect3.Set( v, x, y, z );
  JsgMat3.TransTo( this.CamTrans, JsgVect3.SubFrom( v, this.CamPos ) );
  var f = this.ScreenDist;
  var x = v[1], y = v[2], z = -v[0];
  if (z != 0) {
    x *= f / z * this.Zoom;
    y *= f / z * this.Zoom;
  }
  v[0] = x; v[1] = y; v[2] = z;
  return v;
}

JsgCamera.prototype.TransList = function( vl ) {
  // vl: JsgVect3List
  // returns: new JsgVect3List
  var rl = [], len = vl.length;
  for (var i = 0; i < len; i++) rl.push( this.Trans( vl[i] ) );
  return rl;
}

JsgCamera.prototype.TransGrid = function( vg ) {
  // vg: JsgVect3Grid
  // returns: new JsgVect3Grid
  var rg = [], len = vg.length;
  for (var i = 0; i < len; i++) rg.push( this.TransList( vg[i] ) );
  return rg;
}

JsgCamera.prototype.TransToPoly2D = function( vl, poly ) {
  // vl: JsgVect3List
  // poly: JsgPolygon or undefined
  // returns: poly or new JsgPolygon
  return JsgVect3List.ToPoly2D( this.TransList( vl ), poly );
}


////////////////////////////////////
// JsgScene

function JsgScene( aAmbientLight, aCamLight ) {
  // Creates an new scene without any light sources.
  // aAmbientLight, aCamLight: JsgColor
  this.AmbientLight = JsgColor.Ok(aAmbientLight) ? aAmbientLight : JsgColor.White();
  this.CamLight = JsgColor.Ok(aCamLight) ? aCamLight : JsgColor.White();
  this.LightSourceList = [];
}

JsgScene.prototype.AddLightSource = function( lightSource ) {
  this.LightSourceList.push( lightSource );
  return lightSource;
}

JsgScene.prototype.Set = function( aParams ) {
  // aParams = {
  //   AmbientLight: JsgColor
  //   CamLight: JsgColor
  //   LightSourceList: Array of JsgLightSource
  // }
  if (!xObj(aParams)) return;
  if (JsgColor.Ok(aParams.AmbientLight)) this.AmbientLight = aParams.AmbientLight;
  if (JsgColor.Ok(aParams.CamLight)) this.CamLight = aParams.CamLight;
  if (xArray(aParams.LightSourceList)) {
    this.LightSourceList = [];
    var ls = aParams.LightSourceList;
    for (var i = 0; i < ls.length; i++) {
      this.AddLightSource( new JsgLightSource(ls[i]) );
    }
  }
}

JsgScene.prototype.Save = function( params ) {
  params = params || {};
  params.AmbientLight = this.AmbientLight;
  params.CameraLight = this.CamLight;
  params.LightSourceList = [];
  var lsList = this.LightSourceList;
  var parLsList = params.LightSourceList;
  for (var i = 0; i < lsList.length; i++) {
    parLsList.push( new JsgLightSource( lsList[i] ) );
  }
  return params;
}


////////////////////////////////////
// JsgLightSource

function JsgLightSource( def ) {
  def = def || {};
  this.LightColor = JsgColor.Ok(def.LightColor) ? def.LightColor : JsgColor.White();
  this.DiffInt = xDefNum( def.DiffInt, 1 );
  this.SpecInt = xDefNum( def.SpecInt, 1 );
  if (xNum(def.HAng) || xNum(def.VAng) || xNum(def.Dist) || xArray(def.RefPoint)) {
    var hang = xDefNum( def.HAng, 0 );
    var vang = xDefNum( def.VAng, 0 );
    var dist = xDefNum( def.Dist, 100 );
    var refp = xDefArray( def.RefPoint, [0,0,0] );
    var v = JsgVect3.Add( refp, JsgVect3.FromAngle( hang, vang, dist ) );
    if (xArray(def.RefPoint)) {
      this.LightPos = v;
      this.LightDir = null;
    } else {
      this.LightPos = null;
      this.LightDir = v;
    }
  } else {
    if (xArray(def.LightPos)) {
      this.LightPos = def.LightPos;
      this.LightDir = null;
    } else if (xArray(def.LightDir)) {
      this.LightPos = null;
      this.LightDir = JsgVect3.Norm(def.LightDir);
    } else {
      this.LightPos = JsgVect3( 0, 0, 1 );
      this.LightDir = null;
    }
  }
}


////////////////////////////////////
// JsgEleLighting

function JsgEleLighting( def ) {
  def = def || {};
  this.ApplyCamLight   = xDefBool( def.ApplyCamLight, true );
  this.ApplyAmbient    = xDefBool( def.ApplyAmbient, true );
  this.ApplyPhong      = xDefBool( def.ApplyPhong, false );
  this.BlackAndWhite   = xDefBool( def.BlackAndWhite, false );
  this.ColorResolution = xDefNum( def.ColorResolution, 0 );
  this.Dimm            = xDefNum( def.Dimm, 1 );
  this.CamRefl         = xDefNum( def.CamRefl, 1.0 );
  this.AmbiRefl        = xDefNum( def.AmbiRefl, 0.2 );
  this.DiffRefl        = xDefNum( def.DiffRefl, 0.5 );
  this.SpecRefl        = xDefNum( def.SpecRefl, 0.5 );
  this.Roughness       = xDefNum( def.Roughness, 1.0 );
  this.Shiny           = xDefNum( def.Shiny, 16 );
  this.DiffXRay        = xDefNum( def.DiffXRay, 0 );
  this.SpecXRay        = xDefNum( def.SpecXRay, 0.9 );
}

JsgEleLighting.prototype.Set = function( params ) {
  if (!xObj(params)) return;
  if (xBool(params.ApplyCamLight))  this.ApplyCamLight   = params.ApplyCamLight;
  if (xBool(params.ApplyAmbient))   this.ApplyAmbient    = params.ApplyAmbient;
  if (xBool(params.ApplyPhong))     this.ApplyPhong      = params.ApplyPhong;
  if (xBool(params.BlackAndWhite))  this.BlackAndWhite   = params.BlackAndWhite;
  if (xNum(params.ColorResolution)) this.ColorResolution = params.ColorResolution;
  if (xNum(params.Dimm))            this.Dimm            = params.Dimm;
  if (xNum(params.CamRefl))         this.CamRefl         = params.CamRefl;
  if (xNum(params.AmbiRefl))        this.AmbiRefl        = params.AmbiRefl;
  if (xNum(params.DiffRefl))        this.DiffRefl        = params.DiffRefl;
  if (xNum(params.SpecRefl))        this.SpecRefl        = params.SpecRefl;
  if (xNum(params.Roughness))       this.Roughness       = params.Roughness;
  if (xNum(params.Shiny))           this.Shiny           = params.Shiny;
  if (xNum(params.DiffXRay))        this.DiffXRay        = params.DiffXRay;
  if (xNum(params.SpecXRay))        this.SpecXRay        = params.SpecXRay;
}


////////////////////////////////////
// JsgEleAttr

function JsgEleAttr( def, bCopyColors ) {
  // def = {
  //   AreaColor: Color (default light blue)
  //   AreaBackColor: Color (default light red)
  //   LineColor: Color (default dark blue)
  //   AreaBackLineColor: Color (default dark red)
  //   LineWidth: number > 0 (default 1 pixel)
  //   Symbol: String (default 'Circle')
  //   SymbSize: number > 0 (default 8 pixel)
  // }
  def = def || {};
  bCopyColors = xDefBool( bCopyColors, false );
  if (bCopyColors) {
    this.AreaColor         = JsgColor.RGB(0.8,0.8,1);
    this.AreaBackColor     = JsgColor.RGB(1,0.8,0.8);
    this.LineColor         = JsgColor.RGB(0,0,0.5);
    this.AreaBackLineColor = JsgColor.RGB(0.5,0,0);
    if (JsgColor.Ok(def.AreaColor))         JsgColor.CopyTo(def.AreaColor,         this.AreaColor);
    if (JsgColor.Ok(def.AreaBackColor))     JsgColor.CopyTo(def.AreaBackColor,     this.AreaBackColor);
    if (JsgColor.Ok(def.LineColor))         JsgColor.CopyTo(def.LineColor,         this.LineColor);
    if (JsgColor.Ok(def.AreaBackLineColor)) JsgColor.CopyTo(def.AreaBackLineColor, this.AreaBackLineColor);
  } else {
    this.AreaColor         = JsgColor.Ok(def.AreaColor) ?         def.AreaColor         : JsgColor.RGB(0.8,0.8,1);
    this.AreaBackColor     = JsgColor.Ok(def.AreaBackColor) ?     def.AreaBackColor     : JsgColor.RGB(1,0.8,0.8);
    this.LineColor         = JsgColor.Ok(def.LineColor) ?         def.LineColor         : JsgColor.RGB(0,0,0.5);
    this.AreaBackLineColor = JsgColor.Ok(def.AreaBackLineColor) ? def.AreaBackLineColor : JsgColor.RGB(0.5,0,0);
  }
  this.LineWidth = xDefNum( def.LineWidth, 1 );
  this.Symbol    = xDefStr( def.Symbol, 'Circle' );
  this.SymbSize  = xDefNum( def.SymbSize, 8 );
}

JsgEleAttr.prototype.Set = function( params, bCopyColors ) {
  if (!xObj(params)) return;
  bCopyColors = xDefBool( bCopyColors, false );
  if (bCopyColors) {
    if (JsgColor.Ok(params.AreaColor))         JsgColor.CopyTo(params.AreaColor, this.AreaColor);
    if (JsgColor.Ok(params.AreaBackColor))     JsgColor.CopyTo(params.AreaBackColor, this.AreaBackColor);
    if (JsgColor.Ok(params.LineColor))         JsgColor.CopyTo(params.LineColor, this.LineColor);
    if (JsgColor.Ok(params.AreaBackLineColor)) JsgColor.CopyTo(params.AreaBackLineColor, this.AreaBackLineColor);
  } else {
    if (JsgColor.Ok(params.AreaColor))         this.AreaColor = params.AreaColor;
    if (JsgColor.Ok(params.AreaBackColor))     this.AreaBackColor = params.AreaBackColor;
    if (JsgColor.Ok(params.LineColor))         this.LineColor = params.LineColor;
    if (JsgColor.Ok(params.AreaBackLineColor)) this.AreaBackLineColor = params.AreaBackLineColor;
  }
  if (xNum(params.LineWidth)) this.LineWidth = params.LineWidth;
  if (xStr(params.Symbol))    this.Symbol    = params.Symbol;
  if (xNum(params.SymbSize))  this.SymbSize  = params.SymbSize;
}

JsgEleAttr.prototype.CopyFrom = function( src ) {
  JsgColor.CopyTo( src.AreaColor,         this.AreaColor );
  JsgColor.CopyTo( src.AreaBackColor,     this.AreaBackColor );
  JsgColor.CopyTo( src.LineColor,         this.LineColor );
  JsgColor.CopyTo( src.AreaBackLineColor, this.AreaBackLineColor );
  this.LineWidth = src.LineWidth;
  this.Symbol    = src.Symbol;
  this.SymbSize  = src.SymbSize;
}


////////////////////////////////////
// JsgEleData

function JsgEleData( def ) {
  def = def || {};
  this.Attr          = new JsgEleAttr( def.Attr );
  this.AreaDrawMode  = xDefNum( def.AreaDrawMode, 3 );
  this.PointDrawMode = xDefNum( def.PointDrawMode, 3 );
  this.DrawBackface  = xDefBool( def.DrawBackface, true );
  this.AreaAttrFunc  = xDefFuncOrNull( def.AreaAttrFunc, null );
  this.LineAttrFunc  = xDefFuncOrNull( def.LineAttrFunc, null );
  this.PointAttrFunc = xDefFuncOrNull( def.PointAttrFunc, null );
  this.Lighting      = new JsgEleLighting( def.Lighting );
  this.BackLighting  = new JsgEleLighting( def.Lighting );
  if (xObj(def.BackLighting)) this.BackLighting.Set( def.BackLighting );
  this.ContourMode   = xDefBool( def.ContourMode, false );
  this.ContourSens   = xDefNum( def.ContourSens, 0.1 );
  this.ContourColor  = JsgColor.Ok(def.ContourColor) ? def.ContourColor : JsgColor.Black();
  this.ContourWidth  = xDefNum( def.ContourWidth, 7 );
}

JsgEleData.prototype.Set = function( params ) {
  if (!xObj(params)) return;
  if (xObj(params.Attr))                   this.Attr.Set( params.Attr );
  if (xNum(params.AreaDrawMode))           this.AreaDrawMode  = params.AreaDrawMode;
  if (xNum(params.PointDrawMode))          this.PointDrawMode = params.PointDrawMode;
  if (xBool(params.DrawBackface))          this.DrawBackface  = params.DrawBackface;
  if (xFuncOrNull(params.AreaAttrFunc))    this.AreaAttrFunc  = params.AreaAttrFunc;
  if (xFuncOrNull(params.LineAttrFunc))    this.LineAttrFunc  = params.LineAttrFunc;
  if (xFuncOrNull(params.PointAttrFunc))   this.PointAttrFunc = params.PointAttrFunc;
  if (xObj(params.Lighting))               this.Lighting.Set( params.Lighting );
  if (xObj(params.Lighting))               this.BackLighting.Set( params.Lighting );
  if (xObj(params.BackLighting))           this.BackLighting.Set( params.BackLighting );
  if (xBool(params.ContourMode))           this.ContourMode   = params.ContourMode;
  if (xNum(params.ContourSens))            this.ContourSens   = params.ContourSens;
  if (JsgColor.Ok(params.ContourColor))    this.ContourColor  = params.ContourColor;
  if (xNum(params.ContourWidth))           this.ContourWidth  = params.ContourWidth;
}


////////////////////////////////////
// JsgDisplayList

function JsgDisplayList() {
  // Deletes all elements and element data from the display list
  this.PointList = [];     // array of JsgVect3
  this.GraphEleList = [];  // array of JsgGraphEle
  this.EleDataList = [];   // array of JsgEleData
  this.StaticDataReady = true;
  this.ViewDataReady = true;
}

////////////////////////////////////
// JsgGraphEle

function JsgGraphEle( pointIxList, eleData ) {
  // pointIxList: array of int
  // eleData: JsgEleData
  this.PointIxList = pointIxList;
  this.Data = eleData;
  this.Center = null;  // JsgVect3
  this.Normal = null;  // JsgVect3
  this.CamDist2 = 0;   // distance from this.Center to camera pos squared
}

////////////////////////////////////
// JsgPlane

function JsgPlane( pos, xdir, ydir, normalize ) {
  // pos, xdir, ydir: JsgVect; these are not copied
  // normalize: Boolean; Optional; Default = false;
  // set normalized to true, to make plane vectors unit length and perpendicular

  // some props to hold temp data
  this.WorkPoint  = JsgVect3.Null();
  this.WorkPoint2 = JsgVect3.Null();
  this.WorkPoint3 = JsgVect3.Null();
  this.ExitPoint  = JsgVect3.Null();
  this.EnterPoint = JsgVect3.Null();
  this.FirstEnterPoint = JsgVect3.Null();
  this.Result = JsgVect3.Null();
  this.ResultPoly = new JsgPolygon( true, 'JsgPlane.ResultPoly' );
  this.PolyIterator = new JsgPolyListIter();

  this.Set( pos, xdir, ydir, normalize );
}

JsgPlane.Ok = function( obj ) {
  return xDef(obj) && xDef(obj.Pos);
}

JsgPlane.prototype.Set = function( pos, xdir, ydir, normalize ) {
  // change plane parameters
  // pos, xdir, ydir: JsgVect; these are not copied
  // normalize: Boolean; Optional; Default = false;
  // set normalized to true, to make plane vectors unit length and perpendicular
  this.Pos = pos;
  this.XDir = xdir;
  this.YDir = ydir;
  this.Normal = JsgVect3.Null();
  if (normalize) {
    this.Normalize();
  } else {
    this.CompNormal();
  }
}

JsgPlane.prototype.Normalize = function() {
  // makes plane vectors unit length and perpendicular
  var XdirNorm = JsgVect3.NormTo( this.XDir );
  var ZDir = JsgVect3.MultTo( this.WorkPoint, XdirNorm, this.YDir );
  JsgVect3.NormTo( JsgVect3.MultTo( this.YDir, ZDir, XdirNorm ) );
  this.CompNormal();
  return this;
}

JsgPlane.prototype.CompNormal = function() {
  // if this.XDir and this.YDir are unit vectors, then this.Normal will be a unit vector too
  JsgVect3.MultTo( this.Normal, this.XDir, this.YDir );
}

JsgPlane.prototype.Copy = function( normalize ) {
  // normalize: Boolean; Optional; Default = false;
  // set normalized to true, to make new plane vectors unit length and perpendicular
  return new JsgPlane( this.Pos, this.XDir, this.YDir, normalize );
}

JsgPlane.prototype.PointOnPlane = function( x, y, v ) {
  // or PointOnPlane( JsgVect2, v )
  // v: JsgVect3 or undefined
  // returns this.Result or v
  // transforms x, y coordinates on this plane to 3D coordinates and stores the result in v or this.Result
  // returns this.Result: JsgVect3

  if (JsgVect2.Ok(x)) {
    return this.Point( x[0], x[1], y );
  }

  v = v || this.Result;
  JsgVect3.CopyTo( this.Pos, v );
  JsgVect3.AddTo( v, JsgVect3.ScaleTo( JsgVect3.CopyTo( this.XDir, this.WorkPoint ), x ) );
  JsgVect3.AddTo( v, JsgVect3.ScaleTo( JsgVect3.CopyTo( this.YDir, this.WorkPoint ), y ) );
  return v;
}

JsgPlane.prototype.PolygonOnPlane = function( xArray, yArray, size, planePoly ) {
  // or PolygonOnPlane( poly, planePoly )
  // poly: JsgPolygon
  // planePoly: JsgPolygon(3D) or undefined
  // returns planePoly or this.ResultPoly: JsgPolygon(3D)
  // Note: you have to call planePoly.Reset() yourself to clear it if necessary

  if (JsgPolygon.Ok(xArray)) {
    return this.PolygonOnPlane( xArray.X, xArray.Y, xArray.Size, yArray );
  }

  planePoly = planePoly || this.ResultPoly;
  planePoly.Reset();
  size = xDefNum( size, xArray.length );
  for (var i = 0; i < size; i++) {
    var p = this.PointOnPlane( xArray[i], yArray[i] );
    planePoly.AddPoint3D( p );
  }
  return planePoly;
}

JsgPlane.prototype.Polygon = function( xpoly, ypoly, size ) {
  // depricated! use PolygonOnPlane
  // or Polygon( JsgPolygon )
  // returns new JsgVect3List

  if (JsgPolygon.Ok(xpoly)) {
    return this.Polygon( xpoly.X, xpoly.Y, xpoly.Size );
  }

  size = xDefNum( size, xpoly.length );
  var vl = [];
  for (var i = 0; i < size; i++) {
    vl.push( JsgVect3.Copy( this.Point( xpoly[i], ypoly[i] ) ) );
  }
  return vl;
}

JsgPlane.prototype.IntersectLine = function( p1, p2 ) {
  // p1, p2: JsgVect3; two points of line
  // returns this.Result: JsgVect3 or null; null -> no intersection
  // requires this.Normal is computed and normalized
  // http://walter.bislins.ch/blog/index.asp?page=Schnittpunkt+Ebene+mit+Gerade+einfach+berechnen+%28JavaScript%29
  // function is optimized to not use temporary objects by using static this.WorkPoint instead

  var r = JsgVect3.SubFrom( JsgVect3.CopyTo( p2, this.Result ), p1 );
  var r3 = JsgVect3.ScalarProd( r, this.Normal );
  if (r3 == 0) {
    // rounding error? try with swaped points
    var tmp = p1;
    p1 = p2;
    p2 = tmp;
    r = JsgVect3.SubFrom( JsgVect3.CopyTo( p2, this.Result ), p1 );
    r3 = JsgVect3.ScalarProd( r, this.Normal );
    if (r3 == 0) {
      // definitely no intersection
      return null;
    }
  }
  var pos_p1 = JsgVect3.SubFrom( JsgVect3.CopyTo( p1, this.WorkPoint ), this.Pos );
  var P3 = JsgVect3.ScalarProd( pos_p1, this.Normal );
  var a = -P3 / r3;
  return JsgVect3.AddTo( JsgVect3.ScaleTo( r, a ), p1 );
}

JsgPlane.prototype.IsPoint3DOnTop = function( p ) {
  // returns true if p is at the side of the plane where the normal is pointing to or on the plane
  // p: JsgVect3
  // requires this.Normal is computed
  // function is optimized to not use temporary objects by using static this.WorkPoint instead

  var pos_p = JsgVect3.SubFrom( JsgVect3.CopyTo( p, this.WorkPoint ), this.Pos );
  return JsgVect3.ScalarProd( pos_p, this.Normal ) >= 0;
}

JsgPlane.prototype.IsPointOnTop = function( x, y, z ) {
  // returns true if p(x,y,z) is at the side of the plane where the normal is pointing to or on the plane
  // requires this.Normal is computed
  // function is optimized to not use temporary objects by using static this.WorkPoint instead

  JsgVect3.Set( this.WorkPoint, x, y, z );
  var pos_p = JsgVect3.SubFrom( this.WorkPoint, this.Pos );
  return JsgVect3.ScalarProd( pos_p, this.Normal ) >= 0;
}

JsgPlane.prototype.ClipPoly = function( polys, clippedPolyList, interpolFunc, interpolData ) {
  // polys: JsgPolygon or JsgPolygonList
  // clippedPolyList: JsgPolygonList
  // interpolFunction( this, clippedPolyList ) or undefined
  //   interpolate points between this.ExitPoint and this.EnterPoint
  //   and store them in clippedPolyList.AddPoint3D()

  clippedPolyList.Reset();
  if (JsgPolygonList.Ok(polys)) {
    var size = polys.Size;
    for (var i = 0; i < size; i++) {
      this.Clip( polys.PolyList[i], clippedPolyList, false, interpolFunc, interpolData );
    }
  } else {
    this.Clip( polys, clippedPolyList, false, interpolFunc, interpolData );
  }
}

JsgPlane.prototype.ClipArea = function( polys, clippedPolyList, interpolFunc, interpolData ) {
  // polys: JsgPolygon or JsgPolygonList
  // clippedPolyList: JsgPolygonList
  // interpolFunction( this, clippedPolyList ) or undefined
  //   interpolate points between this.ExitPoint and this.EnterPoint
  //   and store them in clippedPolyList.AddPoint3D()

  clippedPolyList.Reset();
  clippedPolyList.NewPoly();

  // sub areas must be connected with last point of main area
  if (JsgPolygonList.Ok(polys) && polys.Size > 1) {
    var mainPoly = polys.PolyList[0];
    var lastMainPoint = mainPoly.Size-1;
    var x = mainPoly.X[lastMainPoint];
    var y = mainPoly.Y[lastMainPoint];
    var z = mainPoly.Z[lastMainPoint];
    var n = polys.Size;
    for (var i = 1; i < n; i++) {
      polys.PolyList[i].AddPoint( x, y, z );
    }
  }

  // polys must be closed for Clip area to work properly
  var didClose = polys.Close()

  this.Clip( polys, clippedPolyList, true, interpolFunc, interpolData );

  if (didClose) polys.RemoveLastPoint();

  // remove connection points
  if (JsgPolygonList.Ok(polys) && polys.Size > 1) {
    var n = polys.Size;
    for (var i = 1; i < n; i++) {
      polys.PolyList[i].RemoveLastPoint();
    }
  }
}

JsgPlane.prototype.Clip = function( polys, clippedPolyList, isArea, interpolFunc, interpolData ) {
  // polys: JsgPolygon or JsgPolygonList
  // clippedPolyList: JsgPolygonList
  // interpolFunction( this, clippedPolyList ) or undefined
  //   interpolate points between this.ExitPoint and this.EnterPoint
  //   and store them in clippedPolyList.AddPoint3D()
  // return empty polygon if polys is empty
  // require polys is closed if isArea = true
  // Note: if polys is a JsgPolygonList then all polygons of the list are treated as one big polygon

  function addSegmentToClipPoly() {
    if (!isLastP2Added) {
      if (!isArea) {
        clippedPolyList.NewPoly();
      }
      clippedPolyList.AddPoint3D( p1 );
    }
    clippedPolyList.AddPoint3D( p2 );
    isLastP2Added = true;
  }

  if (polys.Size == 0) return;

  var isP1Inside, isP2Inside;
  var validEnter = false;
  var validExit = false;
  var validFirstEnter = false;
  var p1 = this.WorkPoint2;
  var p2 = this.WorkPoint3;
  var polyIter = this.PolyIterator.Reset( polys );
  if (!polyIter.GetNextPoint(p1)) return;

  // check if polys is only one point
  isP1Inside = this.IsPoint3DOnTop( p1 );
  if (!polyIter.GetNextPoint(p2)) {
    if (isP1Inside) {
      if (!isArea) {
        clippedPolyList.NewPoly();
      }
      clippedPolyList.AddPoint3D( p1 );
    }
    return;
  }
  polyIter.Back();

  // loop for all polys segements
  var isLastP2Added = false;
  while (polyIter.GetNextPoint(p2)) {

    isP2Inside = this.IsPoint3DOnTop( p2 );

    if (isP1Inside && isP2Inside) {

      // this code part is reused below, so it is put in a separat function
      addSegmentToClipPoly();

    } else if (isP1Inside != isP2Inside) {

      // segment intersects plane: handle clipping
      var s = this.IntersectLine( p1, p2 );

      if (!s) {
        // due to rounding errors the intersection point could not be computed

        // hanlde point p2 to be on the same side as p1
        isP2Inside = isP1Inside;
        if (isP1Inside) {

          addSegmentToClipPoly()

        }

        // else nothing to do if both points are outside

      } else if (isP1Inside) {

        // EXIT: line segment exits inside
        if (!isLastP2Added) {
          if (!isArea) {
            if (!interpolFunc) {
              clippedPolyList.NewPoly();
            } else if (clippedPolyList.Size == 0) {
              clippedPolyList.NewPoly();
            }
          }
          clippedPolyList.AddPoint3D( p1 );
        }
        clippedPolyList.AddPoint3D( s );
        isLastP2Added = false;

        // handle interpolation
        if (interpolFunc) {
          JsgVect3.CopyTo( s, this.ExitPoint );
          validExit = true;
        }

      } else {

        // ENTER: line segment enters inside
        if (!isArea) {
          if (!interpolFunc) {
            clippedPolyList.NewPoly();
          } else if (clippedPolyList.Size == 0) {
            clippedPolyList.NewPoly();
          }
        }

        // handle interpolation
        if (interpolFunc) {
          JsgVect3.CopyTo( s, this.EnterPoint );
          if (validExit) {
            interpolFunc( this, clippedPolyList, interpolData );
          } else {
            JsgVect3.CopyTo( s, this.FirstEnterPoint );
            validFirstEnter = true;
          }
          validEnter = false;
          validExit = false;
        }

        clippedPolyList.AddPoint3D( s );
        clippedPolyList.AddPoint3D( p2 );
        isLastP2Added = true;

      }

      // else nothing to do if both points are outside

    }

    isP1Inside = isP2Inside;
    JsgVect3.CopyTo( p2, p1 );

  } // next segment

  // handle interpolation
  if (interpolFunc && validFirstEnter && validExit) {
    JsgVect3.CopyTo( this.FirstEnterPoint, this.EnterPoint );
    interpolFunc( this, clippedPolyList, interpolData );
    clippedPolyList.AddPoint3D( this.FirstEnterPoint );
  }

}

////////////////////////////////////
// JsGraph3D Object

function NewGraph3D( aParams ) {
  return new JsGraph3D( aParams );
}

function DrawGraph3D( g3d ) {
  g3d.DrawDisplayList();
}

function JsGraph3D( aParams ) {

  aParams = xDefObj( aParams, {} );

  // pass null as DrawFunc to JsGraph because DrawFunc is a 3D DrawFunc and JsGraph3D installs its own DrawFunc in JsGraph.
  var drawFunc3D = xDefFuncOrNull( aParams.DrawFunc, DrawGraph3D );
  if (xFunc(aParams.DrawFunc)) aParams.DrawFunc = null;

  this.parentClass.constructor.call( this, aParams );

  this.Async = new CAsync();

  this.MinAlpha = 1 / 255;
  this.UpdateInterval = 25; // [ms]

  this.DrawFunc3D = null;

  // some props for temp data
  this.WorkPoly2D   = new JsgPolygon( false, 'JsGraph3D.WorkPoly2D (local)' );
  this.WorkPoint3D  = JsgVect3.Null();
  this.WorkPoint3D2 = JsgVect3.Null();
  this.WorkPoly3D   = new JsgPolygon( true, 'JsGraph3D.WorkPoly3D' );
  this.WorkPoly3D2  = new JsgPolygon( true, 'JsGraph3D.WorkPoly3D2' );
  this.XfmPolys3D   = new JsgPolygonList( true, 'JsGraph3D.XfmPolys3D' );
  this.CamPolys3D   = new JsgPolygonList( true, 'JsGraph3D.CamPolys3D' );
  this.ClipPolys3D1 = new JsgPolygonList( true, 'JsGraph3D.ClipPolys3D1' );
  this.ClipPolys3D2 = new JsgPolygonList( true, 'JsGraph3D.ClipPolys3D2' );

  // caching
  this.WorkingAttr = new JsgEleAttr();
  this.ComputedAreaColor = JsgColor.Black();
  this.CompAreaLSLightingColor = JsgColor.Black();
  this.CompAreaLightingLightColor = JsgColor.Black();
  this.CompAreaLightingColor = JsgColor.Black();
  this.CompAreaPhongLightingRVect = [0,0,0];
  this.CompAreaPhongLightingVVect = [0,0,0];
  this.CompAreaPhongLightingNVect = [0,0,0];

  // objects
  this.DisplayList = new JsgDisplayList();
  this.Plane = new JsgPlane( [ 0, 0, 0 ], [ 0, 1, 0 ], [ 0, 0, 1 ] );
  this.Camera = new JsgCamera();
  this.PathPolys3D = new JsgPolygonList( true, 'JsGraph3D.PathPolys3D (local)' );
  this.IsPath3DOpen = false;
  this.ApplyTransToPath3D = false;
  this.LastPos3D = JsgVect3.Null();
  this.LastPosOnPlane = JsgVect2.Null()
  this.Poly3D = new JsgPolygon( true, 'JsGraph3D.Poly3D' );
  this.ApplyTransToPoly3D = false;
  this.CameraClipPlaneDist = 0;
  this.ClipPlaneList = [ null ]; // first entry is camera clip plane

  this.Reset3D();

  var me = this;
  this.SetBeforeResetFunc(
    function CB_OnBeforeReset(g) {
      me.StopDrawing(g);
    }
  );
  if (drawFunc3D) {
    this.SetDrawFunc( drawFunc3D );
  }

  // restore aParams.DrawFunc
  if (xDef(aParams.DrawFunc)) aParams.DrawFunc = drawFunc3D;

  this.SetAll( aParams );
  this.SetScreenWindow();
}

JsGraph3D.inheritsFrom( JsGraph );

JsGraph3D.prototype.SetSyncMode = function ( bSyncMode ) {
  var oldSyncMode = this.SyncMode;
  this.Async.SyncMode = bSyncMode;
  this.SyncMode = bSyncMode;
  return oldSyncMode;
}

JsGraph3D.prototype.Reset3D = function( reset2D, clear ) {
  reset2D = xDefBool( reset2D, true );
  clear = xDefBool( clear, true );
  if (reset2D) {
    this.Reset( false );
  }
  this.CameraClipPlaneDist = 0;
  this.Plane.Set( [ 0, 0, 0 ], [ 0, 1, 0 ], [ 0, 0, 1 ] );
  this.ClipPlaneList = [ null ]; // first entry is camera clip plane
  this.ClipPlaneListSize = 1;
  this.PathPolys3D.Reset();
  this.Trans3D = null;
  this.Trans3DStack = [];
  this.IsPath3DOpen = false;

  this.ShowProgress = true;
  this.SyncMode = false;
  this.BeforeDrawFunc = null;
  this.AfterDrawFunc = null;
  this.DefaultEleData = new JsgEleData();
  this.DisplayList = new JsgDisplayList();
  this.DisplayList.ViewDataReady = false;
  this.Scene = new JsgScene();
  this.ResetCamera();
  if (clear) {
    this.Clear();
  }
}

JsGraph3D.prototype.SaveAll = function( aParams ) {
  var par = xDefObj( aParams, {} );
  par.ShowProgress = this.ShowProgress;
  par.SyncMode = this.SyncMode;
  par.DrawFunc = this.DrawFunc3D;
  par.BeforeDrawFunc = this.BeforeDrawFunc;
  par.AfterDrawFunc = this.AfterDrawFunc;
  this.SaveDefaultEleData( par );
  this.SaveClipPlanes( par );
  this.SaveCamera( par );
  this.SavePlane( par );
  this.SaveScene( par );
  this.SaveDisplayList( par );
  this.SavePlane( par );
  return par;
}

JsGraph3D.prototype.SetAll = function( aParams ) {
  this.ShowProgress = xDefBool( aParams.ShowProgress, true );
  this.SetSyncMode( xDefBool( aParams.SyncMode, false ) );
  if (xFuncOrNull( aParams.DrawFunc )) { this.SetDrawFunc( aParams.DrawFunc ); }
  this.BeforeDrawFunc = xDefFunc( aParams.BeforeDrawFunc, null );
  this.AfterDrawFunc = xDefFunc( aParams.AfterDrawFunc, null );
  this.SetDefaultEleData( aParams );
  this.SetClipPlanes( aParams );
  this.SetCamera( aParams );
  this.SetScene( aParams );
  this.SetDisplayList( aParams );
  this.SetPlane( aParams );
}

JsGraph3D.prototype.SetScreenWindow = function( ) {
  var ratio = this.VpWidth / this.VpHeight;
  var screenSize = this.Camera.ScreenSize;
  var w, h;
  if (ratio > 0) {
    w = screenSize * ratio;
    h = screenSize;
  } else {
    w = screenSize;
    h = screenSize / ratio;
  }
  this.SetWindowWH( -w/2, -h/2, w, h );
}

JsGraph3D.prototype.SetDrawFunc = function( aDrawFunc3D ) {
  // aDrawFunc3D = function( JsGraph3D )
  this.DrawFunc3D = aDrawFunc3D;
  if (this.DrawFunc3D) {
    var me = this;
    this.parentClass.SetDrawFunc.call(
      this,
      function CB_OnDraw(g) {
        me.Draw3D();
      },
      true
    );
  } else {
    this.parentClass.SetDrawFunc.call( this, null, false );
  }
}

JsGraph3D.prototype.Draw3D = function() {
  if (this.DrawFunc3D) {
    this.SetScreenWindow();
    this.DrawFunc3D( this );
  }
}

JsGraph3D.prototype.StopDrawing = function( aJsGraph ) {
  this.Async.Stop();
}

JsGraph3D.prototype.SetDefaultEleData = function( aParams ) {
  this.DefaultEleData = xObj(aParams.DefaultEleData) ? aParams.DefaultEleData : new JsgEleData( aParams );
}

JsGraph3D.prototype.ChangeDefaultEleData = function( aParams ) {
  this.DefaultEleData.Set( aParams );
}

JsGraph3D.prototype.SaveDefaultEleData = function( aParams ) {
  aParams = aParams || {};
  aParams.DefaultEleData = this.DefaultEleData;
  return aParams;
}

JsGraph3D.prototype.AddOrGetDefaultEleData = function( aEleData, bCopy ) {
  var eleData = this.DefaultEleData;
  bCopy = xDefBool( bCopy, true );
  if (xObj(aEleData)) {
    if (bCopy) {
      eleData = new JsgEleData(aEleData);
    } else {
      eleData = aEleData;
    }
    this.DisplayList.EleDataList.push( eleData );
  }
  return eleData;
}

JsGraph3D.prototype.ResetCamera = function() {
  this.Camera.Reset();
  this.UpdateCameraClipPlane();
  this.DisplayList.ViewDataReady = false;
}

JsGraph3D.prototype.SetCamera = function( aParams ) {
  this.Camera.Set( aParams );
  this.UpdateCameraClipPlane();
  this.DisplayList.ViewDataReady = false;
}

JsGraph3D.prototype.SaveCamera = function( aParams ) {
  return this.Camera.Save( aParams );
}

JsGraph3D.prototype.SetCameraScale = function( aSceneSize, aScreenSize, aObjectZExtend, aZoom ) {
  this.Camera.SetScale( aSceneSize, aScreenSize, aObjectZExtend, aZoom );
  this.DisplayList.ViewDataReady = false;
}

JsGraph3D.prototype.SetCameraPos = function( aPos, aViewCenter, aUp ) {
  this.Camera.SetPos( aPos, aViewCenter, aUp );
  this.DisplayList.ViewDataReady = false;
  this.UpdateCameraClipPlane();
}

JsGraph3D.prototype.SetCameraView = function( aViewCenter, aHAng, aVAng, aDist, aUp ) {
  // Angles are in degree!
  this.Camera.SetView( aViewCenter, aHAng, aVAng, aDist, aUp );
  this.DisplayList.ViewDataReady = false;
  this.UpdateCameraClipPlane();
}

JsGraph3D.prototype.SetCameraZoom = function( aZoom ) {
  this.Camera.SetZoom( aZoom );
  // not needet here: this.DisplayList.ViewDataReady = false;
}

JsGraph3D.prototype.SetCameraAperture = function( aAperture ) {
  this.Camera.SetAperture( aAperture );
}

JsGraph3D.prototype.SetCameraHighResolution = function( aMode ) {
  // aMode: string ('on', 'off', ''), '' -> default see this.HighResolution
  this.Camera.SetHighResolution( aMode );
}

JsGraph3D.prototype.UpdateCameraClipPlane = function() {
  // private function
  // computes new camera clip plane from current camera settings
  if (this.CameraClipPlaneDist <= 0 && this.ClipPlaneList[0] != null) {
    this.ClipPlaneList[0] = null;
  }
  if (this.CameraClipPlaneDist > 0) {
    var cam = this.Camera;
    var n = JsgVect3.Norm( JsgVect3.Sub( cam.CamViewCenter, cam.CamPos ) );
    var pos = JsgVect3.Add( cam.CamPos, JsgVect3.Scale( n, this.CameraClipPlaneDist ) );
    var xdir = JsgVect3.Mult( cam.CamUp, n );
    var ydir = JsgVect3.Mult( n, xdir );
    var plane = new JsgPlane( pos, xdir, ydir, true );
    this.ClipPlaneList[0] = plane;
  }
}

JsGraph3D.prototype.SetCameraClipping = function( clipPlaneDist ) {
  // clipPlaneDist: number; 0 -> no camera clipping, else clipPlaneDist gives distance of clip plane from camera plane
  this.CameraClipPlaneDist = clipPlaneDist;
  this.UpdateCameraClipPlane();
}

JsGraph3D.prototype.DeleteClipPlanes = function() {
  this.ClipPlaneListSize = 1;
}

JsGraph3D.prototype.AddClipPlane = function( planeOrPos, xdir, ydir ) {
  // or AddClipPlane( plane: JsgPlane )
  if (JsgPlane.Ok(planeOrPos)) {
    planeOrPos.Normalize();
    this.ClipPlaneList[this.ClipPlaneListSize] = planeOrPos;
    this.ClipPlaneListSize++;
  } else {
    this.ClipPlaneList[this.ClipPlaneListSize] = new JsgPlane( planeOrPos, xdir, ydir, true );
    this.ClipPlaneListSize++;
  }
}

JsGraph3D.prototype.SaveClipPlanes = function( aParams ) {
  if (this.CameraClipPlaneDist > 0) {
    aParmas.CameraClipPlaneDist = this.CameraClipPlaneDist;
  }
  var n = this.ClipPlaneListSize;
  if (n > 1) {
    var l = [];
    for (var i = 1; i < n; i++) {
      l.push( this.ClipPlaneList[i].Copy() );
    }
    aParams.ClipPlaneList = l;
  }
}

JsGraph3D.prototype.SetClipPlanes = function( aParams ) {
  this.DeleteClipPlanes();
  if (xDefArray(aParams.ClipPlaneList)) {
    var lst = aParams.ClipPlaneList;
    var n = lst.length;
    for (var i = 0; i < n; i++) {
      var plane = lst[i];
      if (JsgPlane.Ok(plane)) {
        this.AddClipPlane( plane );
      }
    }
  }
  if (xDefNum(aParams.CameraClipPlaneDist)) {
    this.SetCameraClipping( aParams.CameraClipPlaneDist );
  }
}

//////////////////////////////////////////
// Scene Functions

JsGraph3D.prototype.NewScene = function( aAmbientLight, aCamLight ) {
  // Creates an new scene without any light sources.
  // aAmbientLight, aCamLight: Color
  this.Scene = new JsgScene( aAmbientLight, aCamLight );
}

JsGraph3D.prototype.SaveScene = function( aParams ) {
  var par = xDefObj( aParams, {} );
  par.Scene = this.Scene;
  return par;
}

JsGraph3D.prototype.SetScene = function( aParams ) {
  if (xObj(aParams.Scene)) {
    this.Scene = aParams.Scene;
  } else {
    this.Scene.Set( aParams );
  }
}

JsGraph3D.prototype.SetLight = function( aParams ) {
  // aParams = {
  //   AmbientLight: JsgColor
  //   CamLight: JsgColor
  //   LightSourceList: Array of LightSource
  // }
  this.Scene.Set( aParams );
}

JsGraph3D.prototype.SaveLight = function( aParams ) {
  return this.Scene.Save( aParams );
}

JsGraph3D.prototype.SetAmbientLight = function( aAmbientLight ) {
  // aAmbientLight = Color
  this.Scene.AmbientLight = JsgColor.Ok(aAmbientLight) ? aAmbientLight : JsgColor.White();
}

JsGraph3D.prototype.SetCameraLight = function( aCamLight ) {
  // aCamLight = Color
  this.Scene.CamLight = JsgColor.Ok(aCamLight) ? aCamLight : JsgColor.White();
}

JsGraph3D.prototype.AddLightSource = function( aParams ) {
  // Adds a light source to this.Scene.LightSourceList.
  // To remove all light sourceses, create a new scene with this.NewScene().
  // Returns the created light source object.
  //
  // If aParams.LightDir is defined, then this is the vector to the light source at infinity (default)
  // If aParams.LightPos is defined, then this is the position of the light.
  // return JsgLightSource
  //
  // With the following properties you can define a light source by angles and distance rather than by vector:
  //
  // aParams: extends JsgLightSource by {
  //   HAng, VAng: number -> angle in degrees (default 0)
  //   Dist: number -> distance of light source from RefPoint
  // }
  // Note: if RefPoint is defined, then the Position of lightsource is RefPoint + Vector(HAng,VAng,Dist)
  // If RefPoint is not defined, then Vector(HAng,VAng,Dist) defines the direction to the light source at infinity.
  //
  return this.Scene.AddLightSource( new JsgLightSource(aParams) );
}

JsGraph3D.prototype.NewDisplayList = function( ) {
  this.DisplayList = new JsgDisplayList();
}

JsGraph3D.prototype.SaveDisplayList = function( aParams ) {
  var par = xDefObj( aParams, {} );
  par.DisplayList = this.DisplayList;
  return par;
}

JsGraph3D.prototype.SetDisplayList = function( aParams ) {
  if(xObj(aParams.DisplayList)) this.DisplayList = aParams.DisplayList;
}

JsGraph3D.prototype.GraphEleNPoints = function( aGraphEle ) {
  return aGraphEle.PointIxList.length;
}

JsGraph3D.prototype.GraphElePoint = function( aGraphEle, aIx ) {
  return this.DisplayList.PointList[aGraphEle.PointIxList[aIx]];
}

JsGraph3D.prototype.GraphEleCenter = function( aGraphEle ) {
  return aGraphEle.Center;
}

JsGraph3D.prototype.GraphEleNormal = function( aGraphEle ) {
  return aGraphEle.Normal;
}

////////////////////////////////
// Polygon, VectList and VectGrid generators

JsGraph3D.prototype.PolygonFromFunc = function( aParams, poly ) {
  // aParams = {
  //   Func: Function( x, aParams, retPoint ) returns [ x, y, z ]
  //   Min, Max: Number; Default = -1, 1
  //   Steps: Integer > 0; Default = 20
  //   Delta: Number
  //   Graph3D: Default = this
  //   ...
  // }
  // poly: JsgPolygon(3D) or undefined
  // returns poly or new JsgPolygon(3D)
  //
  // Func can return retPoint or new [x,y,z]
  //
  // Specify eather Steps or Delta. Min may be greater than Max. The sign of Delta is ignored.
  // You may supply additional properties in Params (e.g. a Plane), to pass them to Function.
  //
  // If Graph3d is not defined, it is set to this.

  aParams.Graph3D = xDefObj( aParams.Graph3D, this );
  var min = xDefNum( aParams.Min, -1 );
  var max = xDefNum( aParams.Max, 1 );
  var delta = 0.1;
  if (xNum( aParams.Steps )) {
    delta = (max - min) / aParams.Steps;
  } else if (xNum( aParams.Delta )) {
    delta = Math.abs( aParams.Delta );
    if (max < min) { delta = -delta; }
  }
  var limit = max + 0.1 * delta;
  poly = poly || new JsgPolygon( true );
  var point = this.WorkPoint3D;
  for (var x = min; (delta > 0) ? (x <= limit) : (x >= limit); x += delta) {
    poly.AddPoint3D( aParams.Func( x, aParams, point ) );
  }
  return poly;
}

JsGraph3D.prototype.VectListFromFunc = function( aParams ) {
  // aParams = {
  //   Func: Function( x, aParams ) returns [ x, y, z ]
  //   Min, Max: Number; Default = -1, 1
  //   Steps: Integer > 0; Default = 20
  //   Delta: Number
  //   Graph3D: Default = this
  //   ...
  // }
  //
  // Specify eather Steps or Delta. Min may be greater than Max. The sign of Delta is ignored.
  // You may supply additional properties in Params (e.g. a Plane), to pass them to Function.
  //
  // If Graph3d is not defined, it is set to this.

  aParams.Graph3D = xDefObj( aParams.Graph3D, this );
  var min = xDefNum( aParams.Min, -1 );
  var max = xDefNum( aParams.Max, 1 );
  var delta = 0.1;
  if (xNum( aParams.Steps )) {
    delta = (max - min) / aParams.Steps;
  } else if (xNum( aParams.Delta )) {
    delta = Math.abs( aParams.Delta );
    if (max < min) { delta = -delta; }
  }
  var limit = max + 0.1 * delta;
  var vectList = [];
  for (var x = min; (delta > 0) ? (x <= limit) : (x >= limit); x += delta) {
    vectList.push( aParams.Func( x, aParams ) );
  }
  return vectList;
}

JsGraph3D.prototype.VectGridFrom3DFunc = function( aParams ) {
  // aParams = {
  //   Func: Function( a, b, aParams ) returns [ x, y, z, ... ]
  //   Min, Max: Number; Default = -1, 1
  //   Min2, Max2: Number; Default = min, max
  //   Steps: Integer > 0; Default = 20
  //   Steps2: Integer > 0; Default = Steps
  //   Delta: Number
  //   Delta2: Number; Default = Delta
  //   Graph3D: Default = this
  //   ...
  // }
  //
  // a runs from min to max in the inner loop
  // b runs from min2 to max2 in the outer loop
  //
  // Specify eather Steps or Delta. Min may be greater than Max. The sign of Delta is ignored.
  // You may supply additional properties in Params (e.g. a Plane), to pass them to Function.
  //
  // If Graph3D is not defined, it is set to this.

  aParams.Graph3D = xDefObj( aParams.Graph3D, this );
  var min = xDefNum( aParams.Min, -1 );
  var max = xDefNum( aParams.Max, 1 );
  var min2 = xDefNum( aParams.Min2, min );
  var max2 = xDefNum( aParams.Max2, max );
  var delta = 0.1;
  if (xNum( aParams.Steps )) {
    delta = (max - min) / aParams.Steps;
  } else if (xNum( aParams.Delta )) {
    delta = Math.abs( aParams.Delta );
    if (max < min) { delta = -delta; }
  }
  var delta2 = delta;
  if (xNum( aParams.Steps2 )) {
    delta2 = (max2 - min2) / aParams.Steps2;
  } else if (xNum( aParams.Delta2 )) {
    delta2 = Math.abs( aParams.Delta2 );
    if (max2 < min2) { delta2 = -delta2; }
  }
  var limit = max + 0.1 * delta;
  var limit2 = max2 + 0.1 * delta2;
  var grid = [];
  for ( var b = min2; (delta2 > 0) ? (b <= limit2) : (b >= limit2); b += delta2 ) {
    var line = [];
    for ( var a = min; (delta > 0) ? (a <= limit) : (a >= limit); a += delta ) {
      line.push( aParams.Func( a, b, aParams ) );
    }
    grid.push( line );
  }
  return grid;
}

JsGraph3D.prototype.VectInterGridFrom3DFunc = function( aParams ) {
  // Creates a InterGrid, that is a special grid with extra points between a normal VectGrid.
  // So each second VectList is one element less and has an offset of Delta/2, Delta2/2.
  // Use AddAreasFromVectInterGrid instead of AddAreasFromVectGrid to create areas in display list.
  //
  // aParams = {
  //   Func: Function( a, b, aParams ) returns [ x, y, z, ... ]
  //   Min, Max: Number; Default = -1, 1
  //   Min2, Max2: Number; Default = min, max
  //   Steps: Integer > 0; Default = 20
  //   Steps2: Integer > 0; Default = Steps
  //   Delta: Number
  //   Delta2: Number; Default = Delta
  //   Graph3D: Default = this
  //   ...
  // }
  //
  // a runs from min to max in the inner loop
  // b runs from min2 to max2 in the outer loop
  //
  // Specify eather Steps or Delta. Min may be greater than Max. The sign of Delta is ignored.
  // You may supply additional properties in Params (e.g. a Plane), to pass them to Function.
  //
  // If Graph3D is not defined, it is set to this.

  aParams.Graph3D = xDefObj( aParams.Graph3D, this );
  var min = xDefNum( aParams.Min, -1 );
  var max = xDefNum( aParams.Max, 1 );
  var min2 = xDefNum( aParams.Min2, min );
  var max2 = xDefNum( aParams.Max2, max );
  var delta = 0.1;
  if (xNum( aParams.Steps )) {
    delta = (max - min) / aParams.Steps;
  } else if (xNum( aParams.Delta )) {
    delta = Math.abs( aParams.Delta );
    if (max < min) { delta = -delta; }
  }
  var delta2 = delta;
  if (xNum( aParams.Steps2 )) {
    delta2 = (max2 - min2) / aParams.Steps2;
  } else if (xNum( aParams.Delta2 )) {
    delta2 = Math.abs( aParams.Delta2 );
    if (max2 < min2) { delta2 = -delta2; }
  }
  var deltaInter = delta / 2;
  var deltaInter2 = delta2 / 2;
  var limit = max + 0.1 * delta;
  var limit2 = max2 + 0.1 * delta2;
  var grid = [];
  for ( var b = min2; (delta2 > 0) ? (b <= limit2) : (b >= limit2); b += delta2 ) {
    var line = [];
    for ( var a = min; (delta > 0) ? (a <= limit) : (a >= limit); a += delta ) {
      line.push( aParams.Func( a, b, aParams ) );
    }
    grid.push( line );
    var bInter = b + deltaInter2;
    if ((delta2 > 0) ? (bInter <= limit2) : (bInter >= limit2)) {
      var line = [];
      for ( var aInter = min + deltaInter; (delta > 0) ? (aInter <= limit) : (aInter >= limit); aInter += delta ) {
        line.push( aParams.Func( aInter, bInter, aParams ) );
      }
      grid.push( line );
    }
  }
  return grid;
}

JsGraph3D.prototype.AddPointsFromFunc = function( aParams ) {
  // aParams = {
  //   Func: Function( x, aParams ) returns [ x, y, z ]
  //   Min, Max: Number; Default = -1, 1
  //   Steps: Integer > 0; Default = 20
  //   Delta: Number
  //   Graph3D: Default = this
  //   EleData: JsgEleData; Default = this.DefaultEleData
  //   CopyEleData: Boolean; Default = true
  //   ...
  // }
  //
  // Specify eather Steps or Delta. Min may be greater than Max. The sign of Delta is ignored.
  // You may supply additional properties in Params (e.g. a Plane), to pass them to Function.
  //
  // If Graph3d is not defined, it is set to this.
  //
  // If aEleData is provided, a copy of that is created filling all not provided data with defaults,
  // and all created areas point to that element data.
  // Otherwise they point to this.DefaultEleData.

  var eleData = xDefObj( aParams.EleData, null );
  this.AddPointsFromVectList( this.VectListFromFunc( aParams ), eleData, aParams.CopyEleData );
}

JsGraph3D.prototype.AddLinesFromFunc = function( aParams ) {
  // aParams = {
  //   Func: Function( x, aParams ) returns [ x, y, z ]
  //   Min, Max: Number; Default = -1, 1
  //   Steps: Integer > 0; Default = 20
  //   Delta: Number
  //   Graph3D: Default = this
  //   EleData: JsgEleData; Default = this.DefaultEleData
  //   CopyEleData: Boolean; Default = true
  //   ...
  // }
  //
  // Specify eather Steps or Delta. Min may be greater than Max. The sign of Delta is ignored.
  // You may supply additional properties in Params (e.g. a Plane), to pass them to Function.
  //
  // If Graph3d is not defined, it is set to this.
  //
  // If aEleData is provided, a copy of that is created filling all not provided data with defaults,
  // and all created areas point to that element data.
  // Otherwise they point to this.DefaultEleData.

  var eleData = xDefObj( aParams.EleData, null );
  this.AddLinesFromVectList( this.VectListFromFunc( aParams ), eleData, aParams.CopyEleData );
}

JsGraph3D.prototype.AddAreasFrom3DFunc = function( aParams ) {
  // aParams = {
  //   Func: Function( a, b, aParams ) returns [ x, y, z, ... ] or null
  //   GridType: String; Default = 'RectGrid'; Values: 'RectGrid', 'InterGrid'
  //   Min, Max: Number; Default = -1, 1
  //   Min2, Max2: Number; Default = min, max
  //   Steps: Integer > 0; Default = 20
  //   Steps2: Integer > 0; Default = Steps
  //   Delta: Number
  //   Delta2: Number; Default = Delta
  //   Graph3D: Default = this
  //   EleData: JsgEleData; Default = this.DefaultEleData
  //   CopyEleData: Boolean; Default = true
  //   ...
  // }
  //
  // a runs from min to max in the inner loop
  // b runs from min2 to max2 in the outer loop
  //
  // Specify eather Steps or Delta. Min may be greater than Max. The sign of Delta is ignored.
  // You may supply additional properties in Params (e.g. a Plane), to pass them to Function.
  //
  // If Graph3d is not defined, it is set to this.
  //
  // If aEleData is provided, a copy of that is created filling all not provided data with defaults,
  // and all created areas point to that element data.
  // Otherwise they point to this.DefaultEleData.

  var eleData = xDefObj( aParams.EleData, null );
  var gridType = xDefStr( aParams.GridType, 'RectGrid' );
  if (gridType === 'InterGrid') {
    this.AddAreasFromVectInterGrid( this.VectInterGridFrom3DFunc( aParams ), eleData, aParams.CopyEleData );
  } else {
    this.AddAreasFromVectGrid( this.VectGridFrom3DFunc( aParams ), eleData, aParams.CopyEleData );
  }
}

JsGraph3D.prototype.AddPointsFrom3DFunc = function( aParams ) {
  // aParams = {
  //   Func: Function( a, b, aParams ) returns [ x, y, z, ... ]
  //   GridType: String; Default = 'RectGrid'; Values: 'RectGrid', 'InterGrid'
  //   Min, Max: Number; Default = -1, 1
  //   Min2, Max2: Number; Default = min, max
  //   Steps: Integer > 0; Default = 20
  //   Steps2: Integer > 0; Default = Steps
  //   Delta: Number
  //   Delta2: Number; Default = Delta
  //   Graph3D: this
  //   EleData: JsgEleData; Default = this.DefaultEleData
  //   CopyEleData: Boolean; Default = true
  //   ...
  // }
  //
  // a runs from min to max in the inner loop
  // b runs from min2 to max2 in the outer loop
  //
  // Specify eather Steps or Delta. Min may be greater than Max. The sign of Delta is ignored.
  // You may supply additional properties in Params (e.g. a Plane), to pass them to Function.
  //
  // If Graph3d is not defined, it is set to this.
  //
  // If aEleData is provided, a copy of that is created filling all not provided data with defaults,
  // and all created areas point to that element data.
  // Otherwise they point to this.DefaultEleData.

  var eleData = xDefObj( aParams.EleData, null );
  var gridType = xDefStr( aParams.GridType, 'RectGrid' );
  if (gridType === 'InterGrid') {
    this.AddPointsFromVectInterGrid( this.VectInterGridFrom3DFunc( aParams ), eleData, aParams.CopyEleData );
  } else {
    this.AddPointsFromVectGrid( this.VectGridFrom3DFunc( aParams ), eleData, aParams.CopyEleData );
  }
}

JsGraph3D.prototype.AddPoint = function( aPoint, aEleData, bCopy ) {
  // aPoint: JsgVect3
  // aEleData: JsgEleData (optional)
  // bCopy: Boolean (optional); Default = true; true -> store a copy of aEleData else store aEleData itself
  // returns point (JsgGraphEle)
  //
  // Creates a points and appends it to the display list.
  //
  // If aEleData is provided, a copy of that is created filling all not provided data with defaults,
  // and all created points are asigned with references to that element data.
  // Otherwise they refere to this.DefaultEleData.

  return this.AddPointsFromVectList( [ aPoint ], aEleData, bCopy );
}

JsGraph3D.prototype.AddPointsFromVectList = function( aVectList, aEleData, bCopy ) {
  // aVectList: array[] of JsgVect3
  // aEleData: JsgEleData (optional)
  // bCopy: Boolean (optional); Default = true; true -> store a copy of aEleData else store aEleData itself
  // returns last point (JsgGraphEle)
  //
  // Creates points from aVectList and appends them to the display list.
  //
  // If aEleData is provided, a copy of that is created filling all not provided data with defaults,
  // and all created points are asigned with references to that element data.
  // Otherwise they refere to this.DefaultEleData.

  var dl = this.DisplayList;
  var pointList = dl.PointList;
  var eleList = dl.GraphEleList;
  var pointListOffset = pointList.length;
  var eleData = this.AddOrGetDefaultEleData( aEleData, bCopy );
  var lastPoint = null;

  var ilen = aVectList.length;
  var ix = pointListOffset;
  for (var i = 0; i < ilen; i++) {
    pointList.push( aVectList[i] );
    lastPoint = new JsgGraphEle( [ ix++ ], eleData );
    eleList.push( lastPoint );
  }

  this.DisplayListUpdated();
  return lastPoint;
}

JsGraph3D.prototype.AddLine = function( aStartPoint, aEndPoint, aEleData, bCopy ) {
  // aStartPoint, aEndPoint: JsgVect3
  // aEleData: JsgEleData (optional)
  // bCopy: Boolean (optional); Default = true; true -> store a copy of aEleData else store aEleData itself
  // returns line (JsgGraphEle)
  //
  // Creates a line from aStartPoint to aEndPoint and appends it to the display list.
  //
  // If aEleData is provided, a copy of that is created filling all not provided data with defaults,
  // and all created points are asigned with references to that element data.
  // Otherwise they refere to this.DefaultEleData.

  return this.AddLinesFromVectList( [ aStartPoint, aEndPoint ], aEleData, bCopy );
}

JsGraph3D.prototype.AddLinesFromVectList = function( aVectList, aEleData, bCopy ) {
  // aVectList: array[] of JsgVect3
  // aEleData: JsgEleData (optional)
  // bCopy: Boolean (optional); Default = true; true -> store a copy of aEleData else store aEleData itself
  // returns JsgGraphEle; last line
  //
  // Creates lines from aVectList and appends them to the display list.
  //
  // If aEleData is provided, a copy of that is created filling all not provided data with defaults,
  // and all created points are asigned with references to that element data.
  // Otherwise they refere to this.DefaultEleData.

  var dl = this.DisplayList;
  var pointList = dl.PointList;
  var eleList = dl.GraphEleList;
  var pointListOffset = pointList.length;
  var eleData = this.AddOrGetDefaultEleData( aEleData, bCopy );
  var lastLine = null;

  var ilen = aVectList.length;
  for (var i = 0; i < ilen; i++) {
    pointList.push( aVectList[i] );
  }

  var ilen = aVectList.length - 1;
  var ix = pointListOffset;
  for (var i = 0; i < ilen; i++) {
    lastLine = new JsgGraphEle( [ ix, ix+1 ], eleData );
    eleList.push( lastLine );
    ix++;
  }

  this.DisplayListUpdated();
  return lastLine;
}

JsGraph3D.prototype.AddTriangle = function( P1, P2, P3, aEleData, bCopy ) {
  // P1, P2, P3: JsgVect3
  // aEleData: JsgEleData (optional)
  // bCopy: Boolean (optional); Default = true; true -> store a copy of aEleData else store aEleData itself
  // returns area (JsgGraphEle)
  //
  // Creates an area from 3 points and appends it to the display list.
  //
  // If aEleData is provided, a copy of that is created filling all not provided data with defaults,
  // and all created areas point to that element data.
  // Otherwise they point to this.DefaultEleData.

  return this.AddAreasFromVectGrid( [[ P1, P3 ],[ P2, null ]], aEleData, bCopy );
}

JsGraph3D.prototype.AddRect = function( P1, P2, P3, P4, aEleData, bCopy ) {
  // P1, P2, P3, P4: JsgVect3
  // aEleData: JsgEleData (optional)
  // bCopy: Boolean (optional); Default = true; true -> store a copy of aEleData else store aEleData itself
  // returns area (JsgGraphEle)
  //
  // Creates an area from 4 points and appends it to the display list.
  //
  // If aEleData is provided, a copy of that is created filling all not provided data with defaults,
  // and all created areas point to that element data.
  // Otherwise they point to this.DefaultEleData.

  return this.AddAreasFromVectGrid( [[ P1, P4 ],[ P2, P3 ]], aEleData, bCopy );
}

JsGraph3D.prototype.AddAreasFromVectGrid = function( aVectGrid, aEleData, bCopy ) {
  // aVectGrid: Array[][] of JsgVect3
  // aEleData: JsgEleData (optional)
  // bCopy: Boolean (optional); Default = true; true -> store a copy of aEleData else store aEleData itself
  // returns last area (JsgGraphEle)
  //
  // Creates areas from aVectGrid and appends them to the display list.
  //
  // If aEleData is provided, a copy of that is created filling all not provided data with defaults,
  // and all created areas point to that element data.
  // Otherwise they point to this.DefaultEleData.
  //
  // Note: some Points in aVectGrid may be null. That means,
  // some area will have only 3 Points. If for an area more than 2 points are
  // null, that area will not be stored in the display list!

  var dl = this.DisplayList;
  var pointList = dl.PointList;
  var areaList = dl.GraphEleList;
  var pointListOffset = pointList.length;
  var eleData = this.AddOrGetDefaultEleData( aEleData, bCopy );

  var ilen = aVectGrid.length;
  var ixGrid = [];
  var ix = pointListOffset;

  // store aVectGrid in a linear list and create a visiGrid with indexes into this list
  for (var i = 0; i < ilen; i++) {
    var ixLine = [];
    var vectList = aVectGrid[i], jlen = vectList.length;
    for (var j = 0; j < jlen; j++) {
      if (vectList[j]) {
        pointList.push( vectList[j] );
        ixLine.push( ix++ );
      } else {
        ixLine.push( -1 );
      }
    }
    ixGrid.push( ixLine );
  }

  // create area list
  var lastArea = null;
  var ilen = aVectGrid.length, imax = ilen-1;
  for (var i = 0; i < imax; i++) {
    var jlen = aVectGrid[i].length, jmax = jlen-1;
    for (var j = 0; j < jmax; j++) {
      var pointIxList = [];
      ix = ixGrid[i][j];
      if (ix >= 0) { pointIxList.push( ix ); }
      ix = ixGrid[i+1][j];
      if (ix >= 0) { pointIxList.push( ix ); }
      ix = ixGrid[i+1][j+1];
      if (ix >= 0) { pointIxList.push( ix ); }
      ix = ixGrid[i][j+1];
      if (ix >= 0) { pointIxList.push( ix ); }
      if (pointIxList.length > 2) {
        lastArea = new JsgGraphEle( pointIxList, eleData );
        areaList.push( lastArea );
      }
    }
  }

  this.DisplayListUpdated();
  return lastArea;
}

JsGraph3D.prototype.AddAreasFromVectInterGrid = function( aVectInterGrid, aEleData, bCopy ) {
  // aVectInterGrid: Array[][] of JsgVect3
  // aEleData: JsgEleData (optional)
  // bCopy: Boolean (optional); Default = true; true -> store a copy of aEleData else store aEleData itself
  //
  // Creates areas from aVectInterGrid and appends them to the display list.
  //
  // If aEleData is provided, a copy of that is created filling all not provided data with defaults,
  // and all created areas point to that element data.
  // Otherwise they point to this.DefaultEleData.
  //
  // Require: No Points in aVectInterGrid may be null.

  if (aVectInterGrid.length === 0) return;
  if (aVectInterGrid[0].length === 0) return;

  var dl = this.DisplayList;
  var pointList = dl.PointList;
  var areaList = dl.GraphEleList;
  var pointListOffset = pointList.length;
  var eleData = this.AddOrGetDefaultEleData( aEleData, bCopy );

  var ilen = aVectInterGrid.length;
  var ixInterGrid = [];
  var ix = pointListOffset;

  // store points of aVectInterGrid in DisplayLists PointList and
  // create a ixInterGrid with references to the points in the PointList
  for (var i = 0; i < ilen; i++) {
    var ixLine = [];
    var vectList = aVectInterGrid[i]
    var jlen = vectList.length;
    for (var j = 0; j < jlen; j++) {
      pointList.push( vectList[j] );
      ixLine.push( ix++ );
    }
    ixInterGrid.push( ixLine );
  }

  // create area list
  var imax = ilen - 1;
  var jlen = aVectInterGrid[0].length;
  var jmax = jlen - 1;
  for (var i = 0; i < imax; i += 2) {
    for (var j = 0; j < jmax; j++) {
      var pointIxList = [];
      pointIxList.push( ixInterGrid[i][j] );
      pointIxList.push( ixInterGrid[i+2][j] );
      pointIxList.push( ixInterGrid[i+1][j] );
      if (!this.IsNullArea( pointIxList, pointList )) {
        areaList.push( new JsgGraphEle( pointIxList, eleData ) );
      }

      var pointIxList = [];
      pointIxList.push( ixInterGrid[i+2][j] );
      pointIxList.push( ixInterGrid[i+2][j+1] );
      pointIxList.push( ixInterGrid[i+1][j] );
      if (!this.IsNullArea( pointIxList, pointList )) {
        areaList.push( new JsgGraphEle( pointIxList, eleData ) );
      }

      var pointIxList = [];
      pointIxList.push( ixInterGrid[i+2][j+1] );
      pointIxList.push( ixInterGrid[i][j+1] );
      pointIxList.push( ixInterGrid[i+1][j] );
      if (!this.IsNullArea( pointIxList, pointList )) {
        areaList.push( new JsgGraphEle( pointIxList, eleData ) );
      }

      var pointIxList = [];
      pointIxList.push( ixInterGrid[i][j+1] );
      pointIxList.push( ixInterGrid[i][j] );
      pointIxList.push( ixInterGrid[i+1][j] );
      if (!this.IsNullArea( pointIxList, pointList )) {
        areaList.push( new JsgGraphEle( pointIxList, eleData ) );
      }
    }
  }

  this.DisplayListUpdated();
}

JsGraph3D.prototype.IsNullArea = function( aPointIxList, aPointList ) {
  // require aPointList.length == 3
  var b = aPointList[aPointIxList[0]];
  var v1 = JsgVect3.Sub( aPointList[aPointIxList[1]], b );
  var v2 = JsgVect3.Sub( aPointList[aPointIxList[2]], b );
  var c = JsgVect3.Mult( v1, v2 );
  return JsgVect3.Length2( c ) == 0;
}

JsGraph3D.prototype.AddPointsFromVectGrid = function( aVectGrid, aEleData, bCopy ) {
  // aVectGrid: array[][] of JsgVect3
  // aEleData: JsgEleData (optional)
  // bCopy: Boolean (optional); Default = true; true -> store a copy of aEleData else store aEleData itself
  //
  // Creates points from aVectGrid and appends them to the display list.
  //
  // If aEleData is provided, a copy of that is created filling all not provided data with defaults,
  // and all created points are asigned with references to that element data.
  // Otherwise they refer to this.DefaultEleData.
  //
  // Note: some Points in aVectGrid may be null. These points are not stored in the display list.

  var dl = this.DisplayList;
  var pointList = dl.PointList;
  var eleList = dl.GraphEleList;
  var pointListOffset = pointList.length;
  var eleData = this.AddOrGetDefaultEleData( aEleData, bCopy );

  var ilen = aVectGrid.length;
  var ix = pointListOffset;
  for (var i = 0; i < ilen; i++) {
    var vectList = aVectGrid[i], jlen = vectList.length;
    for (var j = 0; j < jlen; j++) {
      var pnt = vectList[j];
      if (pnt) {
        pointList.push( pnt );
        var pointIxList = [ ix++ ];
        eleList.push( new JsgGraphEle( pointIxList, eleData ) );
      }
    }
  }

  this.DisplayListUpdated();
}

JsGraph3D.prototype.AddPointsFromVectInterGrid = function( aVectInterGrid, aEleData, bCopy ) {
  // aVectInterGrid: array[][] of JsgVect3
  // aEleData: JsgEleData (optional)
  // bCopy: Boolean (optional); Default = true; true -> store a copy of aEleData else store aEleData itself
  //
  // Creates points from aVectInterGrid and appends them to the display list.
  //
  // If aEleData is provided, a copy of that is created filling all not provided data with defaults,
  // and all created points are asigned with references to that element data.
  // Otherwise they refer to this.DefaultEleData.
  //
  // Require: no Points in aVectInterGrid may be null!

  this.AddPointsFromVectGrid( aVectInterGrid, aEleData, bCopy );
}

JsGraph3D.prototype.DisplayListUpdated = function() {
  // This function must be called, after a global attribute was changed that affects the
  // Drawing modes or after the display list was changed. It forces the DrawDisplayList
  // to recompute area center/normal vectors and to transform all point to the viewing plane.

  this.DisplayList.StaticDataReady = false;
  this.DisplayList.ViewDataReady = false;
}

JsGraph3D.prototype.DrawDisplayList = function( aBeforeDrawFunc, aAfterDrawFunc ) {
  // Draws all elements in the display list with there assosiated Data.
  //
  // Before drawing a graphics element, an installed callback (Data.AreaAttrFunc...Data.PointAttrFunc)
  // is called, in whitch the attributes of each individual element can be changed. E.g. colors of areas
  // can be set as a function of the Z-Position. In that manner colored curves can be produced.
  //
  // Call format:
  //   aAttrFunc: function( this, aGraphEle, aEleAttr )
  //   aBeforDrawFunc, aAfterDrawFunc: function( this )
  //
  // Use aBeforeDrawFunc and aAfterDrawFunc to asynchronously draw backgrounds, decorations or any additional graphic.

  if (xFuncOrNull(aBeforeDrawFunc)) this.BeforeDrawFunc = aBeforeDrawFunc;
  if (xFuncOrNull(aAfterDrawFunc))  this.AfterDrawFunc = aAfterDrawFunc;

  this.CompDLStaticData( );

  this.CompDLViewData( );

  this.Async.CallObj( this,
    this.DoDrawDisplayList,
    {
      AreaIx: 0,
      BeforeDrawFunc: this.BeforeDrawFunc,
      AfterDrawFunc: this.AfterDrawFunc
    }
  );
}

////////////////////////////////////
// Transformation functions

JsGraph3D.prototype.ResetTrans3D = function( clearStack ) {
  this.Trans3D = null;
  if (clearStack) {
    this.Trans3DStack = [];
  }
  return this;
}

JsGraph3D.prototype.SaveTrans3D = function( reset ) {
  // pushes a copy of 3D transformation onto a stack and returns a reference to this copy
  // the returned trans can be used in SetTrans()
  // if the current transformation is the unit transformation then null is returned
  // if reset == true then function ResetTrans3D() is called
  var transCopy = null;
  if (this.Trans3D) transCopy = JsgMat3.Copy( this.Trans3D );
  this.Trans3DStack.push( transCopy );
  if (reset) this.Trans3D = null;
  return transCopy;
}

JsGraph3D.prototype.RestoreTrans3D = function() {
  if (this.Trans3DStack.length > 0) this.Trans3D = this.Trans3DStack.pop();
}

JsGraph3D.prototype.SetTrans3D = function( mat, useMat ) {
  // if useMat is false then a copy of mat is stored in this.Trans3D
  // mat: JsgMat3 or null
  if (mat) {
    if (useMat) {
      this.Trans3D = mat;
    } else {
      this.Trans3D = JsgMat3.Copy( mat );
    }
  } else {
    this.Trans3D = null;
  }
}

JsGraph3D.prototype.TransMove3D = function( x, y, z ) {
  if (JsgVect3.Ok(x)) return this.TransMove3D( x[0], x[1], x[2] );
  this.Trans3D = JsgMat3.Moving( x, y, z, this.Trans3D );
  return this;
}

JsGraph3D.prototype.TransScale3D = function( sx, sy, sz ) {
  if (JsgVect3.Ok(sx)) return this.TransScale3D( sx[0], sx[1], sx[2] );
  this.Trans3D = JsgMat3.Scaling( sx, sy, sz, this.Trans3D );
  return this;
}

JsGraph3D.prototype.TransRotateX3D = function( ang ) {
  this.Trans3D = JsgMat3.RotatingX( this.AngleToRad(ang), this.Trans3D );
  return this;
}

JsGraph3D.prototype.TransRotateY3D = function( ang ) {
  this.Trans3D = JsgMat3.RotatingY( this.AngleToRad(ang), this.Trans3D );
  return this;
}

JsGraph3D.prototype.TransRotateZ3D = function( ang ) {
  this.Trans3D = JsgMat3.RotatingZ( this.AngleToRad(ang), this.Trans3D );
  return this;
}

JsGraph3D.prototype.TransRotateVect3D = function( v, ang ) {
  // set rotation transformation to ang around vector v
  // ang in degrees or radians depending on AngleMeasure

  // compute longitude lambda and latitude phi
  var n = JsgVect3.Norm( [ v[0], v[1], 0 ] );
  var lambda = Math.acos( JsgVect3.ScalarProd( n, [1,0,0] ) );
  if (v[1] < 0) lambda *= -1;

  var vl = JsgVect3.Length( v );
  var phi = 0;
  if (vl != 0) phi = Math.acos( v[2] / vl );

  ang = this.AngleToRad(ang);

  var am = this.SetAngleMeasure( 'rad' );
    // combine rotations
    this.TransRotateZ3D( -lambda );
    this.TransRotateY3D( -phi );
      this.TransRotateZ3D( ang );
    this.TransRotateY3D( phi );
    this.TransRotateZ3D( lambda );
  this.SetAngleMeasure( am );
}

JsGraph3D.prototype.AddTrans3D = function( mat ) {
  // mat: JsgMat3
  if (this.Trans3D) {
    this.Trans3D = JsgMat3.Mult( this.Trans3D, mat );
  } else {
    this.Trans3D = JsgMat3.Copy( mat );
  }
  return this;
}

// private functions ----------------------------------------------------------------------

JsGraph3D.prototype.GetLightSourceVect = function( aLightSource, aAreaPos ) {
  if (aLightSource.LightPos) {
    return JsgVect3.Norm( JsgVect3.Sub( aLightSource.LightPos, aAreaPos ) );
  } else {
    return aLightSource.LightDir;
  }
}

JsGraph3D.prototype.CompAreaPhongLighting = function( aLightSource, aArea, aAreaAttr, scalProdNV, areaLight, lightColorRet ) {
  // returns Color -> over all Intensity for a phong lighting. must be multiplied with area color
  // scalProdNV = scalar product of normal vector with view vector
  // areaLight = aArea.Data.Lighting or .BackLighting, depending on sign of scalProdNV

  var lVect = this.GetLightSourceVect( aLightSource, aArea.Center );
  var nVect = JsgVect3.CopyTo( aArea.Normal, this.CompAreaPhongLightingNVect );
  var rVect = JsgVect3.CopyTo( nVect, this.CompAreaPhongLightingRVect );
  JsgVect3.ScaleTo( rVect, 2 * JsgVect3.ScalarProd( lVect, nVect ) );
  JsgVect3.SubFrom( rVect, lVect );
  JsgVect3.NormTo( rVect );
  var vVect = JsgVect3.CopyTo( this.Camera.CamPos, this.CompAreaPhongLightingVVect );
  JsgVect3.SubFrom( vVect, aArea.Center );
  JsgVect3.NormTo( vVect );

  // init for front or back face lighting
  var areaData = aArea.Data;
  if (scalProdNV < 0) {
    // change to back face lighting
    JsgVect3.ScaleTo( nVect, -1 );
  }

  if (areaLight.ApplyPhong) {

    // diffuse reflection
    var sp = JsgVect3.ScalarProd( lVect, nVect );
    if (sp < 0) {
      sp *= -areaLight.DiffXRay;
    }
    var diff = areaLight.DiffRefl * Math.pow( sp, areaLight.Roughness ) * aLightSource.DiffInt;

    // specular reflection
    sp = JsgVect3.ScalarProd( rVect, vVect );
    if (sp < 0) {
      sp *= -areaLight.SpecXRay;
    }
    var spec = areaLight.SpecRefl * Math.pow( sp, areaLight.Shiny ) * aLightSource.SpecInt;
    JsgColor.CopyTo( aLightSource.LightColor, lightColorRet );
    JsgColor.Scale( lightColorRet, areaLight.Dimm * (diff + spec) );

  } else {

    JsgColor.SetBlack( lightColorRet );

  }
}

JsGraph3D.prototype.CompAreaLSLighting = function( aArea, aAreaAttr, scalProdNV, areaLight, lightColorRet ) {
  // scalProdNV = scalar product of normal vector with view vector
  // areaLight = aArea.Data.Lighting or .BackLighting, depending on sign of scalProdNV
  // returns Color

  JsgColor.SetBlack( lightColorRet );

  if (areaLight.ApplyPhong) {
    // process all phong light souces
    var lightList = this.Scene.LightSourceList;
    var llen = lightList.length;
    var newLightColor = JsgColor.SetBlack( this.CompAreaLSLightingColor );
    for (var i = 0; i < llen; i++) {
      this.CompAreaPhongLighting( lightList[i], aArea, aAreaAttr, scalProdNV, areaLight, newLightColor );
      JsgColor.Add( lightColorRet, newLightColor );
    }
  }
}

JsGraph3D.prototype.CompAreaLighting = function( aArea, aAreaAttr, aScalProd, aAreaColorRet ) {
  // aScalProd = scalar product of vector cam to area center and area normal vector
  var scene = this.Scene;
  var areaData = aArea.Data;

  // check whether front or back face is visible
  var areaLight, areaColor;
  var sp = aScalProd;
  if (sp < 0) {
    sp = -sp;
    areaLight = areaData.BackLighting;
    areaColor = aAreaAttr.AreaBackColor;
  } else {
    areaLight = areaData.Lighting;
    areaColor = aAreaAttr.AreaColor;
  }

  // if no shader is selected return area color times aperture
  if (!(areaLight.ApplyCamLight || areaLight.ApplyAmbient || areaLight.ApplyPhong)) {
    JsgColor.CopyTo( areaColor, aAreaColorRet );
    JsgColor.Scale( aAreaColorRet, this.Camera.Aperture );
    return;
  }

  var lightColor = JsgColor.SetBlack( this.CompAreaLightingLightColor );
  var computedColor = this.CompAreaLightingColor;

  // apply ambient lighting
  if (areaLight.ApplyAmbient) {
    JsgColor.CopyTo( scene.AmbientLight, computedColor );
    JsgColor.Scale( computedColor, areaLight.AmbiRefl * areaLight.Dimm );
    JsgColor.Add( lightColor, computedColor );
  }

  // compute lighting from camera
  if (areaLight.ApplyCamLight) {
    JsgColor.CopyTo( scene.CamLight, computedColor );
    JsgColor.Scale( computedColor, sp * areaLight.CamRefl * areaLight.Dimm );
    JsgColor.Add( lightColor, computedColor );
  }

  // compute lighting from all light sources
  if (areaLight.ApplyPhong && scene.LightSourceList.length > 0) {
    this.CompAreaLSLighting( aArea, aAreaAttr, aScalProd, areaLight, computedColor );
    JsgColor.Add( lightColor, computedColor );
  }

  JsgColor.CopyTo( areaColor, aAreaColorRet );
  JsgColor.Scale( JsgColor.Mult( aAreaColorRet, lightColor ), this.Camera.Aperture );

  // color post processing
  if (areaLight.BlackAndWhite) {
    var mean = (aAreaColorRet[0] + aAreaColorRet[1] + aAreaColorRet[2]) / 3;
    JsgColor.SetBW( aAreaColorRet, mean );
  }
  if (areaLight.ColorResolution > 0) {
    var cr = areaLight.ColorResolution;
    aAreaColorRet[0] = Math.floor( aAreaColorRet[0] * cr + 0.5 ) / cr;
    aAreaColorRet[1] = Math.floor( aAreaColorRet[1] * cr + 0.5 ) / cr;
    aAreaColorRet[2] = Math.floor( aAreaColorRet[2] * cr + 0.5 ) / cr;
  }
}

JsGraph3D.prototype.CompDLStaticData = function( ) {
  if (this.DisplayList.StaticDataReady) return;

  this.Async.CallObj( this,
    this.CompEleListCenter,
    { AreaIx: 0, ListLength: this.DisplayList.GraphEleList.length },
    function CB_ShowMessage(p) {
      this.ShowMessage( 'Computing Element Centers', p.AreaIx, p.ListLength );
    }
  );

  this.Async.CallObj( this,
    this.CompAreaListNormal,
    { AreaIx: 0, ListLength: this.DisplayList.GraphEleList.length },
    function CB_ShowMessage(p) {
      this.ShowMessage( 'Computing Area Normal Vectors', p.AreaIx, p.ListLength );
    }
  );

  this.Async.CallObj( this,
    this.DLStaticDataIsReady,
    { }
  );
}

JsGraph3D.prototype.DLStaticDataIsReady = function( ) {
  //console.log( 'JsGraph3D.DLStaticDataIsReady' );
  this.DisplayList.StaticDataReady = true;
}

JsGraph3D.prototype.CompDLViewData = function( ) {
  if (this.DisplayList.ViewDataReady) return;

  this.Async.CallObj( this,
    this.CompDLCenterToCam,
    { },
    function CB_ShowMessage() {
      this.ShowMessage('Computing Element Distances to Camera');
    }
  );

  this.Async.CallObj( this,
    this.SortDisplayList,
    { },
    function CB_ShowMessage() {
      this.ShowMessage('Sorting Elements by Distance to Camera');
    }
  );

  this.Async.CallObj( this,
    this.DLViewDataIsReady,
    { }
  );
}

JsGraph3D.prototype.DLViewDataIsReady = function( ) {
  //console.log( 'JsGraph3D.DLViewDataIsReady' );
  this.DisplayList.ViewDataReady = true;
  }

JsGraph3D.prototype.DoDrawDisplayList = function( aParam ) {
  //console.log( 'JsGraph3D.DoDrawDisplayList: ', aParam.AreaIx );
  var startIx = aParam.AreaIx;
  var pointList = this.DisplayList.PointList;
  var eleList = this.DisplayList.GraphEleList;
  var elen = eleList.length;
  this.Async.StartTimer();

  var vCenterToCam;
  this.BeginDrawing();
  if (startIx == 0) {
    vCenterToCam = [0,0,0];
    this.Clear();
    if (this.Camera.HighResolution != '') {
      var onOff = (this.Camera.HighResolution == 'on');
      this.SetHighResolution( onOff );
    }
    if (aParam.BeforeDrawFunc) {
      aParam.BeforeDrawFunc( this );
    }
  } else {
    vCenterToCam = aParam.VCenterToCam;
  }

  var attr = this.WorkingAttr;
  var computedAreaColor = this.ComputedAreaColor;
  var computedLineColor = computedAreaColor;
  var computedLineWidth = 0;
  var computedLineAlpha = 1;
  var poly = this.Poly3D;

  for (var i = startIx; i < elen; i++) {
    var graphEle = eleList[i];
    var eleData = graphEle.Data;
    attr.CopyFrom( eleData.Attr );

    var pointIxList = graphEle.PointIxList, plen = pointIxList.length;
    if (plen > 2) {

      // draw area
      var skip = false;

      // skip backface areas
      JsgVect3.CopyTo( this.Camera.CamPos, vCenterToCam );
      JsgVect3.SubFrom( vCenterToCam, graphEle.Center );
      JsgVect3.NormTo( vCenterToCam );
      var sp = JsgVect3.ScalarProd( vCenterToCam, graphEle.Normal );
      var isBackFace = (sp <= 0);
      if (!eleData.DrawBackface) skip = isBackFace;

      if (!skip) {

        // let callback change graphEle attr
        if (eleData.AreaAttrFunc) {
          eleData.AreaAttrFunc( this, graphEle, attr, sp );
        }

        if (eleData.AreaDrawMode & 2) {

          // fill area
          // compute lighting (preserve alpha channel)
          var alpha = isBackFace ? JsgColor.Alpha(attr.AreaBackColor) : JsgColor.Alpha(attr.AreaColor);
          skip = (alpha < this.MinAlpha);
          if (!skip) {

            this.CompAreaLighting( graphEle, attr, sp, computedAreaColor );
            JsgColor.SetAlpha( computedAreaColor, alpha );

            if (!(eleData.AreaDrawMode & 1)) {

              // fill area without border:
              // alpha of area border line must be smaller then alpha of area
              // because borders of different areas overlap
              computedLineColor = computedAreaColor;
              computedLineAlpha = Math.pow( alpha, 4 );
              computedLineWidth = 0;

              // handle ContourMode: draw back faced areas with thick dark border
              if (eleData.ContourMode) {
                if (Math.abs(sp) <= eleData.ContourSens) {
                  JsgColor.CopyTo( eleData.ContourColor, computedLineColor );
                  computedLineWidth = eleData.ContourWidth;
                }
              }

            } else {

              computedLineColor = isBackFace ? attr.AreaBackLineColor : attr.LineColor;
              computedLineAlpha = JsgColor.Alpha(computedLineColor);
              computedLineWidth = attr.LineWidth;

              // fill area with border
              skip = (JsgColor.Alpha(computedAreaColor) < this.MinAlpha && computedLineAlpha < this.MinAlpha);

            }
          } // end (!skip)

        } else {

          computedLineColor = isBackFace ? attr.AreaBackLineColor : attr.LineColor;
          computedLineAlpha = JsgColor.Alpha(computedLineColor);
          computedLineWidth = attr.LineWidth;

          // draw area border only
          skip = (computedLineAlpha < this.MinAlpha);

        }
      }

      if (!skip) {
        if (eleData.AreaDrawMode & 2) {
          this.SetBgColor( JsgColor.ToString(computedAreaColor) );
        }
        // note: always draw line
        JsgColor.SetAlpha( computedLineColor, computedLineAlpha );
        this.SetColor( JsgColor.ToString(computedLineColor) );
        this.SetLineWidth( computedLineWidth );

        // draw graphEle
        poly.Reset();
        for (var p = 0; p < plen; p++) {
          poly.AddPoint3D( pointList[pointIxList[p]] );
        }
        var dmode = eleData.AreaDrawMode | 5; // 5 -> close path and draw line
        this.FastClosedPolygon3D( poly, dmode );
      }

    } else if (plen == 2) {

      // draw line

      // let callback change graphEle attr
      if (eleData.LineAttrFunc) {
        eleData.LineAttrFunc( this, graphEle, attr );
      }

      var skip = (JsgColor.Alpha(attr.LineColor) < this.MinAlpha);
      if (!skip) {
        this.SetLineAttr( JsgColor.ToString(attr.LineColor), attr.LineWidth );
        this.FastLine3D( pointList[pointIxList[0]], pointList[pointIxList[1]] );
      }

    } else {

      // draw point

      // let callback change graphEle attr
      if (eleData.PointAttrFunc) {
        eleData.PointAttrFunc( this, graphEle, attr );
      }

      var skip = (JsgColor.Alpha(attr.AreaColor) < this.MinAlpha && JsgColor.Alpha(attr.LineColor) < this.MinAlpha);
      if (!skip) {
        this.SetMarkerAttr( attr.Symbol, attr.SymbSize, JsgColor.ToString(attr.LineColor), JsgColor.ToString(attr.AreaColor), attr.LineWidth );
        this.FastMarker3D( pointList[pointIxList[0]], eleData.PointDrawMode );
      }

    }

    if (this.Async.IsTimerExpired(this.UpdateInterval)) {
      this.EndDrawing();
      aParam.VCenterToCam = vCenterToCam;
      aParam.AreaIx = i + 1;
      return true;
    }
  }

  if (aParam.AfterDrawFunc) {
    aParam.AfterDrawFunc( this );
  }

  this.EndDrawing();
  return false;
}

JsGraph3D.prototype.CompEleListCenter = function( aParam ) {
  // compute center position for areas, lines and points -> this.DisplayList.GraphEleList[i].Center
  //console.log( 'JsGraph3D.CompEleListCenter: ', aParam.AreaIx );
  var eleList = this.DisplayList.GraphEleList, elen = eleList.length;
  var pointList = this.DisplayList.PointList;
  var startIx = aParam.AreaIx;
  this.Async.StartTimer();
  for (var i = startIx; i < elen; i++) {
    var graphEle = eleList[i];
    var pointIxList = graphEle.PointIxList, plen = pointIxList.length;
    if (plen >= 2) {
      if (graphEle.Center) {
        JsgVect3.Reset( graphEle.Center );
      } else {
        graphEle.Center = JsgVect3.Null();
      }
      for (var p = 0; p < plen; p++) {
        JsgVect3.AddTo( graphEle.Center, pointList[pointIxList[p]] );
      }
      JsgVect3.ScaleTo( graphEle.Center, 1 / plen );
    } else if (plen == 1) {
      graphEle.Center = pointList[pointIxList[0]];
    }
    if (this.Async.IsTimerExpired(this.UpdateInterval)) {
      aParam.AreaIx = i + 1;
      return true;
    }
  }
  return false;
}

JsGraph3D.prototype.CompAreaListNormal = function( aParam ) {
  // compute normal vectors for areas -> this.DisplayList.GraphEleList[i].Normal
  //console.log( 'JsGraph3D.CompAreaListNormal: ', aParam.AreaIx );
  var eleList = this.DisplayList.GraphEleList, elen = eleList.length;
  var pointList = this.DisplayList.PointList;
  var startIx = aParam.AreaIx;
  var normal = JsgVect3.Null();
  var v1 = JsgVect3.Null();
  var v2 = JsgVect3.Null();
  this.Async.StartTimer();
  for (var i = startIx; i < elen; i++) {
    var graphEle = eleList[i];
    var pointIxList = graphEle.PointIxList;
    var plen = pointIxList.length;
    var eleData = graphEle.Data;
    if (plen == 3) {
      // handle triangles
      var center = graphEle.Center;
      if (!graphEle.Normal) graphEle.Normal = JsgVect3.Null();
      JsgVect3.CopyTo( pointList[pointIxList[plen-1]], v1 );
      JsgVect3.SubFrom( v1, center );
      JsgVect3.CopyTo( pointList[pointIxList[0]], v2 );
      JsgVect3.SubFrom( v2, center );
      JsgVect3.MultTo( graphEle.Normal, v2, v1 );
      JsgVect3.NormTo( graphEle.Normal );
    } else if (plen > 3) {
      // compute mean normals for areas with more than 3 edges
      var pp = plen-1;
      var center = graphEle.Center;
      JsgVect3.CopyTo( pointList[pointIxList[pp]], v1 );
      JsgVect3.SubFrom( v1, center );
      if (!graphEle.Normal) {
        graphEle.Normal = JsgVect3.Null();
      } else {
        JsgVect3.Reset( graphEle.Normal );
      }
      for (var p = 0; p < plen; p++) {
        JsgVect3.CopyTo( pointList[pointIxList[p]], v2 );
        JsgVect3.SubFrom( v2, center );
        JsgVect3.MultTo( normal, v2, v1 );
        JsgVect3.NormTo( normal );
        JsgVect3.AddTo( graphEle.Normal, normal );
        JsgVect3.CopyTo( v2, v1 );
        pp = p;
      }
      JsgVect3.NormTo( graphEle.Normal );
    }
    if (this.Async.IsTimerExpired(this.UpdateInterval)) {
      aParam.AreaIx = i + 1;
      return true;
    }
  }
  return false;
}

JsGraph3D.prototype.CompDLCenterToCam = function( ) {
  // compute the distance squared from each element center to the current camera position
  // requires CompEleListCenter is called to compute the elements center
  var eleList = this.DisplayList.GraphEleList, elen = eleList.length;
  var camPos = this.Camera.CamPos;
  var vect = this.WorkPoint3D;
  for (var i = 0; i < elen; i++) {
    var graphEle = eleList[i];
    JsgVect3.CopyTo( graphEle.Center, vect );
    JsgVect3.SubFrom( vect, camPos );
    graphEle.CamDist2 = JsgVect3.Length2( vect );
  }
}

JsGraph3D.prototype.SortDisplayList = function( ) {
  // requires CompDLCenterToCam() is called to compute the CamDist2 of all elements
  this.DisplayList.GraphEleList.sort(
    function CB_Compare_CamDist( a, b ) {
      var aa = a.CamDist2, bb = b.CamDist2;
      return (aa < bb) ? 1 : (aa > bb) ? -1 : 0;
    }
  );
}

JsGraph3D.prototype.ShowMessage = function( aText, aCurrent, aMax ) {
  if (!this.ShowProgress) { return; }
  var text = aText;
  if (xNum(aCurrent) && xNum(aMax)) {
    var procent = (aCurrent * 100 / aMax).toFixed(0);
    text += ': ' + procent + '% (' + aCurrent.toFixed(0) + ' of ' + aMax.toFixed(0) + ')';
  }
  this.Clear();
  var oldTrans = this.SelectTrans( 'viewport' );
  this.SetTextAttr( 'Arial', 15, 'black', 'normal', 'normal', 'center', 'middle' );
  var vpCenterX = this.VpXmin + 0.5 * this.VpWidth;
  var vpCenterY = this.VpYmin + 0.5 * this.VpHeight;
  this.Text( text, vpCenterX, vpCenterY );
  this.SelectTrans( oldTrans );
}

///////////////////////////////////////
// 3D variants of JsGraph 2D functions

JsGraph3D.prototype.NewPoly3D = function( applyTrans ) {
  this.ApplyTransToPoly3D = xDefBool( applyTrans, false );
  this.Poly3D.Reset();
  return this;
}

JsGraph3D.prototype.CopyPoly3D = function( to, reuseArrays ) {
  // returns a copy of this.Poly3D: { X: array(Size) of number, Y: array(Size) of number, Size: integer }

  reuseArrays = xDefBool( reuseArrays, false );
  return this.Poly3D.Copy( to, !reuseArrays );
}

JsGraph3D.prototype.AddPointToPoly3D = function( x, y, z ) {
  // or AddPointToPoly( pt: JsgVect3 )

  if (JsgVect3.Ok(x)) {
    if (this.ApplyTransToPoly3D) {
      this.Poly3D.AddPoint3D( this.TransPoint3D( x[0], x[1], x[2] ) );
    } else {
      this.Poly3D.AddPoint3D( x );
    }
  } else {
    if (this.ApplyTransToPoly3D) {
      this.Poly3D.AddPoint3D( this.TransPoint3D( x, y, z ) );
    } else {
      this.Poly3D.AddPoint( x, y, z );
    }
  }
  return this;
}

JsGraph3D.prototype.DrawPoly3D = function( mode, roundedEdges ) {
  // mode: 1 -> draw border, 2 -> fill, 3 -> border and fill, default = 1
  // mode & 4 -> close polygon by drawing a line from last to first point of polygon
  // mode & 8 -> continue path
  // mode & 16 -> inverse Polygon Poly3D before drawing

  mode = xDefNum( mode, 1 );
  if (mode & 16) this.Poly3D.Invert();
  this.Polygon3D( this.Poly3D, mode, roundedEdges );
}

JsGraph3D.prototype.MoveTo3D = function( x, y, z ) {
  // or MoveTo3D( pos: JsgVect3 )

  if (JsgVect3.Ok(x)) return this.MoveTo3D( x[0], x[1], x[2] );
  JsgVect3.Set( this.LastPos3D, x, y, z );
  return this;
}

JsGraph3D.prototype.LineTo3D = function( x, y, z ) {
  // or LineTo3D( pos: JsgVect3 )

  if (JsgVect3.Ok(x)) {
    return this.LineTo3D( x[0], x[1], x[2] );
  }

  this.WorkPoly3D.Reset();
  this.WorkPoly3D.AddPoint3D( this.LastPos3D );
  this.WorkPoly3D.AddPoint( x, y, z );
  this.TransClipPolygon3D( this.WorkPoly3D, 1 );
  this.WorkPoly3D.GetLastPoint3D( this.LastPos3D );
  return this;
}

JsGraph3D.prototype.Line3D = function( x1, y1, z1, x2, y2, z2, append ) {
  // or Line3D( p1, p2: JsgVect3, append )

  if (JsgVect3.Ok(x1)) {
    return this.Line3D( x1[0], x1[1], x1[2], y1[0], y1[1], y1[2], z1 );
  }

  var mode = 1;
  if (append) mode += 8;
  this.WorkPoly3D.Reset();
  this.WorkPoly3D.AddPoint( x1, y1, z1 );
  this.WorkPoly3D.AddPoint( x2, y2, z2 );
  this.TransClipPolygon3D( this.WorkPoly3D, mode );
  this.WorkPoly3D.GetLastPoint3D( this.LastPos3D );
  return this;
}

JsGraph3D.prototype.FastLine3D = function( p1, p2 ) {
  // private function
  this.WorkPoly3D.Reset();
  this.WorkPoly3D.AddPoint3D( p1 );
  this.WorkPoly3D.AddPoint3D( p2 );
  this.TransClipPolygon3D( this.WorkPoly3D, 1 );
}

JsGraph3D.prototype.VectList3D = function( vectList, mode, roundedEdges ) {
  // stores vectList in this.Poly3D via VectListToPoly3D()
  // and draws it using DrawPoly3D( mode, roundedEdges )
  //
  // vectList: JsgVect3List
  this.VectListToPoly3D( vectList );
  this.DrawPoly3D( mode, roundedEdges );
}

JsGraph3D.prototype.Polygon3D = function( poly, mode, roundedEdges ) {
  // poly: JsgPolygon

  mode = xDefNum( mode, 1 );
  var didClose = false;
  if ((mode & 4) > 0) {
    didClose = poly.Close();
  }

  roundedEdges = xDefBool( roundedEdges, false );
  if (roundedEdges && !this.IsPath3DOpen) {

    var oldJoin = this.LineJoin;
    var oldCap = this.LineCap;
    this.SetLineJoin( 'round' );
    this.SetLineCap( 'round' );

    this.TransClipPolygon3D( poly, mode & ~4 );

    this.SetLineJoin( oldJoin );
    this.SetLineCap( oldCap );

  } else {

    this.TransClipPolygon3D( poly, mode & ~4 );

  }
  poly.GetLastPoint3D( this.LastPos3D );

  if (didClose) {
    poly.RemoveLastPoint();
  }
}

JsGraph3D.prototype.FastClosedPolygon3D = function( poly, mode ) {
  // private function
  // poly: JsgPolygon
  poly.Close();
  this.TransClipPolygon3D( poly, mode & ~4 );
}

JsGraph3D.prototype.PolygonList3D = function( polys, mode, roundedEdges ) {
  for (var i = 0; i < polys.Size; i++) {
    this.Polygon3D( polys.PolyList[i], mode, roundedEdges );
  }
}

JsGraph3D.prototype.VectListToPoly3D = function( vectList, newPoly ) {
  // Stores vectList in this.Poly3D
  // vectList: JsgVect3List (array of [x,y,z])
  // newPoly: true -> clears this.Poly3D (default)
  // set newPoly = false, if you want to append vectList to Poly3D
  // call DrawPoly3D or DrawPolyMarker3D to draw the polygon

  newPoly = xDefBool( newPoly, true );
  if (newPoly) {
    this.NewPoly3D();
  }
  var size = vectList.length;
  for (var i = 0; i < size; i++) {
    var v = vectList[i];
    this.AddPointToPoly3D( v[0], v[1], v[2] );
  }
}


JsGraph3D.prototype.Arrow3D = function( x1, y1, z1, x2, y2, z2, variant, mode, sym1, sym2 ) {
  // or Arrow3D( p1, p2, variant, mode )
  // if an arrow point is outside clip range then that arrow symbol is not drawn
  // Requires this.IsPath3DOpen = false

  if (JsgVect3.Ok(x1)) {
    return this.Arrow3D( x1[0], x1[1], x1[2], y1[0], y1[1], y1[2], z1, x2, y2, z2 );
  }

  var poly = this.WorkPoly3D.Reset();
  poly.AddPoint( x1, y1, z1 );
  poly.AddPoint( x2, y2, z2 );
  poly.GetLastPoint3D( this.LastPos3D );

  // check which points are clipped
  variant = xDefNum( variant, 1 );
  if (variant & 1) { // last point
    if (!this.TransClipPoint3D(poly.X[1],poly.Y[1],poly.Z[1])) variant &= ~1;
  }
  if (variant & 2) { // fist point
    if (!this.TransClipPoint3D(poly.X[0],poly.Y[0],poly.Z[0])) variant &= ~2;
  }

  // clip and trans line
  var transPoly = this.TransClipPolygon3D( poly, 1, false, true );
  if (transPoly.IsEmpty()) return this;
  if (JsgPolygonList.Ok(transPoly)) transPoly = transPoly.GetFirstPoly();

  // draw arrow or line
  if ((variant & 3) > 0) {
    this.Arrow( transPoly.X[0], transPoly.Y[0], transPoly.X[1], transPoly.Y[1], variant, mode, sym1, sym2 );
  } else {
    this.Line( transPoly.X[0], transPoly.Y[0], transPoly.X[1], transPoly.Y[1] );
  }
  JsgVect3.Set( this.LastPos3D, x2, y2, z2 );
  return this;
}

JsGraph3D.prototype.PolygonArrow3D = function( poly, variant, lineMode, arrowMode, sym1, sym2 ) {
  // poly: JsgPolygon(3D)
  // if an arrow point is outside clip range then that arrow symbol is not drawn
  // Requires this.IsPath3DOpen = false

  if (poly.Size < 2) return this;

  // draw arrow line
  if ((variant & 4) == 0) {
    this.Polygon3D( poly, lineMode );
  }

  // check which points are clipped
  var last = poly.Size-1;
  if (variant & 1) { // last point
    if (!this.TransClipPoint3D(poly.X[last],poly.Y[last],poly.Z[last])) variant &= ~1;
  }
  if (variant & 2) { // fist point
    if (!this.TransClipPoint3D(poly.X[0],poly.Y[0],poly.Z[0])) variant &= ~2;
  }

  // draw arrows
  variant |= 4; // don't draw line again
  if ((variant & 2) > 0) { // first point
    this.Arrow3D(
      poly.X[0], poly.Y[0], poly.Z[0], poly.X[1], poly.Y[1], poly.Z[1],
      variant & ~1, arrowMode, sym1
    );
  }
  if ((variant & 1) > 0) { // last point
    var prev = last - 1;
    this.Arrow3D(
      poly.X[prev], poly.Y[prev], poly.Z[prev], poly.X[last], poly.Y[last], poly.Z[last],
      variant & ~2, arrowMode, sym2
    );
  }
  poly.GetLastPoint3D( this.LastPos3D );
  return this;
}

JsGraph3D.prototype.Text3D = function( aText, p, widthOrMode ) {
  var p = this.TransClipPoint3D( p[0], p[1], p[2] );
  if (p) {
    this.Text( aText, p, widthOrMode );
  }
  return this;
}

JsGraph3D.prototype.GetTextBox3D = function( aText, p, width ) {
  // returns JsGraph.WorkRect: JsgRect or null if text is clipped

  var p = this.TransClipPoint3D( p[0], p[1], p[2] );
  if (!p) return null;
  return this.GetTextBox( aText, p, width );
}

JsGraph3D.prototype.DrawPolyMarker3D = function( mode, mat ) {
  // Draws this.Poly3D as Markers
  // mode: 1 -> draw border, 2 -> fill, 3 -> border and fill, default = 1
  this.Marker3D( this.Poly3D, mode, mat );
}

JsGraph3D.prototype.Marker3D = function( x, y, z, mode, mat ) {
  // or Marker3D( JsgVect3, mode, mat )
  // or Marker3D( JsgPolygon, mode, mat )

  if (JsgPolygon.Ok(x)) {
    var polyClipped = this.TransClipPointPoly3D( x );
    this.Marker( polyClipped, y, z );
    return this;
  }

  if (JsgVect3.Ok(x)) return this.Marker3D( x[0], x[1], x[2], y, z );

  var p = this.TransClipPoint3D( x, y, z );
  if (p) {
    this.Marker( p, mode, mat );
  }

  return this;
}

JsGraph3D.prototype.FastMarker3D = function( p, mode ) {
  // private function
  // p: JsgVect3
  var p = this.TransClipPoint3D( p[0], p[1], p[2] );
  if (p) {
    this.Marker( p, mode );
  }
}

///////////////////////
// Path3D

JsGraph3D.prototype.OpenPath3D = function( applyTrans ) {
  this.ApplyTransToPath3D = xDefBool( applyTrans, false );
  this.PathPolys3D.Reset();
  this.IsPath3DOpen = true;
}

JsGraph3D.prototype.ClearPath3D = function() {
  this.PathPolys3D.Reset();
  this.IsPath3DOpen = false;
}

JsGraph3D.prototype.Path3D = function( mode, clear ) {
  mode = xDefNum( mode, 1 );
  clear = xDefBool( clear, true );

  var didClose = false;
  if ((mode & 4) > 0) {
    didClose = this.PathPolys3D.Close();
  }

  this.TransClipPolygon3D( this.PathPolys3D, mode & ~4, true, true );

  if (clear) {
    this.ClearPath3D();
  } else {
    if (didClose) {
      this.PathPolys3D.RemoveLastPoint();
    }
  }
}

///////////////////////
// Clip and transform

JsGraph3D.prototype.TransClipPolygon3D = function( polys, mode, draw, bypassPath ) {
  // private function
  // polys: JsgPolygon or JsgPolygonList
  // returns JsgPolygonList: either this.PathPolys3D or this.XfmPolys3D or this.CamPolys3D

  bypassPath = xDefBool( bypassPath, false );
  draw = xDefBool( draw, true );
  if (this.IsPath3DOpen && !bypassPath) {
    var appendMode = ((mode & 8) > 0) ? 2 : 1;
    if (this.ApplyTransToPath3D) {
      this.PathPolys3D.AddPoly( this.TransPolys3D( polys ), appendMode );
    } else {
      this.PathPolys3D.AddPoly( polys, appendMode );
    }
    return this.PathPolys3D;
  }

  var transformedPolys = this.TransPolys3D( polys );
  var polysRet = transformedPolys;

  if ((mode & 2) > 0) {
    // clip, trans and draw area
    var clippedPolys = this.ClipPolygon3D( transformedPolys, true );
    var cameraPolys = this.CameraTrans3D( clippedPolys );
    if (draw) {
      this.DrawPolygonList3D( cameraPolys, mode & ~1 );
    }
    polysRet = cameraPolys;
  }

  if ((mode & 1) > 0) {
    // clip, trans and draw contour
    var clippedPolys = this.ClipPolygon3D( transformedPolys, false );
    var cameraPolys = this.CameraTrans3D( clippedPolys );
    if (draw) {
      this.DrawPolygonList3D( cameraPolys, mode & ~(2+4) );
    }
    polysRet = cameraPolys;
  }

  return polysRet;
}

JsGraph3D.prototype.TransClipPointPoly3D = function( poly, polyRet ) {
  // transforms and clips poly.
  // polyRet will contain only transformed points from poly which are no clipped
  // poly: JsgPolygon
  // polyRet: JsgPolygon
  // returns JsgPolygon: polyRet or this.WorkPoly3D

  polyRet = polyRet || this.WorkPoly3D;
  polyRet.Reset();
  var xs = poly.X;
  var ys = poly.Y;
  var zs = poly.Z;
  var len = poly.Size;
  for (var i = 0; i < len; i++) {
    var p = this.TransClipPoint3D( xs[i], ys[i], z[i] );
    if (p) {
      polyRet.AddPoint3D( p );
    }
  }
}

JsGraph3D.prototype.TransPolys3D = function( polys, reset ) {
  // polys: JsgPolygon or JsgPolygonList
  // returns this.XfmPolys3D or polys if no transformation is defined
  // if reset = true (default) XfmPolys3D is reset
  // this function is called recusively if polys is of type JsgPolygonList for each polygon in the list
  // all transformed polygons are stored in XfmPolys3D: JsgPolygonList

  if (!this.Trans3D) return polys;

  reset = xDefBool( reset, true );
  var polysRet = this.XfmPolys3D;
  if (reset) {
    polysRet.Reset();
  }
  if (JsgPolygonList.Ok(polys)) {
    for (var i = 0; i < polys.Size; i++) {
      this.TransPolys3D( polys.PolyList[i], false );
    }
  } else {
    polysRet.NewPoly();
    var polyTrans = polysRet.GetLastPoly();
    var mat = this.Trans3D;
    var xs = polys.X, ys = polys.Y, zs = polys.Z, p = this.WorkPoint3D;
    var n = polys.Size;
    for (var i = 0; i < n; i++) {
      JsgMat3.TransXYZTo( mat, xs[i], ys[i], zs[i], p );
      polyTrans.AddPoint3D( p );
    }
  }
  return polysRet;
}

JsGraph3D.prototype.TransClipPoint3D = function( x, y, z ) {
  // or TransClipPoint3D( JsgVect3 )
  // applies 3D-Transformation and camera transformation
  // if point is outside one of the clipping ranges, then null is returned
  // returns this.Camera.WorkPoint or null
  if (JsgVect3.Ok(x)) return this.TransClipPoint3D( x[0], x[1], x[2] );
  var p = this.TransPoint3D( x, y, z );
  if (!this.IsPointInsideClipRange3D( p[0], p[1], p[2] )) return null;
  return this.Camera.TransTo( p[0], p[1], p[2] );
}

JsGraph3D.prototype.VTransPoint3D = function( x, y, z ) {
  // or VTransPoint3D( JsgVect3 )
  // applies 3D-Transformation and camera transformation
  // see also TransPoint3D()
  // returns this.Camera.WorkPoint
  var p;
  if (xNum(x)) {
    p = this.TransPoint3D( x, y, z );
  } else {
    p = this.TransPoint3D( x[0], x[1], x[2] );
  }
  return this.Camera.TransTo( p[0], p[1], p[2] );
}

JsGraph3D.prototype.TransPoint3D = function( x, y, z ) {
  // or TransPoint3D( JsgVect3 )
  // only applies 3D-Transformation (if defined), but not camera transformation
  // see also VTransPoint3D()
  // returns this.WorkPoint3D
  if (JsgVect3.Ok(x)) return this.TransPoint3D( x[0], x[1], x[2] );
  var p = JsgVect3.Set( this.WorkPoint3D, x, y, z );
  if (this.Trans3D) {
    JsgMat3.TransTo( this.Trans3D, p );
  }
  return p;
}

JsGraph3D.prototype.DrawPolygonList3D = function( polys, mode ) {
  // private function
  if (JsgPolygonList.Ok(polys)) {
    var n = polys.Size;
    for (var i = 0; i < n; i++) {
      this.Polygon( polys.PolyList[i], mode );
    }
  } else {
    this.Polygon( polys, mode );
  }
}

JsGraph3D.prototype.CameraTrans3D = function( polys ) {
  // private function
  // polys: JsgPolygon or JsgPolygonList
  // returns this.CamPolys3D

  var transPolys = this.CamPolys3D.Reset();
  if (JsgPolygonList.Ok(polys)) {
    var n = polys.Size;
    for (var i = 0; i < n; i++) {
      transPolys.NewPoly();
      this.Camera.TransPoly( polys.PolyList[i], transPolys );
    }
  } else {
    transPolys.NewPoly();
    this.Camera.TransPoly( polys, transPolys );
  }
  return transPolys;
}

JsGraph3D.prototype.ClipPolygon3D = function( polys, isArea ) {
  // private function
  // polys: JsgPolygon or JsgPolygonList
  // returns this.ClipPolys3D1 or this.ClipPolys3D2

  var n = this.ClipPlaneListSize;
  if (n == 1 && this.ClipPlaneList[0] == null) {
    if (!isArea || JsgPolygon.Ok(polys)) {
      return polys;
    } else {
      return this.MergeAreaPolys( polys, this.ClipPolys3D1 );
    }
  }
  var nextClipPolys = this.ClipPolys3D2;
  var currClipPolys = this.ClipPolys3D1;
  for (var i = 0; i < n; i++) {
    var clipPlane = this.ClipPlaneList[i];
    if (clipPlane) {
      var clippedPolys = currClipPolys.Reset();
      if (isArea) {
        clipPlane.ClipArea( polys, clippedPolys );
      } else {
        clipPlane.ClipPoly( polys, clippedPolys );
      }
      polys = clippedPolys;
      // swap temp clip polys
      var tmp = currClipPolys;
      currClipPolys = nextClipPolys;
      nextClipPolys = tmp;
    }
  }
  return polys;
}

JsGraph3D.prototype.MergeAreaPolys = function( polys, mergedPolys ) {
  // merge all polys into one polygon by closing all polys and
  // connecting them to the first point of first polygon
  // polys: JsgPolygonList
  // returns mergedPolys or polys.PolyList[0] if polys contains only one polygon
  // Note: a single returned polygon may be left open

  if (polys.Size == 0) return polys;
  if (polys.Size == 1) return polys.PolyList[0];

  mergedPolys.Reset();
  mergedPolys.NewPoly();

  for (var i = 0; i < polys.Size; i++) {
    var poly = polys.PolyList[i];
    var hasClosed = poly.Close();
    mergedPolys.AddPoly( poly, 2 );  // append mode 2
    if (i > 0) {
      // connect last poly with first poly
      mergedPolys.Close();
    }
    if (hasClosed) {
      poly.RemoveLastPoint();
    }
  }
  return mergedPolys;
}

JsGraph3D.prototype.IsPointInsideClipRange3D = function( x, y, z ) {
  // or IsPointInsideClipRange3D( p: JsgVect3 )
  // returns true if p or (x,y,z) is on top of all clip planes, that means inside clipping range
  var isVect = JsgVect3.Ok(x);
  var n = this.ClipPlaneListSize;
  if (n == 1 && this.ClipPlaneList[0] == null) return true;
  for (var i = 0; i < n; i++) {
    var clipPlane = this.ClipPlaneList[i];
    if (clipPlane) {
      if (isVect) {
        if (!clipPlane.IsPoint3DOnTop(x)) return false;
      } else {
        if (!clipPlane.IsPointOnTop(x,y,z)) return false;
      }
    }
  }
  return true;
}

///////////////////
// Plane functions

JsGraph3D.prototype.SavePlane = function( aParams ) {
  var par = xDefObj( aParams, {} );
  par.Plane = this.Plane;
  return par;
}

JsGraph3D.prototype.SetPlane = function( PosOrObj, xDir, yDir, normalize ) {
  // or SetPlane( JsgPlane )
  // or SetPlane( { Plane: JsgPlane, ... } )
  // Use JsgPlane.Copy() to assign a copy of a plane to JsgGraph3D.Plane
  if (xObj(PosOrObj)) {
    if (JsgPlane.Ok(PosOrObj)) {
      this.Plane = PosOrObj;
    } else if (xDef(PosOrObj.Plane)) {
      this.Plane = PosOrObj.Plane;
    }
  } else {
    this.Plane.Set( PosOrObj, xDir, yDir, normalize );
  }
}

JsGraph3D.prototype.PolygonToPlane = function( xpoly, ypoly, size, planePoly ) {
  // or PolygonToPlane( poly: JsgPolygon, planePoly: JsgPolygon(3D) )
  // return planePoly or this.Plane.ResultPoly
  // Note: you have to call planePoly.Reset() yourself to clear it if necessary

  return this.Plane.PolygonOnPlane( xpoly, ypoly, size, planePoly );
}

JsGraph3D.prototype.GetPointOnPlane = function( x, y, v ) {
  // v: JsgVect3 or undefined
  // returns v or this.WorkPoint3D
  // does not apply 3D-transformation, use GetTransPointOnPlane instead
  v = v || this.WorkPoint3D;
  return this.Plane.PointOnPlane( x, y, v );
}

JsGraph3D.prototype.GetTransPointOnPlane = function( x, y, v ) {
  // v: JsgVect3 or undefined
  // returns v or this.WorkPoint3D
  // applies 3D-transformation
  return this.GetTransPoint3D( this.Plane.PointOnPlane( x, y ), v );
}

JsGraph3D.prototype.GetTransPoint3D = function( x, y, z, v ) {
  // or GetTransPoint3D( JsgVect3, v )
  // applies current 3D-transformation on point
  // v: JsgVect3 or undefined
  // returns v or this.WorkPoint3D

  if (JsgVect3.Ok(x)) {
    return this.GetTransPoint3D( x[0], x[1], x[2], y );
  }

  v = v || this.WorkPoint3D;
  if (this.Trans3D) {
    JsgMat3.TransXYZTo( this.Trans3D, x, y, z, v );
  } else {
    JsgVect3.Set( v, x, y, z );
  }
  return v;
}

JsGraph3D.prototype.AddPointToPoly3DOnPlane = function( x, y ) {
  // or AddPointToPolyOnPlane( JsgVect2 )

  this.Poly3D.AddPoint3D( this.Plane.PointOnPlane( x, y ) );
}

JsGraph3D.prototype.MoveToOnPlane = function( x, y ) {
  // or MoveToOnPlane( JsgVect2 )

  if (JsgVect2.Ok(x)) {
    return this.MoveToOnPlane( x[0], x[1] );
  }

  JsgVect2.Set( this.LastPosOnPlane, x, y );
  return this;
}

JsGraph3D.prototype.LineToOnPlane = function( x, y ) {
  // or LineToOnPlane( JsgVect2 )

  if (JsgVect2.Ok(x)) {
    return this.LineToOnPlane( x[0], x[1] );
  }

  this.LineOnPlane( this.LastPosOnPlane[0], this.LastPosOnPlane[1], x, y );
  return this;
}

JsGraph3D.prototype.LineOnPlane = function( x1, y1, x2, y2, append ) {
  // or LineOnPlane( pt1, pt2, append )

  if (JsgVect2.Ok(x1)) {
    return this.LineOnPlane( x1[0], x1[1], y1[0], y1[1], x2 );
  }

  this.Plane.PointOnPlane( x1, y1, this.WorkPoint3D );
  this.Plane.PointOnPlane( x2, y2, this.WorkPoint3D2 );
  this.Line3D( this.WorkPoint3D, this.WorkPoint3D2, append );
  JsgVect2.Set( this.LastPosOnPlane, x2, y2 );
  return this;
}

JsGraph3D.prototype.PolygonOnPlane = function( xpoly, ypoly, mode, size, roundedEdges ) {
  // or PolygonOnPlane( JsgPolygon, mode, roundedEdges )

  if (JsgPolygon.Ok(xpoly)) {
    return this.PolygonOnPlane( xpoly.X, xpoly.Y, ypoly, xpoly.Size, mode );
  }

  size = xDefNum( size, xpoly.length );
  var poly = this.Plane.PolygonOnPlane( xpoly, ypoly, size );
  this.Polygon3D( poly, mode, roundedEdges );

  JsgVect2.Set( this.LastPosOnPlane, xpoly[size-1], ypoly[size-1] );
  return this;
}

JsGraph3D.prototype.BezierCurveOnPlane = function( sx, sy, cx1, cy1, cx2, cy2, ex, ey, mode, nSegments ) {
  // or BezierCurveOnPlane( spt, cpt1, cpt2, ept, mode, nSegments )
  // or BezierCurveOnPlane( JsgPolygon, mode, startIx, nSegments )
  // nSegements defaults to this.NumBezierSegments

  if (JsgPolygon.Ok(sx)) {
    var i = xDefNum( cx1, 0 );
    return this.BezierCurveOnPlane(
      sx.X[i+0], sx.Y[i+0], sx.X[i+1], sx.Y[i+1], sx.X[i+2], sx.Y[i+2], sx.X[i+3], sx.Y[i+3], sy, cx1
    );
  }

  if (JsgVect2.Ok(sx)) {
    return this.BezierCurveOnPlane(
      sx[0], sx[1], sy[0], sy[1], cx1[0], cx1[1], cy1[0], cy1[1], cx2, cy2
    );
  }

  var poly2D = this.MakeBezierPolygon( sx, sy, cx1, cy1, cx2, cy2, ex, ey, nSegments );
  this.PolygonOnPlane( poly2D, mode );
  return this;
}

JsGraph3D.prototype.BezierCurveToOnPlane = function( cx1, cy1, cx2, cy2, ex, ey, mode, nSegments ) {
  // or BezierCurveToOnPlane( cpt1, cpt2, ept, mode, nSegments )
  // or BezierCurveToOnPlane( JsgPolygon, mode, startIx, nSegments )
  // nSegements defaults to this.NumBezierSegments

  if (JsgPolygon.Ok(cx1)) {
    var i = xDefNum( cx2, 0 );
    return this.BezierCurveToOnPlane(
      cx1.X[i+0], cx1.Y[i+0], cx1.X[i+1], cx1.Y[i+1], cx1.X[i+2], cx1.Y[i+2], cy1, cx2
    );
  }

  if (JsgVect2.Ok(cx1)) {
    return this.BezierCurveToOnPlane(
      cx1[0], cx1[1], cy1[0], cy1[1], cx2[0], cx2[1], cy2, ex
    );
  }

  this.BezierCurveOnPlane(
    this.LastPosOnPlane[0], this.LastPosOnPlane[1],
    cx1, cy1,
    cx2, cy2,
    ex,  ey,
    mode, nSegments
  );
  return this;
}

JsGraph3D.prototype.SplineCurveOnPlane = function( xpoly, ypoly, tension, mode, size, nSegments ) {
  // or SplineCurveOnPlane( JsgPolygon, tension, mode, nSegments )

  if (JsgPolygon.Ok(xpoly)) {
    return this.SplineCurveOnPlane( xpoly.X, xpoly.Y, ypoly, tension, xpoly.Size, mode );
  }

  // poly2D <- this.WorkPoly2
  var poly2D = this.MakeSplineCurve( xpoly, ypoly, tension, mode, size, nSegments );
  var roundedEdges = (mode & 4) > 0;
  this.PolygonOnPlane( poly2D, mode, roundedEdges );
  return this;
}

JsGraph3D.prototype.ArrowOnPlane = function( x1, y1, x2, y2, variant, mode, sym1, sym2 ) {
  // or ArrowOnPlane( pt1, pt2, variant, mode )

  if (JsgVect2.Ok(x1)) {
    return this.ArrowOnPlane( x1[0], x1[1], y1[0], y1[1], x2, y2, variant, mode );
  }

  this.Plane.PointOnPlane( x1, y1, this.WorkPoint3D );
  this.Plane.PointOnPlane( x2, y2, this.WorkPoint3D2 );
  this.Arrow3D( this.WorkPoint3D, this.WorkPoint3D2, variant, mode, sym1, sym2 );
  JsgVect2.Set( this.LastPosOnPlane, x2, y2 );
  return this;
}

JsGraph3D.prototype.PolygonArrowOnPlane = function( xpoly, ypoly, variant, lineMode, arrowMode, size, sym1, sym2 ) {
  // or PolygonArrowOnPlane( JsgPolygon, variant, lineMode, arrowMode )

  if (JsgPolygon.Ok(xpoly)) {
    return this.PolygonArrowOnPlane( xpoly.X, xpoly.Y, ypoly, variant, lineMode, xpoly.Size, arrowMode, size );
  }

  var poly = this.Plane.PolygonOnPlane( xpoly, ypoly, size );
  this.PolygonArrow3D( poly, variant, lineMode, arrowMode, sym1, sym2 );

  size = size || xpoly.length;
  JsgVect2.Set( this.LastPosOnPlane, xpoly[size-1], ypoly[size-1] );
  return this;
}

JsGraph3D.prototype.RectOnPlane = function( x1, y1, x2, y2, mode, roll ) {
  // or RectOnPlane( pt1, pt2, mode, roll )
  // or RectOnPlane( Rect, mode, roll )
  // or RectOnPlane( { xmin, ymin, xmax, ymax }, mode, roll )
  //
  // pt1, pt2: JsgVect2
  // Rect: JsgRect
  // x1, y1, x2, y2: real coordinates of any two opposite edges
  // mode: 1 -> draw border, 2 -> fill, 3 -> border and fill, default = 1
  // mode & 4 -> inverse drawing direction for holes
  // mode & 8 -> continue path
  // roll: Integer: rolls point of rectangle down (n > 0) or up (n < 0) n steps.
  // n = 1 rotates start/endpoint clockwise one step, lower left point becomes lower top point.

  if (JsgVect2.Ok(x1)) {
    return this.RectOnPlane( x1[0], x1[1], y1[0], y1[1], x2, y2 );
  }

  mode  = xDefNum( mode, 1 );

  var poly;
  if (xObj(x1)) {
    // x1: JsgRect or { xmin, ymin, xmax, ymax }
    mode = xDefNum( y1, 1 );
    roll = xDefNum( x2, 0 );
    var clockWise = !!(mode & 4);
    poly = this.MakeRectPolygon( x1, clockWise, roll );
  } else {
    var clockWise = !!(mode & 4);
    poly = this.MakeRectPolygon( x1, y1, x2, y2, clockWise, roll );
  }

  this.PolygonOnPlane( poly, mode, true );

  return this;
}

JsGraph3D.prototype.RectWHOnPlane = function( x, y, w, h, mode, roll ) {
  // or RectWH( JsgRect, mode )
  //
  // x, y: real coordinates of edge with least or most negativ values
  // w, h: real > 0 width and height
  // mode: 1 -> draw border, 2 -> fill, 3 -> border and fill, default = 1
  // mode & 4 -> inverse drawing direction for holes in paths
  // mode & 8 -> continue path
  // roll: Integer: rolls point of rectangle down (n > 0) or up (n < 0) n steps.
  // n = 1 rotates start/endpoint clockwise one step, lower left point becomes lower top point.

  if (JsgRect.Ok(x)) {
    this.RectOnPlane( x.x, x.y, x.x+x.w, x.y+x.h, y, w );
  } else {
    this.RectOnPlane( x, y, x+w, y+h, mode, roll );
  }
}

JsGraph3D.prototype.CompViewportRadius = function( x, y, rx, ry ) {
  // Private Function to EllipseArcOnPlane to estimate the radius in viewport coordinates
  // Compute max radius in viewport coordinates:
  // First build 4 points of the bounding rectangle of a circle with radius maxR
  // on the plane and transform them into 3D coordinates.
  // These are then transformed into the viewplane (2D world coordinates).
  // These 4 2D points represent a perspectivly distorted bounding rectangle.
  // We can compute from them the maximal radius in 2D world coordinates.
  // And finally transform this radius into viewport coordinates to get the rPixel radius.
  // Returns rPixel
  function len( xs, ys, i, j ) {
    var vx = xs[i] - xs[j];
    var vy = ys[i] - ys[j];
    return vx * vx + vy * vy;
  }
  var abs = Math.abs, max = Math.max;
  var absRx = abs(rx);
  var absRy = abs(ry);
  var maxR  = max(absRx, absRy);
  var plane = this.Plane;
  var poly = this.WorkPoly3D2.Reset();
  poly.AddPoint3D( this.VTransPoint3D( plane.PointOnPlane( x-maxR, y-maxR ) ) );
  poly.AddPoint3D( this.VTransPoint3D( plane.PointOnPlane( x+maxR, y-maxR ) ) );
  poly.AddPoint3D( this.VTransPoint3D( plane.PointOnPlane( x+maxR, y+maxR ) ) );
  poly.AddPoint3D( this.VTransPoint3D( plane.PointOnPlane( x-maxR, y+maxR ) ) );
  var xs = poly.X, ys = poly.Y;
  var l1 = len( xs, ys, 1, 0 );
  var l2 = len( xs, ys, 2, 3 );
  var ll1 = (l1 + l2) / 2;
  var l1 = len( xs, ys, 3, 0 );
  var l2 = len( xs, ys, 2, 1 );
  var ll2 = (l1 + l2) / 2;
  var maxRVT = Math.sqrt( max( ll1, ll2 ) ) / 2;
  var cnvsRx = abs(this.CurrTrans.ScaleX) * maxRVT;
  var cnvsRy = abs(this.CurrTrans.ScaleY) * maxRVT;
  var rPixel = max(cnvsRx, cnvsRy);
  return rPixel;
}

JsGraph3D.prototype.CircleOnPlane = function( x, y, r, mode, startAngle ) {
  // or CircleOnPlane( JsgVect2, r, mode, startAngle )
  // Draws a circle on an plane in the 3D world
  // r < 0 -> clockwise, r > 0 -> counterclockwise
  // mode: 1 -> draw border, 2 -> fill, 3 -> border and fill, default = 1
  // (mode & 4 -> close circle) ignored, circles are closed anyway
  // mode & 8 -> continue path

  if (JsgVect2.Ok(x)) {
    return this.CircleOnPlane( x[0], x[1], y, r, mode );
  }

  this.EllipseOnPlane( x, y, r, Math.abs(r), 0, mode, startAngle );
  return this;
}

JsGraph3D.prototype.ArcOnPlane = function( x, y, r, start, end, mode ) {
  // or ArgOnPlane( JsgVect2, r, start, end, mode )
  // Draws a circular arc on an plane in the 3D world
  // x, y: real coordinates
  // r: real radius
  // r < 0 -> clockwise, r > 0 -> counterclockwise
  // start, end: real angles in radians or degress (see AngleMeasure)
  // mode: 1 -> draw border, 2 -> fill, 3 -> border and fill, default = 1
  // mode & 4 -> close arc by drawing a line from last to first point of arc
  // mode & 8 -> continue path

  if (JsgVect2.Ok(x)) {
    return this.ArcOnPlane( x[0], x[1], y, r, start, end );
  }

  this.EllipseArcOnPlane( x, y, r, Math.abs(r), 0, start, end, mode );
  return this;
}

JsGraph3D.prototype.ArcToOnPlane = function( x, y, r, big, mode ) {
  // or ArcToOnPlane( JsgVect2, r, big, mode )
  // Draws a circular arc from last position to a point on an plane in the 3D world
  // x, y: real endpoint
  // r: real radius
  // r < 0 -> clockwise, r > 0 -> counterclockwise
  // big: boolean: chose short or long arc
  // mode: 1 -> draw border, 2 -> fill, 3 -> border and fill, default = 1
  // mode & 4 -> close polygon

  if (JsgVect2.Ok(x)) {
    return this.ArcToOnPlane( x, x, y, r, big );
  }

  this.ArcPtOnPlane( this.LastX, this.LastY, x, y, r, big, mode|8 );
  return this;
}

JsGraph3D.prototype.ArcPtOnPlane = function( x1, y1, x2, y2, r, big, mode ) {
  // or ArcPtOnPlane( pt1, pt2, r, big, mode )
  // Draws a circular arc given from 2 points on an plane in the 3D world
  // x1, y1, x2, y2: real startpoint and endpoint
  // r: real radius
  // r < 0 -> clockwise, r > 0 -> counterclockwise
  // big: boolean: chose short or long arc
  // mode: 1 -> draw border, 2 -> fill, 3 -> border and fill, default = 1
  // mode & 4 -> close polygon
  // mode & 8 -> continue path

  if (JsgVect2.Ok(x1)) {
    return this.ArcPtOnPlane( x1[0], x1[1], y1[0], y1[1], x2, y2, r );
  }

  big = xDefBool( big, false );
  mode = xDefNum( mode, 1 );
  var arc = this.MakeArcFromPoints( x1, y1, x2, y2, r, big );
  this.ArcOnPlane( arc.x, arc.y, arc.r, arc.start, arc.end, mode );
  return this;
}

JsGraph3D.prototype.EllipseOnPlane = function( x, y, rx, ry, rot, mode, startAngle ) {
  // or EllipseOnPlane( JsgVect2, rx, ry, rot, mode, startAngle )
  // Draws an ellipse on an plane
  // x, y: real center of arc
  // rx, ry: real radius; rx < 0 -> clockwise, rx > 0 -> counterclockwise
  // rot: real rotation of ellipse in radian or degree (see AngleMeasure)
  // mode: 1 -> draw border, 2 -> fill, 3 -> border and fill, default = 1
  // mode & 4 -> force close polygon
  // mode & 8 -> continue path

  if (JsgVect2.Ok(x)) {
    return this.EllipseOnPlane( x[0], x[1], y, rx, ry, rot, mode );
  }

  startAngle = xDefNum( startAngle, 0 );
  var start = startAngle;
  var end = startAngle + this.RadToAngle(2*Math.PI);
  if (rx < 0) {
    start = end;
    end = startAngle;
  }

  this.EllipseArcOnPlane( x, y, rx, ry, rot, start, end, mode );
  return this;
}

JsGraph3D.prototype.EllipseArcOnPlane = function( x, y, rx, ry, rot, start, end, mode ) {
  // or EllipseArcOnPlane( JsgVect2, rx, ry, rot, start, end, mode )
  // draws elliptic arc on plane
  // x, y: real center of arc
  // rx, ry: real radius; rx < 0 -> clockwise, rx > 0 -> counterclockwise
  // rot: real rotation of ellipse in radian or degree (see AngleMeasure)
  // start, end: real angles in ellipse coordinates as radian or degree (see AngleMeasure)
  // mode: 1 -> draw border, 2 -> fill, 3 -> border and fill, default = 1
  // mode & 4 -> force close polygon
  // mode & 8 -> continue path

  if (JsgVect2.Ok(x)) {
    return this.EllipseArcOnPlane( x[0], x[1], y, rx, ry, rot, start, end );
  }

  ry    = xDefNum( ry, Math.abs(rx) );
  rot   = xDefNum( rot, 0 );
  start = xDefNum( start, 0 );
  end   = xDefNum( end, start+this.RadToAngle(2*Math.PI) );
  mode  = xDefNum( mode, 1 );
  var rPixel = this.CompViewportRadius( x, y, rx, ry );

  var ell = this.MakeEllipseArcPolygon( x, y, rx, ry, rot, start, end, rPixel );

  var roundedEdges = ((mode & 1) && this.IsClosedPolygon( ell.X, ell.Y, ell.Size ));

  this.PolygonOnPlane( ell, mode, roundedEdges );

  return this;
}

JsGraph3D.prototype.TextOnPlane = function( txt, x, y, WidthOrMode ) {
  // or TextOnPlane( txt, JsgVect2, WidthOrMode )
  // x, y: real text reference point on plane
  // w: optional limiting text rectangle width in pixels
  // mode: default = 0: (canvas text only)
  // x is left, center or right coordinate, depending on horizontal align
  // y is top, middle or bottom coordinate, depending on vertical align
  // Note: this function does not transform the letters, only the position of the text!

  if (JsgVect2.Ok(x)) {
    return this.TextOnPlane( txt, x[0], x[1], y );
  }

  return this.Text3D( txt, this.Plane.PointOnPlane( x, y ), WidthOrMode );
}

JsGraph3D.prototype.GetTextBoxOnPlane = function( aText, x, y, width ) {
  // or GetTextBoxOnPlane( aText, JsgVect2, width )
  // returns JsGraph.WorkRect: JsgRect

  if (JsgVect2.Ok(x)) {
    return this.GetTextBoxOnPlane( aText, x[0], x[1], y );
  }

  var pos = this.Plane.PointOnPlane( x, y );
  var posVT = this.Camera.Trans( pos );
  return this.GetTextBox( aText, posVT[0], posVT[1], width );
}

JsGraph3D.prototype.MarkerOnPlane = function( x, y, mode, mat, size ) {
  // or MarkerOnPlane( JsgVect2, mode, mat )
  // or MarkerOnPlane( xArr, yArr, mode, mat, size )
  // or MarkerOnPlane( poly, mode, mat )
  // xArr, yArr: array of Number
  // poly: JsgPolygon
  // x, y: Number or Array of Number marker position(s) on plane
  // mode: int: 1 -> border, 2 -> fill, 3 -> fill and border
  // mat: JsgMat2 (optional) -> additional transformation matrix (e.g. rotation)

  if (JsgPolygon.Ok(x)) {
    return this.MarkerOnPlane( x.X, x.Y, y, mode, x.Size );
  }

  if (xArray(x) && xArray(y)) {
    // draw markers from arrays of x/y coordinates
    return this.Marker3D( this.Plane.PolygonOnPlane( x, y, size ), mode, mat );
  }

  if (JsgVect2.Ok(x)) return this.MarkerOnPlane( x[0], x[1], y, mode );

  // draw single marker (x,y)
  return this.Marker3D( this.Plane.PointOnPlane( x, y ), mode, mat );
}

// attribute functions -------------------------------------------------------------------

JsGraph3D.prototype.CreateLinearGradient3D = function( aGradientDef ) {
  // aGradientDef = { P1, P2, Stops: [ { Pos, Color }, ... ] }
  // aGradientDef = { Plane, X1, Y1, X2, Y2, Stops: [ { Pos, Color }, ... ] }
  // returns { Def: jsGraphGradientDef, Obj: gradientObjekt, Def3D: aGradientDef }

  var p1VT, p2VT;
  var gradDef = { Stops: aGradientDef.Stops };
  if (aGradientDef.Plane) {
    var plane = aGradientDef.Plane;
    p1VT = this.Camera.Trans( plane.Point( aGradientDef.X1, aGradientDef.Y1 ) );
    p2VT = this.Camera.Trans( plane.Point( aGradientDef.X2, aGradientDef.Y2 ) );
  } else {
    p1VT = this.Camera.Trans( aGradientDef.P1 );
    p2VT = this.Camera.Trans( aGradientDef.P2 );
  }
  gradDef.X1 = p1VT[0];
  gradDef.Y1 = p1VT[1];
  gradDef.X2 = p2VT[0];
  gradDef.Y2 = p2VT[1];
  var grad = this.CreateLinearGradient( gradDef );
  grad.Def3D = aGradientDef;
  return grad
}

JsGraph3D.prototype.SetLinearGradientGeom3D = function( aLinearGradient3D, aGeom ) {
  // aGeom = { P1, P2 }
  // aGeom = { Plane, X1, Y1, X2, Y2 }

  var p1VT, p2VT;
  var grad = aLinearGradient3D.Def3D;
  if (grad.Plane) {
    var plane = grad.Plane;
    grad.X1 = xDefNum( aGeom.X1, grad.X1 );
    grad.Y1 = xDefNum( aGeom.Y1, grad.Y1 );
    grad.X2 = xDefNum( aGeom.X2, grad.X2 );
    grad.Y2 = xDefNum( aGeom.Y2, grad.Y2 );
    p1VT = this.Camera.Trans( plane.Point( grad.X1, grad.Y1 ) );
    p2VT = this.Camera.Trans( plane.Point( grad.X2, grad.Y2 ) );
  } else {
    grad.P1 = xDefArray( aGeom.P1, grad.P1 );
    grad.P2 = xDefArray( aGeom.P2, grad.P2 );
    p1VT = this.Camera.Trans( grad.P1 );
    p2VT = this.Camera.Trans( grad.P2 );
  }
  this.SetLinearGradientGeom( aLinearGradient3D, { X1: p1VT[0], Y1: p1VT[1], X1: p2VT[0], Y1: p2VT[1] } );
}


More Page Infos / Sitemap
Created Freitag, 11. Juli 2014
Scroll to Top of Page
Changed Sonntag, 8. Oktober 2017