Получение координаты точки после поворота. AS3

1
Я не очень силен в тригонометрии, поэтому постоянно приходится искать формулы или же готовые решения к моим проблемам. Будь то движение по окружности, движение к точки по радиусу и т.д.
Одна из таких давних проблем — как найти координату точки, после поворота объекта.
пример

Не найдя решения на скорую руку, я обычно по средствам флеша, запихивал какой нибудь мувиклип внутрь нужного объекта и проверял уже его координаты. Но сейчас понадобилась именно формула.
В общем наткнулся на эту статью и написал функцию для получения координаты.
<br />
private function getNewPoint ( startPoint:Point, centerPoint:Point, currentAngleRad:Number ) :Point {
var endPoint:Point = new Point ();
startPoint = new Point ( startPoint.x - centerPoint.x, startPoint.y - centerPoint.y );
endPoint.x = startPoint.x * Math.cos( currentAngleRad ) - startPoint.y * Math.sin( currentAngleRad );
endPoint.y = startPoint.x * Math.sin( currentAngleRad) + startPoint.y * Math.cos( currentAngleRad );
endPoint.x += centerPoint.x;
endPoint.y += centerPoint.y;
return endPoint;
}

И так, чтобы получить новую координату точки в startPoint передаем координаты точки при начальном ее положении, то есть как бы она располагалась без поворота объекта ( Cx + Px1, Cy + Py1 ).
В centerPoint точку относительно которой было произведен поворот ( Cx, Cy ).
И в currentAngleRad угол на который был повернут объект в радианах.

Ссылка на фла файл.
  • +6

Комментарии (20)

0
можно использовать матрицу трансформаций и никакой тригонометрии
private function getNewPoint ( startPoint:Point, centerPoint:Point, currentAngleRad:Number ) :Point {
    var endPoint:Point = new Point ();
    var mx : Matrix = new Matrix();
    mx.translate(startPoint.x, startPoint.y);
    mx.rotate(currentAngleRad:Number );
    mx.translate(centerPoint.x, centerPoint.y);
    endPoint = mx.transformPoint(endPoint);
    return endPoint;
}

кстати, в некоторых случаях, при получении необходимого угла для передачи в аргументы можно брать угол поворота у контейнера:
getNewPoint ( startPt, centerPt, container.matrix.transform.rotation )
где container — это спрайт, который вы поворачиваете
0
cemaprjl
Во-первых, «никакой тригонометрии» — это ты пошутил :) она есть, внутри метода rotate()
Во-вторых, даже если вынести mx в поле класса, у тебя все равно создается новый объект Point при вызове метода transformPoint. И от этого никак не избавиться (привет тормозному Adobe, который никак не научится передавать необязательную ссылку на объект-результат).

Если добавить обязательный (хороший тон да) для таких функций необязательный (пардон за каламбур) параметр endPoint в оба ваших варианта и провести тест, то выяснится, что вариант автора работает как минимум вдвое, а то и почти втрое быстрей.

Код.

import flash.geom.Point;
import flash.geom.Matrix;
import flash.utils.getTimer;
import flash.utils.setTimeout;

const MNOGO:int = 100000;

var startPoint:Point = new Point(123.5, 232.1);
var centerPoint:Point = new Point(12.4, 45.6);
var endPoint:Point = new Point();
var angle:Number = Math.PI / 3;

setTimeout(test, 3000);

// Собственно тест
function test():void
{
	var i = MNOGO;
	var startTime1:int = getTimer();
	while (i--)
	{
		getNewPoint1(startPoint, centerPoint, angle, endPoint);
	}
	startTime1 = getTimer() - startTime1;

	i = MNOGO;
	var startTime2:int = getTimer();
	while (i--)
	{
		getNewPoint2(startPoint, centerPoint, angle, endPoint);
	}
	startTime2 = getTimer() - startTime2;
	
	trace(startTime1, startTime2);
}

// Несколько оптимизированный вариант Nrjwolf
function getNewPoint1 (startPoint:Point, centerPoint:Point, currentAngleRad:Number, endPoint:Point = null):Point
{
	if (!endPoint) { endPoint = new Point(); }

	var dx:Number = startPoint.x - centerPoint.x;
	var dy:Number = startPoint.y - centerPoint.y;
	var cos:Number = Math.cos(currentAngleRad);
	var sin:Number = Math.sin(currentAngleRad);
	endPoint.x = dx * cos - dy * sin + centerPoint.x;
	endPoint.y = dx * sin + dy * cos + centerPoint.y;

	return endPoint;
}

// Несколько оптимизированный вариант cemaprjl
var mtx:Matrix = new Matrix();
function getNewPoint2 (startPoint:Point, centerPoint:Point, currentAngleRad:Number, endPoint:Point = null):Point
{
    if (!endPoint) { endPoint = new Point(); }

    mtx.identity();
    mtx.translate(startPoint.x, startPoint.y);
    mtx.rotate(currentAngleRad);
    mtx.translate(centerPoint.x, centerPoint.y);
    endPoint = mtx.transformPoint(endPoint);

    return endPoint;
}

У меня выводит что-то такое:

39 110
42 109
43 107
0
Хотя вот сейчас вижу, что манипуляции с необязательным endPoint во втором варианте бесполезны (спасибо Adobe).
0
хотел написать, но если мой способ все-равно медленнее то уже пофиг )
0
Вообще мой вариант не про оптимизацию, а про немного более понятно выглядящий код.
Так то все верно вы с кроликом говорите и про переиспользование матриц и про тригонометрию внутри
+1
Это я понял, что не про оптимизацию. Просто важней, чтобы подобные функции работали быстрей, чем выглядели понятней.

