Как я могу определить, содержит ли замкнутый путь заданную точку?

В Android у меня есть объект Path, который, как мне известно, определяет закрытый путь, и мне нужно выяснить, содержится ли данная точка в пути. То, что я надеялся, было чем-то вроде

path.contains(int x, int y)

но это, кажется, не существует.

Конкретная причина, по которой я ищу это, состоит в том, что у меня есть набор фигур на экране, определенных как пути, и я хочу выяснить, на какой из них пользователь нажал. Если есть лучший способ приблизиться к этому, например, использовать различные элементы пользовательского интерфейса, а не делать это "трудным путем", я открыт для предложений.

Я открыт для написания алгоритма самостоятельно, если это необходимо, но это означает, что я предполагаю различные исследования.

Ответ 1

Класс android.graphics.Path не имеет такого метода. Класс Canvas имеет область отсечения, которая может быть установлена ​​в путь, и нет возможности протестировать ее против точки. Вы можете попробовать Canvas.quickReject, тестируя один прямоугольник точки (или 1x1 Rect). Я не знаю, действительно ли это будет проверять путь или только охватывающий прямоугольник.

Класс Region явно отслеживает только содержащий прямоугольник.

Вы можете рассмотреть возможность рисования каждой из ваших областей в 8-битном битовом слое альфа-слоя с каждым Path, заполненным собственным значением "color" (убедитесь, что сглаживание отключено в вашем Paint). Это создает маску для каждого пути, заполненного индексом, на путь, который его заполняет. Затем вы можете просто использовать значение пикселя в качестве индекса в списке путей.

Bitmap lookup = Bitmap.createBitmap(width, height, Bitmap.Config.ALPHA_8);
//do this so that regions outside any path have a default
//path index of 255
lookup.eraseColor(0xFF000000);

Canvas canvas = new Canvas(lookup);
Paint paint = new Paint();

//these are defaults, you only need them if reusing a Paint
paint.setAntiAlias(false);
paint.setStyle(Paint.Style.FILL);

for(int i=0;i<paths.size();i++)
    {
    paint.setColor(i<<24); // use only alpha value for color 0xXX000000
    canvas.drawPath(paths.get(i), paint); 
    }

Затем просмотрите точки,

int pathIndex = lookup.getPixel(x, y);
pathIndex >>>= 24;

Обязательно проверьте 255 (нет пути), если есть незаполненные точки.

Ответ 2

Вот что я сделал и, похоже, работает:

RectF rectF = new RectF();
path.computeBounds(rectF, true);
region = new Region();
region.setPath(path, new Region((int) rectF.left, (int) rectF.top, (int) rectF.right, (int) rectF.bottom));

Теперь вы можете использовать метод region.contains(x,y).

Point point = new Point();
mapView.getProjection().toPixels(geoPoint, point);

if (region.contains(point.x, point.y)) {
  // Within the path.
}

** Обновление от 6/7/2010 ** Метод region.setPath приведет к сбою моего приложения (без предупреждения), если rectF слишком большой. Вот мое решение:

// Get the screen rect.  If this intersects with the path rect
// then lets display this zone.  The rectF will become the 
// intersection of the two rects.  This will decrease the size therefor no more crashes.
Rect drawableRect = new Rect();
mapView.getDrawingRect(drawableRect);

if (rectF.intersects(drawableRect.left, drawableRect.top, drawableRect.right, drawableRect.bottom)) {
   // ... Display Zone.
}

Ответ 3

WebKit SkiaUtils имеет обход С++ для ошибки Randy Findley:

bool SkPathContainsPoint(SkPath* originalPath, const FloatPoint& point, SkPath::FillType ft)
{
  SkRegion rgn;
  SkRegion clip;

  SkPath::FillType originalFillType = originalPath->getFillType();

  const SkPath* path = originalPath;
  SkPath scaledPath;
  int scale = 1;

  SkRect bounds = originalPath->getBounds();

  // We can immediately return false if the point is outside the bounding rect
  if (!bounds.contains(SkFloatToScalar(point.x()), SkFloatToScalar(point.y())))
      return false;

  originalPath->setFillType(ft);

  // Skia has trouble with coordinates close to the max signed 16-bit values
  // If we have those, we need to scale. 
  //
  // TODO: remove this code once Skia is patched to work properly with large
  // values
  const SkScalar kMaxCoordinate = SkIntToScalar(1<<15);
  SkScalar biggestCoord = std::max(std::max(std::max(bounds.fRight, bounds.fBottom), -bounds.fLeft), -bounds.fTop);

  if (biggestCoord > kMaxCoordinate) {
      scale = SkScalarCeil(SkScalarDiv(biggestCoord, kMaxCoordinate));

      SkMatrix m;
      m.setScale(SkScalarInvert(SkIntToScalar(scale)), SkScalarInvert(SkIntToScalar(scale)));
      originalPath->transform(m, &scaledPath);
      path = &scaledPath;
  }

  int x = static_cast<int>(floorf(point.x() / scale));
  int y = static_cast<int>(floorf(point.y() / scale));
  clip.setRect(x, y, x + 1, y + 1);

  bool contains = rgn.setPath(*path, clip);

  originalPath->setFillType(originalFillType);
  return contains;
}

Ответ 4

Я знаю, что я немного опаздываю на вечеринку, но я бы решил эту проблему, подумав об этом, как определить, находится ли точка в полигоне.

http://en.wikipedia.org/wiki/Point_in_polygon

Математика вычисляется медленнее, когда вы смотрите на сплайны Безье вместо сегментов линии, но рисование луча от точки все еще работает.

Ответ 5

Для полноты, я хочу сделать пару примечаний здесь:

По API 19 существует операция пересечения для путей. Вы можете создать очень маленькую квадратную дорожку вокруг своей тестовой точки, пересечь ее с помощью Path и посмотреть, пуст ли результат или нет.

Вы можете преобразовать пути в регионы и выполнить contains(). Однако регионы работают в целых координатах, и я думаю, что они используют преобразованные (пиксельные) координаты, поэтому вам придется работать с этим. Я также подозреваю, что процесс преобразования является вычислительно интенсивным.

Алгоритм кросс-пересечения, который Ханс опубликовал, хорош и быстр, но вы должны быть очень осторожны в определенных случаях угла, например, когда луч проходит непосредственно через вершину или пересекает горизонтальный край или при ошибке округления это проблема, которая всегда есть.

Метод номер обмотки является довольно убедительным доказательством, но включает в себя много триггеров и является дорогостоящим с точки зрения затрат.

Настоящая статья Дэна Воскреснения дает гибридный алгоритм, который столь же точным, как и число обмотки, но как вычислительно простое, как алгоритм лучевого каста. Это отпугло меня, как это было элегантно.

См. fooobar.com/info/512558/... для моего кода, который будет выполнять вычисление точки в пути для пути, состоящего из сегментов, дуг и кругов.