/// <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 ; }
} // 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> /// Test a triangle's subtriangle whether they are partial. // if level is nonzero, recurse, unless some conditions are met /// </summary> /// <param name="min_addlevel"></param> /// <param name="level"></param> /// <param name="hid"></param> /// <param name="v0"></param> /// <param name="v1"></param> /// <param name="v2"></param> /// <param name="PPrev"></param> private void testPartial(int min_addlevel, int level, Int64 hid, Cartesian v0, Cartesian v1, Cartesian v2, int PPrev){ Int64[] ids = new Int64[4]; Int64 id0; Convex.Savecode why; Convex.Markup[] m = new Convex.Markup[4]; bool keepgoing; // force later termination of recorsion int P, F;// count number of partials and fulls // Console.WriteLine("testpartial level {0}", level); Cartesian w0 = new Cartesian(v1); w0.addMe(v2); w0.normalizeMe(); Cartesian w1 = new Cartesian(v0); w1.addMe(v2); w1.normalizeMe(); Cartesian w2 = new Cartesian(v1); w2.addMe(v0); w2.normalizeMe(); ids[0] = id0 = hid << 2; ids[1] = id0 + 1; ids[2] = id0 + 2; ids[3] = id0 + 3; m[0] = testNode(v0, w2, w1, ids[0]); m[1] = testNode(v1, w0, w2, ids[1]); m[2] = testNode(v2, w1, w0, ids[2]); m[3] = testNode(w0, w1, w2, ids[3]); // [DEBUG] After 36 entries F = 0; if (m[0] == Markup.Full) F++; if (m[1] == Markup.Full) F++; if (m[2] == Markup.Full) F++; if (m[3] == Markup.Full) F++; P = 0; if (m[0] == Markup.Partial) P++; if (m[1] == Markup.Partial) P++; if (m[2] == Markup.Partial) P++; if (m[3] == Markup.Partial) P++; // // Several interesting cases for saving this (the parent) trixel. // Case P==4, all four children are partials, so pretend parent is full, we save and return // Case P==3, and F==1, most of the parent is in, so pretend that parent is full again // Case P==2 or 3, but the previous testPartial had three partials, so parent was in an arc // as opposed to previous partials being fewer, so parent was in a tiny corner... why = Savecode.Nosave; // // The Big Idea: several heuristics cut off further subdivisions // but this may cause huge overshoots! // So, I added an option to tweak the heuristics // with a flagarray to ignore certain heuristic on demand // // code abbrev summary // ---------------------------------------------------- // p4 peq4 all four children are partial // f2 fge2 more than two children are full // p3 peq3feq1 3 are partial one is full // p1 pgt1ppreveq3 more than 1 partial, parent had three partials // a good halfspace to test this on is // (1, 0.8, 0.5, 0.9998) normalized, of course. // /* <EXP> */ //if (HtmState.Instance.maxlevel - level >= this._smallestLevel) { // why = Savecode.Reachedlevel; //} /* </EXP> */ // if you remove EXP code, make sure that else is also removed below // We sometimes force the smallest level // maxlevel is usually 20 - level is levels yet to go. // _smalletstLevel keepgoing = (HtmState.Instance.maxlevel - level >= this._smallestLevel); if (level <= 0) why = Savecode.Reachedlevel; else if (P == 4) why = Savecode.Peq4; else if (F >= 2) why = Savecode.Fge2; else if ((P == 3) && (F == 1)) why = Savecode.Peq3Feq1; // // Feb 07, 2005, ruls // if the feature size (side) is in the same ballpark // as the radius of the smallest circle among the halfspaces // Use this condition to stop if current level is greater than // smallest desired level else if (keepgoing && (P > 1 && PPrev == 3)) // WAS1 : else if ((P > 1 && PPrev == 3)) // WAS2 : else if ((level <= this._smallestLevel) && (P > 1 && PPrev == 3)) // level is levels yet to go, so currect level is HtmState.Instance.maxlevel - level { why = Savecode.Pgt1Ppreveq3; } // // from the C++ source: // if ((level-- <= 0) || ((P == 4) || (F >= 2) || (P == 3 && F == 1) || (P > 1 && PPrev == 3))){ // in above legacy line, level-- was wrong, it should be decremented after // all the tests are made // GYF: added to ensure a minimum depth // if (min_addlevel > 0 && level > 0) why = Savecode.Nosave; // Force recursion, unless if (why != Savecode.Nosave){ /// /// <LOG>"Saving <hid> code <why> "</LOG> /// if(_mode == Mode.File){ this._tracefile.dump("Saving {0} why {1} (level to go {2})", hid, why, level); } saveTrixel(hid, "1", level); return; } else { // look at each child, see if some need saving; for(int i=0; i<4; i++){ if (m[i] == Markup.Full){ /// /// <LOG>"saving <hid> code Child is full"</LOG> if (_mode == Mode.File){ this._tracefile.dump("Saving {0} why {1} (level to go {2})", ids[i], Savecode.Childisfull, level); } saveTrixel(ids[i], "2", level); } } // look at the four kids again, for partials level--; // <--- moved here from above, see legacy comment min_addlevel--; if (m[0] == Markup.Partial) testPartial(min_addlevel, level, ids[0], v0, w2, w1, P); if (m[1] == Markup.Partial) testPartial(min_addlevel, level, ids[1], v1, w0, w2, P); if (m[2] == Markup.Partial) testPartial(min_addlevel, level, ids[2], v2, w1, w0, P); if (m[3] == Markup.Partial) testPartial(min_addlevel, level, ids[3], w0, w1, w2, P); } }
/////////////SIMPLIFY0//////////////////////////////////// // // /// <summary> /// Simplify ZERO convexes. calculate corners of convex /// and the bounding circle. /// /// ZERO convexes are made up of constraints which are all great /// circles. It can happen that some of the constraints are redundant. /// For example, if 3 of the great circles define a triangle as the convex /// which lies fully inside the half sphere of the fourth constraint, /// that fourth constraint is redundant and will be removed. /// /// The algorithm is the following: /// /// zero-constraints are half-spheres, defined by a single normalized /// vector v, pointing in the direction of that half-sphere. /// /// Two zero-constraints intersect at /// /// i = +- v x v /// 1,2 1 2 /// /// the vector cross product of their two defining vectors. /// /// The two vectors i1,2 are tested against every other constraint in /// the convex if they lie within their half-spheres. Those /// intersections i which lie within every other constraint, are stored /// into corners_. /// /// Constraints that do not have a single corner on them, are dropped. /// </summary> private void simplify0() { int i; int j,k; Cartesian vi1, vi2; //typedef std::vector<size_t> ValueVectorSzt; //ValueVectorSzt cornerConstr1, cornerConstr2, removeConstr; //ValueVectorSpvec corner; // bool ruledout; ArrayList corner = new ArrayList(); ArrayList cornerConstr1 = new ArrayList(); ArrayList cornerConstr2 = new ArrayList(); ArrayList removeConstr = new ArrayList(); bool vi1ok, vi2ok; bool[] ruledout = null; if (_halfspaces.Count == 1) { // for one constraint, it is itself the BC _boundingCircle.copy((Halfspace) _halfspaces[0]); return; } else if(_halfspaces.Count == 2) { // For 2 constraints, take the bounding circle a 0-constraint... // this is by no means optimal, but the code is optimized for at least // 3 ZERO constraints... so this is acceptable. // test for constraints being identical - rule 1 out Halfspace hs0, hs1; hs0 = (Halfspace) _halfspaces[0]; hs1 = (Halfspace) _halfspaces[1]; if(hs0.sv.eq(hs1.sv)){ // delete 1 _halfspaces[1] = null; // was:constraints_.erase(constraints_.end()-1); _boundingCircle.copy(hs0); return; } // test for constraints being two disjoint half spheres - empty convex! if(hs0.sv.opposite(hs1.sv)){ _halfspaces.Clear(); return; } _boundingCircle = new Halfspace(); // TODO: halfspace constructor from sum of two vectors _boundingCircle.d = 0; _boundingCircle.sv.assign(hs0.sv); _boundingCircle.sv.addMe(hs1.sv); _boundingCircle.sv.normalizeMe(); // SpatialConstraint(constraints_[0].v() + // constraints_[1].v(),0); return; } // Go over all pairs of constraints ruledout = new bool[_halfspaces.Count]; for(i=0; i<(int)_halfspaces.Count; i++){ ruledout[i] = true; } for(i = 0; i < _halfspaces.Count - 1; i++) { // ruledout = true; // was: Halfspace hi, hj; hi = (Halfspace) _halfspaces[i]; for(j = i+1; j < _halfspaces.Count; j ++) { hj = (Halfspace) _halfspaces[j]; // test for constraints being identical - rule i out if(hi.sv.eq(hj.sv)) //constraints_[i].a_ == constraints_[j].a_) break; // test for constraints being two disjoint half spheres - empty convex! if(hi.sv.opposite(hj.sv)){ _halfspaces.Clear(); return; } // vi1 and vi2 are their intersection points // vi1 = constraints_[i].a_ ^ constraints_[j].a_ ; // vi1.normalize(); // vi2 = (-1.0) * vi1; vi1 = new Cartesian(hi.sv); vi1.crossMe(hj.sv); vi1.normalizeMe(); vi2 = new Cartesian(vi1); vi2.scaleMe(-1.0); vi1ok = true; vi2ok = true; // // now test whether vi1 or vi2 or both are inside every constraint. // other than i or j // if yes, store them in the corner array. // vi1ok is false, if vi1 is outisde even one constraint // for(k = 0; k < _halfspaces.Count; k++) { Halfspace hk; if( k == i || k == j) continue; hk = (Halfspace)_halfspaces[k]; if(vi1ok && vi1.dot(hk.sv) <= 0.0) { vi1ok = false; } if(vi2ok && vi2.dot(hk.sv) <= 0.0) { vi2ok = false; } if(!vi1ok && !vi2ok) // don't look further break; } if(vi1ok) { // vi1 is inside all other constraints corner.Add(vi1); // was: corner.push_back(vi1); cornerConstr1.Add(i); cornerConstr2.Add(j); // was: cornerConstr1.push_back(i); // was: cornerConstr2.push_back(j); ruledout[i] = false; ruledout[j] = false; // ruledout = false; } if(vi2ok) { // corner.push_back(vi2); // cornerConstr1.push_back(i); // cornerConstr2.push_back(j); corner.Add(vi2); cornerConstr1.Add(i); cornerConstr2.Add(j); ruledout[i] = false; ruledout[j] = false; // ruledout = false; } } // END of J loop // is this constraint ruled out? i.e. none of its intersections // with other constraints are corners... remove it from constraints_ list. /******************** WAS: if(ruledout) { removeConstr.push_back(i); } **********************************************/ } // END of i loop // See if you can rule out a constraint // for(i=0; i< (int) _halfspaces.Count; i++){ if (ruledout[i]){ removeConstr.Add(i); } } ruledout = null; // delete ruledout; // Now set the corners into their correct order, which is an // anti-clockwise walk around the polygon. // // start at any corner. so take the first. if (corner.Count == 0){ _halfspaces.Clear(); return; } _corners.Clear(); _corners.Add(corner[0]); // The trick is now to start off into the correct direction. // this corner has two edges it can walk. we have to take the // one where the convex lies on its left side. i = (int) cornerConstr1[0]; // the i'th constraint and j'th constraint j = (int) cornerConstr2[0]; // intersect at 0'th corner int c1,c2,k1,k2; // Now find the other corner where the i'th and j'th constraints intersect. // Store the corner in vi1 and vi2, and the other constraint indices // in c1,c2. c1 = c2 = k1 = k2 = -1; vi1 = null; vi2 = null; for( k = 1; k < cornerConstr1.Count; k ++) { if((int) cornerConstr1[k] == i) { vi1 = (Cartesian) corner[k]; c1 = (int) cornerConstr2[k]; k1 = k; } if((int) cornerConstr2[k] == i) { vi1 = (Cartesian) corner[k]; c1 = (int) cornerConstr1[k]; k1 = k; } if((int) cornerConstr1[k] == j) { vi2 = (Cartesian) corner[k]; c2 = (int) cornerConstr2[k]; k2 = k; } if((int) cornerConstr2[k] == j) { vi2 = (Cartesian) corner[k]; c2 = (int) cornerConstr1[k]; k2 = k; } } // Now test i'th constraint-edge ( corner 0 and corner k ) whether // it is on the correct side (left) // // ( (corner(k) - corner(0)) x constraint(i) ) * corner(0) // // is >0 if yes, <0 if no... // int c,currentCorner; Cartesian va; va = new Cartesian(vi1); va.subMe((Cartesian) corner[0]); va.crossMe( ((Halfspace) _halfspaces[i]).sv); if (va.dot((Cartesian) corner[0]) > 0.0){ if (k1 == -1) { throw (new Exception("Simplify0:k1 uninitialized")); } _corners.Add(vi1); c = c1; currentCorner = k1; } else { if (k2 == -1) { throw (new Exception("Simplify0:k2 uninitialized")); } _corners.Add(vi2); c = c2; currentCorner = k2; } // if( ((vi1 - corner[0]) ^ constraints_[i].a_) * corner[0] > 0 ) { // corners_.push_back(vi1); // c = c1; // currentCorner = k1; // } else { // corners_.push_back(vi2); // c = c2; // currentCorner = k2; // } // Now append the corners that match the index c until we got corner 0 again // currentCorner holds the current corners index // c holds the index of the constraint that has just been intersected with // So: // x We are on a constraint now (i or j from before), the second corner // is the one intersecting with constraint c. // x Find other corner for constraint c. // x Save that corner, and set c to the constraint that intersects with c // at that corner. Set currentcorner to that corners index. // x Loop until 0th corner reached. while( currentCorner > 0 ) { for (k = 0; k < cornerConstr1.Count; k++) { if(k == currentCorner)continue; if( (int) cornerConstr1[k] == c) { if( (currentCorner = k) == 0) break; _corners.Add(corner[k]); c = (int) cornerConstr2[k]; break; } if((int) cornerConstr2[k] == c) { if( (currentCorner = k) == 0) break; _corners.Add(corner[k]); c = (int) cornerConstr1[k]; break; } } } // Remove all redundant constraints for ( i = removeConstr.Count - 1; i>=0; i--){ //Start from END // WAS earlier: constraints_.erase(constraints_.end()-removeConstr[i]-1); /// in C++:constraints_.erase(constraints_.begin()+removeConstr[i]); _halfspaces.RemoveAt( (int) removeConstr[i]); } // Now calculate the bounding circle for the convex. // We take it as the bounding circle of the triangle with // the widest opening angle. All triangles made out of 3 corners // are considered. _boundingCircle.d = 1.0; if (_halfspaces.Count >=3 ) { for(i = 0; i < (int) _corners.Count; i++){ for(j = i+1; j < _corners.Count; j++){ for(k = j+1; k < _corners.Count; k++) { Cartesian v = new Cartesian(); Cartesian tv = new Cartesian(); v.assign((Cartesian) _corners[j]); v.subMe((Cartesian) _corners[i]); tv.assign((Cartesian) _corners[k]); tv.subMe((Cartesian) _corners[j]); v.crossMe(tv); v.normalizeMe(); // SpatialVector v = ( corners_[j] - corners_[i] ) ^ // ( corners_[k] - corners_[j] ); // v.normalize(); // 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 = v.dot((Cartesian) _corners[i]); if(_boundingCircle.d > d){ _boundingCircle.d = d; _boundingCircle.sv.assign(v); //new Halfspace(v,d); } } } } } }
public Polygon.Error add(double[] x, double[] y, double[] z, int len){ bool DIRECTION = false; bool FIRST_DIRECTION = false; // The constraint we have for each side is a 0-constraint (great circle) // passing through the 2 corners. Since we are in counterclockwise order, // the vector product of the two successive corners just gives the correct // constraint. // // Polygons should be counterclockwise // Polygons are assumed to be convex, otherwise windingerror is // computed wrong. int ix; Cartesian v = new Cartesian(); Convex cvx = new Convex(Convex.Mode.Normal); int i; /* PASS 1: check for winding error */ for(i = 0; i < len; i++) { // Keep track of winding direction. Should be positive // that is, CCW. ix = (i==len-1 ? 0 : i+1); if (i>0){ // test third corner against the constraint just formed // v is computed in the previous iteration // Look at a corner dot v if (v.dot(x[ix], y[ix], z[ix]) < Cartesian.Epsilon) { DIRECTION = true; if (i == 1) { FIRST_DIRECTION = true; } // break; // Move to pass 2 } else { DIRECTION = false; if (i == 1) { FIRST_DIRECTION = false; } } if (i > 1) { if (DIRECTION != FIRST_DIRECTION) { // C++: must clea up new Cartesian and convex // or better yet, do no allocate on top until you know // you need it return Polygon.Error.errBowtieOrConcave; // BOWTie error } } } // v = corners[i] ^ corners[ i == len-1 ? 0 : i + 1]; v.assign(x[i], y[i], z[i]); v.crossMe(x[ix], y[ix], z[ix]); if (v.isLength(0.0, Cartesian.Epsilon)) { return Polygon.Error.errZeroLength; } // WARNING! if v = zerovector, then edge error!!! } /* PASS 2: build convex in either original or reverse order */ /* forward: Go from 0 to len-1 by +1, cross i and i+1 (or 0) reverse: Go from len-1 to 0 by -1, cross i and i-1 (or len-1) */ if (DIRECTION) { for(i=len-1; i>=0; i--){ // v = corners[i] ^ corners[ i == 0 ? len-1 : i-1]; // v.normalize(); ix = (i==0? len-1 : i-1); v.assign(x[i], y[i], z[i]); v.crossMe(x[ix], y[ix], z[ix]); // WARNING! if v = zerovector, then edge error!!! v.normalizeMe(); Halfspace c = new Halfspace(v, 0.0); // SpatialConstraint c(v,0); cvx.add(c); } } else { for(i=0; i<len; i++){ //v = corners[i] ^ corners[ i == len-1 ? 0 : i+1]; // v.normalize(); ix = (i==len-1 ? 0 : i+1); v.assign(x[i], y[i], z[i]); v.crossMe(x[ix], y[ix], z[ix]); // WARNING! if v = zerovector, then edge error!!! v.normalizeMe(); Halfspace c = new Halfspace(v, 0.0); cvx.add(c); } } _reg.add(cvx); return Polygon.Error.Ok; }