Однако твой вариант тоже показателен. По правде сказать, первый вариант выигрывает лишь в нетипичной задаче «поворот тысяч произвольных точек вокруг тысяч произвольных центров на тысячи произвольных углов» или в случае с парой-тройкой точек. На практике же матрицу, которая присутствует в твоем варианте, мы готовим один раз для целой группы точек. Тот же поворот квада аннулирует сразу три вызова троицы «translate-rotate-translate» (а ведь сюда еще может добавиться и scale, и skew). Ну и последним гвоздем относительно первого варианта может стать кастомный класс матрицы, которая таки умеет в метод transformPoint получать не только точку, подлежащую трансформации, но и точку, в которую мы запишем результат. В таком случае в полевых условиях подход как у автора потерпит фиаско.

Вот так все непросто :)
+1
главное знать разные доступные варианты и понимать где и что лучше применять

з.ы. навскидку еще приходит в голову третий вариант, использовать Point.polar(), который тоже позволяет «на центрах вертеть» какую-нибудь точку. Только применимо к задаче автора к входному углу надо будет добавлять угол стартовой точки.
0
Ага, только еще корень квадратный добавится — баш на баш выйдет(?)
+1
var mx: Matrix = new Matrix();
это пипец :) Да еще и в EnterFrame???

для таких случаев придумали indentity(); у матрицы, чтоб не создавать каждый раз новый объект.

Если из Point ничего кроме x/y не используется — рекомендую создать свой класс из 2х полей x/y для экономии памяти + снижения обращений к GC (если это в EnterFrame все).

Да и вообще в релиз советую «разворачивать» руками такие методы в точку вызова. А в идеале инлийн использовать.
0
ИМХО пипец — это безобразная работа сборщика мусора такая, что люди вынуждены пулы объектов и прочую муть изобретать вроде хранения экземпляра матрицы, чтобы не дай бог новую не создавать. Это ж всю красоту и стройность кода рушит.

А inline разве есть в as3? Вручную же разворачивать — путь к проблемам с поддержкой кода.
+1
Inline есть. Вот тут почитать можно gamespoweredby.com/blog/2014/01/where-how-and-why-to-use-inline-in-as3/
Что касается сборщика мусора — дело не в том. Он соберет его качественно. Но если мусора будет много — то приостановит runtime до завершения цикла сборки. И многие тормоза вызываются уже не тем, что проц слабый, а тем, что слишком много мусора для GC наплодилось
0
О, спасибо за инфу. Не знал.
+1
имхо пипец не знать, что операции выделения памяти даже в нативном коде — одни из ресурсоемких…
0
Ну у нас же тут не нативный код. И предопределенный объект Matrix, который вполне ясно как используется. Что мешает добавить оптимизировать рантайм, избавив от этого пользователей?

На самом деле с управляемым кодом выделение памяти может быть очень быстрым, гораздо быстрее чем стандартное выделение памяти на куче в с/с++. По сути это может быть просто return nextFreePtr; nextFreePtr += sizeof(newObject); Да, память будет фрагментироваться, но при очередном проходе сборщика мусора ее можно дефрагментировать.

Ну и в нативном коде на c++ никто не мешает написать custom allocator, и прекрасно пользоваться дальше new/delete не теряя ни производительности, ни удобства написания кода.

А вообще, прошу прощения за холиварность моего комментария.
0
У каждой виртуальной машины есть свои правила и рекомендации работы. Что нам дали — с тем работаем :) В идеале должна быть вообще кнопка «сделать хорошо». Но раз её нет — приходится работать с тем, что нам дали :)

Никто же не жалуется на то, что Nape самый быстрый среди 2D физики для Flash :) Все радуются этому гениальному движку. Который как раз и разрабатывался с учетом специфики AVM :) Он с ней считался — он получился лидером )
0
И чтобы сделать nape, Лука был вынужден использовать haxe и написать к нему еще и препроцессор свой. Т.к. иначе — никак.
0
Отговорка :) Что на выходе? ABC файл? Да! Тогда Haxe не имеет ничего весомого в этой цепочки на сегодня.
Без препроцессора он не смог бы реализовать в бородатые годы банальный инлайн. Просто в то время не мог стандартный флешовый компилятор сделать то, что он задумал.

И заметь — это все не отменяет правил и рекомендаций по работе с виртуальной машиной флеш плеера. Haxe ничего нового не вписывал в swf файл, чего небыло во флеш плеере.
0
Вобще-то типа да. Твой посыл насчет плохой архитектуры ВМ флеша — верен. В манажед-коде можно и оптимизацию выделения своим манагером пямяти делать и встроенные пулы объектов, чтоб не напрягать этим конечного программиста, — но этого там нет. Поэтому и исходить надо из этого. По факту код с матрицами медленнее, чем без них. И это из-за неглубоких знаний особенностей флеша в частности. Я как-бы больше про это, т.к. на код реализации флешплеера мы не можем повлиять, поэтому должны учитывать его особенности.
0
Советую глянуть как работает флешовая функци localToGlobal (особенно полезно когда нужна точка вложенная многократно и прокрученная и отскейленная)
  • Vel
  • Vel
0
Спасибо, пригодилось
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.