// jsgx3d.js, (C) Walter Bislin, walter.bislins.ch, Februar 2016
//
// description and download:
// http://walter.bislins.ch/doku/jsgx3d
//
// 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
// 13.02.2016 First Version as a subset of jsg3d.js
/////////////////////////////////
// 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 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
// 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.Zoom = 1;
this.SceneSize = 2;
this.ScreenSize = 1;
this.ObjectZExtend = 0;
this.CamViewCenter = JsgVect3.Null();
this.CamPos = JsgVect3.New(100,0,0);
this.CamUp = JsgVect3.New(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 (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;
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.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 );
}
////////////////////////////////////
// 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 );
}
}
////////////////////////////////////
// JsGraphX3D Object
function NewGraphX3D( aParams ) {
return new JsGraphX3D( aParams );
}
function JsGraphX3D( aParams ) {
aParams = xDefObj( aParams, {} );
this.parentClass.constructor.call( this, aParams );
this.ClientResetFunc = function(g) { g.Reset3D(); }
// some props for temp data
this.WorkPoly2D = new JsgPolygon( false, 'JsGraphX3D.WorkPoly2D (local)' );
this.WorkPoint3D = JsgVect3.Null();
this.WorkPoint3D2 = JsgVect3.Null();
this.WorkPoly3D = new JsgPolygon( true, 'JsGraphX3D.WorkPoly3D' );
this.WorkPoly3D2 = new JsgPolygon( true, 'JsGraphX3D.WorkPoly3D2' );
this.XfmPolys3D = new JsgPolygonList( true, 'JsGraphX3D.XfmPolys3D' );
this.CamPolys3D = new JsgPolygonList( true, 'JsGraphX3D.CamPolys3D' );
this.ClipPolys3D1 = new JsgPolygonList( true, 'JsGraphX3D.ClipPolys3D1' );
this.ClipPolys3D2 = new JsgPolygonList( true, 'JsGraphX3D.ClipPolys3D2' );
this.Trans3D = null;
this.Trans3DStack = [];
this.Plane = new JsgPlane( [ 0, 0, 0 ], [ 0, 1, 0 ], [ 0, 0, 1 ] );
this.Camera = new JsgCamera();
this.PathPolys3D = new JsgPolygonList( true, 'JsGraphX3D.PathPolys3D (local)' );
this.IsPath3DOpen = false;
this.ApplyTransToPath3D = false;
this.LastPos3D = JsgVect3.Null();
this.LastPosOnPlane = JsgVect2.Null()
this.Poly3D = new JsgPolygon( true, 'JsGraphX3D.Poly3D' );
this.ApplyTransToPoly3D = false;
this.CameraClipPlaneDist = 0;
this.ClipPlaneList = [ null ]; // first entry is camera clip plane
this.Reset3D();
this.SetAll( aParams );
this.SetWindowToCameraScreen();
}
JsGraphX3D.inheritsFrom( JsGraph );
JsGraphX3D.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.ResetCamera();
if (clear) {
this.Clear();
}
}
JsGraphX3D.prototype.SaveAll = function( aParams ) {
var par = xDefObj( aParams, {} );
this.SaveClipPlanes( par );
this.SaveCamera( par );
this.SavePlane( par );
return par;
}
JsGraphX3D.prototype.SetAll = function( aParams ) {
if (xFuncOrNull( aParams.DrawFunc )) { this.SetDrawFunc( aParams.DrawFunc ); }
this.SetClipPlanes( aParams );
this.SetCamera( aParams );
this.SetPlane( aParams );
}
JsGraphX3D.prototype.SetWindowToCameraScreen = 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 );
}
JsGraphX3D.prototype.ResetCamera = function() {
this.Camera.Reset();
this.UpdateCameraClipPlane();
}
JsGraphX3D.prototype.SetCamera = function( aParams ) {
this.Camera.Set( aParams );
this.UpdateCameraClipPlane();
}
JsGraphX3D.prototype.SaveCamera = function( aParams ) {
return this.Camera.Save( aParams );
}
JsGraphX3D.prototype.SetCameraScale = function( aSceneSize, aScreenSize, aObjectZExtend, aZoom ) {
this.Camera.SetScale( aSceneSize, aScreenSize, aObjectZExtend, aZoom );
}
JsGraphX3D.prototype.SetCameraPos = function( aPos, aViewCenter, aUp ) {
this.Camera.SetPos( aPos, aViewCenter, aUp );
this.UpdateCameraClipPlane();
}
JsGraphX3D.prototype.SetCameraView = function( aViewCenter, aHAng, aVAng, aDist, aUp ) {
// Angles are in degree!
this.Camera.SetView( aViewCenter, aHAng, aVAng, aDist, aUp );
this.UpdateCameraClipPlane();
}
JsGraphX3D.prototype.SetCameraZoom = function( aZoom ) {
this.Camera.SetZoom( aZoom );
}
JsGraphX3D.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;
}
}
JsGraphX3D.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();
}
JsGraphX3D.prototype.DeleteClipPlanes = function() {
this.ClipPlaneListSize = 1;
}
JsGraphX3D.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++;
}
}
JsGraphX3D.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;
}
}
JsGraphX3D.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 );
}
}
////////////////////////////////
// Polygon, VectList and VectGrid generators
JsGraphX3D.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;
}
JsGraphX3D.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;
}
JsGraphX3D.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;
}
////////////////////////////////////
// Transformation functions
JsGraphX3D.prototype.ResetTrans3D = function( clearStack ) {
this.Trans3D = null;
if (clearStack) {
this.Trans3DStack = [];
}
return this;
}
JsGraphX3D.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;
}
JsGraphX3D.prototype.RestoreTrans3D = function() {
if (this.Trans3DStack.length > 0) this.Trans3D = this.Trans3DStack.pop();
}
JsGraphX3D.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;
}
}
JsGraphX3D.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;
}
JsGraphX3D.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;
}
JsGraphX3D.prototype.TransRotateX3D = function( ang ) {
this.Trans3D = JsgMat3.RotatingX( this.AngleToRad(ang), this.Trans3D );
return this;
}
JsGraphX3D.prototype.TransRotateY3D = function( ang ) {
this.Trans3D = JsgMat3.RotatingY( this.AngleToRad(ang), this.Trans3D );
return this;
}
JsGraphX3D.prototype.TransRotateZ3D = function( ang ) {
this.Trans3D = JsgMat3.RotatingZ( this.AngleToRad(ang), this.Trans3D );
return this;
}
JsGraphX3D.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 );
}
JsGraphX3D.prototype.AddTrans3D = function( mat ) {
// mat: JsgMat3
if (this.Trans3D) {
this.Trans3D = JsgMat3.Mult( this.Trans3D, mat );
} else {
this.Trans3D = JsgMat3.Copy( mat );
}
return this;
}
///////////////////////////////////////
// 3D variants of JsGraph 2D functions
JsGraphX3D.prototype.NewPoly3D = function( applyTrans ) {
this.ApplyTransToPoly3D = xDefBool( applyTrans, false );
this.Poly3D.Reset();
return this;
}
JsGraphX3D.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 );
}
JsGraphX3D.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;
}
JsGraphX3D.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 );
}
JsGraphX3D.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;
}
JsGraphX3D.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;
}
JsGraphX3D.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;
}
JsGraphX3D.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 );
}
JsGraphX3D.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();
}
}
JsGraphX3D.prototype.PolygonList3D = function( polys, mode, roundedEdges ) {
for (var i = 0; i < polys.Size; i++) {
this.Polygon3D( polys.PolyList[i], mode, roundedEdges );
}
}
JsGraphX3D.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] );
}
}
JsGraphX3D.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;
}
JsGraphX3D.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;
}
JsGraphX3D.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;
}
JsGraphX3D.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 );
}
JsGraphX3D.prototype.DrawPolyMarker3D = function( mode, mat ) {
// Draws this.Poly3D as Markers
// mode: 1 -> draw border, 2 -> fill, 3 -> border and fill, default = 3
this.Marker3D( this.Poly3D, mode, mat );
}
JsGraphX3D.prototype.Marker3D = function( x, y, z, mode, mat ) {
// or Marker3D( JsgVect3, mode, mat )
// or Marker3D( JsgPolygon, mode, mat )
// mode: 1 -> draw border, 2 -> fill, 3 -> border and fill, default = 3
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;
}
///////////////////////
// Path3D
JsGraphX3D.prototype.OpenPath3D = function( applyTrans ) {
this.ApplyTransToPath3D = xDefBool( applyTrans, false );
this.PathPolys3D.Reset();
this.IsPath3DOpen = true;
}
JsGraphX3D.prototype.ClearPath3D = function() {
this.PathPolys3D.Reset();
this.IsPath3DOpen = false;
}
JsGraphX3D.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
JsGraphX3D.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;
}
JsGraphX3D.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 );
}
}
}
JsGraphX3D.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;
}
JsGraphX3D.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] );
}
JsGraphX3D.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] );
}
JsGraphX3D.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;
}
JsGraphX3D.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 );
}
}
JsGraphX3D.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;
}
JsGraphX3D.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;
}
JsGraphX3D.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;
}
JsGraphX3D.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
JsGraphX3D.prototype.SavePlane = function( aParams ) {
var par = xDefObj( aParams, {} );
par.Plane = this.Plane;
return par;
}
JsGraphX3D.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 );
}
}
JsGraphX3D.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 );
}
JsGraphX3D.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 );
}
JsGraphX3D.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 );
}
JsGraphX3D.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;
}
JsGraphX3D.prototype.AddPointToPoly3DOnPlane = function( x, y ) {
// or AddPointToPolyOnPlane( JsgVect2 )
this.Poly3D.AddPoint3D( this.Plane.PointOnPlane( x, y ) );
}
JsGraphX3D.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;
}
JsGraphX3D.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;
}
JsGraphX3D.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;
}
JsGraphX3D.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;
}
JsGraphX3D.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;
}
JsGraphX3D.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;
}
JsGraphX3D.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;
}
JsGraphX3D.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;
}
JsGraphX3D.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;
}
JsGraphX3D.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;
}
JsGraphX3D.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 );
}
}
JsGraphX3D.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;
}
JsGraphX3D.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;
}
JsGraphX3D.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;
}
JsGraphX3D.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;
}
JsGraphX3D.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;
}
JsGraphX3D.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;
}
JsGraphX3D.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;
}
JsGraphX3D.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 );
}
JsGraphX3D.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 );
}
JsGraphX3D.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 -------------------------------------------------------------------
JsGraphX3D.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
}
JsGraphX3D.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] } );
}