/// <summary> /// Copy constructor. /// </summary> /// <param name="v">A Cartesian from which to copy</param> public SpatialVector(Cartesian v) { _x = v.x; _y = v.y; _z = v.z; _okXYZ = false; // updateRaDec(); }
private Convex.Markup testTriangle( Cartesian v0, Cartesian v1, Cartesian v2, int vsum){ Halfspace hs; if(vsum == 1 || vsum == 2){ return Markup.Partial; } // If vsum = 3 then we have all vertices inside the convex. // Now use the following decision tree: // // * If the sign of the convex is POS or ZERO : mark as FULL intersection. // // * Else, test for holes inside the triangle. A 'hole' is a NEG constraint // that has its center inside the triangle. If there is such a hole, // return PARTIAL intersection. // // * Else (no holes, sign NEG or MIXED) test for intersection of NEG // constraints with the edges of the triangle. If there are such, // return PARTIAL intersection. // // * Else return FULL intersection. if(vsum == 3) { if(_sign == Halfspace.Sign.Positive || _sign == Halfspace.Sign.Zero){ return Markup.Full; } if ( testHole(v0,v1,v2) ) { return Markup.Partial; } if ( testEdge(v0,v1,v2) ) { return Markup.Partial; } return Markup.Full; } // If we have reached that far, we have vsum=0. There is no definite // decision making possible here with our methods, the markup may result // in DONTKNOW. The decision tree is the following: // // * Test with bounding circle of the triangle. // // # If the sign of the convex ZERO test with the precalculated // bounding circle of the convex. If it does not intersect with the // triangle's bounding circle, REJECT. // // # If the sign of the convex is nonZERO: if the bounding circle // lies outside of one of the constraints, REJECT. // // * Else: there was an intersection with the bounding circle. // // # For ZERO convexes, test whether the convex intersects the edges. // If none of the edges of the convex intersects with the edges of // the triangle, we have a REJECT. Else, PARTIAL. // // # If sign of convex is POS, or MIXED and the smallest constraint does // not intersect the edges and has its center inside the triangle, // return SWALLOW. If no intersection of edges and center outside // triangle, return REJECT. // // # So the smallest constraint DOES intersect with the edges. If // there is another POS constraint which does not intersect with // the edges, and has its center outside the triangle, return // REJECT. If its center is inside the triangle return SWALLOW. // Else, return PARTIAL for POS and DONTKNOW for MIXED signs. // // * If we are here, return DONTKNOW. There is an intersection with // the bounding circle, none of the vertices is inside the convex and // we have very strange possibilities left for POS and MIXED signs. For // NEG, i.e. all constraints negative, we also have some complicated // things left for which we cannot test further. if ( !testBoundingCircle(v0,v1,v2) ) { return Markup.Reject; } if ( _sign == Halfspace.Sign.Positive || _sign == Halfspace.Sign.Mixed || (_sign == Halfspace.Sign.Zero && _halfspaces.Count <= 2)) { // Does the smallest constraint intersect with the edges? if ( testEdgeConstraint(v0,v1,v2,0) ) { // Is there another positive constraint that does NOT intersect with // the edges? cIndex is the position in _halspaces int cIndex; if ((cIndex = testOtherPosNone(v0,v1,v2))>= 0) { // Does that constraint lie inside or outside of the triangle? if ( testConstraintInside(v0,v1,v2, cIndex) ){ return Markup.Partial; } else if( (hs = (Halfspace)_halfspaces[cIndex]).contains(v0) ){ // Does the triangle lie completely within that constr? return Markup.Partial; } else { return Markup.Reject; // FIXED:was: Markup.Partial; } } else { if(_sign == Halfspace.Sign.Positive || _sign == Halfspace.Sign.Zero){ return Markup.Partial; } else { return Markup.Dontknow; } } } else { if (_sign == Halfspace.Sign.Positive || _sign == Halfspace.Sign.Zero) { // Does the smallest lie inside or outside the triangle? if( testConstraintInside(v0,v1,v2, 0)){ return Markup.Partial; } else { return Markup.Reject; } } else { return Markup.Dontknow; } } } else if (_sign == Halfspace.Sign.Zero) { if (_halfspaces.Count > 0 && testEdge0(v0,v1,v2)){ return Markup.Partial; } else { return Markup.Reject; } } return Markup.Partial; }
/// <summary> /// Test whether or not this vector is equal to /// another vector /// </summary> /// <param name="that">The other vector</param> /// <returns>true if other vector is close enough, /// false otherwise</returns> public bool eq(Cartesian that){ if (Math.Abs(that._x - _x) > Epsilon) return false; if (Math.Abs(that._y - _y) > Epsilon) return false; if (Math.Abs(that._z - _z) > Epsilon) return false; return true; }
/// <summary> /// Perform cross product of this and another vector. /// The result is a new cartesian instance. /// </summary> /// <param name="that">The other vector</param> /// <returns>The cross product as a new Cartesian</returns> public Cartesian cross(Cartesian that){ return new Cartesian( y*that.z - z*that.y, z*that.x - x*that.z, x*that.y - y*that.x); }
/// <summary> /// Subtract other vector from this, but create a new instance with /// the result. /// </summary> /// <param name="that">The other Cartesian</param> /// <returns>the difference as a new Catesian</returns> public Cartesian sub(Cartesian that){ return new Cartesian( _x - that._x, _y - that._y, _z - that._z); }
/// <summary> /// Make this vector the same as the other (given) one. /// </summary> /// <param name="that">The other Cartesian</param> public void assign(Cartesian that){ _x = that._x; _y = that._y; _z = that._z; }
/// <summary> /// Compute cross product of two given vectors. /// Result is written into a suppied Cartesian /// </summary> /// <param name="t">the result of operation</param> /// <param name="u">Array of 3 doubles with (x,y,z)</param> /// <param name="v">Array of 3 doubles with (x,y,z)</param> public static void cross(Cartesian t, double[] u, double[] v){ t.x = u[1] * v[2] - u[2] * v[1]; t.y = u[2] * v[0] - u[0] * v[2]; t.z = u[0] * v[1] - u[1] * v[0]; return; }
/// <summary> /// Compute dot product of this and a given vector /// </summary> /// <param name="that">The other Cartesian</param> /// <returns>The dot product</returns> public double dot(Cartesian that) { return x*that.x + y*that.y + z*that.z; }
} // end METHOD testBoundingCircle /// <summary> /// Test if edges intersect with a given constraint. /// </summary> /// <param name="v0"></param> /// <param name="v1"></param> /// <param name="v2"></param> /// <param name="cIndex"></param> /// <returns>true if edges intersect with the constraint</returns> private bool testEdgeConstraint(Cartesian v0, Cartesian v1, Cartesian v2, int cIndex){ if ( eSolve(v0, v1, cIndex) ) return true; if ( eSolve(v1, v2, cIndex) ) return true; if ( eSolve(v2, v0, cIndex) ) return true; return false; }
} // METHOD /// <summary> /// Test for boundingCircles intersecting with halfspace /// </summary> /// <param name="v0"></param> /// <param name="v1"></param> /// <param name="v2"></param> /// <returns></returns> private bool testBoundingCircle(Cartesian v0, Cartesian v1, Cartesian v2){ // Set the correct direction: The normal vector to the triangle plane //Cartesian c = ( v1 - v0 ) ^ ( v2 - v1 ); //c.normalize(); Cartesian c = new Cartesian(v1); c.subMe(v0); Cartesian t = new Cartesian(v2); t.subMe(v1); c.crossMe(t); c.normalizeMe(); // Set the correct opening angle: Since the plane cutting out the triangle // also correctly cuts out the bounding cap of the triangle on the sphere, // we can take any corner to calculate the opening angle double d = Math.Acos (c.dot(v0)); // for zero convexes, we have calculated a bounding circle for the convex. // only test with this single bounding circle. // // NOTE! Bounding circle gets changed by simplify0 // If you did not run simplify, bounding circle may contain // unpredictable numbers. // if(_sign == Halfspace.Sign.Zero && _boundingCircle != null) { double tst; tst = (tst = c.dot(_boundingCircle.sv)); if ((tst < -1.0 + Cartesian.Epsilon ? Cartesian.Pi : Math.Acos(tst)) > (d + _boundingCircle.hangle)) return false; return true; } // for all other convexes, test every constraint. If the bounding // circle lies completely outside of one of the constraints, reject. // else, accept. int i; double ftmp; Halfspace hs; for(i = 0; i < _halfspaces.Count; i++) { hs = (Halfspace) _halfspaces[i]; if (c.dot(hs.sv) < -1.0 + Cartesian.Epsilon){ ftmp = Cartesian.Pi; } else { ftmp = Math.Acos(c.dot(hs.sv)); } if (ftmp > (d + hs.hangle)){ return false; } } return true; } // end METHOD testBoundingCircle
/// <summary> /// Solve the quadratic eq. for intersection of an edge with a circle /// halfspace. Edge given by great circle running through v1, v2 /// halfspace given by cIndex. /// </summary> /// <param name="v1"></param> /// <param name="v2"></param> /// <param name="cIndex"></param> /// <returns></returns> private bool eSolve(Cartesian v1, Cartesian v2, int cIndex){ Halfspace hs; hs = (Halfspace) _halfspaces[cIndex]; double gamma1 = v1.dot(hs.sv); double gamma2 = v2.dot(hs.sv); double mu = v1.dot(v2); double u2 = (1.0 - mu) / (1.0 + mu); double a = - u2 * (gamma1 + hs.d); double b = gamma1 * (u2 - 1.0) + gamma2 * (u2 + 1.0); double c = gamma1 - hs.d; double D = b * b - 4 * a * c; if (D < 0.0){ return false; // no intersection } // calculate roots a'la Numerical Recipes double SGN; if (b < 0.0){ SGN = -1.0; } else { if (b > 0){ SGN = 1.0; } else { SGN = 0.0; } } double q = -0.5 * (b + (SGN * Math.Sqrt(D))); double root1 = -999.0, root2 = -999.0; int i = 0; if ( a > Cartesian.Epsilon || a < -Cartesian.Epsilon ) { root1 = q / a; i++; } if ( q > Cartesian.Epsilon || q < -Cartesian.Epsilon ) { root2 = c / q; i++; } // Check whether the roots lie within [0,1]. If not, the intersection // is outside the edge. if (i == 0) { return false; // no solution } // if i > 0, then root1 is assigned a value; if (root1 >= 0.0 && root1 <= 1.0){ return true; } // If i == 2, then root2 was assigned a value; if (i == 2 && ((root1 >= 0.0 && root1 <= 1.0) || (root2 >= 0.0 && root2 <= 1.0))){ return true; } return false; } // METHOD
/// <summary> /// Test if edges intersect with halfspace. This problem /// is solved by a quadratic equation. Return true if there is /// an intersection. /// </summary> /// <param name="v0"></param> /// <param name="v1"></param> /// <param name="v2"></param> /// <returns></returns> private bool testEdge(Cartesian v0, Cartesian v1, Cartesian v2){ Halfspace hs; for(int i = 0; i < _halfspaces.Count; i++) { hs = (Halfspace) _halfspaces[i]; if ( hs.sign < 0 ) { // test only 'holes' if ( eSolve(v0, v1, i) ) return true; if ( eSolve(v1, v2, i) ) return true; if ( eSolve(v2, v0, i) ) return true; } } return false; }
/////////////TESTEDGE0//////////////////////////////////// // testEdge0: // /// <summary> /// Test if the edges intersect with the ZERO convex. // The edges are given by the vertex vectors e[0-2] // All constraints are great circles, so test if their intersect // with the edges is inside or outside the convex. // If any edge intersection is inside the convex, return true. // If all edge intersections are outside, check whether one of // the corners is inside the triangle. If yes, all of them must be // inside -> return true. /// </summary> /// <param name="v0">vertex vector e[0]</param> /// <param name="v1">vertex vector e[1]</param> /// <param name="v2">vertex vector e[2]</param> /// <returns></returns> private bool testEdge0(Cartesian v0, Cartesian v1, Cartesian v2){ // We have constructed the corners_ array in a certain direction. // now we can run around the convex, check each side against the 3 // triangle edges. If any of the sides has its intersection INSIDE // the side, return true. At the end test if a corner lies inside // (because if we get there, none of the edges intersect, but it // can be that the convex is fully inside the triangle. so to test // one single edge is enough) edgeStruct[] edge = new edgeStruct[3]; if (_corners.Count < 1) { throw new Exception("testEdge0: There are no corners"); } // fill the edge structure for each side of this triangle Cartesian c01 = new Cartesian(v0); Cartesian c12 = new Cartesian(v1); Cartesian c20 = new Cartesian(v2); c01.crossMe(v1); c12.crossMe(v2); c20.crossMe(v0); edge[0].e = c01; edge[0].e1 = v0; edge[0].e2 = v1; edge[0].l = Math.Acos(v0.dot(v1)); edge[1].e = c12; edge[1].e1 = v1; edge[1].e2 = v2; edge[1].l = Math.Acos(v1.dot(v2)); edge[2].e = c20; edge[2].e1 = v2; edge[2].e2 = v0; edge[2].l = Math.Acos(v2.dot(v0)); // edge[0].e = v0 ^ v1; edge[0].e1 = &v0; edge[0].e2 = &v1; // edge[1].e = v1 ^ v2; edge[1].e1 = &v1; edge[1].e2 = &v2; // edge[2].e = v2 ^ v0; edge[2].e1 = &v2; edge[2].e2 = &v0; // edge[0].l = Acos(v0 * v1); // edge[1].l = Acos(v1 * v2); // edge[2].l = Acos(v2 * v0); for(int i = 0; i < _corners.Count; i++) { int j = 0; if(i < _corners.Count - 1){ j = i+1; } Cartesian a1 = new Cartesian(); Cartesian tmp; double arg1, arg2; double l1,l2; // lengths of the arcs from intersection to edge corners double cedgelen = Math.Acos( ((Cartesian) _corners[i]).dot ((Cartesian) _corners[j])); // length of edge of convex // calculate the intersection - all 3 edges for (int iedge = 0; iedge < 3; iedge++) { a1.assign(edge[iedge].e); tmp = ((Cartesian) _corners[i]).cross ((Cartesian) _corners[j]); a1.crossMe(tmp); if (a1.normalizeMeSafely()){ // WARNING // Keep your eye on this, used to crash here... // // if the intersection a1 is inside the edge of the convex, // its distance to the corners is smaller than the edgelength. // this test has to be done for both the edge of the convex and // the edge of the triangle. for(int k = 0; k < 2; k++) { l1 = Math.Acos(((Cartesian) _corners[i]).dot(a1)); l2 = Math.Acos(((Cartesian) _corners[j]).dot(a1)); if( l1 - cedgelen <= Cartesian.Epsilon && l2 - cedgelen <= Cartesian.Epsilon){ arg1 = (edge[iedge].e1).dot(a1); arg2 = (edge[iedge].e2).dot(a1); if (arg1 > 1.0) arg1 = 1.0; if (arg2 > 1.0) arg2 = 1.0; l1 = Math.Acos(arg1); l2 = Math.Acos(arg2); if( l1 - edge[iedge].l <= Cartesian.Epsilon && l2 - edge[iedge].l <= Cartesian.Epsilon) return true; } a1.scaleMe(-1.0); // do the same for the other intersection } } } } return testVectorInside(v0,v1,v2,(Cartesian) _corners[0]); }
private bool testHole(Cartesian v0, Cartesian v1, Cartesian v2){ Cartesian r1, r2, r3; bool test = false; Halfspace hs; r1 = new Cartesian(); r2 = new Cartesian(); r3 = new Cartesian(); for(int i = 0; i < _halfspaces.Count; i++) { hs = (Halfspace) _halfspaces[i]; if ( hs.sign < 0) { // test only 'holes' // If (a ^ b * c) < 0, vectors abc point clockwise. // -> center c not inside triangle, since vertices a,b are ordered // counter-clockwise. The comparison here is the other way // round because c points into the opposite direction as the hole // // The old code said this: // if ( ( ( v0 ^ v1 ) * // _halfspaces[i].a_) > 0.0L ) continue; // if ( ( ( v1 ^ v2 ) * // _halfspaces[i].a_) > 0.0L ) continue; // if ( ( ( v2 ^ v0 ) * // _halfspaces[i].a_) > 0.0L ) continue; r1.assign(v0); r1.crossMe(v1); if ( r1.dot( hs.sv) > 0.0) continue; r1.assign(v1); r1.crossMe(v2); if ( r1.dot( hs.sv) > 0.0) continue; r1.assign(v2); r1.crossMe(v0); if ( r1.dot( hs.sv) > 0.0) continue; test = true; break; } } return test; }
/// <summary> /// This test fails if the vertex v is inside any halfspace /// in this convex. In other words, it succeeds if v is /// not inside any of them /// </summary> /// <param name="v">vertex to test</param> /// <returns></returns> private bool isVertexOutsideAllHalfspaces(Cartesian v) { Halfspace hs; for ( int i = 0; i < _halfspaces.Count; i++){ hs = (Halfspace) _halfspaces[i]; if ( v.dot(hs.sv) < hs.d ){ return false; } } return true; }
/// <summary> /// Copy creator /// </summary> /// <param name="v">Another Cartesian</param> public Cartesian (Cartesian v){ _x = v.x; _y = v.y; _z = v.z; }
/// <summary> /// Test if other Cartesian is "close enough" to this. /// The test is based on the magnitude of the /// difference between this vector and /// the given other Cartesian. Test succeeds if the /// difference is Epsilon or less. /// </summary> /// <param name="a">The other Cartesian</param> /// <returns>true if the other cartesian is close enough</returns> public bool Equivalent(Cartesian a){ double dx, dy, dz, diff; dx = this._x - a._x; dy = this._y - a._y; dz = this._z - a._z; diff = dx * dx + dy * dy + dz * dz; return (diff <= Epsilon2); }
/// <summary> /// Test for other positive constraints that do // not intersect with an edge. Return its index /// </summary> /// <param name="v0"></param> /// <param name="v1"></param> /// <param name="v2"></param> /// <returns>The index of a positive constraint which does not interspect with an edge</returns> private int testOtherPosNone(Cartesian v0, Cartesian v1, Cartesian v2){ int i = 0; while ( i < _halfspaces.Count && ((Halfspace) _halfspaces[i]).sign == Halfspace.Sign.Positive){ if ( !testEdgeConstraint(v0, v1, v2, i)){ return i; } i++; } return -1; }
/// <summary> /// Compute dot product of two given vectors /// </summary> /// <param name="u">First (Cartesian) vector</param> /// <param name="v">Second (Cartesian) vector</param> /// <returns>The dot product</returns> public static double dot(Cartesian u, Cartesian v){ return u.x*v.x + u.y*v.y + u.z*v.z; }
/// <summary> /// Look if a constraint is inside the triangle /// </summary> /// <param name="v0"></param> /// <param name="v1"></param> /// <param name="v2"></param> /// <param name="i"></param> /// <returns>true if the constraint is inside the triage</returns> private bool testConstraintInside( Cartesian v0, Cartesian v1, Cartesian v2, int i){ Halfspace hs; hs = (Halfspace) _halfspaces[i]; return testVectorInside(v0, v1, v2, hs.sv); }
/// <summary> /// Make a new instance of Cartesian /// the contents of which is a vector obtained by adding /// this to another Cartesian. /// </summary> /// <param name="that">The other Cartesian</param> /// <returns>The sum as a new instance of a Cartesian</returns> public Cartesian add(Cartesian that){ return new Cartesian( _x + that._x, _y + that._y, _z + that._z); }
/// <summary> /// Look if a vector is inside the triangle /// returns true if vector is inside /// </summary> /// <param name="v0"></param> /// <param name="v1"></param> /// <param name="v2"></param> /// <param name="v"></param> /// <returns>true if vector is inside the triangle</returns> private bool testVectorInside( // SUSPECT Cartesian v0, Cartesian v1, Cartesian v2, Cartesian v){ // If (a ^ b * c) < 0, vectors abc point clockwise. // -> center c not inside triangle, since vertices are ordered // counter-clockwise. Cartesian c01 = new Cartesian(v0); Cartesian c12 = new Cartesian(v1); Cartesian c20 = new Cartesian(v2); c01.crossMe(v1); c12.crossMe(v2); c20.crossMe(v0); // if (((( v0 ^ v1 ) * v) < 0 ) || // ( (( v1 ^ v2 ) * v) < 0 ) || // ( (( v2 ^ v0 ) * v) < 0 ) ) // return false; // return true; if (c01.dot(v) < 0) return false; if (c12.dot(v) < 0) return false; if (c20.dot(v) < 0) return false; return true; }
/// <summary> /// Add another cartesian to this. /// </summary> /// <param name="that">The other Cartesian</param> public void addMe(Cartesian that){ _x += that._x; _y += that._y; _z += that._z; }
/// <summary> /// This is the main test of a triangle vs a Convex. /// </summary> /// <param name="index"></param> private void testTrixel(int index){ Int64 hid; Cartesian v0, v1, v2; double[] fa, fb, fc; fa = new double[3]; fb = new double[3]; fc = new double[3]; // // Compute the vertices of // S0, S1, ... , N3 // // Wee need to ask HtmTrixel for the initial // values of v0,...,v2 for HID tid; hid = 8 + index; // if you consider letting addlevel be zero, // then this top level trixel must be tested // too. _htm.level0vertices(index, fa, fb, fc); v0 = new Cartesian(fa[0], fa[1], fa[2]); v1 = new Cartesian(fb[0], fb[1], fb[2]); v2 = new Cartesian(fc[0], fc[1], fc[2]); if (_addlevel > 0){ testPartial(_minlevel, _addlevel, hid, v0, v1, v2, 0); // Used to die here... no more } }
/// <summary> /// Subtract another vector from this one. /// </summary> /// <param name="that">The other vector</param> public void subMe(Cartesian that){ _x -= that._x; _y -= that._y; _z -= that._z; }
/// <summary> /// Make a halfspace with parameters (V, D) /// /// It is not necessary that the directional vector, though normal /// to the cutting plane, by definition, be a normal vector. /// </summary> /// <param name="v">The cutting plane's normal vector</param> /// <param name="d">The distance of the cutting plane along the normal /// vector's direction. Can be negative.</param> public Halfspace(Cartesian v, double d) { init(); if (d < -1.0) _D = -1.0; else if (d > 1.0) _D = 1.0; else _D = d; _SV.assign(v); _SV.normalizeMeSafely(); if (_D < -SpatialVector.Epsilon) { _sign = Sign.Negative; } else if (_D >= SpatialVector.Epsilon) { _sign = Sign.Positive; } else { _sign = Sign.Zero; } _hangle = Math.Acos(_D); }
/// <summary> /// Perform cross prodcut of this and another /// vector. Replaced with the result. /// </summary> /// <param name="that">The other vector</param> public void crossMe(Cartesian that){ double tx, ty, tz; tx = _y*that.z - _z*that.y; ty = _z*that.x - _x*that.z; tz = _x*that.y - _y*that.x; _x = tx; _y = ty; _z = tz; }
/// <summary> /// Test whether or not point described by vector v is inside /// this halfspace /// </summary> /// <param name="v">A point</param> /// <returns>true if the specified point described by v is inside this halfspace</returns> public bool contains(Cartesian v) { // v is inside the halfspace, if the angle between v and // the direction vector is smaller than the cone half-angle. // Note that _D = cos(half-angle) // return (v.dot(_SV.x, _SV.y, _SV.z) >= _D); }
/// <summary> /// Test whether or not this vector is the opposite /// of another vector. /// Opposite means same magnitude, opposite direction. /// </summary> /// <param name="that">The other vector</param> /// <returns>true if other vector is close enough to /// being opposite, false otherwise</returns> public bool opposite(Cartesian that){ if (Math.Abs(that._x + _x) > Epsilon) return false; if (Math.Abs(that._y + _y) > Epsilon) return false; if (Math.Abs(that._z + _z) > Epsilon) return false; return true; }
/// <summary> /// Get the internal angles of the trixel described by /// a given HID. /// </summary> /// <param name="hid">The 64-bit HID</param> /// <param name="t1">First internal angle</param> /// <param name="t2">Second internal angle</param> /// <param name="t3">Third internal angle</param> /// <returns>true if successful, false if HID is invalid</returns> public bool getAngles(Int64 hid, out double t1, out double t2, out double t3){ // formula for area of spherical triangle // A = R^2[(t1 + t2 + t3 ) - Pi], but R=1. // Cartesian n0, n1, n2; double[] v0, v1, v2; v0 = new double[3]; v1 = new double[3]; v2 = new double[3]; char[] name = new char[HtmTrixel.eMaxNameSize]; int level = this.hid2name(name, hid); if (level < 0) { t1 = t2 = t3 = 0.0; return false; } else { this.name2Triangle(name, v0, v1, v2); // // Compute the plane normals for the three GC arcs // n0 = new Cartesian(); n1 = new Cartesian(); n2 = new Cartesian(); Cartesian.cross(n2, v1, v0); Cartesian.cross(n0, v2, v1); Cartesian.cross(n1, v0, v2); n0.normalizeMe(); n1.normalizeMe(); n2.normalizeMe(); t1 = Math.PI - Math.Acos(Cartesian.dot(n0, n1)); t2 = Math.PI - Math.Acos(Cartesian.dot(n1, n2)); t3 = Math.PI - Math.Acos(Cartesian.dot(n2, n0)); } return true ; }