// jsg3d.js, (C) Walter Bislin, walter.bislins.ch, August 2013 // // description and download: // http://walter.bislins.ch/doku/jsg3d // // dependecies: // x.js // jsg.js // // History: // 27.05.2016 new functions BezierCurveOnPlane, BezierCurveToOnPlane, SplineCurveOnPlane // 24.05.2016 some functions also accept JsgVect2 instead of x, y ///////////////////////////////// // JsgPolygon and JsgPolygonList iterator function JsgPolyListIter( polys ) { // polys: JsgPolygon or JsgPolygonList polys = xDefObjOrNull( polys, null ); this.Reset( polys ); } JsgPolyListIter.prototype.Reset = function( polys ) { // polys: JsgPolygon or JsgPolygonList or undefined this.CurrPolyIx = -1; this.CurrPointIx = -1; if (JsgPolygonList.Ok(polys)) { this.Poly = null; this.PolyList = polys; } else if (JsgPolygon.Ok(polys)) { this.Poly = polys; this.PolyList = null; } if (this.PolyList && this.PolyList.Size > 0) { this.CurrPolyIx = 0; this.Poly = polys.PolyList[0]; } return this; } JsgPolyListIter.prototype.GetNextPoint = function( p ) { // p: JsgVect3 // returns false if no more points found var poly = this.Poly; if (!poly) return false; this.CurrPointIx++; if (this.PolyList) { if (this.CurrPointIx >= poly.Size) { this.CurrPolyIx++; if (this.CurrPolyIx >= this.PolyList.Size) return false; this.Poly = this.PolyList.PolyList[this.CurrPolyIx]; this.CurrPointIx = -1; return this.GetNextPoint( p ); } } else { if (this.CurrPointIx >= poly.Size) return false; } var i = this.CurrPointIx; JsgVect3.Set( p, poly.X[i], poly.Y[i], poly.Z[i] ); return true; } JsgPolyListIter.prototype.Back = function() { // requires this.CurrPointIx >= 0 this.CurrPointIx--; } ///////////////////////////////// // Vectors and Matrices // Note: JsgVect3 is also a JsgVect2. Functions that accept a JsgVect2 also accept a JsgVect3 (but not in the reverse!) var JsgVect3 = { New: function( x, y, z ) { return [ x, y, z ]; }, Null: function() { return [ 0, 0, 0 ]; }, Ok: function( vec ) { return xArray(vec) && (vec.length == 3); }, Reset: function( vec ) { vec[0] = 0; vec[1] = 0; vec[2] = 0; return vec; }, Set: function( vec, x, y, z ) { vec[0] = x; vec[1] = y; vec[2] = z; return vec; }, Copy: function( src ) { return [ src[0], src[1], src[2] ]; }, CopyTo: function( src, dest ) { dest[0] = src[0]; dest[1] = src[1]; dest[2] = src[2]; return dest; }, FromAngle: function( aHAng, aVAng, aDist ) { // angles are in degree. returns a vector of length aDist and direction // direction defined by the angles. // aXAng: number as degrees // aDist: number // return: JsgVect3 aVAng *= Math.PI / 180; aHAng *= Math.PI / 180; var z = aDist * Math.sin( aVAng ); var a = aDist * Math.cos( aVAng ); var x = a * Math.cos( aHAng ); var y = a * Math.sin( aHAng ); return [x,y,z]; }, Scale: function( v, s ) { return [ v[0]*s, v[1]*s, v[2]*s ]; }, ScaleTo: function( v, s ) { v[0] *= s; v[1] *= s; v[2] *= s; return v; }, Length2: function( v ) { return v[0]*v[0] + v[1]*v[1] + v[2]*v[2]; }, Length: function( v ) { return Math.sqrt( v[0]*v[0] + v[1]*v[1] + v[2]*v[2] ); }, Norm: function( v ) { var s = this.Length( v ); if (s == 0.0) { return [ v[0], v[1], v[2] ]; } else { return [ v[0]/s, v[1]/s, v[2]/s ]; } }, NormTo: function( v ) { var s = this.Length( v ); if (s != 0.0) this.ScaleTo( v, 1/s ); return v; }, Add: function( a, b ) { return [ a[0]+b[0], a[1]+b[1], a[2]+b[2] ]; }, AddTo: function( a, b ) { a[0] += b[0]; a[1] += b[1]; a[2] += b[2]; return a; }, Sub: function( a, b ) { return [ a[0]-b[0], a[1]-b[1], a[2]-b[2] ]; }, SubFrom: function( a, b ) { a[0] -= b[0]; a[1] -= b[1]; a[2] -= b[2]; return a; }, ScalarProd: function( a, b ) { return a[0]*b[0] + a[1]*b[1] + a[2]*b[2]; }, Mult: function( a, b ) { return [ a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0] ]; }, MultTo: function( v, a, b ) { v[0] = a[1] * b[2] - a[2] * b[1]; v[1] = a[2] * b[0] - a[0] * b[2]; v[2] = a[0] * b[1] - a[1] * b[0]; return v; }, }; /////////////////// var JsgVect3List = { Ok: function( obj ) { if (!xArray(obj)) return false; if (obj.length == 0) return true; return JsgVect3.Ok(obj[0]); }, ToPoly2D: function( vl, poly ) { // vl: array of JsgVect3 // poly: JsgPolygon or undefined // returns: poly or new JsgPolygon poly = poly || new JsgPolygon(); poly.Reset(); var len = vl.length; for (var i = 0; i < len; i++) { var v = vl[i]; poly.AddPoint( v[0], v[1] ); } return poly; }, }; /////////////////// var JsgVect3Grid = { Ok: function( obj ) { if (!xArray(obj)) return false; if (obj.length == 0) return true; return JsgVect3List.Ok(obj[0]); }, }; ////////////////// var JsgVect3InterGrid = { Ok: function( obj ) { return JsgVect3Grid.Ok( obj ); }, }; ////////////////// var JsgMat3 = { Zero: function( ) { return [ [ 0, 0, 0, 0 ], [ 0, 0, 0, 0 ], [ 0, 0, 0, 0 ] ]; }, Unit: function( ) { return [ [ 1, 0, 0, 0 ], [ 0, 1, 0, 0 ], [ 0, 0, 1, 0 ] ]; }, FromVect: function( v1, v2, v3 ) { return [ [ v1[0], v1[1], v1[2], 0 ], [ v2[0], v2[1], v2[2], 0 ], [ v3[0], v3[1], v3[2], 0 ] ]; }, Moving: function( x, y, z, m ) { var r = [ [ 1, 0, 0, x ], [ 0, 1, 0, y ], [ 0, 0, 1, z ] ]; if (m) r = this.Mult( r, m ); return r; }, Scaling: function( sx, sy, sz, m ) { var r = [ [ sx, 0, 0, 0 ], [ 0, sy, 0, 0 ], [ 0, 0, sz, 0 ] ]; if (m) r = this.Mult( r, m ); return r; }, RotatingX: function( aAngle, m ) { var c = Math.cos( aAngle ), s = Math.sin( aAngle ); var r = [ [ 1, 0, 0, 0 ], [ 0, c, -s, 0 ], [ 0, s, c, 0 ] ]; if (m) r = this.Mult( r, m ); return r; }, RotatingY: function( aAngle, m ) { var c = Math.cos( aAngle ), s = Math.sin( aAngle ); var r = [ [ c, 0, s, 0 ], [ 0, 1, 0, 0 ], [ -s, 0, c, 0 ] ]; if (m) r = this.Mult( r, m ); return r; }, RotatingZ: function( aAngle, m ) { var c = Math.cos( aAngle ), s = Math.sin( aAngle ); var r = [ [ c, -s, 0, 0 ], [ s, c, 0, 0 ], [ 0, 0, 1, 0 ] ]; if (m) r = this.Mult( r, m ); return r; }, Copy: function( m ) { return [ [m[0][0],m[0][1],m[0][2],m[0][3]], [m[1][0],m[1][1],m[1][2],m[1][3]], [m[2][0],m[2][1],m[2][2],m[2][3]] ]; }, CopyTo: function( src, dest ) { for (var row = 0; row < 3; row++) { for (var col = 0; col < 4; col++) { dest[row][col] = src[row][col]; } } return dest; }, Mult: function( a, b ) { return this.MultTo( a, b, this.Zero() ); }, MultTo: function( a, b, to ) { for (var i = 0; i < 3; i++) { for (var j = 0; j < 3; j++) { to[i][j] = a[i][0] * b[0][j] + a[i][1] * b[1][j] + a[i][2] * b[2][j]; } to[i][3] = a[i][0] * b[0][3] + a[i][1] * b[1][3] + a[i][2] * b[2][3] + a[i][3]; } return to; }, Trans: function( m, v ) { return this.TransTo( m, v, JsgVect3.Null() ); }, TransTo: function( m, v, to ) { // m: JsgMat3 // v: JsgVect3 // to: JsgVect3 or undefined // return to or v // if to is undefined result is stored in v (v = to allowed) to = to || v; return this.TransXYZTo( m, v[0], v[1], v[2], to ) }, TransXYZTo: function( m, x, y, z, to ) { // m: JsgMat3 // x,y,z: number // to: JsgVect3 // stores result of m * v in to to[0] = m[0][0] * x + m[0][1] * y + m[0][2] * z + m[0][3]; to[1] = m[1][0] * x + m[1][1] * y + m[1][2] * z + m[1][3]; to[2] = m[2][0] * x + m[2][1] * y + m[2][2] * z + m[2][3]; return to; }, TransList: function( m, vl ) { // vl: array[] of JsgVect3 var rl = [], len = vl.length; for (var i = 0; i < len; i++) { rl.push( this.Trans( m, vl[i] ) ); } return rl; }, TransGrid: function( m, vg ) { // vg: array[i][j] of JsgVect3 var rg = [], len = vg.length; for (var i = 0; i < len; i++) { rg.push( this.TransList( m, vg[i] ) ); } return rg; }, }; //////////////////////////////////// // JsgCamera function JsgCamera( ) { // Note: primary properties are CamViewCenter, CamPos, CamUp, ScreenSize, SceneSize // Computed from these: ViewCenterDist, ViewDir, CamTrans, ScreenDist this.CamViewCenter = JsgVect3.Null(); this.CamPos = JsgVect3.New( 100, 0, 0 ); this.CamUp = JsgVect3.New( 0, 0, 1 ); this.ViewDir = JsgVect3.New( -1, 0, 0 ); // some props to hold temp data this.WorkPoint = JsgVect3.Null(); this.WorkPoint2 = JsgVect3.Null(); this.ResultPoly = new JsgPolygon( true, 'JsgCamera.ResultPoly' ); this.Reset(); } JsgCamera.prototype.Reset = function() { this.Aperture = 1; this.Zoom = 1; this.SceneSize = 2; this.ScreenSize = 1; this.ObjectZExtend = 0; this.HighResolution = ''; JsgVect3.Reset( this.CamViewCenter ); JsgVect3.Set( this.CamPos, 100, 0, 0 ); JsgVect3.Set( this.CamUp, 0, 0, 1 ); this.Update(); } JsgCamera.prototype.Update = function() { // is called after each camera position and view change to compute/normalize internal data var vVect = JsgVect3.Sub( this.CamViewCenter, this.CamPos ); this.ViewCenterDist = JsgVect3.Length( vVect ); if (this.ViewCenterDist == 0) { this.CamPos = JsgVect3.Add( this.CamViewCenter, [1,0,0] ); this.ViewCenterDist = 1; vVect = [-1,0,0]; } this.ViewDir = JsgVect3.Norm( vVect ); var xVect = JsgVect3.Scale( this.ViewDir, -1 ); var zVect = JsgVect3.Norm( this.CamUp ); if (JsgVect3.Length2(zVect) == 0) { zVect = [0,0,1]; } var yVect = JsgVect3.Norm( JsgVect3.Mult( zVect, xVect ) ); if (JsgVect3.Length2(yVect) == 0) { this.CamUp = JsgMat3.Trans( JsgMat3.FromVect([0,0,1],[0,0,0],[1,1,0]), this.CamUp ); this.Update(); return; } zVect = JsgVect3.Norm( JsgVect3.Mult( xVect, yVect ) ); this.CamTrans = JsgMat3.FromVect( xVect, yVect, zVect ); this.ScreenDist = 1; if (this.SceneSize != 0) { this.ScreenDist = (this.ScreenSize / this.SceneSize) * (this.ViewCenterDist - this.ObjectZExtend); } } JsgCamera.prototype.Set = function( aParams ) { if (!xObj(aParams)) return; if (xNum(aParams.SceneSize)) this.SceneSize = aParams.SceneSize; if (xNum(aParams.ScreenSize)) this.ScreenSize = aParams.ScreenSize; if (xNum(aParams.ObjectZExtend)) this.ObjectZExtend = aParams.ObjectZExtend; if (xNum(aParams.Zoom)) this.Zoom = aParams.Zoom; if (xNum(aParams.Aperture)) this.Aperture = aParams.Aperture; if (xStr(aParams.HighResolution)) this.HighResolution = aParams.HighResolution; if (JsgVect3.Ok(aParams.CamViewCenter)) this.CamViewCenter = aParams.CamViewCenter; if (JsgVect3.Ok(aParams.CamUp)) this.CamUp = aParams.CamUp; if (JsgVect3.Ok(aParams.CamPos)) this.CamPos = aParams.CamPos; this.SetAngle( aParams.CamHAng, aParams.CamVAng, aParams.CamDist ); this.Update(); } JsgCamera.prototype.SetAngle = function( aHAng, aVAng, aDist ) { // private function if (xNum(aHAng) || xNum(aVAng)) { var hAng = xDefNum( aHAng, 0 ); var vAng = xDefNum( aVAng, 0 ); var vVect = JsgVect3.Sub( this.CamViewCenter, this.CamPos ); var dist = JsgVect3.Length( vVect ); if (xNum(aDist)) dist = aDist; this.CamPos = JsgVect3.Add( this.CamViewCenter, JsgVect3.FromAngle( hAng, vAng, dist ) ); } } JsgCamera.prototype.Save = function( aParams ) { var par = xDefObj( aParams, {} ); par.SceneSize = this.SceneSize; par.ScreenSize = this.ScreenSize; par.ObjectZExtend = this.ObjectZExtend; par.CamViewCenter = this.CamViewCenter; par.CamUp = this.CamUp; par.CamPos = this.CamPos; par.Zoom = this.Zoom; par.Aperture = this.Aperture; par.HighResolution = this.HighResolution; return par; } JsgCamera.prototype.SetScale = function( aSceneSize, aScreenSize, aObjectZExtend, aZoom ) { if (xNum(aSceneSize)) this.SceneSize = aSceneSize; if (xNum(aScreenSize)) this.ScreenSize = aScreenSize; if (xNum(aObjectZExtend)) this.ObjectZExtend = aObjectZExtend; if (xNum(aZoom)) this.Zoom = aZoom; this.Update(); } JsgCamera.prototype.SetPos = function( aPos, aViewCenter, aUp ) { if (JsgVect3.Ok(aPos)) this.CamPos = aPos; if (JsgVect3.Ok(aViewCenter)) this.CamViewCenter = aViewCenter; if (JsgVect3.Ok(aUp)) this.CamUp = aUp; this.Update(); } JsgCamera.prototype.SetView = function( aViewCenter, aHAng, aVAng, aDist, aUp ) { // Angles are in degree! if (JsgVect3.Ok(aViewCenter)) this.CamViewCenter = aViewCenter; if (JsgVect3.Ok(aUp)) this.CamUp = aUp; this.SetAngle( aHAng, aVAng, aDist ); this.Update(); } JsgCamera.prototype.SetZoom = function( aZoom ) { this.Zoom = xDefNum( aZoom, 1 ); // this.UpdateCamera(); not needet! } JsgCamera.prototype.SetAperture = function( aAperture ) { this.Aperture = xDefNum( aAperture, 1 ); // this.UpdateCamera(); not needet! } JsgCamera.prototype.SetHighResolution = function( aMode ) { // aMode: string ('on', 'off', ''), '' -> default see this.HighResolution this.HighResolution = xDefStr( aMode, '' ); // this.UpdateCamera(); not needet! } JsgCamera.prototype.TransPoly = function( poly, transPolys ) { // poly: JsgPolygon(3D) // transPolys: JsgPolygon or JsgPolygonList or undefined // if transPolys is undefined then this.ResultPoly is used // returns transPolys or this.ResultPoly // applies camera perspective transformation on poly transPolys = transPolys || this.ResultPoly.Reset(); var p = this.WorkPoint; var xs = poly.X; var ys = poly.Y; var zs = poly.Z; var f = this.ScreenDist; var zoom = this.Zoom; var size = poly.Size; for (var i = 0; i < size; i++) { JsgVect3.Set( p, xs[i], ys[i], zs[i] ); JsgVect3.SubFrom( p, this.CamPos ); JsgMat3.TransTo( this.CamTrans, p ); var x = p[1]; var y = p[2]; var z = -p[0]; if (z != 0) { x *= f / z * zoom; y *= f / z * zoom; } transPolys.AddPoint( x, y, z ); } return transPolys; } JsgCamera.prototype.Trans = function( v ) { // v: JsgVect3 // returns new JsgVect3 return this.TransTo( v[0], v[1], v[2], JsgVect3.Copy(v) ); } JsgCamera.prototype.TransTo = function( x, y, z, v ) { // v: JsgVect3 // returns v or this.WorkPoint v = v || this.WorkPoint; JsgVect3.Set( v, x, y, z ); JsgMat3.TransTo( this.CamTrans, JsgVect3.SubFrom( v, this.CamPos ) ); var f = this.ScreenDist; var x = v[1], y = v[2], z = -v[0]; if (z != 0) { x *= f / z * this.Zoom; y *= f / z * this.Zoom; } v[0] = x; v[1] = y; v[2] = z; return v; } JsgCamera.prototype.TransList = function( vl ) { // vl: JsgVect3List // returns: new JsgVect3List var rl = [], len = vl.length; for (var i = 0; i < len; i++) rl.push( this.Trans( vl[i] ) ); return rl; } JsgCamera.prototype.TransGrid = function( vg ) { // vg: JsgVect3Grid // returns: new JsgVect3Grid var rg = [], len = vg.length; for (var i = 0; i < len; i++) rg.push( this.TransList( vg[i] ) ); return rg; } JsgCamera.prototype.TransToPoly2D = function( vl, poly ) { // vl: JsgVect3List // poly: JsgPolygon or undefined // returns: poly or new JsgPolygon return JsgVect3List.ToPoly2D( this.TransList( vl ), poly ); } //////////////////////////////////// // JsgScene function JsgScene( aAmbientLight, aCamLight ) { // Creates an new scene without any light sources. // aAmbientLight, aCamLight: JsgColor this.AmbientLight = JsgColor.Ok(aAmbientLight) ? aAmbientLight : JsgColor.White(); this.CamLight = JsgColor.Ok(aCamLight) ? aCamLight : JsgColor.White(); this.LightSourceList = []; } JsgScene.prototype.AddLightSource = function( lightSource ) { this.LightSourceList.push( lightSource ); return lightSource; } JsgScene.prototype.Set = function( aParams ) { // aParams = { // AmbientLight: JsgColor // CamLight: JsgColor // LightSourceList: Array of JsgLightSource // } if (!xObj(aParams)) return; if (JsgColor.Ok(aParams.AmbientLight)) this.AmbientLight = aParams.AmbientLight; if (JsgColor.Ok(aParams.CamLight)) this.CamLight = aParams.CamLight; if (xArray(aParams.LightSourceList)) { this.LightSourceList = []; var ls = aParams.LightSourceList; for (var i = 0; i < ls.length; i++) { this.AddLightSource( new JsgLightSource(ls[i]) ); } } } JsgScene.prototype.Save = function( params ) { params = params || {}; params.AmbientLight = this.AmbientLight; params.CameraLight = this.CamLight; params.LightSourceList = []; var lsList = this.LightSourceList; var parLsList = params.LightSourceList; for (var i = 0; i < lsList.length; i++) { parLsList.push( new JsgLightSource( lsList[i] ) ); } return params; } //////////////////////////////////// // JsgLightSource function JsgLightSource( def ) { def = def || {}; this.LightColor = JsgColor.Ok(def.LightColor) ? def.LightColor : JsgColor.White(); this.DiffInt = xDefNum( def.DiffInt, 1 ); this.SpecInt = xDefNum( def.SpecInt, 1 ); if (xNum(def.HAng) || xNum(def.VAng) || xNum(def.Dist) || xArray(def.RefPoint)) { var hang = xDefNum( def.HAng, 0 ); var vang = xDefNum( def.VAng, 0 ); var dist = xDefNum( def.Dist, 100 ); var refp = xDefArray( def.RefPoint, [0,0,0] ); var v = JsgVect3.Add( refp, JsgVect3.FromAngle( hang, vang, dist ) ); if (xArray(def.RefPoint)) { this.LightPos = v; this.LightDir = null; } else { this.LightPos = null; this.LightDir = v; } } else { if (xArray(def.LightPos)) { this.LightPos = def.LightPos; this.LightDir = null; } else if (xArray(def.LightDir)) { this.LightPos = null; this.LightDir = JsgVect3.Norm(def.LightDir); } else { this.LightPos = JsgVect3( 0, 0, 1 ); this.LightDir = null; } } } //////////////////////////////////// // JsgEleLighting function JsgEleLighting( def ) { def = def || {}; this.ApplyCamLight = xDefBool( def.ApplyCamLight, true ); this.ApplyAmbient = xDefBool( def.ApplyAmbient, true ); this.ApplyPhong = xDefBool( def.ApplyPhong, false ); this.BlackAndWhite = xDefBool( def.BlackAndWhite, false ); this.ColorResolution = xDefNum( def.ColorResolution, 0 ); this.Dimm = xDefNum( def.Dimm, 1 ); this.CamRefl = xDefNum( def.CamRefl, 1.0 ); this.AmbiRefl = xDefNum( def.AmbiRefl, 0.2 ); this.DiffRefl = xDefNum( def.DiffRefl, 0.5 ); this.SpecRefl = xDefNum( def.SpecRefl, 0.5 ); this.Roughness = xDefNum( def.Roughness, 1.0 ); this.Shiny = xDefNum( def.Shiny, 16 ); this.DiffXRay = xDefNum( def.DiffXRay, 0 ); this.SpecXRay = xDefNum( def.SpecXRay, 0.9 ); } JsgEleLighting.prototype.Set = function( params ) { if (!xObj(params)) return; if (xBool(params.ApplyCamLight)) this.ApplyCamLight = params.ApplyCamLight; if (xBool(params.ApplyAmbient)) this.ApplyAmbient = params.ApplyAmbient; if (xBool(params.ApplyPhong)) this.ApplyPhong = params.ApplyPhong; if (xBool(params.BlackAndWhite)) this.BlackAndWhite = params.BlackAndWhite; if (xNum(params.ColorResolution)) this.ColorResolution = params.ColorResolution; if (xNum(params.Dimm)) this.Dimm = params.Dimm; if (xNum(params.CamRefl)) this.CamRefl = params.CamRefl; if (xNum(params.AmbiRefl)) this.AmbiRefl = params.AmbiRefl; if (xNum(params.DiffRefl)) this.DiffRefl = params.DiffRefl; if (xNum(params.SpecRefl)) this.SpecRefl = params.SpecRefl; if (xNum(params.Roughness)) this.Roughness = params.Roughness; if (xNum(params.Shiny)) this.Shiny = params.Shiny; if (xNum(params.DiffXRay)) this.DiffXRay = params.DiffXRay; if (xNum(params.SpecXRay)) this.SpecXRay = params.SpecXRay; } //////////////////////////////////// // JsgEleAttr function JsgEleAttr( def, bCopyColors ) { // def = { // AreaColor: Color (default light blue) // AreaBackColor: Color (default light red) // LineColor: Color (default dark blue) // AreaBackLineColor: Color (default dark red) // LineWidth: number > 0 (default 1 pixel) // Symbol: String (default 'Circle') // SymbSize: number > 0 (default 8 pixel) // } def = def || {}; bCopyColors = xDefBool( bCopyColors, false ); if (bCopyColors) { this.AreaColor = JsgColor.RGB(0.8,0.8,1); this.AreaBackColor = JsgColor.RGB(1,0.8,0.8); this.LineColor = JsgColor.RGB(0,0,0.5); this.AreaBackLineColor = JsgColor.RGB(0.5,0,0); if (JsgColor.Ok(def.AreaColor)) JsgColor.CopyTo(def.AreaColor, this.AreaColor); if (JsgColor.Ok(def.AreaBackColor)) JsgColor.CopyTo(def.AreaBackColor, this.AreaBackColor); if (JsgColor.Ok(def.LineColor)) JsgColor.CopyTo(def.LineColor, this.LineColor); if (JsgColor.Ok(def.AreaBackLineColor)) JsgColor.CopyTo(def.AreaBackLineColor, this.AreaBackLineColor); } else { this.AreaColor = JsgColor.Ok(def.AreaColor) ? def.AreaColor : JsgColor.RGB(0.8,0.8,1); this.AreaBackColor = JsgColor.Ok(def.AreaBackColor) ? def.AreaBackColor : JsgColor.RGB(1,0.8,0.8); this.LineColor = JsgColor.Ok(def.LineColor) ? def.LineColor : JsgColor.RGB(0,0,0.5); this.AreaBackLineColor = JsgColor.Ok(def.AreaBackLineColor) ? def.AreaBackLineColor : JsgColor.RGB(0.5,0,0); } this.LineWidth = xDefNum( def.LineWidth, 1 ); this.Symbol = xDefStr( def.Symbol, 'Circle' ); this.SymbSize = xDefNum( def.SymbSize, 8 ); } JsgEleAttr.prototype.Set = function( params, bCopyColors ) { if (!xObj(params)) return; bCopyColors = xDefBool( bCopyColors, false ); if (bCopyColors) { if (JsgColor.Ok(params.AreaColor)) JsgColor.CopyTo(params.AreaColor, this.AreaColor); if (JsgColor.Ok(params.AreaBackColor)) JsgColor.CopyTo(params.AreaBackColor, this.AreaBackColor); if (JsgColor.Ok(params.LineColor)) JsgColor.CopyTo(params.LineColor, this.LineColor); if (JsgColor.Ok(params.AreaBackLineColor)) JsgColor.CopyTo(params.AreaBackLineColor, this.AreaBackLineColor); } else { if (JsgColor.Ok(params.AreaColor)) this.AreaColor = params.AreaColor; if (JsgColor.Ok(params.AreaBackColor)) this.AreaBackColor = params.AreaBackColor; if (JsgColor.Ok(params.LineColor)) this.LineColor = params.LineColor; if (JsgColor.Ok(params.AreaBackLineColor)) this.AreaBackLineColor = params.AreaBackLineColor; } if (xNum(params.LineWidth)) this.LineWidth = params.LineWidth; if (xStr(params.Symbol)) this.Symbol = params.Symbol; if (xNum(params.SymbSize)) this.SymbSize = params.SymbSize; } JsgEleAttr.prototype.CopyFrom = function( src ) { JsgColor.CopyTo( src.AreaColor, this.AreaColor ); JsgColor.CopyTo( src.AreaBackColor, this.AreaBackColor ); JsgColor.CopyTo( src.LineColor, this.LineColor ); JsgColor.CopyTo( src.AreaBackLineColor, this.AreaBackLineColor ); this.LineWidth = src.LineWidth; this.Symbol = src.Symbol; this.SymbSize = src.SymbSize; } //////////////////////////////////// // JsgEleData function JsgEleData( def ) { def = def || {}; this.Attr = new JsgEleAttr( def.Attr ); this.AreaDrawMode = xDefNum( def.AreaDrawMode, 3 ); this.PointDrawMode = xDefNum( def.PointDrawMode, 3 ); this.DrawBackface = xDefBool( def.DrawBackface, true ); this.AreaAttrFunc = xDefFuncOrNull( def.AreaAttrFunc, null ); this.LineAttrFunc = xDefFuncOrNull( def.LineAttrFunc, null ); this.PointAttrFunc = xDefFuncOrNull( def.PointAttrFunc, null ); this.Lighting = new JsgEleLighting( def.Lighting ); this.BackLighting = new JsgEleLighting( def.Lighting ); if (xObj(def.BackLighting)) this.BackLighting.Set( def.BackLighting ); this.ContourMode = xDefBool( def.ContourMode, false ); this.ContourSens = xDefNum( def.ContourSens, 0.1 ); this.ContourColor = JsgColor.Ok(def.ContourColor) ? def.ContourColor : JsgColor.Black(); this.ContourWidth = xDefNum( def.ContourWidth, 7 ); } JsgEleData.prototype.Set = function( params ) { if (!xObj(params)) return; if (xObj(params.Attr)) this.Attr.Set( params.Attr ); if (xNum(params.AreaDrawMode)) this.AreaDrawMode = params.AreaDrawMode; if (xNum(params.PointDrawMode)) this.PointDrawMode = params.PointDrawMode; if (xBool(params.DrawBackface)) this.DrawBackface = params.DrawBackface; if (xFuncOrNull(params.AreaAttrFunc)) this.AreaAttrFunc = params.AreaAttrFunc; if (xFuncOrNull(params.LineAttrFunc)) this.LineAttrFunc = params.LineAttrFunc; if (xFuncOrNull(params.PointAttrFunc)) this.PointAttrFunc = params.PointAttrFunc; if (xObj(params.Lighting)) this.Lighting.Set( params.Lighting ); if (xObj(params.Lighting)) this.BackLighting.Set( params.Lighting ); if (xObj(params.BackLighting)) this.BackLighting.Set( params.BackLighting ); if (xBool(params.ContourMode)) this.ContourMode = params.ContourMode; if (xNum(params.ContourSens)) this.ContourSens = params.ContourSens; if (JsgColor.Ok(params.ContourColor)) this.ContourColor = params.ContourColor; if (xNum(params.ContourWidth)) this.ContourWidth = params.ContourWidth; } //////////////////////////////////// // JsgDisplayList function JsgDisplayList() { // Deletes all elements and element data from the display list this.PointList = []; // array of JsgVect3 this.GraphEleList = []; // array of JsgGraphEle this.EleDataList = []; // array of JsgEleData this.StaticDataReady = true; this.ViewDataReady = true; } //////////////////////////////////// // JsgGraphEle function JsgGraphEle( pointIxList, eleData ) { // pointIxList: array of int // eleData: JsgEleData this.PointIxList = pointIxList; this.Data = eleData; this.Center = null; // JsgVect3 this.Normal = null; // JsgVect3 this.CamDist2 = 0; // distance from this.Center to camera pos squared } //////////////////////////////////// // JsgPlane function JsgPlane( pos, xdir, ydir, normalize ) { // pos, xdir, ydir: JsgVect; these are not copied // normalize: Boolean; Optional; Default = false; // set normalized to true, to make plane vectors unit length and perpendicular // some props to hold temp data this.WorkPoint = JsgVect3.Null(); this.WorkPoint2 = JsgVect3.Null(); this.WorkPoint3 = JsgVect3.Null(); this.ExitPoint = JsgVect3.Null(); this.EnterPoint = JsgVect3.Null(); this.FirstEnterPoint = JsgVect3.Null(); this.Result = JsgVect3.Null(); this.ResultPoly = new JsgPolygon( true, 'JsgPlane.ResultPoly' ); this.PolyIterator = new JsgPolyListIter(); this.Set( pos, xdir, ydir, normalize ); } JsgPlane.Ok = function( obj ) { return xDef(obj) && xDef(obj.Pos); } JsgPlane.prototype.Set = function( pos, xdir, ydir, normalize ) { // change plane parameters // pos, xdir, ydir: JsgVect; these are not copied // normalize: Boolean; Optional; Default = false; // set normalized to true, to make plane vectors unit length and perpendicular this.Pos = pos; this.XDir = xdir; this.YDir = ydir; this.Normal = JsgVect3.Null(); if (normalize) { this.Normalize(); } else { this.CompNormal(); } } JsgPlane.prototype.Normalize = function() { // makes plane vectors unit length and perpendicular var XdirNorm = JsgVect3.NormTo( this.XDir ); var ZDir = JsgVect3.MultTo( this.WorkPoint, XdirNorm, this.YDir ); JsgVect3.NormTo( JsgVect3.MultTo( this.YDir, ZDir, XdirNorm ) ); this.CompNormal(); return this; } JsgPlane.prototype.CompNormal = function() { // if this.XDir and this.YDir are unit vectors, then this.Normal will be a unit vector too JsgVect3.MultTo( this.Normal, this.XDir, this.YDir ); } JsgPlane.prototype.Copy = function( normalize ) { // normalize: Boolean; Optional; Default = false; // set normalized to true, to make new plane vectors unit length and perpendicular return new JsgPlane( this.Pos, this.XDir, this.YDir, normalize ); } JsgPlane.prototype.PointOnPlane = function( x, y, v ) { // or PointOnPlane( JsgVect2, v ) // v: JsgVect3 or undefined // returns this.Result or v // transforms x, y coordinates on this plane to 3D coordinates and stores the result in v or this.Result // returns this.Result: JsgVect3 if (JsgVect2.Ok(x)) { return this.Point( x[0], x[1], y ); } v = v || this.Result; JsgVect3.CopyTo( this.Pos, v ); JsgVect3.AddTo( v, JsgVect3.ScaleTo( JsgVect3.CopyTo( this.XDir, this.WorkPoint ), x ) ); JsgVect3.AddTo( v, JsgVect3.ScaleTo( JsgVect3.CopyTo( this.YDir, this.WorkPoint ), y ) ); return v; } JsgPlane.prototype.PolygonOnPlane = function( xArray, yArray, size, planePoly ) { // or PolygonOnPlane( poly, planePoly ) // poly: JsgPolygon // planePoly: JsgPolygon(3D) or undefined // returns planePoly or this.ResultPoly: JsgPolygon(3D) // Note: you have to call planePoly.Reset() yourself to clear it if necessary if (JsgPolygon.Ok(xArray)) { return this.PolygonOnPlane( xArray.X, xArray.Y, xArray.Size, yArray ); } planePoly = planePoly || this.ResultPoly; planePoly.Reset(); size = xDefNum( size, xArray.length ); for (var i = 0; i < size; i++) { var p = this.PointOnPlane( xArray[i], yArray[i] ); planePoly.AddPoint3D( p ); } return planePoly; } JsgPlane.prototype.Polygon = function( xpoly, ypoly, size ) { // depricated! use PolygonOnPlane // or Polygon( JsgPolygon ) // returns new JsgVect3List if (JsgPolygon.Ok(xpoly)) { return this.Polygon( xpoly.X, xpoly.Y, xpoly.Size ); } size = xDefNum( size, xpoly.length ); var vl = []; for (var i = 0; i < size; i++) { vl.push( JsgVect3.Copy( this.Point( xpoly[i], ypoly[i] ) ) ); } return vl; } JsgPlane.prototype.IntersectLine = function( p1, p2 ) { // p1, p2: JsgVect3; two points of line // returns this.Result: JsgVect3 or null; null -> no intersection // requires this.Normal is computed and normalized // http://walter.bislins.ch/blog/index.asp?page=Schnittpunkt+Ebene+mit+Gerade+einfach+berechnen+%28JavaScript%29 // function is optimized to not use temporary objects by using static this.WorkPoint instead var r = JsgVect3.SubFrom( JsgVect3.CopyTo( p2, this.Result ), p1 ); var r3 = JsgVect3.ScalarProd( r, this.Normal ); if (r3 == 0) { // rounding error? try with swaped points var tmp = p1; p1 = p2; p2 = tmp; r = JsgVect3.SubFrom( JsgVect3.CopyTo( p2, this.Result ), p1 ); r3 = JsgVect3.ScalarProd( r, this.Normal ); if (r3 == 0) { // definitely no intersection return null; } } var pos_p1 = JsgVect3.SubFrom( JsgVect3.CopyTo( p1, this.WorkPoint ), this.Pos ); var P3 = JsgVect3.ScalarProd( pos_p1, this.Normal ); var a = -P3 / r3; return JsgVect3.AddTo( JsgVect3.ScaleTo( r, a ), p1 ); } JsgPlane.prototype.IsPoint3DOnTop = function( p ) { // returns true if p is at the side of the plane where the normal is pointing to or on the plane // p: JsgVect3 // requires this.Normal is computed // function is optimized to not use temporary objects by using static this.WorkPoint instead var pos_p = JsgVect3.SubFrom( JsgVect3.CopyTo( p, this.WorkPoint ), this.Pos ); return JsgVect3.ScalarProd( pos_p, this.Normal ) >= 0; } JsgPlane.prototype.IsPointOnTop = function( x, y, z ) { // returns true if p(x,y,z) is at the side of the plane where the normal is pointing to or on the plane // requires this.Normal is computed // function is optimized to not use temporary objects by using static this.WorkPoint instead JsgVect3.Set( this.WorkPoint, x, y, z ); var pos_p = JsgVect3.SubFrom( this.WorkPoint, this.Pos ); return JsgVect3.ScalarProd( pos_p, this.Normal ) >= 0; } JsgPlane.prototype.ClipPoly = function( polys, clippedPolyList, interpolFunc, interpolData ) { // polys: JsgPolygon or JsgPolygonList // clippedPolyList: JsgPolygonList // interpolFunction( this, clippedPolyList ) or undefined // interpolate points between this.ExitPoint and this.EnterPoint // and store them in clippedPolyList.AddPoint3D() clippedPolyList.Reset(); if (JsgPolygonList.Ok(polys)) { var size = polys.Size; for (var i = 0; i < size; i++) { this.Clip( polys.PolyList[i], clippedPolyList, false, interpolFunc, interpolData ); } } else { this.Clip( polys, clippedPolyList, false, interpolFunc, interpolData ); } } JsgPlane.prototype.ClipArea = function( polys, clippedPolyList, interpolFunc, interpolData ) { // polys: JsgPolygon or JsgPolygonList // clippedPolyList: JsgPolygonList // interpolFunction( this, clippedPolyList ) or undefined // interpolate points between this.ExitPoint and this.EnterPoint // and store them in clippedPolyList.AddPoint3D() clippedPolyList.Reset(); clippedPolyList.NewPoly(); // sub areas must be connected with last point of main area if (JsgPolygonList.Ok(polys) && polys.Size > 1) { var mainPoly = polys.PolyList[0]; var lastMainPoint = mainPoly.Size-1; var x = mainPoly.X[lastMainPoint]; var y = mainPoly.Y[lastMainPoint]; var z = mainPoly.Z[lastMainPoint]; var n = polys.Size; for (var i = 1; i < n; i++) { polys.PolyList[i].AddPoint( x, y, z ); } } // polys must be closed for Clip area to work properly var didClose = polys.Close() this.Clip( polys, clippedPolyList, true, interpolFunc, interpolData ); if (didClose) polys.RemoveLastPoint(); // remove connection points if (JsgPolygonList.Ok(polys) && polys.Size > 1) { var n = polys.Size; for (var i = 1; i < n; i++) { polys.PolyList[i].RemoveLastPoint(); } } } JsgPlane.prototype.Clip = function( polys, clippedPolyList, isArea, interpolFunc, interpolData ) { // polys: JsgPolygon or JsgPolygonList // clippedPolyList: JsgPolygonList // interpolFunction( this, clippedPolyList ) or undefined // interpolate points between this.ExitPoint and this.EnterPoint // and store them in clippedPolyList.AddPoint3D() // return empty polygon if polys is empty // require polys is closed if isArea = true // Note: if polys is a JsgPolygonList then all polygons of the list are treated as one big polygon function addSegmentToClipPoly() { if (!isLastP2Added) { if (!isArea) { clippedPolyList.NewPoly(); } clippedPolyList.AddPoint3D( p1 ); } clippedPolyList.AddPoint3D( p2 ); isLastP2Added = true; } if (polys.Size == 0) return; var isP1Inside, isP2Inside; var validEnter = false; var validExit = false; var validFirstEnter = false; var p1 = this.WorkPoint2; var p2 = this.WorkPoint3; var polyIter = this.PolyIterator.Reset( polys ); if (!polyIter.GetNextPoint(p1)) return; // check if polys is only one point isP1Inside = this.IsPoint3DOnTop( p1 ); if (!polyIter.GetNextPoint(p2)) { if (isP1Inside) { if (!isArea) { clippedPolyList.NewPoly(); } clippedPolyList.AddPoint3D( p1 ); } return; } polyIter.Back(); // loop for all polys segements var isLastP2Added = false; while (polyIter.GetNextPoint(p2)) { isP2Inside = this.IsPoint3DOnTop( p2 ); if (isP1Inside && isP2Inside) { // this code part is reused below, so it is put in a separat function addSegmentToClipPoly(); } else if (isP1Inside != isP2Inside) { // segment intersects plane: handle clipping var s = this.IntersectLine( p1, p2 ); if (!s) { // due to rounding errors the intersection point could not be computed // hanlde point p2 to be on the same side as p1 isP2Inside = isP1Inside; if (isP1Inside) { addSegmentToClipPoly() } // else nothing to do if both points are outside } else if (isP1Inside) { // EXIT: line segment exits inside if (!isLastP2Added) { if (!isArea) { if (!interpolFunc) { clippedPolyList.NewPoly(); } else if (clippedPolyList.Size == 0) { clippedPolyList.NewPoly(); } } clippedPolyList.AddPoint3D( p1 ); } clippedPolyList.AddPoint3D( s ); isLastP2Added = false; // handle interpolation if (interpolFunc) { JsgVect3.CopyTo( s, this.ExitPoint ); validExit = true; } } else { // ENTER: line segment enters inside if (!isArea) { if (!interpolFunc) { clippedPolyList.NewPoly(); } else if (clippedPolyList.Size == 0) { clippedPolyList.NewPoly(); } } // handle interpolation if (interpolFunc) { JsgVect3.CopyTo( s, this.EnterPoint ); if (validExit) { interpolFunc( this, clippedPolyList, interpolData ); } else { JsgVect3.CopyTo( s, this.FirstEnterPoint ); validFirstEnter = true; } validEnter = false; validExit = false; } clippedPolyList.AddPoint3D( s ); clippedPolyList.AddPoint3D( p2 ); isLastP2Added = true; } // else nothing to do if both points are outside } isP1Inside = isP2Inside; JsgVect3.CopyTo( p2, p1 ); } // next segment // handle interpolation if (interpolFunc && validFirstEnter && validExit) { JsgVect3.CopyTo( this.FirstEnterPoint, this.EnterPoint ); interpolFunc( this, clippedPolyList, interpolData ); clippedPolyList.AddPoint3D( this.FirstEnterPoint ); } } //////////////////////////////////// // JsGraph3D Object function NewGraph3D( aParams ) { return new JsGraph3D( aParams ); } function DrawGraph3D( g3d ) { g3d.DrawDisplayList(); } function JsGraph3D( aParams ) { aParams = xDefObj( aParams, {} ); // pass null as DrawFunc to JsGraph because DrawFunc is a 3D DrawFunc and JsGraph3D installs its own DrawFunc in JsGraph. var drawFunc3D = xDefFuncOrNull( aParams.DrawFunc, DrawGraph3D ); if (xFunc(aParams.DrawFunc)) aParams.DrawFunc = null; this.parentClass.constructor.call( this, aParams ); this.Async = new CAsync(); this.MinAlpha = 1 / 255; this.UpdateInterval = 25; // [ms] this.DrawFunc3D = null; // some props for temp data this.WorkPoly2D = new JsgPolygon( false, 'JsGraph3D.WorkPoly2D (local)' ); this.WorkPoint3D = JsgVect3.Null(); this.WorkPoint3D2 = JsgVect3.Null(); this.WorkPoly3D = new JsgPolygon( true, 'JsGraph3D.WorkPoly3D' ); this.WorkPoly3D2 = new JsgPolygon( true, 'JsGraph3D.WorkPoly3D2' ); this.XfmPolys3D = new JsgPolygonList( true, 'JsGraph3D.XfmPolys3D' ); this.CamPolys3D = new JsgPolygonList( true, 'JsGraph3D.CamPolys3D' ); this.ClipPolys3D1 = new JsgPolygonList( true, 'JsGraph3D.ClipPolys3D1' ); this.ClipPolys3D2 = new JsgPolygonList( true, 'JsGraph3D.ClipPolys3D2' ); // caching this.WorkingAttr = new JsgEleAttr(); this.ComputedAreaColor = JsgColor.Black(); this.CompAreaLSLightingColor = JsgColor.Black(); this.CompAreaLightingLightColor = JsgColor.Black(); this.CompAreaLightingColor = JsgColor.Black(); this.CompAreaPhongLightingRVect = [0,0,0]; this.CompAreaPhongLightingVVect = [0,0,0]; this.CompAreaPhongLightingNVect = [0,0,0]; // objects this.DisplayList = new JsgDisplayList(); this.Plane = new JsgPlane( [ 0, 0, 0 ], [ 0, 1, 0 ], [ 0, 0, 1 ] ); this.Camera = new JsgCamera(); this.PathPolys3D = new JsgPolygonList( true, 'JsGraph3D.PathPolys3D (local)' ); this.IsPath3DOpen = false; this.ApplyTransToPath3D = false; this.LastPos3D = JsgVect3.Null(); this.LastPosOnPlane = JsgVect2.Null() this.Poly3D = new JsgPolygon( true, 'JsGraph3D.Poly3D' ); this.ApplyTransToPoly3D = false; this.CameraClipPlaneDist = 0; this.ClipPlaneList = [ null ]; // first entry is camera clip plane this.Reset3D(); var me = this; this.SetBeforeResetFunc( function CB_OnBeforeReset(g) { me.StopDrawing(g); } ); if (drawFunc3D) { this.SetDrawFunc( drawFunc3D ); } // restore aParams.DrawFunc if (xDef(aParams.DrawFunc)) aParams.DrawFunc = drawFunc3D; this.SetAll( aParams ); this.SetScreenWindow(); } JsGraph3D.inheritsFrom( JsGraph ); JsGraph3D.prototype.SetSyncMode = function ( bSyncMode ) { var oldSyncMode = this.SyncMode; this.Async.SyncMode = bSyncMode; this.SyncMode = bSyncMode; return oldSyncMode; } JsGraph3D.prototype.Reset3D = function( reset2D, clear ) { reset2D = xDefBool( reset2D, true ); clear = xDefBool( clear, true ); if (reset2D) { this.Reset( false ); } this.CameraClipPlaneDist = 0; this.Plane.Set( [ 0, 0, 0 ], [ 0, 1, 0 ], [ 0, 0, 1 ] ); this.ClipPlaneList = [ null ]; // first entry is camera clip plane this.ClipPlaneListSize = 1; this.PathPolys3D.Reset(); this.Trans3D = null; this.Trans3DStack = []; this.IsPath3DOpen = false; this.ShowProgress = true; this.SyncMode = false; this.BeforeDrawFunc = null; this.AfterDrawFunc = null; this.DefaultEleData = new JsgEleData(); this.DisplayList = new JsgDisplayList(); this.DisplayList.ViewDataReady = false; this.Scene = new JsgScene(); this.ResetCamera(); if (clear) { this.Clear(); } } JsGraph3D.prototype.SaveAll = function( aParams ) { var par = xDefObj( aParams, {} ); par.ShowProgress = this.ShowProgress; par.SyncMode = this.SyncMode; par.DrawFunc = this.DrawFunc3D; par.BeforeDrawFunc = this.BeforeDrawFunc; par.AfterDrawFunc = this.AfterDrawFunc; this.SaveDefaultEleData( par ); this.SaveClipPlanes( par ); this.SaveCamera( par ); this.SavePlane( par ); this.SaveScene( par ); this.SaveDisplayList( par ); this.SavePlane( par ); return par; } JsGraph3D.prototype.SetAll = function( aParams ) { this.ShowProgress = xDefBool( aParams.ShowProgress, true ); this.SetSyncMode( xDefBool( aParams.SyncMode, false ) ); if (xFuncOrNull( aParams.DrawFunc )) { this.SetDrawFunc( aParams.DrawFunc ); } this.BeforeDrawFunc = xDefFunc( aParams.BeforeDrawFunc, null ); this.AfterDrawFunc = xDefFunc( aParams.AfterDrawFunc, null ); this.SetDefaultEleData( aParams ); this.SetClipPlanes( aParams ); this.SetCamera( aParams ); this.SetScene( aParams ); this.SetDisplayList( aParams ); this.SetPlane( aParams ); } JsGraph3D.prototype.SetScreenWindow = function( ) { var ratio = this.VpWidth / this.VpHeight; var screenSize = this.Camera.ScreenSize; var w, h; if (ratio > 0) { w = screenSize * ratio; h = screenSize; } else { w = screenSize; h = screenSize / ratio; } this.SetWindowWH( -w/2, -h/2, w, h ); } JsGraph3D.prototype.SetDrawFunc = function( aDrawFunc3D ) { // aDrawFunc3D = function( JsGraph3D ) this.DrawFunc3D = aDrawFunc3D; if (this.DrawFunc3D) { var me = this; this.parentClass.SetDrawFunc.call( this, function CB_OnDraw(g) { me.Draw3D(); }, true ); } else { this.parentClass.SetDrawFunc.call( this, null, false ); } } JsGraph3D.prototype.Draw3D = function() { if (this.DrawFunc3D) { this.SetScreenWindow(); this.DrawFunc3D( this ); } } JsGraph3D.prototype.StopDrawing = function( aJsGraph ) { this.Async.Stop(); } JsGraph3D.prototype.SetDefaultEleData = function( aParams ) { this.DefaultEleData = xObj(aParams.DefaultEleData) ? aParams.DefaultEleData : new JsgEleData( aParams ); } JsGraph3D.prototype.ChangeDefaultEleData = function( aParams ) { this.DefaultEleData.Set( aParams ); } JsGraph3D.prototype.SaveDefaultEleData = function( aParams ) { aParams = aParams || {}; aParams.DefaultEleData = this.DefaultEleData; return aParams; } JsGraph3D.prototype.AddOrGetDefaultEleData = function( aEleData, bCopy ) { var eleData = this.DefaultEleData; bCopy = xDefBool( bCopy, true ); if (xObj(aEleData)) { if (bCopy) { eleData = new JsgEleData(aEleData); } else { eleData = aEleData; } this.DisplayList.EleDataList.push( eleData ); } return eleData; } JsGraph3D.prototype.ResetCamera = function() { this.Camera.Reset(); this.UpdateCameraClipPlane(); this.DisplayList.ViewDataReady = false; } JsGraph3D.prototype.SetCamera = function( aParams ) { this.Camera.Set( aParams ); this.UpdateCameraClipPlane(); this.DisplayList.ViewDataReady = false; } JsGraph3D.prototype.SaveCamera = function( aParams ) { return this.Camera.Save( aParams ); } JsGraph3D.prototype.SetCameraScale = function( aSceneSize, aScreenSize, aObjectZExtend, aZoom ) { this.Camera.SetScale( aSceneSize, aScreenSize, aObjectZExtend, aZoom ); this.DisplayList.ViewDataReady = false; } JsGraph3D.prototype.SetCameraPos = function( aPos, aViewCenter, aUp ) { this.Camera.SetPos( aPos, aViewCenter, aUp ); this.DisplayList.ViewDataReady = false; this.UpdateCameraClipPlane(); } JsGraph3D.prototype.SetCameraView = function( aViewCenter, aHAng, aVAng, aDist, aUp ) { // Angles are in degree! this.Camera.SetView( aViewCenter, aHAng, aVAng, aDist, aUp ); this.DisplayList.ViewDataReady = false; this.UpdateCameraClipPlane(); } JsGraph3D.prototype.SetCameraZoom = function( aZoom ) { this.Camera.SetZoom( aZoom ); // not needet here: this.DisplayList.ViewDataReady = false; } JsGraph3D.prototype.SetCameraAperture = function( aAperture ) { this.Camera.SetAperture( aAperture ); } JsGraph3D.prototype.SetCameraHighResolution = function( aMode ) { // aMode: string ('on', 'off', ''), '' -> default see this.HighResolution this.Camera.SetHighResolution( aMode ); } JsGraph3D.prototype.UpdateCameraClipPlane = function() { // private function // computes new camera clip plane from current camera settings if (this.CameraClipPlaneDist <= 0 && this.ClipPlaneList[0] != null) { this.ClipPlaneList[0] = null; } if (this.CameraClipPlaneDist > 0) { var cam = this.Camera; var n = JsgVect3.Norm( JsgVect3.Sub( cam.CamViewCenter, cam.CamPos ) ); var pos = JsgVect3.Add( cam.CamPos, JsgVect3.Scale( n, this.CameraClipPlaneDist ) ); var xdir = JsgVect3.Mult( cam.CamUp, n ); var ydir = JsgVect3.Mult( n, xdir ); var plane = new JsgPlane( pos, xdir, ydir, true ); this.ClipPlaneList[0] = plane; } } JsGraph3D.prototype.SetCameraClipping = function( clipPlaneDist ) { // clipPlaneDist: number; 0 -> no camera clipping, else clipPlaneDist gives distance of clip plane from camera plane this.CameraClipPlaneDist = clipPlaneDist; this.UpdateCameraClipPlane(); } JsGraph3D.prototype.DeleteClipPlanes = function() { this.ClipPlaneListSize = 1; } JsGraph3D.prototype.AddClipPlane = function( planeOrPos, xdir, ydir ) { // or AddClipPlane( plane: JsgPlane ) if (JsgPlane.Ok(planeOrPos)) { planeOrPos.Normalize(); this.ClipPlaneList[this.ClipPlaneListSize] = planeOrPos; this.ClipPlaneListSize++; } else { this.ClipPlaneList[this.ClipPlaneListSize] = new JsgPlane( planeOrPos, xdir, ydir, true ); this.ClipPlaneListSize++; } } JsGraph3D.prototype.SaveClipPlanes = function( aParams ) { if (this.CameraClipPlaneDist > 0) { aParmas.CameraClipPlaneDist = this.CameraClipPlaneDist; } var n = this.ClipPlaneListSize; if (n > 1) { var l = []; for (var i = 1; i < n; i++) { l.push( this.ClipPlaneList[i].Copy() ); } aParams.ClipPlaneList = l; } } JsGraph3D.prototype.SetClipPlanes = function( aParams ) { this.DeleteClipPlanes(); if (xDefArray(aParams.ClipPlaneList)) { var lst = aParams.ClipPlaneList; var n = lst.length; for (var i = 0; i < n; i++) { var plane = lst[i]; if (JsgPlane.Ok(plane)) { this.AddClipPlane( plane ); } } } if (xDefNum(aParams.CameraClipPlaneDist)) { this.SetCameraClipping( aParams.CameraClipPlaneDist ); } } ////////////////////////////////////////// // Scene Functions JsGraph3D.prototype.NewScene = function( aAmbientLight, aCamLight ) { // Creates an new scene without any light sources. // aAmbientLight, aCamLight: Color this.Scene = new JsgScene( aAmbientLight, aCamLight ); } JsGraph3D.prototype.SaveScene = function( aParams ) { var par = xDefObj( aParams, {} ); par.Scene = this.Scene; return par; } JsGraph3D.prototype.SetScene = function( aParams ) { if (xObj(aParams.Scene)) { this.Scene = aParams.Scene; } else { this.Scene.Set( aParams ); } } JsGraph3D.prototype.SetLight = function( aParams ) { // aParams = { // AmbientLight: JsgColor // CamLight: JsgColor // LightSourceList: Array of LightSource // } this.Scene.Set( aParams ); } JsGraph3D.prototype.SaveLight = function( aParams ) { return this.Scene.Save( aParams ); } JsGraph3D.prototype.SetAmbientLight = function( aAmbientLight ) { // aAmbientLight = Color this.Scene.AmbientLight = JsgColor.Ok(aAmbientLight) ? aAmbientLight : JsgColor.White(); } JsGraph3D.prototype.SetCameraLight = function( aCamLight ) { // aCamLight = Color this.Scene.CamLight = JsgColor.Ok(aCamLight) ? aCamLight : JsgColor.White(); } JsGraph3D.prototype.AddLightSource = function( aParams ) { // Adds a light source to this.Scene.LightSourceList. // To remove all light sourceses, create a new scene with this.NewScene(). // Returns the created light source object. // // If aParams.LightDir is defined, then this is the vector to the light source at infinity (default) // If aParams.LightPos is defined, then this is the position of the light. // return JsgLightSource // // With the following properties you can define a light source by angles and distance rather than by vector: // // aParams: extends JsgLightSource by { // HAng, VAng: number -> angle in degrees (default 0) // Dist: number -> distance of light source from RefPoint // } // Note: if RefPoint is defined, then the Position of lightsource is RefPoint + Vector(HAng,VAng,Dist) // If RefPoint is not defined, then Vector(HAng,VAng,Dist) defines the direction to the light source at infinity. // return this.Scene.AddLightSource( new JsgLightSource(aParams) ); } JsGraph3D.prototype.NewDisplayList = function( ) { this.DisplayList = new JsgDisplayList(); } JsGraph3D.prototype.SaveDisplayList = function( aParams ) { var par = xDefObj( aParams, {} ); par.DisplayList = this.DisplayList; return par; } JsGraph3D.prototype.SetDisplayList = function( aParams ) { if(xObj(aParams.DisplayList)) this.DisplayList = aParams.DisplayList; } JsGraph3D.prototype.GraphEleNPoints = function( aGraphEle ) { return aGraphEle.PointIxList.length; } JsGraph3D.prototype.GraphElePoint = function( aGraphEle, aIx ) { return this.DisplayList.PointList[aGraphEle.PointIxList[aIx]]; } JsGraph3D.prototype.GraphEleCenter = function( aGraphEle ) { return aGraphEle.Center; } JsGraph3D.prototype.GraphEleNormal = function( aGraphEle ) { return aGraphEle.Normal; } //////////////////////////////// // Polygon, VectList and VectGrid generators JsGraph3D.prototype.PolygonFromFunc = function( aParams, poly ) { // aParams = { // Func: Function( x, aParams, retPoint ) returns [ x, y, z ] // Min, Max: Number; Default = -1, 1 // Steps: Integer > 0; Default = 20 // Delta: Number // Graph3D: Default = this // ... // } // poly: JsgPolygon(3D) or undefined // returns poly or new JsgPolygon(3D) // // Func can return retPoint or new [x,y,z] // // Specify eather Steps or Delta. Min may be greater than Max. The sign of Delta is ignored. // You may supply additional properties in Params (e.g. a Plane), to pass them to Function. // // If Graph3d is not defined, it is set to this. aParams.Graph3D = xDefObj( aParams.Graph3D, this ); var min = xDefNum( aParams.Min, -1 ); var max = xDefNum( aParams.Max, 1 ); var delta = 0.1; if (xNum( aParams.Steps )) { delta = (max - min) / aParams.Steps; } else if (xNum( aParams.Delta )) { delta = Math.abs( aParams.Delta ); if (max < min) { delta = -delta; } } var limit = max + 0.1 * delta; poly = poly || new JsgPolygon( true ); var point = this.WorkPoint3D; for (var x = min; (delta > 0) ? (x <= limit) : (x >= limit); x += delta) { poly.AddPoint3D( aParams.Func( x, aParams, point ) ); } return poly; } JsGraph3D.prototype.VectListFromFunc = function( aParams ) { // aParams = { // Func: Function( x, aParams ) returns [ x, y, z ] // Min, Max: Number; Default = -1, 1 // Steps: Integer > 0; Default = 20 // Delta: Number // Graph3D: Default = this // ... // } // // Specify eather Steps or Delta. Min may be greater than Max. The sign of Delta is ignored. // You may supply additional properties in Params (e.g. a Plane), to pass them to Function. // // If Graph3d is not defined, it is set to this. aParams.Graph3D = xDefObj( aParams.Graph3D, this ); var min = xDefNum( aParams.Min, -1 ); var max = xDefNum( aParams.Max, 1 ); var delta = 0.1; if (xNum( aParams.Steps )) { delta = (max - min) / aParams.Steps; } else if (xNum( aParams.Delta )) { delta = Math.abs( aParams.Delta ); if (max < min) { delta = -delta; } } var limit = max + 0.1 * delta; var vectList = []; for (var x = min; (delta > 0) ? (x <= limit) : (x >= limit); x += delta) { vectList.push( aParams.Func( x, aParams ) ); } return vectList; } JsGraph3D.prototype.VectGridFrom3DFunc = function( aParams ) { // aParams = { // Func: Function( a, b, aParams ) returns [ x, y, z, ... ] // Min, Max: Number; Default = -1, 1 // Min2, Max2: Number; Default = min, max // Steps: Integer > 0; Default = 20 // Steps2: Integer > 0; Default = Steps // Delta: Number // Delta2: Number; Default = Delta // Graph3D: Default = this // ... // } // // a runs from min to max in the inner loop // b runs from min2 to max2 in the outer loop // // Specify eather Steps or Delta. Min may be greater than Max. The sign of Delta is ignored. // You may supply additional properties in Params (e.g. a Plane), to pass them to Function. // // If Graph3D is not defined, it is set to this. aParams.Graph3D = xDefObj( aParams.Graph3D, this ); var min = xDefNum( aParams.Min, -1 ); var max = xDefNum( aParams.Max, 1 ); var min2 = xDefNum( aParams.Min2, min ); var max2 = xDefNum( aParams.Max2, max ); var delta = 0.1; if (xNum( aParams.Steps )) { delta = (max - min) / aParams.Steps; } else if (xNum( aParams.Delta )) { delta = Math.abs( aParams.Delta ); if (max < min) { delta = -delta; } } var delta2 = delta; if (xNum( aParams.Steps2 )) { delta2 = (max2 - min2) / aParams.Steps2; } else if (xNum( aParams.Delta2 )) { delta2 = Math.abs( aParams.Delta2 ); if (max2 < min2) { delta2 = -delta2; } } var limit = max + 0.1 * delta; var limit2 = max2 + 0.1 * delta2; var grid = []; for ( var b = min2; (delta2 > 0) ? (b <= limit2) : (b >= limit2); b += delta2 ) { var line = []; for ( var a = min; (delta > 0) ? (a <= limit) : (a >= limit); a += delta ) { line.push( aParams.Func( a, b, aParams ) ); } grid.push( line ); } return grid; } JsGraph3D.prototype.VectInterGridFrom3DFunc = function( aParams ) { // Creates a InterGrid, that is a special grid with extra points between a normal VectGrid. // So each second VectList is one element less and has an offset of Delta/2, Delta2/2. // Use AddAreasFromVectInterGrid instead of AddAreasFromVectGrid to create areas in display list. // // aParams = { // Func: Function( a, b, aParams ) returns [ x, y, z, ... ] // Min, Max: Number; Default = -1, 1 // Min2, Max2: Number; Default = min, max // Steps: Integer > 0; Default = 20 // Steps2: Integer > 0; Default = Steps // Delta: Number // Delta2: Number; Default = Delta // Graph3D: Default = this // ... // } // // a runs from min to max in the inner loop // b runs from min2 to max2 in the outer loop // // Specify eather Steps or Delta. Min may be greater than Max. The sign of Delta is ignored. // You may supply additional properties in Params (e.g. a Plane), to pass them to Function. // // If Graph3D is not defined, it is set to this. aParams.Graph3D = xDefObj( aParams.Graph3D, this ); var min = xDefNum( aParams.Min, -1 ); var max = xDefNum( aParams.Max, 1 ); var min2 = xDefNum( aParams.Min2, min ); var max2 = xDefNum( aParams.Max2, max ); var delta = 0.1; if (xNum( aParams.Steps )) { delta = (max - min) / aParams.Steps; } else if (xNum( aParams.Delta )) { delta = Math.abs( aParams.Delta ); if (max < min) { delta = -delta; } } var delta2 = delta; if (xNum( aParams.Steps2 )) { delta2 = (max2 - min2) / aParams.Steps2; } else if (xNum( aParams.Delta2 )) { delta2 = Math.abs( aParams.Delta2 ); if (max2 < min2) { delta2 = -delta2; } } var deltaInter = delta / 2; var deltaInter2 = delta2 / 2; var limit = max + 0.1 * delta; var limit2 = max2 + 0.1 * delta2; var grid = []; for ( var b = min2; (delta2 > 0) ? (b <= limit2) : (b >= limit2); b += delta2 ) { var line = []; for ( var a = min; (delta > 0) ? (a <= limit) : (a >= limit); a += delta ) { line.push( aParams.Func( a, b, aParams ) ); } grid.push( line ); var bInter = b + deltaInter2; if ((delta2 > 0) ? (bInter <= limit2) : (bInter >= limit2)) { var line = []; for ( var aInter = min + deltaInter; (delta > 0) ? (aInter <= limit) : (aInter >= limit); aInter += delta ) { line.push( aParams.Func( aInter, bInter, aParams ) ); } grid.push( line ); } } return grid; } JsGraph3D.prototype.AddPointsFromFunc = function( aParams ) { // aParams = { // Func: Function( x, aParams ) returns [ x, y, z ] // Min, Max: Number; Default = -1, 1 // Steps: Integer > 0; Default = 20 // Delta: Number // Graph3D: Default = this // EleData: JsgEleData; Default = this.DefaultEleData // CopyEleData: Boolean; Default = true // ... // } // // Specify eather Steps or Delta. Min may be greater than Max. The sign of Delta is ignored. // You may supply additional properties in Params (e.g. a Plane), to pass them to Function. // // If Graph3d is not defined, it is set to this. // // If aEleData is provided, a copy of that is created filling all not provided data with defaults, // and all created areas point to that element data. // Otherwise they point to this.DefaultEleData. var eleData = xDefObj( aParams.EleData, null ); this.AddPointsFromVectList( this.VectListFromFunc( aParams ), eleData, aParams.CopyEleData ); } JsGraph3D.prototype.AddLinesFromFunc = function( aParams ) { // aParams = { // Func: Function( x, aParams ) returns [ x, y, z ] // Min, Max: Number; Default = -1, 1 // Steps: Integer > 0; Default = 20 // Delta: Number // Graph3D: Default = this // EleData: JsgEleData; Default = this.DefaultEleData // CopyEleData: Boolean; Default = true // ... // } // // Specify eather Steps or Delta. Min may be greater than Max. The sign of Delta is ignored. // You may supply additional properties in Params (e.g. a Plane), to pass them to Function. // // If Graph3d is not defined, it is set to this. // // If aEleData is provided, a copy of that is created filling all not provided data with defaults, // and all created areas point to that element data. // Otherwise they point to this.DefaultEleData. var eleData = xDefObj( aParams.EleData, null ); this.AddLinesFromVectList( this.VectListFromFunc( aParams ), eleData, aParams.CopyEleData ); } JsGraph3D.prototype.AddAreasFrom3DFunc = function( aParams ) { // aParams = { // Func: Function( a, b, aParams ) returns [ x, y, z, ... ] or null // GridType: String; Default = 'RectGrid'; Values: 'RectGrid', 'InterGrid' // Min, Max: Number; Default = -1, 1 // Min2, Max2: Number; Default = min, max // Steps: Integer > 0; Default = 20 // Steps2: Integer > 0; Default = Steps // Delta: Number // Delta2: Number; Default = Delta // Graph3D: Default = this // EleData: JsgEleData; Default = this.DefaultEleData // CopyEleData: Boolean; Default = true // ... // } // // a runs from min to max in the inner loop // b runs from min2 to max2 in the outer loop // // Specify eather Steps or Delta. Min may be greater than Max. The sign of Delta is ignored. // You may supply additional properties in Params (e.g. a Plane), to pass them to Function. // // If Graph3d is not defined, it is set to this. // // If aEleData is provided, a copy of that is created filling all not provided data with defaults, // and all created areas point to that element data. // Otherwise they point to this.DefaultEleData. var eleData = xDefObj( aParams.EleData, null ); var gridType = xDefStr( aParams.GridType, 'RectGrid' ); if (gridType === 'InterGrid') { this.AddAreasFromVectInterGrid( this.VectInterGridFrom3DFunc( aParams ), eleData, aParams.CopyEleData ); } else { this.AddAreasFromVectGrid( this.VectGridFrom3DFunc( aParams ), eleData, aParams.CopyEleData ); } } JsGraph3D.prototype.AddPointsFrom3DFunc = function( aParams ) { // aParams = { // Func: Function( a, b, aParams ) returns [ x, y, z, ... ] // GridType: String; Default = 'RectGrid'; Values: 'RectGrid', 'InterGrid' // Min, Max: Number; Default = -1, 1 // Min2, Max2: Number; Default = min, max // Steps: Integer > 0; Default = 20 // Steps2: Integer > 0; Default = Steps // Delta: Number // Delta2: Number; Default = Delta // Graph3D: this // EleData: JsgEleData; Default = this.DefaultEleData // CopyEleData: Boolean; Default = true // ... // } // // a runs from min to max in the inner loop // b runs from min2 to max2 in the outer loop // // Specify eather Steps or Delta. Min may be greater than Max. The sign of Delta is ignored. // You may supply additional properties in Params (e.g. a Plane), to pass them to Function. // // If Graph3d is not defined, it is set to this. // // If aEleData is provided, a copy of that is created filling all not provided data with defaults, // and all created areas point to that element data. // Otherwise they point to this.DefaultEleData. var eleData = xDefObj( aParams.EleData, null ); var gridType = xDefStr( aParams.GridType, 'RectGrid' ); if (gridType === 'InterGrid') { this.AddPointsFromVectInterGrid( this.VectInterGridFrom3DFunc( aParams ), eleData, aParams.CopyEleData ); } else { this.AddPointsFromVectGrid( this.VectGridFrom3DFunc( aParams ), eleData, aParams.CopyEleData ); } } JsGraph3D.prototype.AddPoint = function( aPoint, aEleData, bCopy ) { // aPoint: JsgVect3 // aEleData: JsgEleData (optional) // bCopy: Boolean (optional); Default = true; true -> store a copy of aEleData else store aEleData itself // returns point (JsgGraphEle) // // Creates a points and appends it to the display list. // // If aEleData is provided, a copy of that is created filling all not provided data with defaults, // and all created points are asigned with references to that element data. // Otherwise they refere to this.DefaultEleData. return this.AddPointsFromVectList( [ aPoint ], aEleData, bCopy ); } JsGraph3D.prototype.AddPointsFromVectList = function( aVectList, aEleData, bCopy ) { // aVectList: array[] of JsgVect3 // aEleData: JsgEleData (optional) // bCopy: Boolean (optional); Default = true; true -> store a copy of aEleData else store aEleData itself // returns last point (JsgGraphEle) // // Creates points from aVectList and appends them to the display list. // // If aEleData is provided, a copy of that is created filling all not provided data with defaults, // and all created points are asigned with references to that element data. // Otherwise they refere to this.DefaultEleData. var dl = this.DisplayList; var pointList = dl.PointList; var eleList = dl.GraphEleList; var pointListOffset = pointList.length; var eleData = this.AddOrGetDefaultEleData( aEleData, bCopy ); var lastPoint = null; var ilen = aVectList.length; var ix = pointListOffset; for (var i = 0; i < ilen; i++) { pointList.push( aVectList[i] ); lastPoint = new JsgGraphEle( [ ix++ ], eleData ); eleList.push( lastPoint ); } this.DisplayListUpdated(); return lastPoint; } JsGraph3D.prototype.AddLine = function( aStartPoint, aEndPoint, aEleData, bCopy ) { // aStartPoint, aEndPoint: JsgVect3 // aEleData: JsgEleData (optional) // bCopy: Boolean (optional); Default = true; true -> store a copy of aEleData else store aEleData itself // returns line (JsgGraphEle) // // Creates a line from aStartPoint to aEndPoint and appends it to the display list. // // If aEleData is provided, a copy of that is created filling all not provided data with defaults, // and all created points are asigned with references to that element data. // Otherwise they refere to this.DefaultEleData. return this.AddLinesFromVectList( [ aStartPoint, aEndPoint ], aEleData, bCopy ); } JsGraph3D.prototype.AddLinesFromVectList = function( aVectList, aEleData, bCopy ) { // aVectList: array[] of JsgVect3 // aEleData: JsgEleData (optional) // bCopy: Boolean (optional); Default = true; true -> store a copy of aEleData else store aEleData itself // returns JsgGraphEle; last line // // Creates lines from aVectList and appends them to the display list. // // If aEleData is provided, a copy of that is created filling all not provided data with defaults, // and all created points are asigned with references to that element data. // Otherwise they refere to this.DefaultEleData. var dl = this.DisplayList; var pointList = dl.PointList; var eleList = dl.GraphEleList; var pointListOffset = pointList.length; var eleData = this.AddOrGetDefaultEleData( aEleData, bCopy ); var lastLine = null; var ilen = aVectList.length; for (var i = 0; i < ilen; i++) { pointList.push( aVectList[i] ); } var ilen = aVectList.length - 1; var ix = pointListOffset; for (var i = 0; i < ilen; i++) { lastLine = new JsgGraphEle( [ ix, ix+1 ], eleData ); eleList.push( lastLine ); ix++; } this.DisplayListUpdated(); return lastLine; } JsGraph3D.prototype.AddTriangle = function( P1, P2, P3, aEleData, bCopy ) { // P1, P2, P3: JsgVect3 // aEleData: JsgEleData (optional) // bCopy: Boolean (optional); Default = true; true -> store a copy of aEleData else store aEleData itself // returns area (JsgGraphEle) // // Creates an area from 3 points and appends it to the display list. // // If aEleData is provided, a copy of that is created filling all not provided data with defaults, // and all created areas point to that element data. // Otherwise they point to this.DefaultEleData. return this.AddAreasFromVectGrid( [[ P1, P3 ],[ P2, null ]], aEleData, bCopy ); } JsGraph3D.prototype.AddRect = function( P1, P2, P3, P4, aEleData, bCopy ) { // P1, P2, P3, P4: JsgVect3 // aEleData: JsgEleData (optional) // bCopy: Boolean (optional); Default = true; true -> store a copy of aEleData else store aEleData itself // returns area (JsgGraphEle) // // Creates an area from 4 points and appends it to the display list. // // If aEleData is provided, a copy of that is created filling all not provided data with defaults, // and all created areas point to that element data. // Otherwise they point to this.DefaultEleData. return this.AddAreasFromVectGrid( [[ P1, P4 ],[ P2, P3 ]], aEleData, bCopy ); } JsGraph3D.prototype.AddAreasFromVectGrid = function( aVectGrid, aEleData, bCopy ) { // aVectGrid: Array[][] of JsgVect3 // aEleData: JsgEleData (optional) // bCopy: Boolean (optional); Default = true; true -> store a copy of aEleData else store aEleData itself // returns last area (JsgGraphEle) // // Creates areas from aVectGrid and appends them to the display list. // // If aEleData is provided, a copy of that is created filling all not provided data with defaults, // and all created areas point to that element data. // Otherwise they point to this.DefaultEleData. // // Note: some Points in aVectGrid may be null. That means, // some area will have only 3 Points. If for an area more than 2 points are // null, that area will not be stored in the display list! var dl = this.DisplayList; var pointList = dl.PointList; var areaList = dl.GraphEleList; var pointListOffset = pointList.length; var eleData = this.AddOrGetDefaultEleData( aEleData, bCopy ); var ilen = aVectGrid.length; var ixGrid = []; var ix = pointListOffset; // store aVectGrid in a linear list and create a visiGrid with indexes into this list for (var i = 0; i < ilen; i++) { var ixLine = []; var vectList = aVectGrid[i], jlen = vectList.length; for (var j = 0; j < jlen; j++) { if (vectList[j]) { pointList.push( vectList[j] ); ixLine.push( ix++ ); } else { ixLine.push( -1 ); } } ixGrid.push( ixLine ); } // create area list var lastArea = null; var ilen = aVectGrid.length, imax = ilen-1; for (var i = 0; i < imax; i++) { var jlen = aVectGrid[i].length, jmax = jlen-1; for (var j = 0; j < jmax; j++) { var pointIxList = []; ix = ixGrid[i][j]; if (ix >= 0) { pointIxList.push( ix ); } ix = ixGrid[i+1][j]; if (ix >= 0) { pointIxList.push( ix ); } ix = ixGrid[i+1][j+1]; if (ix >= 0) { pointIxList.push( ix ); } ix = ixGrid[i][j+1]; if (ix >= 0) { pointIxList.push( ix ); } if (pointIxList.length > 2) { lastArea = new JsgGraphEle( pointIxList, eleData ); areaList.push( lastArea ); } } } this.DisplayListUpdated(); return lastArea; } JsGraph3D.prototype.AddAreasFromVectInterGrid = function( aVectInterGrid, aEleData, bCopy ) { // aVectInterGrid: Array[][] of JsgVect3 // aEleData: JsgEleData (optional) // bCopy: Boolean (optional); Default = true; true -> store a copy of aEleData else store aEleData itself // // Creates areas from aVectInterGrid and appends them to the display list. // // If aEleData is provided, a copy of that is created filling all not provided data with defaults, // and all created areas point to that element data. // Otherwise they point to this.DefaultEleData. // // Require: No Points in aVectInterGrid may be null. if (aVectInterGrid.length === 0) return; if (aVectInterGrid[0].length === 0) return; var dl = this.DisplayList; var pointList = dl.PointList; var areaList = dl.GraphEleList; var pointListOffset = pointList.length; var eleData = this.AddOrGetDefaultEleData( aEleData, bCopy ); var ilen = aVectInterGrid.length; var ixInterGrid = []; var ix = pointListOffset; // store points of aVectInterGrid in DisplayLists PointList and // create a ixInterGrid with references to the points in the PointList for (var i = 0; i < ilen; i++) { var ixLine = []; var vectList = aVectInterGrid[i] var jlen = vectList.length; for (var j = 0; j < jlen; j++) { pointList.push( vectList[j] ); ixLine.push( ix++ ); } ixInterGrid.push( ixLine ); } // create area list var imax = ilen - 1; var jlen = aVectInterGrid[0].length; var jmax = jlen - 1; for (var i = 0; i < imax; i += 2) { for (var j = 0; j < jmax; j++) { var pointIxList = []; pointIxList.push( ixInterGrid[i][j] ); pointIxList.push( ixInterGrid[i+2][j] ); pointIxList.push( ixInterGrid[i+1][j] ); if (!this.IsNullArea( pointIxList, pointList )) { areaList.push( new JsgGraphEle( pointIxList, eleData ) ); } var pointIxList = []; pointIxList.push( ixInterGrid[i+2][j] ); pointIxList.push( ixInterGrid[i+2][j+1] ); pointIxList.push( ixInterGrid[i+1][j] ); if (!this.IsNullArea( pointIxList, pointList )) { areaList.push( new JsgGraphEle( pointIxList, eleData ) ); } var pointIxList = []; pointIxList.push( ixInterGrid[i+2][j+1] ); pointIxList.push( ixInterGrid[i][j+1] ); pointIxList.push( ixInterGrid[i+1][j] ); if (!this.IsNullArea( pointIxList, pointList )) { areaList.push( new JsgGraphEle( pointIxList, eleData ) ); } var pointIxList = []; pointIxList.push( ixInterGrid[i][j+1] ); pointIxList.push( ixInterGrid[i][j] ); pointIxList.push( ixInterGrid[i+1][j] ); if (!this.IsNullArea( pointIxList, pointList )) { areaList.push( new JsgGraphEle( pointIxList, eleData ) ); } } } this.DisplayListUpdated(); } JsGraph3D.prototype.IsNullArea = function( aPointIxList, aPointList ) { // require aPointList.length == 3 var b = aPointList[aPointIxList[0]]; var v1 = JsgVect3.Sub( aPointList[aPointIxList[1]], b ); var v2 = JsgVect3.Sub( aPointList[aPointIxList[2]], b ); var c = JsgVect3.Mult( v1, v2 ); return JsgVect3.Length2( c ) == 0; } JsGraph3D.prototype.AddPointsFromVectGrid = function( aVectGrid, aEleData, bCopy ) { // aVectGrid: array[][] of JsgVect3 // aEleData: JsgEleData (optional) // bCopy: Boolean (optional); Default = true; true -> store a copy of aEleData else store aEleData itself // // Creates points from aVectGrid and appends them to the display list. // // If aEleData is provided, a copy of that is created filling all not provided data with defaults, // and all created points are asigned with references to that element data. // Otherwise they refer to this.DefaultEleData. // // Note: some Points in aVectGrid may be null. These points are not stored in the display list. var dl = this.DisplayList; var pointList = dl.PointList; var eleList = dl.GraphEleList; var pointListOffset = pointList.length; var eleData = this.AddOrGetDefaultEleData( aEleData, bCopy ); var ilen = aVectGrid.length; var ix = pointListOffset; for (var i = 0; i < ilen; i++) { var vectList = aVectGrid[i], jlen = vectList.length; for (var j = 0; j < jlen; j++) { var pnt = vectList[j]; if (pnt) { pointList.push( pnt ); var pointIxList = [ ix++ ]; eleList.push( new JsgGraphEle( pointIxList, eleData ) ); } } } this.DisplayListUpdated(); } JsGraph3D.prototype.AddPointsFromVectInterGrid = function( aVectInterGrid, aEleData, bCopy ) { // aVectInterGrid: array[][] of JsgVect3 // aEleData: JsgEleData (optional) // bCopy: Boolean (optional); Default = true; true -> store a copy of aEleData else store aEleData itself // // Creates points from aVectInterGrid and appends them to the display list. // // If aEleData is provided, a copy of that is created filling all not provided data with defaults, // and all created points are asigned with references to that element data. // Otherwise they refer to this.DefaultEleData. // // Require: no Points in aVectInterGrid may be null! this.AddPointsFromVectGrid( aVectInterGrid, aEleData, bCopy ); } JsGraph3D.prototype.DisplayListUpdated = function() { // This function must be called, after a global attribute was changed that affects the // Drawing modes or after the display list was changed. It forces the DrawDisplayList // to recompute area center/normal vectors and to transform all point to the viewing plane. this.DisplayList.StaticDataReady = false; this.DisplayList.ViewDataReady = false; } JsGraph3D.prototype.DrawDisplayList = function( aBeforeDrawFunc, aAfterDrawFunc ) { // Draws all elements in the display list with there assosiated Data. // // Before drawing a graphics element, an installed callback (Data.AreaAttrFunc...Data.PointAttrFunc) // is called, in whitch the attributes of each individual element can be changed. E.g. colors of areas // can be set as a function of the Z-Position. In that manner colored curves can be produced. // // Call format: // aAttrFunc: function( this, aGraphEle, aEleAttr ) // aBeforDrawFunc, aAfterDrawFunc: function( this ) // // Use aBeforeDrawFunc and aAfterDrawFunc to asynchronously draw backgrounds, decorations or any additional graphic. if (xFuncOrNull(aBeforeDrawFunc)) this.BeforeDrawFunc = aBeforeDrawFunc; if (xFuncOrNull(aAfterDrawFunc)) this.AfterDrawFunc = aAfterDrawFunc; this.CompDLStaticData( ); this.CompDLViewData( ); this.Async.CallObj( this, this.DoDrawDisplayList, { AreaIx: 0, BeforeDrawFunc: this.BeforeDrawFunc, AfterDrawFunc: this.AfterDrawFunc } ); } //////////////////////////////////// // Transformation functions JsGraph3D.prototype.ResetTrans3D = function( clearStack ) { this.Trans3D = null; if (clearStack) { this.Trans3DStack = []; } return this; } JsGraph3D.prototype.SaveTrans3D = function( reset ) { // pushes a copy of 3D transformation onto a stack and returns a reference to this copy // the returned trans can be used in SetTrans() // if the current transformation is the unit transformation then null is returned // if reset == true then function ResetTrans3D() is called var transCopy = null; if (this.Trans3D) transCopy = JsgMat3.Copy( this.Trans3D ); this.Trans3DStack.push( transCopy ); if (reset) this.Trans3D = null; return transCopy; } JsGraph3D.prototype.RestoreTrans3D = function() { if (this.Trans3DStack.length > 0) this.Trans3D = this.Trans3DStack.pop(); } JsGraph3D.prototype.SetTrans3D = function( mat, useMat ) { // if useMat is false then a copy of mat is stored in this.Trans3D // mat: JsgMat3 or null if (mat) { if (useMat) { this.Trans3D = mat; } else { this.Trans3D = JsgMat3.Copy( mat ); } } else { this.Trans3D = null; } } JsGraph3D.prototype.TransMove3D = function( x, y, z ) { if (JsgVect3.Ok(x)) return this.TransMove3D( x[0], x[1], x[2] ); this.Trans3D = JsgMat3.Moving( x, y, z, this.Trans3D ); return this; } JsGraph3D.prototype.TransScale3D = function( sx, sy, sz ) { if (JsgVect3.Ok(sx)) return this.TransScale3D( sx[0], sx[1], sx[2] ); this.Trans3D = JsgMat3.Scaling( sx, sy, sz, this.Trans3D ); return this; } JsGraph3D.prototype.TransRotateX3D = function( ang ) { this.Trans3D = JsgMat3.RotatingX( this.AngleToRad(ang), this.Trans3D ); return this; } JsGraph3D.prototype.TransRotateY3D = function( ang ) { this.Trans3D = JsgMat3.RotatingY( this.AngleToRad(ang), this.Trans3D ); return this; } JsGraph3D.prototype.TransRotateZ3D = function( ang ) { this.Trans3D = JsgMat3.RotatingZ( this.AngleToRad(ang), this.Trans3D ); return this; } JsGraph3D.prototype.TransRotateVect3D = function( v, ang ) { // set rotation transformation to ang around vector v // ang in degrees or radians depending on AngleMeasure // compute longitude lambda and latitude phi var n = JsgVect3.Norm( [ v[0], v[1], 0 ] ); var lambda = Math.acos( JsgVect3.ScalarProd( n, [1,0,0] ) ); if (v[1] < 0) lambda *= -1; var vl = JsgVect3.Length( v ); var phi = 0; if (vl != 0) phi = Math.acos( v[2] / vl ); ang = this.AngleToRad(ang); var am = this.SetAngleMeasure( 'rad' ); // combine rotations this.TransRotateZ3D( -lambda ); this.TransRotateY3D( -phi ); this.TransRotateZ3D( ang ); this.TransRotateY3D( phi ); this.TransRotateZ3D( lambda ); this.SetAngleMeasure( am ); } JsGraph3D.prototype.AddTrans3D = function( mat ) { // mat: JsgMat3 if (this.Trans3D) { this.Trans3D = JsgMat3.Mult( this.Trans3D, mat ); } else { this.Trans3D = JsgMat3.Copy( mat ); } return this; } // private functions ---------------------------------------------------------------------- JsGraph3D.prototype.GetLightSourceVect = function( aLightSource, aAreaPos ) { if (aLightSource.LightPos) { return JsgVect3.Norm( JsgVect3.Sub( aLightSource.LightPos, aAreaPos ) ); } else { return aLightSource.LightDir; } } JsGraph3D.prototype.CompAreaPhongLighting = function( aLightSource, aArea, aAreaAttr, scalProdNV, areaLight, lightColorRet ) { // returns Color -> over all Intensity for a phong lighting. must be multiplied with area color // scalProdNV = scalar product of normal vector with view vector // areaLight = aArea.Data.Lighting or .BackLighting, depending on sign of scalProdNV var lVect = this.GetLightSourceVect( aLightSource, aArea.Center ); var nVect = JsgVect3.CopyTo( aArea.Normal, this.CompAreaPhongLightingNVect ); var rVect = JsgVect3.CopyTo( nVect, this.CompAreaPhongLightingRVect ); JsgVect3.ScaleTo( rVect, 2 * JsgVect3.ScalarProd( lVect, nVect ) ); JsgVect3.SubFrom( rVect, lVect ); JsgVect3.NormTo( rVect ); var vVect = JsgVect3.CopyTo( this.Camera.CamPos, this.CompAreaPhongLightingVVect ); JsgVect3.SubFrom( vVect, aArea.Center ); JsgVect3.NormTo( vVect ); // init for front or back face lighting var areaData = aArea.Data; if (scalProdNV < 0) { // change to back face lighting JsgVect3.ScaleTo( nVect, -1 ); } if (areaLight.ApplyPhong) { // diffuse reflection var sp = JsgVect3.ScalarProd( lVect, nVect ); if (sp < 0) { sp *= -areaLight.DiffXRay; } var diff = areaLight.DiffRefl * Math.pow( sp, areaLight.Roughness ) * aLightSource.DiffInt; // specular reflection sp = JsgVect3.ScalarProd( rVect, vVect ); if (sp < 0) { sp *= -areaLight.SpecXRay; } var spec = areaLight.SpecRefl * Math.pow( sp, areaLight.Shiny ) * aLightSource.SpecInt; JsgColor.CopyTo( aLightSource.LightColor, lightColorRet ); JsgColor.Scale( lightColorRet, areaLight.Dimm * (diff + spec) ); } else { JsgColor.SetBlack( lightColorRet ); } } JsGraph3D.prototype.CompAreaLSLighting = function( aArea, aAreaAttr, scalProdNV, areaLight, lightColorRet ) { // scalProdNV = scalar product of normal vector with view vector // areaLight = aArea.Data.Lighting or .BackLighting, depending on sign of scalProdNV // returns Color JsgColor.SetBlack( lightColorRet ); if (areaLight.ApplyPhong) { // process all phong light souces var lightList = this.Scene.LightSourceList; var llen = lightList.length; var newLightColor = JsgColor.SetBlack( this.CompAreaLSLightingColor ); for (var i = 0; i < llen; i++) { this.CompAreaPhongLighting( lightList[i], aArea, aAreaAttr, scalProdNV, areaLight, newLightColor ); JsgColor.Add( lightColorRet, newLightColor ); } } } JsGraph3D.prototype.CompAreaLighting = function( aArea, aAreaAttr, aScalProd, aAreaColorRet ) { // aScalProd = scalar product of vector cam to area center and area normal vector var scene = this.Scene; var areaData = aArea.Data; // check whether front or back face is visible var areaLight, areaColor; var sp = aScalProd; if (sp < 0) { sp = -sp; areaLight = areaData.BackLighting; areaColor = aAreaAttr.AreaBackColor; } else { areaLight = areaData.Lighting; areaColor = aAreaAttr.AreaColor; } // if no shader is selected return area color times aperture if (!(areaLight.ApplyCamLight || areaLight.ApplyAmbient || areaLight.ApplyPhong)) { JsgColor.CopyTo( areaColor, aAreaColorRet ); JsgColor.Scale( aAreaColorRet, this.Camera.Aperture ); return; } var lightColor = JsgColor.SetBlack( this.CompAreaLightingLightColor ); var computedColor = this.CompAreaLightingColor; // apply ambient lighting if (areaLight.ApplyAmbient) { JsgColor.CopyTo( scene.AmbientLight, computedColor ); JsgColor.Scale( computedColor, areaLight.AmbiRefl * areaLight.Dimm ); JsgColor.Add( lightColor, computedColor ); } // compute lighting from camera if (areaLight.ApplyCamLight) { JsgColor.CopyTo( scene.CamLight, computedColor ); JsgColor.Scale( computedColor, sp * areaLight.CamRefl * areaLight.Dimm ); JsgColor.Add( lightColor, computedColor ); } // compute lighting from all light sources if (areaLight.ApplyPhong && scene.LightSourceList.length > 0) { this.CompAreaLSLighting( aArea, aAreaAttr, aScalProd, areaLight, computedColor ); JsgColor.Add( lightColor, computedColor ); } JsgColor.CopyTo( areaColor, aAreaColorRet ); JsgColor.Scale( JsgColor.Mult( aAreaColorRet, lightColor ), this.Camera.Aperture ); // color post processing if (areaLight.BlackAndWhite) { var mean = (aAreaColorRet[0] + aAreaColorRet[1] + aAreaColorRet[2]) / 3; JsgColor.SetBW( aAreaColorRet, mean ); } if (areaLight.ColorResolution > 0) { var cr = areaLight.ColorResolution; aAreaColorRet[0] = Math.floor( aAreaColorRet[0] * cr + 0.5 ) / cr; aAreaColorRet[1] = Math.floor( aAreaColorRet[1] * cr + 0.5 ) / cr; aAreaColorRet[2] = Math.floor( aAreaColorRet[2] * cr + 0.5 ) / cr; } } JsGraph3D.prototype.CompDLStaticData = function( ) { if (this.DisplayList.StaticDataReady) return; this.Async.CallObj( this, this.CompEleListCenter, { AreaIx: 0, ListLength: this.DisplayList.GraphEleList.length }, function CB_ShowMessage(p) { this.ShowMessage( 'Computing Element Centers', p.AreaIx, p.ListLength ); } ); this.Async.CallObj( this, this.CompAreaListNormal, { AreaIx: 0, ListLength: this.DisplayList.GraphEleList.length }, function CB_ShowMessage(p) { this.ShowMessage( 'Computing Area Normal Vectors', p.AreaIx, p.ListLength ); } ); this.Async.CallObj( this, this.DLStaticDataIsReady, { } ); } JsGraph3D.prototype.DLStaticDataIsReady = function( ) { //console.log( 'JsGraph3D.DLStaticDataIsReady' ); this.DisplayList.StaticDataReady = true; } JsGraph3D.prototype.CompDLViewData = function( ) { if (this.DisplayList.ViewDataReady) return; this.Async.CallObj( this, this.CompDLCenterToCam, { }, function CB_ShowMessage() { this.ShowMessage('Computing Element Distances to Camera'); } ); this.Async.CallObj( this, this.SortDisplayList, { }, function CB_ShowMessage() { this.ShowMessage('Sorting Elements by Distance to Camera'); } ); this.Async.CallObj( this, this.DLViewDataIsReady, { } ); } JsGraph3D.prototype.DLViewDataIsReady = function( ) { //console.log( 'JsGraph3D.DLViewDataIsReady' ); this.DisplayList.ViewDataReady = true; } JsGraph3D.prototype.DoDrawDisplayList = function( aParam ) { //console.log( 'JsGraph3D.DoDrawDisplayList: ', aParam.AreaIx ); var startIx = aParam.AreaIx; var pointList = this.DisplayList.PointList; var eleList = this.DisplayList.GraphEleList; var elen = eleList.length; this.Async.StartTimer(); var vCenterToCam; this.BeginDrawing(); if (startIx == 0) { vCenterToCam = [0,0,0]; this.Clear(); if (this.Camera.HighResolution != '') { var onOff = (this.Camera.HighResolution == 'on'); this.SetHighResolution( onOff ); } if (aParam.BeforeDrawFunc) { aParam.BeforeDrawFunc( this ); } } else { vCenterToCam = aParam.VCenterToCam; } var attr = this.WorkingAttr; var computedAreaColor = this.ComputedAreaColor; var computedLineColor = computedAreaColor; var computedLineWidth = 0; var computedLineAlpha = 1; var poly = this.Poly3D; for (var i = startIx; i < elen; i++) { var graphEle = eleList[i]; var eleData = graphEle.Data; attr.CopyFrom( eleData.Attr ); var pointIxList = graphEle.PointIxList, plen = pointIxList.length; if (plen > 2) { // draw area var skip = false; // skip backface areas JsgVect3.CopyTo( this.Camera.CamPos, vCenterToCam ); JsgVect3.SubFrom( vCenterToCam, graphEle.Center ); JsgVect3.NormTo( vCenterToCam ); var sp = JsgVect3.ScalarProd( vCenterToCam, graphEle.Normal ); var isBackFace = (sp <= 0); if (!eleData.DrawBackface) skip = isBackFace; if (!skip) { // let callback change graphEle attr if (eleData.AreaAttrFunc) { eleData.AreaAttrFunc( this, graphEle, attr, sp ); } if (eleData.AreaDrawMode & 2) { // fill area // compute lighting (preserve alpha channel) var alpha = isBackFace ? JsgColor.Alpha(attr.AreaBackColor) : JsgColor.Alpha(attr.AreaColor); skip = (alpha < this.MinAlpha); if (!skip) { this.CompAreaLighting( graphEle, attr, sp, computedAreaColor ); JsgColor.SetAlpha( computedAreaColor, alpha ); if (!(eleData.AreaDrawMode & 1)) { // fill area without border: // alpha of area border line must be smaller then alpha of area // because borders of different areas overlap computedLineColor = computedAreaColor; computedLineAlpha = Math.pow( alpha, 4 ); computedLineWidth = 0; // handle ContourMode: draw back faced areas with thick dark border if (eleData.ContourMode) { if (Math.abs(sp) <= eleData.ContourSens) { JsgColor.CopyTo( eleData.ContourColor, computedLineColor ); computedLineWidth = eleData.ContourWidth; } } } else { computedLineColor = isBackFace ? attr.AreaBackLineColor : attr.LineColor; computedLineAlpha = JsgColor.Alpha(computedLineColor); computedLineWidth = attr.LineWidth; // fill area with border skip = (JsgColor.Alpha(computedAreaColor) < this.MinAlpha && computedLineAlpha < this.MinAlpha); } } // end (!skip) } else { computedLineColor = isBackFace ? attr.AreaBackLineColor : attr.LineColor; computedLineAlpha = JsgColor.Alpha(computedLineColor); computedLineWidth = attr.LineWidth; // draw area border only skip = (computedLineAlpha < this.MinAlpha); } } if (!skip) { if (eleData.AreaDrawMode & 2) { this.SetBgColor( JsgColor.ToString(computedAreaColor) ); } // note: always draw line JsgColor.SetAlpha( computedLineColor, computedLineAlpha ); this.SetColor( JsgColor.ToString(computedLineColor) ); this.SetLineWidth( computedLineWidth ); // draw graphEle poly.Reset(); for (var p = 0; p < plen; p++) { poly.AddPoint3D( pointList[pointIxList[p]] ); } var dmode = eleData.AreaDrawMode | 5; // 5 -> close path and draw line this.FastClosedPolygon3D( poly, dmode ); } } else if (plen == 2) { // draw line // let callback change graphEle attr if (eleData.LineAttrFunc) { eleData.LineAttrFunc( this, graphEle, attr ); } var skip = (JsgColor.Alpha(attr.LineColor) < this.MinAlpha); if (!skip) { this.SetLineAttr( JsgColor.ToString(attr.LineColor), attr.LineWidth ); this.FastLine3D( pointList[pointIxList[0]], pointList[pointIxList[1]] ); } } else { // draw point // let callback change graphEle attr if (eleData.PointAttrFunc) { eleData.PointAttrFunc( this, graphEle, attr ); } var skip = (JsgColor.Alpha(attr.AreaColor) < this.MinAlpha && JsgColor.Alpha(attr.LineColor) < this.MinAlpha); if (!skip) { this.SetMarkerAttr( attr.Symbol, attr.SymbSize, JsgColor.ToString(attr.LineColor), JsgColor.ToString(attr.AreaColor), attr.LineWidth ); this.FastMarker3D( pointList[pointIxList[0]], eleData.PointDrawMode ); } } if (this.Async.IsTimerExpired(this.UpdateInterval)) { this.EndDrawing(); aParam.VCenterToCam = vCenterToCam; aParam.AreaIx = i + 1; return true; } } if (aParam.AfterDrawFunc) { aParam.AfterDrawFunc( this ); } this.EndDrawing(); return false; } JsGraph3D.prototype.CompEleListCenter = function( aParam ) { // compute center position for areas, lines and points -> this.DisplayList.GraphEleList[i].Center //console.log( 'JsGraph3D.CompEleListCenter: ', aParam.AreaIx ); var eleList = this.DisplayList.GraphEleList, elen = eleList.length; var pointList = this.DisplayList.PointList; var startIx = aParam.AreaIx; this.Async.StartTimer(); for (var i = startIx; i < elen; i++) { var graphEle = eleList[i]; var pointIxList = graphEle.PointIxList, plen = pointIxList.length; if (plen >= 2) { if (graphEle.Center) { JsgVect3.Reset( graphEle.Center ); } else { graphEle.Center = JsgVect3.Null(); } for (var p = 0; p < plen; p++) { JsgVect3.AddTo( graphEle.Center, pointList[pointIxList[p]] ); } JsgVect3.ScaleTo( graphEle.Center, 1 / plen ); } else if (plen == 1) { graphEle.Center = pointList[pointIxList[0]]; } if (this.Async.IsTimerExpired(this.UpdateInterval)) { aParam.AreaIx = i + 1; return true; } } return false; } JsGraph3D.prototype.CompAreaListNormal = function( aParam ) { // compute normal vectors for areas -> this.DisplayList.GraphEleList[i].Normal //console.log( 'JsGraph3D.CompAreaListNormal: ', aParam.AreaIx ); var eleList = this.DisplayList.GraphEleList, elen = eleList.length; var pointList = this.DisplayList.PointList; var startIx = aParam.AreaIx; var normal = JsgVect3.Null(); var v1 = JsgVect3.Null(); var v2 = JsgVect3.Null(); this.Async.StartTimer(); for (var i = startIx; i < elen; i++) { var graphEle = eleList[i]; var pointIxList = graphEle.PointIxList; var plen = pointIxList.length; var eleData = graphEle.Data; if (plen == 3) { // handle triangles var center = graphEle.Center; if (!graphEle.Normal) graphEle.Normal = JsgVect3.Null(); JsgVect3.CopyTo( pointList[pointIxList[plen-1]], v1 ); JsgVect3.SubFrom( v1, center ); JsgVect3.CopyTo( pointList[pointIxList[0]], v2 ); JsgVect3.SubFrom( v2, center ); JsgVect3.MultTo( graphEle.Normal, v2, v1 ); JsgVect3.NormTo( graphEle.Normal ); } else if (plen > 3) { // compute mean normals for areas with more than 3 edges var pp = plen-1; var center = graphEle.Center; JsgVect3.CopyTo( pointList[pointIxList[pp]], v1 ); JsgVect3.SubFrom( v1, center ); if (!graphEle.Normal) { graphEle.Normal = JsgVect3.Null(); } else { JsgVect3.Reset( graphEle.Normal ); } for (var p = 0; p < plen; p++) { JsgVect3.CopyTo( pointList[pointIxList[p]], v2 ); JsgVect3.SubFrom( v2, center ); JsgVect3.MultTo( normal, v2, v1 ); JsgVect3.NormTo( normal ); JsgVect3.AddTo( graphEle.Normal, normal ); JsgVect3.CopyTo( v2, v1 ); pp = p; } JsgVect3.NormTo( graphEle.Normal ); } if (this.Async.IsTimerExpired(this.UpdateInterval)) { aParam.AreaIx = i + 1; return true; } } return false; } JsGraph3D.prototype.CompDLCenterToCam = function( ) { // compute the distance squared from each element center to the current camera position // requires CompEleListCenter is called to compute the elements center var eleList = this.DisplayList.GraphEleList, elen = eleList.length; var camPos = this.Camera.CamPos; var vect = this.WorkPoint3D; for (var i = 0; i < elen; i++) { var graphEle = eleList[i]; JsgVect3.CopyTo( graphEle.Center, vect ); JsgVect3.SubFrom( vect, camPos ); graphEle.CamDist2 = JsgVect3.Length2( vect ); } } JsGraph3D.prototype.SortDisplayList = function( ) { // requires CompDLCenterToCam() is called to compute the CamDist2 of all elements this.DisplayList.GraphEleList.sort( function CB_Compare_CamDist( a, b ) { var aa = a.CamDist2, bb = b.CamDist2; return (aa < bb) ? 1 : (aa > bb) ? -1 : 0; } ); } JsGraph3D.prototype.ShowMessage = function( aText, aCurrent, aMax ) { if (!this.ShowProgress) { return; } var text = aText; if (xNum(aCurrent) && xNum(aMax)) { var procent = (aCurrent * 100 / aMax).toFixed(0); text += ': ' + procent + '% (' + aCurrent.toFixed(0) + ' of ' + aMax.toFixed(0) + ')'; } this.Clear(); var oldTrans = this.SelectTrans( 'viewport' ); this.SetTextAttr( 'Arial', 15, 'black', 'normal', 'normal', 'center', 'middle' ); var vpCenterX = this.VpXmin + 0.5 * this.VpWidth; var vpCenterY = this.VpYmin + 0.5 * this.VpHeight; this.Text( text, vpCenterX, vpCenterY ); this.SelectTrans( oldTrans ); } /////////////////////////////////////// // 3D variants of JsGraph 2D functions JsGraph3D.prototype.NewPoly3D = function( applyTrans ) { this.ApplyTransToPoly3D = xDefBool( applyTrans, false ); this.Poly3D.Reset(); return this; } JsGraph3D.prototype.CopyPoly3D = function( to, reuseArrays ) { // returns a copy of this.Poly3D: { X: array(Size) of number, Y: array(Size) of number, Size: integer } reuseArrays = xDefBool( reuseArrays, false ); return this.Poly3D.Copy( to, !reuseArrays ); } JsGraph3D.prototype.AddPointToPoly3D = function( x, y, z ) { // or AddPointToPoly( pt: JsgVect3 ) if (JsgVect3.Ok(x)) { if (this.ApplyTransToPoly3D) { this.Poly3D.AddPoint3D( this.TransPoint3D( x[0], x[1], x[2] ) ); } else { this.Poly3D.AddPoint3D( x ); } } else { if (this.ApplyTransToPoly3D) { this.Poly3D.AddPoint3D( this.TransPoint3D( x, y, z ) ); } else { this.Poly3D.AddPoint( x, y, z ); } } return this; } JsGraph3D.prototype.DrawPoly3D = function( mode, roundedEdges ) { // mode: 1 -> draw border, 2 -> fill, 3 -> border and fill, default = 1 // mode & 4 -> close polygon by drawing a line from last to first point of polygon // mode & 8 -> continue path // mode & 16 -> inverse Polygon Poly3D before drawing mode = xDefNum( mode, 1 ); if (mode & 16) this.Poly3D.Invert(); this.Polygon3D( this.Poly3D, mode, roundedEdges ); } JsGraph3D.prototype.MoveTo3D = function( x, y, z ) { // or MoveTo3D( pos: JsgVect3 ) if (JsgVect3.Ok(x)) return this.MoveTo3D( x[0], x[1], x[2] ); JsgVect3.Set( this.LastPos3D, x, y, z ); return this; } JsGraph3D.prototype.LineTo3D = function( x, y, z ) { // or LineTo3D( pos: JsgVect3 ) if (JsgVect3.Ok(x)) { return this.LineTo3D( x[0], x[1], x[2] ); } this.WorkPoly3D.Reset(); this.WorkPoly3D.AddPoint3D( this.LastPos3D ); this.WorkPoly3D.AddPoint( x, y, z ); this.TransClipPolygon3D( this.WorkPoly3D, 1 ); this.WorkPoly3D.GetLastPoint3D( this.LastPos3D ); return this; } JsGraph3D.prototype.Line3D = function( x1, y1, z1, x2, y2, z2, append ) { // or Line3D( p1, p2: JsgVect3, append ) if (JsgVect3.Ok(x1)) { return this.Line3D( x1[0], x1[1], x1[2], y1[0], y1[1], y1[2], z1 ); } var mode = 1; if (append) mode += 8; this.WorkPoly3D.Reset(); this.WorkPoly3D.AddPoint( x1, y1, z1 ); this.WorkPoly3D.AddPoint( x2, y2, z2 ); this.TransClipPolygon3D( this.WorkPoly3D, mode ); this.WorkPoly3D.GetLastPoint3D( this.LastPos3D ); return this; } JsGraph3D.prototype.FastLine3D = function( p1, p2 ) { // private function this.WorkPoly3D.Reset(); this.WorkPoly3D.AddPoint3D( p1 ); this.WorkPoly3D.AddPoint3D( p2 ); this.TransClipPolygon3D( this.WorkPoly3D, 1 ); } JsGraph3D.prototype.VectList3D = function( vectList, mode, roundedEdges ) { // stores vectList in this.Poly3D via VectListToPoly3D() // and draws it using DrawPoly3D( mode, roundedEdges ) // // vectList: JsgVect3List this.VectListToPoly3D( vectList ); this.DrawPoly3D( mode, roundedEdges ); } JsGraph3D.prototype.Polygon3D = function( poly, mode, roundedEdges ) { // poly: JsgPolygon mode = xDefNum( mode, 1 ); var didClose = false; if ((mode & 4) > 0) { didClose = poly.Close(); } roundedEdges = xDefBool( roundedEdges, false ); if (roundedEdges && !this.IsPath3DOpen) { var oldJoin = this.LineJoin; var oldCap = this.LineCap; this.SetLineJoin( 'round' ); this.SetLineCap( 'round' ); this.TransClipPolygon3D( poly, mode & ~4 ); this.SetLineJoin( oldJoin ); this.SetLineCap( oldCap ); } else { this.TransClipPolygon3D( poly, mode & ~4 ); } poly.GetLastPoint3D( this.LastPos3D ); if (didClose) { poly.RemoveLastPoint(); } } JsGraph3D.prototype.FastClosedPolygon3D = function( poly, mode ) { // private function // poly: JsgPolygon poly.Close(); this.TransClipPolygon3D( poly, mode & ~4 ); } JsGraph3D.prototype.PolygonList3D = function( polys, mode, roundedEdges ) { for (var i = 0; i < polys.Size; i++) { this.Polygon3D( polys.PolyList[i], mode, roundedEdges ); } } JsGraph3D.prototype.VectListToPoly3D = function( vectList, newPoly ) { // Stores vectList in this.Poly3D // vectList: JsgVect3List (array of [x,y,z]) // newPoly: true -> clears this.Poly3D (default) // set newPoly = false, if you want to append vectList to Poly3D // call DrawPoly3D or DrawPolyMarker3D to draw the polygon newPoly = xDefBool( newPoly, true ); if (newPoly) { this.NewPoly3D(); } var size = vectList.length; for (var i = 0; i < size; i++) { var v = vectList[i]; this.AddPointToPoly3D( v[0], v[1], v[2] ); } } JsGraph3D.prototype.Arrow3D = function( x1, y1, z1, x2, y2, z2, variant, mode, sym1, sym2 ) { // or Arrow3D( p1, p2, variant, mode ) // if an arrow point is outside clip range then that arrow symbol is not drawn // Requires this.IsPath3DOpen = false if (JsgVect3.Ok(x1)) { return this.Arrow3D( x1[0], x1[1], x1[2], y1[0], y1[1], y1[2], z1, x2, y2, z2 ); } var poly = this.WorkPoly3D.Reset(); poly.AddPoint( x1, y1, z1 ); poly.AddPoint( x2, y2, z2 ); poly.GetLastPoint3D( this.LastPos3D ); // check which points are clipped variant = xDefNum( variant, 1 ); if (variant & 1) { // last point if (!this.TransClipPoint3D(poly.X[1],poly.Y[1],poly.Z[1])) variant &= ~1; } if (variant & 2) { // fist point if (!this.TransClipPoint3D(poly.X[0],poly.Y[0],poly.Z[0])) variant &= ~2; } // clip and trans line var transPoly = this.TransClipPolygon3D( poly, 1, false, true ); if (transPoly.IsEmpty()) return this; if (JsgPolygonList.Ok(transPoly)) transPoly = transPoly.GetFirstPoly(); // draw arrow or line if ((variant & 3) > 0) { this.Arrow( transPoly.X[0], transPoly.Y[0], transPoly.X[1], transPoly.Y[1], variant, mode, sym1, sym2 ); } else { this.Line( transPoly.X[0], transPoly.Y[0], transPoly.X[1], transPoly.Y[1] ); } JsgVect3.Set( this.LastPos3D, x2, y2, z2 ); return this; } JsGraph3D.prototype.PolygonArrow3D = function( poly, variant, lineMode, arrowMode, sym1, sym2 ) { // poly: JsgPolygon(3D) // if an arrow point is outside clip range then that arrow symbol is not drawn // Requires this.IsPath3DOpen = false if (poly.Size < 2) return this; // draw arrow line if ((variant & 4) == 0) { this.Polygon3D( poly, lineMode ); } // check which points are clipped var last = poly.Size-1; if (variant & 1) { // last point if (!this.TransClipPoint3D(poly.X[last],poly.Y[last],poly.Z[last])) variant &= ~1; } if (variant & 2) { // fist point if (!this.TransClipPoint3D(poly.X[0],poly.Y[0],poly.Z[0])) variant &= ~2; } // draw arrows variant |= 4; // don't draw line again if ((variant & 2) > 0) { // first point this.Arrow3D( poly.X[0], poly.Y[0], poly.Z[0], poly.X[1], poly.Y[1], poly.Z[1], variant & ~1, arrowMode, sym1 ); } if ((variant & 1) > 0) { // last point var prev = last - 1; this.Arrow3D( poly.X[prev], poly.Y[prev], poly.Z[prev], poly.X[last], poly.Y[last], poly.Z[last], variant & ~2, arrowMode, sym2 ); } poly.GetLastPoint3D( this.LastPos3D ); return this; } JsGraph3D.prototype.Text3D = function( aText, p, widthOrMode ) { var p = this.TransClipPoint3D( p[0], p[1], p[2] ); if (p) { this.Text( aText, p, widthOrMode ); } return this; } JsGraph3D.prototype.GetTextBox3D = function( aText, p, width ) { // returns JsGraph.WorkRect: JsgRect or null if text is clipped var p = this.TransClipPoint3D( p[0], p[1], p[2] ); if (!p) return null; return this.GetTextBox( aText, p, width ); } JsGraph3D.prototype.DrawPolyMarker3D = function( mode, mat ) { // Draws this.Poly3D as Markers // mode: 1 -> draw border, 2 -> fill, 3 -> border and fill, default = 1 this.Marker3D( this.Poly3D, mode, mat ); } JsGraph3D.prototype.Marker3D = function( x, y, z, mode, mat ) { // or Marker3D( JsgVect3, mode, mat ) // or Marker3D( JsgPolygon, mode, mat ) if (JsgPolygon.Ok(x)) { var polyClipped = this.TransClipPointPoly3D( x ); this.Marker( polyClipped, y, z ); return this; } if (JsgVect3.Ok(x)) return this.Marker3D( x[0], x[1], x[2], y, z ); var p = this.TransClipPoint3D( x, y, z ); if (p) { this.Marker( p, mode, mat ); } return this; } JsGraph3D.prototype.FastMarker3D = function( p, mode ) { // private function // p: JsgVect3 var p = this.TransClipPoint3D( p[0], p[1], p[2] ); if (p) { this.Marker( p, mode ); } } /////////////////////// // Path3D JsGraph3D.prototype.OpenPath3D = function( applyTrans ) { this.ApplyTransToPath3D = xDefBool( applyTrans, false ); this.PathPolys3D.Reset(); this.IsPath3DOpen = true; } JsGraph3D.prototype.ClearPath3D = function() { this.PathPolys3D.Reset(); this.IsPath3DOpen = false; } JsGraph3D.prototype.Path3D = function( mode, clear ) { mode = xDefNum( mode, 1 ); clear = xDefBool( clear, true ); var didClose = false; if ((mode & 4) > 0) { didClose = this.PathPolys3D.Close(); } this.TransClipPolygon3D( this.PathPolys3D, mode & ~4, true, true ); if (clear) { this.ClearPath3D(); } else { if (didClose) { this.PathPolys3D.RemoveLastPoint(); } } } /////////////////////// // Clip and transform JsGraph3D.prototype.TransClipPolygon3D = function( polys, mode, draw, bypassPath ) { // private function // polys: JsgPolygon or JsgPolygonList // returns JsgPolygonList: either this.PathPolys3D or this.XfmPolys3D or this.CamPolys3D bypassPath = xDefBool( bypassPath, false ); draw = xDefBool( draw, true ); if (this.IsPath3DOpen && !bypassPath) { var appendMode = ((mode & 8) > 0) ? 2 : 1; if (this.ApplyTransToPath3D) { this.PathPolys3D.AddPoly( this.TransPolys3D( polys ), appendMode ); } else { this.PathPolys3D.AddPoly( polys, appendMode ); } return this.PathPolys3D; } var transformedPolys = this.TransPolys3D( polys ); var polysRet = transformedPolys; if ((mode & 2) > 0) { // clip, trans and draw area var clippedPolys = this.ClipPolygon3D( transformedPolys, true ); var cameraPolys = this.CameraTrans3D( clippedPolys ); if (draw) { this.DrawPolygonList3D( cameraPolys, mode & ~1 ); } polysRet = cameraPolys; } if ((mode & 1) > 0) { // clip, trans and draw contour var clippedPolys = this.ClipPolygon3D( transformedPolys, false ); var cameraPolys = this.CameraTrans3D( clippedPolys ); if (draw) { this.DrawPolygonList3D( cameraPolys, mode & ~(2+4) ); } polysRet = cameraPolys; } return polysRet; } JsGraph3D.prototype.TransClipPointPoly3D = function( poly, polyRet ) { // transforms and clips poly. // polyRet will contain only transformed points from poly which are no clipped // poly: JsgPolygon // polyRet: JsgPolygon // returns JsgPolygon: polyRet or this.WorkPoly3D polyRet = polyRet || this.WorkPoly3D; polyRet.Reset(); var xs = poly.X; var ys = poly.Y; var zs = poly.Z; var len = poly.Size; for (var i = 0; i < len; i++) { var p = this.TransClipPoint3D( xs[i], ys[i], z[i] ); if (p) { polyRet.AddPoint3D( p ); } } } JsGraph3D.prototype.TransPolys3D = function( polys, reset ) { // polys: JsgPolygon or JsgPolygonList // returns this.XfmPolys3D or polys if no transformation is defined // if reset = true (default) XfmPolys3D is reset // this function is called recusively if polys is of type JsgPolygonList for each polygon in the list // all transformed polygons are stored in XfmPolys3D: JsgPolygonList if (!this.Trans3D) return polys; reset = xDefBool( reset, true ); var polysRet = this.XfmPolys3D; if (reset) { polysRet.Reset(); } if (JsgPolygonList.Ok(polys)) { for (var i = 0; i < polys.Size; i++) { this.TransPolys3D( polys.PolyList[i], false ); } } else { polysRet.NewPoly(); var polyTrans = polysRet.GetLastPoly(); var mat = this.Trans3D; var xs = polys.X, ys = polys.Y, zs = polys.Z, p = this.WorkPoint3D; var n = polys.Size; for (var i = 0; i < n; i++) { JsgMat3.TransXYZTo( mat, xs[i], ys[i], zs[i], p ); polyTrans.AddPoint3D( p ); } } return polysRet; } JsGraph3D.prototype.TransClipPoint3D = function( x, y, z ) { // or TransClipPoint3D( JsgVect3 ) // applies 3D-Transformation and camera transformation // if point is outside one of the clipping ranges, then null is returned // returns this.Camera.WorkPoint or null if (JsgVect3.Ok(x)) return this.TransClipPoint3D( x[0], x[1], x[2] ); var p = this.TransPoint3D( x, y, z ); if (!this.IsPointInsideClipRange3D( p[0], p[1], p[2] )) return null; return this.Camera.TransTo( p[0], p[1], p[2] ); } JsGraph3D.prototype.VTransPoint3D = function( x, y, z ) { // or VTransPoint3D( JsgVect3 ) // applies 3D-Transformation and camera transformation // see also TransPoint3D() // returns this.Camera.WorkPoint var p; if (xNum(x)) { p = this.TransPoint3D( x, y, z ); } else { p = this.TransPoint3D( x[0], x[1], x[2] ); } return this.Camera.TransTo( p[0], p[1], p[2] ); } JsGraph3D.prototype.TransPoint3D = function( x, y, z ) { // or TransPoint3D( JsgVect3 ) // only applies 3D-Transformation (if defined), but not camera transformation // see also VTransPoint3D() // returns this.WorkPoint3D if (JsgVect3.Ok(x)) return this.TransPoint3D( x[0], x[1], x[2] ); var p = JsgVect3.Set( this.WorkPoint3D, x, y, z ); if (this.Trans3D) { JsgMat3.TransTo( this.Trans3D, p ); } return p; } JsGraph3D.prototype.DrawPolygonList3D = function( polys, mode ) { // private function if (JsgPolygonList.Ok(polys)) { var n = polys.Size; for (var i = 0; i < n; i++) { this.Polygon( polys.PolyList[i], mode ); } } else { this.Polygon( polys, mode ); } } JsGraph3D.prototype.CameraTrans3D = function( polys ) { // private function // polys: JsgPolygon or JsgPolygonList // returns this.CamPolys3D var transPolys = this.CamPolys3D.Reset(); if (JsgPolygonList.Ok(polys)) { var n = polys.Size; for (var i = 0; i < n; i++) { transPolys.NewPoly(); this.Camera.TransPoly( polys.PolyList[i], transPolys ); } } else { transPolys.NewPoly(); this.Camera.TransPoly( polys, transPolys ); } return transPolys; } JsGraph3D.prototype.ClipPolygon3D = function( polys, isArea ) { // private function // polys: JsgPolygon or JsgPolygonList // returns this.ClipPolys3D1 or this.ClipPolys3D2 var n = this.ClipPlaneListSize; if (n == 1 && this.ClipPlaneList[0] == null) { if (!isArea || JsgPolygon.Ok(polys)) { return polys; } else { return this.MergeAreaPolys( polys, this.ClipPolys3D1 ); } } var nextClipPolys = this.ClipPolys3D2; var currClipPolys = this.ClipPolys3D1; for (var i = 0; i < n; i++) { var clipPlane = this.ClipPlaneList[i]; if (clipPlane) { var clippedPolys = currClipPolys.Reset(); if (isArea) { clipPlane.ClipArea( polys, clippedPolys ); } else { clipPlane.ClipPoly( polys, clippedPolys ); } polys = clippedPolys; // swap temp clip polys var tmp = currClipPolys; currClipPolys = nextClipPolys; nextClipPolys = tmp; } } return polys; } JsGraph3D.prototype.MergeAreaPolys = function( polys, mergedPolys ) { // merge all polys into one polygon by closing all polys and // connecting them to the first point of first polygon // polys: JsgPolygonList // returns mergedPolys or polys.PolyList[0] if polys contains only one polygon // Note: a single returned polygon may be left open if (polys.Size == 0) return polys; if (polys.Size == 1) return polys.PolyList[0]; mergedPolys.Reset(); mergedPolys.NewPoly(); for (var i = 0; i < polys.Size; i++) { var poly = polys.PolyList[i]; var hasClosed = poly.Close(); mergedPolys.AddPoly( poly, 2 ); // append mode 2 if (i > 0) { // connect last poly with first poly mergedPolys.Close(); } if (hasClosed) { poly.RemoveLastPoint(); } } return mergedPolys; } JsGraph3D.prototype.IsPointInsideClipRange3D = function( x, y, z ) { // or IsPointInsideClipRange3D( p: JsgVect3 ) // returns true if p or (x,y,z) is on top of all clip planes, that means inside clipping range var isVect = JsgVect3.Ok(x); var n = this.ClipPlaneListSize; if (n == 1 && this.ClipPlaneList[0] == null) return true; for (var i = 0; i < n; i++) { var clipPlane = this.ClipPlaneList[i]; if (clipPlane) { if (isVect) { if (!clipPlane.IsPoint3DOnTop(x)) return false; } else { if (!clipPlane.IsPointOnTop(x,y,z)) return false; } } } return true; } /////////////////// // Plane functions JsGraph3D.prototype.SavePlane = function( aParams ) { var par = xDefObj( aParams, {} ); par.Plane = this.Plane; return par; } JsGraph3D.prototype.SetPlane = function( PosOrObj, xDir, yDir, normalize ) { // or SetPlane( JsgPlane ) // or SetPlane( { Plane: JsgPlane, ... } ) // Use JsgPlane.Copy() to assign a copy of a plane to JsgGraph3D.Plane if (xObj(PosOrObj)) { if (JsgPlane.Ok(PosOrObj)) { this.Plane = PosOrObj; } else if (xDef(PosOrObj.Plane)) { this.Plane = PosOrObj.Plane; } } else { this.Plane.Set( PosOrObj, xDir, yDir, normalize ); } } JsGraph3D.prototype.PolygonToPlane = function( xpoly, ypoly, size, planePoly ) { // or PolygonToPlane( poly: JsgPolygon, planePoly: JsgPolygon(3D) ) // return planePoly or this.Plane.ResultPoly // Note: you have to call planePoly.Reset() yourself to clear it if necessary return this.Plane.PolygonOnPlane( xpoly, ypoly, size, planePoly ); } JsGraph3D.prototype.GetPointOnPlane = function( x, y, v ) { // v: JsgVect3 or undefined // returns v or this.WorkPoint3D // does not apply 3D-transformation, use GetTransPointOnPlane instead v = v || this.WorkPoint3D; return this.Plane.PointOnPlane( x, y, v ); } JsGraph3D.prototype.GetTransPointOnPlane = function( x, y, v ) { // v: JsgVect3 or undefined // returns v or this.WorkPoint3D // applies 3D-transformation return this.GetTransPoint3D( this.Plane.PointOnPlane( x, y ), v ); } JsGraph3D.prototype.GetTransPoint3D = function( x, y, z, v ) { // or GetTransPoint3D( JsgVect3, v ) // applies current 3D-transformation on point // v: JsgVect3 or undefined // returns v or this.WorkPoint3D if (JsgVect3.Ok(x)) { return this.GetTransPoint3D( x[0], x[1], x[2], y ); } v = v || this.WorkPoint3D; if (this.Trans3D) { JsgMat3.TransXYZTo( this.Trans3D, x, y, z, v ); } else { JsgVect3.Set( v, x, y, z ); } return v; } JsGraph3D.prototype.AddPointToPoly3DOnPlane = function( x, y ) { // or AddPointToPolyOnPlane( JsgVect2 ) this.Poly3D.AddPoint3D( this.Plane.PointOnPlane( x, y ) ); } JsGraph3D.prototype.MoveToOnPlane = function( x, y ) { // or MoveToOnPlane( JsgVect2 ) if (JsgVect2.Ok(x)) { return this.MoveToOnPlane( x[0], x[1] ); } JsgVect2.Set( this.LastPosOnPlane, x, y ); return this; } JsGraph3D.prototype.LineToOnPlane = function( x, y ) { // or LineToOnPlane( JsgVect2 ) if (JsgVect2.Ok(x)) { return this.LineToOnPlane( x[0], x[1] ); } this.LineOnPlane( this.LastPosOnPlane[0], this.LastPosOnPlane[1], x, y ); return this; } JsGraph3D.prototype.LineOnPlane = function( x1, y1, x2, y2, append ) { // or LineOnPlane( pt1, pt2, append ) if (JsgVect2.Ok(x1)) { return this.LineOnPlane( x1[0], x1[1], y1[0], y1[1], x2 ); } this.Plane.PointOnPlane( x1, y1, this.WorkPoint3D ); this.Plane.PointOnPlane( x2, y2, this.WorkPoint3D2 ); this.Line3D( this.WorkPoint3D, this.WorkPoint3D2, append ); JsgVect2.Set( this.LastPosOnPlane, x2, y2 ); return this; } JsGraph3D.prototype.PolygonOnPlane = function( xpoly, ypoly, mode, size, roundedEdges ) { // or PolygonOnPlane( JsgPolygon, mode, roundedEdges ) if (JsgPolygon.Ok(xpoly)) { return this.PolygonOnPlane( xpoly.X, xpoly.Y, ypoly, xpoly.Size, mode ); } size = xDefNum( size, xpoly.length ); var poly = this.Plane.PolygonOnPlane( xpoly, ypoly, size ); this.Polygon3D( poly, mode, roundedEdges ); JsgVect2.Set( this.LastPosOnPlane, xpoly[size-1], ypoly[size-1] ); return this; } JsGraph3D.prototype.BezierCurveOnPlane = function( sx, sy, cx1, cy1, cx2, cy2, ex, ey, mode, nSegments ) { // or BezierCurveOnPlane( spt, cpt1, cpt2, ept, mode, nSegments ) // or BezierCurveOnPlane( JsgPolygon, mode, startIx, nSegments ) // nSegements defaults to this.NumBezierSegments if (JsgPolygon.Ok(sx)) { var i = xDefNum( cx1, 0 ); return this.BezierCurveOnPlane( sx.X[i+0], sx.Y[i+0], sx.X[i+1], sx.Y[i+1], sx.X[i+2], sx.Y[i+2], sx.X[i+3], sx.Y[i+3], sy, cx1 ); } if (JsgVect2.Ok(sx)) { return this.BezierCurveOnPlane( sx[0], sx[1], sy[0], sy[1], cx1[0], cx1[1], cy1[0], cy1[1], cx2, cy2 ); } var poly2D = this.MakeBezierPolygon( sx, sy, cx1, cy1, cx2, cy2, ex, ey, nSegments ); this.PolygonOnPlane( poly2D, mode ); return this; } JsGraph3D.prototype.BezierCurveToOnPlane = function( cx1, cy1, cx2, cy2, ex, ey, mode, nSegments ) { // or BezierCurveToOnPlane( cpt1, cpt2, ept, mode, nSegments ) // or BezierCurveToOnPlane( JsgPolygon, mode, startIx, nSegments ) // nSegements defaults to this.NumBezierSegments if (JsgPolygon.Ok(cx1)) { var i = xDefNum( cx2, 0 ); return this.BezierCurveToOnPlane( cx1.X[i+0], cx1.Y[i+0], cx1.X[i+1], cx1.Y[i+1], cx1.X[i+2], cx1.Y[i+2], cy1, cx2 ); } if (JsgVect2.Ok(cx1)) { return this.BezierCurveToOnPlane( cx1[0], cx1[1], cy1[0], cy1[1], cx2[0], cx2[1], cy2, ex ); } this.BezierCurveOnPlane( this.LastPosOnPlane[0], this.LastPosOnPlane[1], cx1, cy1, cx2, cy2, ex, ey, mode, nSegments ); return this; } JsGraph3D.prototype.SplineCurveOnPlane = function( xpoly, ypoly, tension, mode, size, nSegments ) { // or SplineCurveOnPlane( JsgPolygon, tension, mode, nSegments ) if (JsgPolygon.Ok(xpoly)) { return this.SplineCurveOnPlane( xpoly.X, xpoly.Y, ypoly, tension, xpoly.Size, mode ); } // poly2D <- this.WorkPoly2 var poly2D = this.MakeSplineCurve( xpoly, ypoly, tension, mode, size, nSegments ); var roundedEdges = (mode & 4) > 0; this.PolygonOnPlane( poly2D, mode, roundedEdges ); return this; } JsGraph3D.prototype.ArrowOnPlane = function( x1, y1, x2, y2, variant, mode, sym1, sym2 ) { // or ArrowOnPlane( pt1, pt2, variant, mode ) if (JsgVect2.Ok(x1)) { return this.ArrowOnPlane( x1[0], x1[1], y1[0], y1[1], x2, y2, variant, mode ); } this.Plane.PointOnPlane( x1, y1, this.WorkPoint3D ); this.Plane.PointOnPlane( x2, y2, this.WorkPoint3D2 ); this.Arrow3D( this.WorkPoint3D, this.WorkPoint3D2, variant, mode, sym1, sym2 ); JsgVect2.Set( this.LastPosOnPlane, x2, y2 ); return this; } JsGraph3D.prototype.PolygonArrowOnPlane = function( xpoly, ypoly, variant, lineMode, arrowMode, size, sym1, sym2 ) { // or PolygonArrowOnPlane( JsgPolygon, variant, lineMode, arrowMode ) if (JsgPolygon.Ok(xpoly)) { return this.PolygonArrowOnPlane( xpoly.X, xpoly.Y, ypoly, variant, lineMode, xpoly.Size, arrowMode, size ); } var poly = this.Plane.PolygonOnPlane( xpoly, ypoly, size ); this.PolygonArrow3D( poly, variant, lineMode, arrowMode, sym1, sym2 ); size = size || xpoly.length; JsgVect2.Set( this.LastPosOnPlane, xpoly[size-1], ypoly[size-1] ); return this; } JsGraph3D.prototype.RectOnPlane = function( x1, y1, x2, y2, mode, roll ) { // or RectOnPlane( pt1, pt2, mode, roll ) // or RectOnPlane( Rect, mode, roll ) // or RectOnPlane( { xmin, ymin, xmax, ymax }, mode, roll ) // // pt1, pt2: JsgVect2 // Rect: JsgRect // x1, y1, x2, y2: real coordinates of any two opposite edges // mode: 1 -> draw border, 2 -> fill, 3 -> border and fill, default = 1 // mode & 4 -> inverse drawing direction for holes // mode & 8 -> continue path // roll: Integer: rolls point of rectangle down (n > 0) or up (n < 0) n steps. // n = 1 rotates start/endpoint clockwise one step, lower left point becomes lower top point. if (JsgVect2.Ok(x1)) { return this.RectOnPlane( x1[0], x1[1], y1[0], y1[1], x2, y2 ); } mode = xDefNum( mode, 1 ); var poly; if (xObj(x1)) { // x1: JsgRect or { xmin, ymin, xmax, ymax } mode = xDefNum( y1, 1 ); roll = xDefNum( x2, 0 ); var clockWise = !!(mode & 4); poly = this.MakeRectPolygon( x1, clockWise, roll ); } else { var clockWise = !!(mode & 4); poly = this.MakeRectPolygon( x1, y1, x2, y2, clockWise, roll ); } this.PolygonOnPlane( poly, mode, true ); return this; } JsGraph3D.prototype.RectWHOnPlane = function( x, y, w, h, mode, roll ) { // or RectWH( JsgRect, mode ) // // x, y: real coordinates of edge with least or most negativ values // w, h: real > 0 width and height // mode: 1 -> draw border, 2 -> fill, 3 -> border and fill, default = 1 // mode & 4 -> inverse drawing direction for holes in paths // mode & 8 -> continue path // roll: Integer: rolls point of rectangle down (n > 0) or up (n < 0) n steps. // n = 1 rotates start/endpoint clockwise one step, lower left point becomes lower top point. if (JsgRect.Ok(x)) { this.RectOnPlane( x.x, x.y, x.x+x.w, x.y+x.h, y, w ); } else { this.RectOnPlane( x, y, x+w, y+h, mode, roll ); } } JsGraph3D.prototype.CompViewportRadius = function( x, y, rx, ry ) { // Private Function to EllipseArcOnPlane to estimate the radius in viewport coordinates // Compute max radius in viewport coordinates: // First build 4 points of the bounding rectangle of a circle with radius maxR // on the plane and transform them into 3D coordinates. // These are then transformed into the viewplane (2D world coordinates). // These 4 2D points represent a perspectivly distorted bounding rectangle. // We can compute from them the maximal radius in 2D world coordinates. // And finally transform this radius into viewport coordinates to get the rPixel radius. // Returns rPixel function len( xs, ys, i, j ) { var vx = xs[i] - xs[j]; var vy = ys[i] - ys[j]; return vx * vx + vy * vy; } var abs = Math.abs, max = Math.max; var absRx = abs(rx); var absRy = abs(ry); var maxR = max(absRx, absRy); var plane = this.Plane; var poly = this.WorkPoly3D2.Reset(); poly.AddPoint3D( this.VTransPoint3D( plane.PointOnPlane( x-maxR, y-maxR ) ) ); poly.AddPoint3D( this.VTransPoint3D( plane.PointOnPlane( x+maxR, y-maxR ) ) ); poly.AddPoint3D( this.VTransPoint3D( plane.PointOnPlane( x+maxR, y+maxR ) ) ); poly.AddPoint3D( this.VTransPoint3D( plane.PointOnPlane( x-maxR, y+maxR ) ) ); var xs = poly.X, ys = poly.Y; var l1 = len( xs, ys, 1, 0 ); var l2 = len( xs, ys, 2, 3 ); var ll1 = (l1 + l2) / 2; var l1 = len( xs, ys, 3, 0 ); var l2 = len( xs, ys, 2, 1 ); var ll2 = (l1 + l2) / 2; var maxRVT = Math.sqrt( max( ll1, ll2 ) ) / 2; var cnvsRx = abs(this.CurrTrans.ScaleX) * maxRVT; var cnvsRy = abs(this.CurrTrans.ScaleY) * maxRVT; var rPixel = max(cnvsRx, cnvsRy); return rPixel; } JsGraph3D.prototype.CircleOnPlane = function( x, y, r, mode, startAngle ) { // or CircleOnPlane( JsgVect2, r, mode, startAngle ) // Draws a circle on an plane in the 3D world // r < 0 -> clockwise, r > 0 -> counterclockwise // mode: 1 -> draw border, 2 -> fill, 3 -> border and fill, default = 1 // (mode & 4 -> close circle) ignored, circles are closed anyway // mode & 8 -> continue path if (JsgVect2.Ok(x)) { return this.CircleOnPlane( x[0], x[1], y, r, mode ); } this.EllipseOnPlane( x, y, r, Math.abs(r), 0, mode, startAngle ); return this; } JsGraph3D.prototype.ArcOnPlane = function( x, y, r, start, end, mode ) { // or ArgOnPlane( JsgVect2, r, start, end, mode ) // Draws a circular arc on an plane in the 3D world // x, y: real coordinates // r: real radius // r < 0 -> clockwise, r > 0 -> counterclockwise // start, end: real angles in radians or degress (see AngleMeasure) // mode: 1 -> draw border, 2 -> fill, 3 -> border and fill, default = 1 // mode & 4 -> close arc by drawing a line from last to first point of arc // mode & 8 -> continue path if (JsgVect2.Ok(x)) { return this.ArcOnPlane( x[0], x[1], y, r, start, end ); } this.EllipseArcOnPlane( x, y, r, Math.abs(r), 0, start, end, mode ); return this; } JsGraph3D.prototype.ArcToOnPlane = function( x, y, r, big, mode ) { // or ArcToOnPlane( JsgVect2, r, big, mode ) // Draws a circular arc from last position to a point on an plane in the 3D world // x, y: real endpoint // r: real radius // r < 0 -> clockwise, r > 0 -> counterclockwise // big: boolean: chose short or long arc // mode: 1 -> draw border, 2 -> fill, 3 -> border and fill, default = 1 // mode & 4 -> close polygon if (JsgVect2.Ok(x)) { return this.ArcToOnPlane( x, x, y, r, big ); } this.ArcPtOnPlane( this.LastX, this.LastY, x, y, r, big, mode|8 ); return this; } JsGraph3D.prototype.ArcPtOnPlane = function( x1, y1, x2, y2, r, big, mode ) { // or ArcPtOnPlane( pt1, pt2, r, big, mode ) // Draws a circular arc given from 2 points on an plane in the 3D world // x1, y1, x2, y2: real startpoint and endpoint // r: real radius // r < 0 -> clockwise, r > 0 -> counterclockwise // big: boolean: chose short or long arc // mode: 1 -> draw border, 2 -> fill, 3 -> border and fill, default = 1 // mode & 4 -> close polygon // mode & 8 -> continue path if (JsgVect2.Ok(x1)) { return this.ArcPtOnPlane( x1[0], x1[1], y1[0], y1[1], x2, y2, r ); } big = xDefBool( big, false ); mode = xDefNum( mode, 1 ); var arc = this.MakeArcFromPoints( x1, y1, x2, y2, r, big ); this.ArcOnPlane( arc.x, arc.y, arc.r, arc.start, arc.end, mode ); return this; } JsGraph3D.prototype.EllipseOnPlane = function( x, y, rx, ry, rot, mode, startAngle ) { // or EllipseOnPlane( JsgVect2, rx, ry, rot, mode, startAngle ) // Draws an ellipse on an plane // x, y: real center of arc // rx, ry: real radius; rx < 0 -> clockwise, rx > 0 -> counterclockwise // rot: real rotation of ellipse in radian or degree (see AngleMeasure) // mode: 1 -> draw border, 2 -> fill, 3 -> border and fill, default = 1 // mode & 4 -> force close polygon // mode & 8 -> continue path if (JsgVect2.Ok(x)) { return this.EllipseOnPlane( x[0], x[1], y, rx, ry, rot, mode ); } startAngle = xDefNum( startAngle, 0 ); var start = startAngle; var end = startAngle + this.RadToAngle(2*Math.PI); if (rx < 0) { start = end; end = startAngle; } this.EllipseArcOnPlane( x, y, rx, ry, rot, start, end, mode ); return this; } JsGraph3D.prototype.EllipseArcOnPlane = function( x, y, rx, ry, rot, start, end, mode ) { // or EllipseArcOnPlane( JsgVect2, rx, ry, rot, start, end, mode ) // draws elliptic arc on plane // x, y: real center of arc // rx, ry: real radius; rx < 0 -> clockwise, rx > 0 -> counterclockwise // rot: real rotation of ellipse in radian or degree (see AngleMeasure) // start, end: real angles in ellipse coordinates as radian or degree (see AngleMeasure) // mode: 1 -> draw border, 2 -> fill, 3 -> border and fill, default = 1 // mode & 4 -> force close polygon // mode & 8 -> continue path if (JsgVect2.Ok(x)) { return this.EllipseArcOnPlane( x[0], x[1], y, rx, ry, rot, start, end ); } ry = xDefNum( ry, Math.abs(rx) ); rot = xDefNum( rot, 0 ); start = xDefNum( start, 0 ); end = xDefNum( end, start+this.RadToAngle(2*Math.PI) ); mode = xDefNum( mode, 1 ); var rPixel = this.CompViewportRadius( x, y, rx, ry ); var ell = this.MakeEllipseArcPolygon( x, y, rx, ry, rot, start, end, rPixel ); var roundedEdges = ((mode & 1) && this.IsClosedPolygon( ell.X, ell.Y, ell.Size )); this.PolygonOnPlane( ell, mode, roundedEdges ); return this; } JsGraph3D.prototype.TextOnPlane = function( txt, x, y, WidthOrMode ) { // or TextOnPlane( txt, JsgVect2, WidthOrMode ) // x, y: real text reference point on plane // w: optional limiting text rectangle width in pixels // mode: default = 0: (canvas text only) // x is left, center or right coordinate, depending on horizontal align // y is top, middle or bottom coordinate, depending on vertical align // Note: this function does not transform the letters, only the position of the text! if (JsgVect2.Ok(x)) { return this.TextOnPlane( txt, x[0], x[1], y ); } return this.Text3D( txt, this.Plane.PointOnPlane( x, y ), WidthOrMode ); } JsGraph3D.prototype.GetTextBoxOnPlane = function( aText, x, y, width ) { // or GetTextBoxOnPlane( aText, JsgVect2, width ) // returns JsGraph.WorkRect: JsgRect if (JsgVect2.Ok(x)) { return this.GetTextBoxOnPlane( aText, x[0], x[1], y ); } var pos = this.Plane.PointOnPlane( x, y ); var posVT = this.Camera.Trans( pos ); return this.GetTextBox( aText, posVT[0], posVT[1], width ); } JsGraph3D.prototype.MarkerOnPlane = function( x, y, mode, mat, size ) { // or MarkerOnPlane( JsgVect2, mode, mat ) // or MarkerOnPlane( xArr, yArr, mode, mat, size ) // or MarkerOnPlane( poly, mode, mat ) // xArr, yArr: array of Number // poly: JsgPolygon // x, y: Number or Array of Number marker position(s) on plane // mode: int: 1 -> border, 2 -> fill, 3 -> fill and border // mat: JsgMat2 (optional) -> additional transformation matrix (e.g. rotation) if (JsgPolygon.Ok(x)) { return this.MarkerOnPlane( x.X, x.Y, y, mode, x.Size ); } if (xArray(x) && xArray(y)) { // draw markers from arrays of x/y coordinates return this.Marker3D( this.Plane.PolygonOnPlane( x, y, size ), mode, mat ); } if (JsgVect2.Ok(x)) return this.MarkerOnPlane( x[0], x[1], y, mode ); // draw single marker (x,y) return this.Marker3D( this.Plane.PointOnPlane( x, y ), mode, mat ); } // attribute functions ------------------------------------------------------------------- JsGraph3D.prototype.CreateLinearGradient3D = function( aGradientDef ) { // aGradientDef = { P1, P2, Stops: [ { Pos, Color }, ... ] } // aGradientDef = { Plane, X1, Y1, X2, Y2, Stops: [ { Pos, Color }, ... ] } // returns { Def: jsGraphGradientDef, Obj: gradientObjekt, Def3D: aGradientDef } var p1VT, p2VT; var gradDef = { Stops: aGradientDef.Stops }; if (aGradientDef.Plane) { var plane = aGradientDef.Plane; p1VT = this.Camera.Trans( plane.Point( aGradientDef.X1, aGradientDef.Y1 ) ); p2VT = this.Camera.Trans( plane.Point( aGradientDef.X2, aGradientDef.Y2 ) ); } else { p1VT = this.Camera.Trans( aGradientDef.P1 ); p2VT = this.Camera.Trans( aGradientDef.P2 ); } gradDef.X1 = p1VT[0]; gradDef.Y1 = p1VT[1]; gradDef.X2 = p2VT[0]; gradDef.Y2 = p2VT[1]; var grad = this.CreateLinearGradient( gradDef ); grad.Def3D = aGradientDef; return grad } JsGraph3D.prototype.SetLinearGradientGeom3D = function( aLinearGradient3D, aGeom ) { // aGeom = { P1, P2 } // aGeom = { Plane, X1, Y1, X2, Y2 } var p1VT, p2VT; var grad = aLinearGradient3D.Def3D; if (grad.Plane) { var plane = grad.Plane; grad.X1 = xDefNum( aGeom.X1, grad.X1 ); grad.Y1 = xDefNum( aGeom.Y1, grad.Y1 ); grad.X2 = xDefNum( aGeom.X2, grad.X2 ); grad.Y2 = xDefNum( aGeom.Y2, grad.Y2 ); p1VT = this.Camera.Trans( plane.Point( grad.X1, grad.Y1 ) ); p2VT = this.Camera.Trans( plane.Point( grad.X2, grad.Y2 ) ); } else { grad.P1 = xDefArray( aGeom.P1, grad.P1 ); grad.P2 = xDefArray( aGeom.P2, grad.P2 ); p1VT = this.Camera.Trans( grad.P1 ); p2VT = this.Camera.Trans( grad.P2 ); } this.SetLinearGradientGeom( aLinearGradient3D, { X1: p1VT[0], Y1: p1VT[1], X1: p2VT[0], Y1: p2VT[1] } ); }