/// <summary> /// Reassign points based on the new faces added by ConstructCone(). /// /// Only points that were previous assigned to a removed face need to /// be updated, so check litFaces while looping through the open set. /// /// There is a potential optimization here: there's no reason to loop /// through the entire openSet here. If each face had it's own /// openSet, we could just loop through the openSets in the removed /// faces. That would make the loop here shorter. /// /// However, to do that, we would have to juggle A LOT more List<CurveParameter>'s, /// and we would need an object pool to manage them all without /// generating a whole bunch of garbage. I don't think it's worth /// doing that to make this loop shorter, a straight for-loop through /// a list is pretty darn fast. Still, it might be worth trying /// </summary> private void ReassignPoints(List <Point3> points) { for (int i = 0; i <= _openSetTail; i++) { PointFace fp = _openSet[i]; if (_litFaces.Contains(fp.Face)) { bool assigned = false; Vector3 point = points[fp.Point]; foreach (KeyValuePair <int, Face> kvp in _faces) { int fi = kvp.Key; Face face = kvp.Value; double dist = PointFaceDistance( point, points[face.Vertex0], face); if (dist > EPSILON) { assigned = true; fp.Face = fi; fp.Distance = dist; _openSet[i] = fp; break; } } if (!assigned) { // If point hasn't been assigned, then it's inside the // convex hull. Swap it with openSetTail, and decrement // openSetTail. We also have to decrement i, because // there's now a new thing in openSet[i], so we need i // to remain the same the next iteration of the loop. fp.Face = INSIDE; fp.Distance = double.NaN; _openSet[i] = _openSet[_openSetTail]; _openSet[_openSetTail] = fp; i--; _openSetTail--; } } } }
void ReassignPoints() { for (int i = 0; i <= openSetTail; i++) { PointFace pf = openSet[i]; if (litFaces.Contains(pf.Face)) { bool assigned = false; Vector3 point = positionList[pf.Point]; foreach (var kvp in faces) { var fi = kvp.Key; var face = kvp.Value; float dist = PointFaceDistance(point, positionList[face.Vertex0], face); if (dist > EPSILON) { assigned = true; pf.Face = fi; pf.Distance = dist; openSet[i] = pf; break; } } if (!assigned) { pf.Face = INSIDE; pf.Distance = float.NaN; openSet[i] = openSet[openSetTail]; openSet[openSetTail] = pf; i--; openSetTail--; } } } }
public Mesh CreateMesh() { Assert.IsTrue(pointList.Count >= 4); if (pointList.Count == 4) { initializeHull(); //assign each point to a specific face, unless they are inside the hull. for (int i = 0; i < openSet.Count; i++) { PointFace pf = openSet[i]; bool assigned = false; for (int j = 0; j < 4; j++) { Face face = faces[j]; float dist = PointFaceDistance(positionList[pf.Point], positionList[face.Vertex0], face); if (dist > 0) { pf.Face = j; pf.Distance = dist; openSet[i] = pf; assigned = true; break; } } if (!assigned) { pf.Face = INSIDE; pf.Distance = float.NaN; } } } if (pointList.Count > 4) { initializeHull(); openSetTail = openSet.Count - 5; Assert.IsTrue(openSet.Count == pointList.Count); for (int k = 0; k <= openSetTail; k++) { bool assigned = false; PointFace pf = openSet[k]; for (int i = 0; i < faces.Count; i++) { Face face = faces[i]; var distance = PointFaceDistance(positionList[pf.Point], positionList[face.Vertex0], face); if (distance > EPSILON) { pf.Face = i; pf.Distance = distance; openSet[k] = pf; assigned = true; break; } } if (!assigned) { pf.Face = INSIDE; pf.Distance = float.NaN; openSet[k] = openSet[openSetTail]; openSet[openSetTail] = pf; openSetTail -= 1; k--; } } while (openSetTail >= 0) { int farthestPoint = 0; float dist = openSet[0].Distance; for (int l = 1; l <= openSetTail; l++) { if (openSet[l].Distance > dist) { farthestPoint = l; dist = openSet[l].Distance; } } FindHorizon(positionList, positionList[openSet[farthestPoint].Point], openSet[farthestPoint].Face, faces[openSet[farthestPoint].Face]); ConstructCone(openSet[farthestPoint].Point); ReassignPoints(); } } Mesh mesh = ExportMesh(); return(mesh); }
/// <summary> /// Create initial seed hull. /// </summary> private void GenerateInitialHull(List <Point3> points) { // Find points suitable for use as the seed hull. Some varieties of // this algorithm pick extreme points here, but I'm not convinced // you gain all that much from that. Currently what it does is just // find the first four points that are not coplanar. FindInitialHullIndices(points, out int b0, out int b1, out int b2, out int b3); Vector3 v0 = points[b0]; Vector3 v1 = points[b1]; Vector3 v2 = points[b2]; Vector3 v3 = points[b3]; bool above = Vector3.DotProduct(v3 - v1, Vector3.CrossProduct(v1 - v0, v2 - v0)) > 0.0f; // Create the faces of the seed hull. You need to draw a diagram // here, otherwise it's impossible to know what's going on :) // Basically: there are two different possible start-tetrahedrons, // depending on whether the fourth point is above or below the base // triangle. If you draw a tetrahedron with these coordinates (in a // right-handed coordinate-system): // b0 = (0,0,0) // b1 = (1,0,0) // b2 = (0,1,0) // b3 = (0,0,1) // you can see the first case (set b3 = (0,0,-1) for the second // case). The faces are added with the proper references to the // faces opposite each vertex _faceCount = 0; if (above) { _faces[_faceCount++] = new Face(b0, b2, b1, 3, 1, 2, Normal(points[b0], points[b2], points[b1])); _faces[_faceCount++] = new Face(b0, b1, b3, 3, 2, 0, Normal(points[b0], points[b1], points[b3])); _faces[_faceCount++] = new Face(b0, b3, b2, 3, 0, 1, Normal(points[b0], points[b3], points[b2])); _faces[_faceCount++] = new Face(b1, b2, b3, 2, 1, 0, Normal(points[b1], points[b2], points[b3])); } else { _faces[_faceCount++] = new Face(b0, b1, b2, 3, 2, 1, Normal(points[b0], points[b1], points[b2])); _faces[_faceCount++] = new Face(b0, b3, b1, 3, 0, 2, Normal(points[b0], points[b3], points[b1])); _faces[_faceCount++] = new Face(b0, b2, b3, 3, 1, 0, Normal(points[b0], points[b2], points[b3])); _faces[_faceCount++] = new Face(b1, b3, b2, 2, 0, 1, Normal(points[b1], points[b3], points[b2])); } VerifyFaces(points); // Create the openSet. Add all points except the points of the seed // hull. for (int i = 0; i < points.Count; i++) { if (i == b0 || i == b1 || i == b2 || i == b3) { continue; } _openSet.Add(new PointFace(i, UNASSIGNED, 0.0f)); } // Add the seed hull verts to the tail of the list. _openSet.Add(new PointFace(b0, INSIDE, double.NaN)); _openSet.Add(new PointFace(b1, INSIDE, double.NaN)); _openSet.Add(new PointFace(b2, INSIDE, double.NaN)); _openSet.Add(new PointFace(b3, INSIDE, double.NaN)); // Set the openSetTail value. Last item in the array is // openSet.Count - 1, but four of the points (the verts of the seed // hull) are part of the closed set, so move openSetTail to just // before those. _openSetTail = _openSet.Count - 5; Assert(_openSet.Count == points.Count); // Assign all points of the open set. This does basically the same // thing as ReassignPoints() for (int i = 0; i <= _openSetTail; i++) { Assert(_openSet[i].Face == UNASSIGNED); Assert(_openSet[_openSetTail].Face == UNASSIGNED); Assert(_openSet[_openSetTail + 1].Face == INSIDE); bool assigned = false; PointFace fp = _openSet[i]; Assert(_faces.Count == 4); Assert(_faces.Count == _faceCount); for (int j = 0; j < 4; j++) { Assert(_faces.ContainsKey(j)); Face face = _faces[j]; double dist = PointFaceDistance(points[fp.Point], points[face.Vertex0], face); if (dist > 0) { fp.Face = j; fp.Distance = dist; _openSet[i] = fp; assigned = true; break; } } if (!assigned) { // Point is inside fp.Face = INSIDE; fp.Distance = double.NaN; // Point is inside seed hull: swap point with tail, and move // openSetTail back. We also have to decrement i, because // there's a new item at openSet[i], and we need to process // it next iteration _openSet[i] = _openSet[_openSetTail]; _openSet[_openSetTail] = fp; _openSetTail -= 1; i -= 1; } } VerifyOpenSet(points); }