Как связать круг внутри эллипса?

Название для этого сообщения было довольно сложно думать, поэтому, если вы можете придумать более описательный заголовок, пожалуйста, скажите мне. Во всяком случае, моя проблема довольно специфична и требует некоторых простых математических знаний. Я пишу приложение С# WinForms, которое немного похоже на старое приложение xeyes Linux. В основном это набор глаз, которые следуют вокруг вашего курсора мыши. Сначала это может показаться легким, однако может стать довольно сложным, если вы такой перфекционист, как я: P. Это мой код до сих пор (только метод paint, который вызывается с интервалом 16).

int lx = 35;
int ly = 50;
int rx;
int ry;

int wx = Location.X + Width / 2;
int wy = Location.Y + Height / 2;

Rectangle bounds = Screen.FromControl(this).Bounds;

// Calculate X

float tempX = (mx - wx) / (float)(bounds.Width / 2);

// Calculate Y

float tempY = (my - wy) / (float)(bounds.Height / 2);

// Draw eyes

e.Graphics.FillEllipse(Brushes.LightGray, 10, 10, 70, 100);
e.Graphics.FillEllipse(Brushes.LightGray, 90, 10, 70, 100);

// Draw pupils (this only draws the left one)

e.Graphics.FillEllipse(Brushes.Black, lx += (int)(25 * tempX), ly += (int)(40 * tempY), 20, 20);

Теперь это работает на базовом уровне, однако иногда это может произойти, если пользователь помещает курсор в 0,0.

Ученик может выйти из главного глаза.

Теперь мой вопрос: как это исправить? Что бы оператор IF был проверить, где находится указатель мыши, а затем уменьшить зрачок X в зависимости от этого?

Спасибо.

Изменить: Здесь я получаю позиции мыши (my и mx):

private void timer_Tick(object sender, EventArgs e)
{
    mx = Cursor.Position.X;
    my = Cursor.Position.Y;

    Invalidate();
}

Таймер запускается в событии eyes_Load, а интервал - 16.

Изменить 2: Окончательное решение: http://pastebin.com/fT5HfiQR

Ответы

Ответ 1

Моделирование глазного яблока как следующего эллипса:

введите описание изображения здесь

Его уравнение:

введите описание изображения здесь

И строка, соединяющая ее центр и курсор:

введите описание изображения здесь

(не беспокойтесь об сингулярности)

Затем мы можем решить, чтобы получить точку пересечения:

введите описание изображения здесь

Где

введите описание изображения здесь

Теперь вы можете рассчитать расстояние до края глазного яблока, разделив расстояние от центра на курсор сигмой. Остается только интерполировать, чтобы ограничить положение ученика:

введите описание изображения здесь

введите описание изображения здесь

Оператор if, который вы хотите, затем

введите описание изображения здесь


(N.B. для math-mo там выше было небольшое упрощение, предполагающее, что ваш эллипс не слишком узкий, точное решение не аналитично)


EDIT: мои тесты в VB.NET:

введите описание изображения здесь

введите описание изображения здесь

введите описание изображения здесь


EDIT 2: порт С#

PointF Bound(double xc, double yc, double w, double h, double xm, double ym, double r)
{
   double dx = xm - xc, dy = ym - yc;
   if (Math.Abs(dx) > 0.001 && Math.Abs(dy) > 0.001) 
   {
      double dx2 = dx * dx, dy2 = dy * dy;
      double sig = 1.0 / Math.Sqrt(dx2 / (w * w * 0.25) + dy2 / (h * h * 0.25));
      double d = Math.Sqrt(dx2 + dy2), e = d * sig;
      if (d > e - r)
      {
         double ratio = (e - r) / d;
         return new PointF((float)(xc + dx * ratio),
                        (float)(yc + dy * ratio));
      }
   }
   return new PointF((float)xm, (float)ym);
}
  • xc, yc: центральные координаты эллипса
  • w, h: Ширина и высота эллипса
  • xm, ym: координаты мыши
  • r: Радиус круга, который вы хотите ограничить (ученик).
  • Возвращает: точка, в которой вы хотите разместить центр круга

EDIT 3: Большое спасибо Quinchilion за следующую оптимизацию (gawd, черт побери, это сильно ударило меня в лицо)

PointF Bound(double xc, double yc, double w, double h, double xm, double ym, double r)
{
    double x = (xm - xc) / (w - r);
    double y = (ym - yc) / (h - r);
    double dot = x*x + y*y;
    if (dot > 1) {
        double mag = 1.0 / Math.Sqrt(dot);
        x *= mag; y *= mag;
    }
    return new PointF((float)(x * (w - r) + xc), (float)(y * (h - r) + yc));
}