// 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] } ); }