/// <summary> /// Computes the approximate convex hull. /// </summary> public void Compute() { if (!this.Source.AnyElement()) { this.result = new Coordinate[0]; this.hasResult = true; return; } if (this.Source.Count < 4) { this.result = this.Source.Distinct().ToArray(); this.hasResult = true; return; } Int32 minMin = 0, minMax = 0; Int32 maxMin = 0, maxMax = 0; Coordinate first = this.Source.FirstOrDefaultElement(); Double minX = first.X, maxX = first.X; Int32 numberOfBins = this.Source.Count / 2; Int32 bottomOfStack = 0, topOfStack = -1; // indexes for bottom and top of the stack Coordinate currentCoordinate; // the current coordinate being considered Coordinate[] hullStack = new Coordinate[this.Source.Count]; // get the coordinates with min-max X, and min-max Y for (Int32 sourceIndex = 1; sourceIndex < this.Source.Count; sourceIndex++) { if (this.Source[sourceIndex] == null) { continue; } if (this.Source[sourceIndex].X <= minX) { if (this.Source[sourceIndex].X < minX) { minX = this.Source[sourceIndex].X; minMin = minMax = sourceIndex; } else { if (this.Source[sourceIndex].Y < this.Source[minMin].Y) { minMin = sourceIndex; } else if (this.Source[sourceIndex].Y > this.Source[minMax].Y) { minMax = sourceIndex; } } } if (this.Source[sourceIndex].X >= maxX) { if (this.Source[sourceIndex].X > maxX) { maxX = this.Source[sourceIndex].X; maxMin = maxMax = sourceIndex; } else { if (this.Source[sourceIndex].Y < this.Source[maxMin].Y) { maxMin = sourceIndex; } else if (this.Source[sourceIndex].Y > this.Source[maxMax].Y) { maxMax = sourceIndex; } } } } // degenerate case: all x coordinates are equal to minX if (minX == maxX) { if (minMax != minMin) { this.result = new Coordinate[] { this.Source[minMin], this.Source[minMax] } } ; else { this.result = new Coordinate[] { this.Source[minMin] } }; } // get the max and min coordinates in the range bins RangeBin[] binArray = new RangeBin[numberOfBins + 2]; binArray[0].Min = minMin; binArray[0].Max = minMax; binArray[numberOfBins + 1].Min = maxMin; binArray[numberOfBins + 1].Max = maxMax; for (Int32 binIndex = 1; binIndex <= numberOfBins; binIndex++) { binArray[binIndex].Min = binArray[binIndex].Max = null; } for (Int32 sourceIndex = 0; sourceIndex < this.Source.Count; sourceIndex++) { if (this.Source[sourceIndex] == null) { continue; } if (this.Source[sourceIndex].X == minX || this.Source[sourceIndex].X == maxX) { continue; } Int32 binIndex; if (Coordinate.Orientation(this.Source[minMin], this.Source[maxMin], this.Source[sourceIndex], this.PrecisionModel) == Orientation.Clockwise) { // below lower line binIndex = Convert.ToInt32(numberOfBins * (this.Source[sourceIndex].X - minX) / (maxX - minX)) + 1; if (binArray[binIndex].Min == null || this.Source[sourceIndex].Y < this.Source[binArray[binIndex].Min.Value].Y) { binArray[binIndex].Min = sourceIndex; } } else if (Coordinate.Orientation(this.Source[minMin], this.Source[maxMin], this.Source[sourceIndex], this.PrecisionModel) == Orientation.Counterclockwise) { // above upper line binIndex = Convert.ToInt32(numberOfBins * (this.Source[sourceIndex].X - minX) / (maxX - minX)) + 1; if (binArray[binIndex].Max == null || this.Source[sourceIndex].Y > this.Source[binArray[binIndex].Max.Value].Y) { binArray[binIndex].Max = sourceIndex; } } } // use the chain algorithm to get the lower and upper hulls // compute the lower hull on the stack for (Int32 binIndex = 0; binIndex <= numberOfBins + 1; ++binIndex) { if (binArray[binIndex].Min == null) { continue; } currentCoordinate = this.Source[binArray[binIndex].Min.Value]; while (topOfStack > 0) { // there are at least 2 points on the stack if (Coordinate.Orientation(hullStack[topOfStack - 1], hullStack[topOfStack], currentCoordinate, this.PrecisionModel) == Orientation.Counterclockwise) { break; } else { --topOfStack; } } topOfStack++; hullStack[topOfStack] = currentCoordinate; } // compute the upper hull on the stack above the bottom hull if (maxMax != maxMin) { topOfStack++; hullStack[topOfStack] = this.Source[maxMax]; } bottomOfStack = topOfStack; for (Int32 binIndex = numberOfBins; binIndex >= 0; --binIndex) { if (binArray[binIndex].Max == null) { continue; } currentCoordinate = this.Source[binArray[binIndex].Max.Value]; while (topOfStack > bottomOfStack) { // there are at least 2 points on the upper stack if (Coordinate.Orientation(hullStack[topOfStack - 1], hullStack[topOfStack], currentCoordinate, this.PrecisionModel) == Orientation.Counterclockwise) { break; } else { topOfStack--; } } topOfStack++; hullStack[topOfStack] = currentCoordinate; } // push joining endpoint onto stack if (minMax != minMin) { topOfStack++; hullStack[topOfStack] = this.Source[minMin]; } // generate result from stack hullStack[topOfStack] = hullStack[0]; this.result = hullStack.GetRange(topOfStack + 1); this.hasResult = true; }
/// <summary> /// Computes the approximate convex hull. /// </summary> public void Compute() { if (_source.Count < 4) { _result = _source.Distinct().ToArray(); _hasResult = true; return; } // source: http://geomalgorithms.com/a11-_hull-2.html Int32 minMin = 0, minMax = 0; Int32 maxMin = 0, maxMax = 0; Double xMin = _source[0].X, xMax = _source[0].X; Int32 numberOfContainers = _source.Count / 2; Int32 bottomOfStack = 0, topOfStack = -1; // indices for bottom and top of the stack Coordinate currentCoordinate; // the current coordinate being considered Coordinate[] hullStack = new Coordinate[_source.Count]; // get the coordinates with min-max X, and min-max Y for (Int32 i = 1; i < _source.Count; i++) { if (_source[i].X <= xMin) { if (_source[i].X < xMin) { xMin = _source[i].X; minMin = minMax = i; } else { if (_source[i].Y < _source[minMin].Y) { minMin = i; } else if (_source[i].Y > _source[minMax].Y) { minMax = i; } } } if (_source[i].X >= xMax) { if (_source[i].X > xMax) { xMax = _source[i].X; maxMin = maxMax = i; } else { if (_source[i].Y < _source[maxMin].Y) { maxMin = i; } else if (_source[i].Y > _source[maxMax].Y) { maxMax = i; } } } } // degenerate case: all x coordinates are equal to xmin if (xMin == xMax) { if (minMax != minMin) { _result = new Coordinate[] { _source[minMin], _source[minMax] } } ; else { _result = new Coordinate[] { _source[minMin] } }; } // get the max and min coordinates in the range bins RangeBin[] binArray = new RangeBin[numberOfContainers + 2]; binArray[0].Min = minMin; binArray[0].Max = minMax; binArray[numberOfContainers + 1].Min = maxMin; binArray[numberOfContainers + 1].Max = maxMax; for (Int32 b = 1; b <= numberOfContainers; b++) { binArray[b].Min = binArray[b].Max = null; } for (Int32 b, i = 0; i < _source.Count; i++) { if (_source[i].X == xMin || _source[i].X == xMax) { continue; } if (Coordinate.Orientation(_source[minMin], _source[maxMin], _source[i], PrecisionModel) == Orientation.Clockwise) // below lower line { b = Convert.ToInt32(numberOfContainers * (_source[i].X - xMin) / (xMax - xMin)) + 1; if (binArray[b].Min == null || _source[i].Y < _source[binArray[b].Min.Value].Y) { binArray[b].Min = i; } } else if (Coordinate.Orientation(_source[minMin], _source[maxMin], _source[i], PrecisionModel) == Orientation.CounterClockwise) // above upper line { b = Convert.ToInt32(numberOfContainers * (_source[i].X - xMin) / (xMax - xMin)) + 1; if (binArray[b].Max == null || _source[i].Y > _source[binArray[b].Max.Value].Y) { binArray[b].Max = i; } } } // use the chain algorithm to get the lower and upper hulls // compute the lower hull on the stack for (Int32 i = 0; i <= numberOfContainers + 1; ++i) { if (binArray[i].Min == null) { continue; } currentCoordinate = _source[binArray[i].Min.Value]; while (topOfStack > 0) // there are at least 2 points on the stack { if (Coordinate.Orientation(hullStack[topOfStack - 1], hullStack[topOfStack], currentCoordinate, PrecisionModel) == Orientation.CounterClockwise) { break; } else { --topOfStack; } } topOfStack++; hullStack[topOfStack] = currentCoordinate; } // compute the upper hull on the stack above the bottom hull if (maxMax != maxMin) { topOfStack++; hullStack[topOfStack] = _source[maxMax]; } bottomOfStack = topOfStack; for (Int32 i = numberOfContainers; i >= 0; --i) { if (binArray[i].Max == null) { continue; } currentCoordinate = _source[binArray[i].Max.Value]; while (topOfStack > bottomOfStack) // there are at least 2 points on the upper stack { if (Coordinate.Orientation(hullStack[topOfStack - 1], hullStack[topOfStack], currentCoordinate, PrecisionModel) == Orientation.CounterClockwise) { break; } else { topOfStack--; } } topOfStack++; hullStack[topOfStack] = currentCoordinate; } // push joining endpoint onto stack if (minMax != minMin) { topOfStack++; hullStack[topOfStack] = _source[minMin]; } // generate result from stack _result = new Coordinate[topOfStack + 1]; Array.Copy(hullStack, _result, topOfStack); _result[topOfStack] = _result[0]; _hasResult = true; }