Как я могу избежать смещения внутри пола при наведении мыши на малых скоростях?

У меня есть простой алгоритм для объекта, который следует за указателем мыши с заданной максимальной скоростью в Java. Суть алгоритма такова:

// Set up displacement trackers for later.
// (These are used in another part of the algorithm, which rotates
//  the object to face along its path tangent with a moving average.)
double dx = 0, dy = 0;

// ... more code

// Find the angle of travel.
double angle = Math.atan2(m.y - target.getY(), m.x - target.getX());

// Set displacements with polar coordinate transforms.
dx = maxSpeed * Math.cos(angle);
dy = maxSpeed * Math.sin(angle);

// Now go there.
target.setX((int) Math.round(target.getX() + dx));
target.setY((int) Math.round(target.getY() + dy));

Это работает со скоростью 30 кадров в секунду. Производительность не проблема.

Код работает нормально при средних и больших значениях maxSpeed (5 и выше подходят), но при очень низких значениях код заставляет объект двигаться только под определенными углами. Например, в maxSpeed = 1 цель может двигаться только на 45 углов.


Вот как я понимаю проблему:

Пусть maxSpeed равно 1. Поэтому, поскольку Math.sin и Math.cos всегда возвращают значения в диапазоне [-1, 1], dy и dx также будут в диапазоне [-1, 1]. При преобразовании в целое число путем округления (поскольку целевые позиции x и y определены как переменные int), каждое смещение округляется до -1, 0 или 1, эффективно ограничивая возможное перемещение теми же восемью углами.

Так, например, если объект начинается в точке (0, 0), а я помещаю мышь в точку (300, 100), объект сначала будет двигаться строго горизонтально, а затем под углом -45. Я хотел бы, чтобы объект двигался под (приблизительно) постоянным углом по (приблизительно) прямой линии от точки отправления до места назначения.

Каков наилучший способ сделать это, если не считать преобразования базовых координат x и y в значения double?


person wchargin    schedule 23.03.2013    source источник
comment
Почему отвращение к использованию значений double?   -  person clstrfsck    schedule 24.03.2013
comment
Лучше всего, чтобы у вас была какая-то модель, которая использует double в качестве резервных значений, в то время как при их использовании для отображения вы вынуждены округлять их до int, потому что дисплей может использовать только их.   -  person Guillaume Polet    schedule 24.03.2013


Ответы (2)


Я чувствую, что ваше последнее предложение является главной подсказкой здесь. Чем более «правильным» будет ваше решение, тем больше оно начнет приближаться, просто отслеживая дробные части положения медленно движущегося объекта. Почему бы просто не сделать это?

Однако, если это абсолютно невозможно, всегда есть хаки.

Упростим задачу, сведя к одному направлению в одном измерении, исключив целую часть скорости и знак. У вас есть объект, движущийся со скоростью dx пикселей за кадр, dx находится в диапазоне от 0,0 до 1,0.

Вы делаете обновление кадра. На сколько целых пикселей вы перемещаете объект? Ноль или один?

Мы хотим обновляться не только с правильной вероятностью, но и более-менее плавно (равномерно). Более того, поскольку нашей целью является предсказуемое визуальное поведение, мы не хотим использовать случайность.

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

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

int last_frame = (int) (((double)(current_frame_number-1)) * dx);
int this_frame = (int) (((double)current_frame_number) * dx);
if( this_frame != last_frame ) {
   ++x; // advance one pixel
}

Для любого заданного (постоянного!) dx это должно дать правильный результат. На практике я ожидаю, что это даст достойный результат с dx, который время от времени меняется.

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

Вам придется добавить обратно неотъемлемые части, знаки и тот факт, что есть два измерения. Если вы двигаетесь со скоростью (2,7,-0,4) за кадр, то вы всегда перемещаетесь (2,0) за каждый кадр и используете описанный выше метод с dx=0,7, чтобы определить, двигаетесь ли вы дополнительно (1,0), и с dx=0,4, чтобы определить, двигаетесь ли вы дополнительно (0,-1).

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

person svk    schedule 23.03.2013
comment
Спасибо за ваш комментарий и предложение кода. Я решил просто перейти на double - вы правы, это намного проще. - person wchargin; 24.03.2013

Я немного заржавел в Java, но, возможно, вы могли бы использовать векторная математика в отличие от триггера?

double xPos, yPos; // should be initialized with the starting position and updated with each change in position - rounding on each update will accumulate errors and produce odd behavior as you described
// start of update
Vector2d direction = (new Vector2d(m.x - target.getX(), m.y - target.getY())).normalize(); // create unit vector in the direction of the target
Vector2d velocity = direction.scale(maxSpeed); // set the scalar speed in the direction of the target
xPos += velocity.x;
yPos += velocity.y; 
target.setX((int) xPos); // move (maxSpeed)*cos(angle) units in the x direction
target.setY((int) yPos); // move (maxSpeed)*sin(angle) units in the y direction
// end of update

... должно работать - быстрее, чем триггерный подход :).

person Nolo    schedule 23.03.2013
comment
Спасибо за ваш ответ, но я не думаю, что это решит проблему добавочных ошибок округления. Я также был бы удивлен, если бы векторные классы не использовали внутреннюю тригонометрию, по крайней мере, для метода normalize. Из-за аппроксимации ряда Тейлора (и др.) Тригонометрия может быть очень быстрой на современных компьютерах. - person wchargin; 24.03.2013