01 апреля 2011

Чужой код или "перепроверяй за камрадом"

Расскажу одну историю, которая приключилась со мной недавно. Если говорить "с начала", тогда стоит упомянуть, что недавно я познакомился с tween'ингом во flash. Понравились удобство, красота и прозрачность исполнения. Захотелось чего-то подобного в Delphi. Мне подсказали, что существует вот этот проект, в котором добрый человек как раз и реализовал анимацию между заданными значениями. Супер! О чем еще мечтать?! Но меня оттолкнули следующие вещи:
  • меняются только integer-параметры
  • событие на изменение параметра (хочется, чтобы оно само все меняло)
  • все работает за счет срабатывания стандартного таймера, нужно все перебросить в Update игрового цикла
  • нужен также tween'инг Vector-класса

Кода вроде немного - в основном идут загадочные функции для манипуляции над значениями. Поэтому я решил создать свой класс, "заточить" под себя, под текущие нужды. И вот сижу я, кручу свой класс, добавляю всякие методы, свойства, события. И добираюсь до основы - кода сложных функций, которые и создают плавную динамику изменения. Итак, я решил воспользоваться наработками камрада, залез в его AnimateEasing.pas, нашел функцию:

class function TAnimateEasing.elasticEaseOut(p: Extended; firstNum: integer; diff: integer): Extended;
var
  c, period, s, amplitude: Extended;
begin
  c := diff;

  if diff = 0 then Exit(c); //Divide by zero protect
  if p = 0 then Exit(firstNum);
  if p = 1 then Exit(c);

  period := 0.25;
  amplitude := c;

  if (amplitude < abs(c)) then
  begin
    amplitude := c;
    s := period / 4;
  end
  else
  begin
    s := period/(2*PI) * Math.ArcSin(c/amplitude);
  end;
  result := -(amplitude*Math.Power(2, -10*p) * sin( (p*1-s)*(2*PI)/period)) + c + firstNum;
end;

и начал копировать по частям к себе. И здесь для всяких копи-пастеров заготовлена бомба! Если бы я бездумно копировал код из Интернета, то попался бы на вот какие расставленные капканы:
  • при выполнении первого условия возвращается неверное значение (нужно вернуть firstNum)
  • третье условие также возвращает неверное значение (нужно вернуть diff + firstNum)
  • идем по коду дальше: мы сначала присваиваем amplitude := c; а потом при выполнении условия делаем это еще раз, зачем?
  • если последнее условие не выполняется, тогда мы присваиваем s := period/(2*PI) * Math.ArcSin(c/amplitude); но ведь чуть раньше мы указали amplitude := c; получается, под арксинусом у нас единица, а результат такого арксинуса равен PI/2, итого присвоение сводится к s := period / 4; так зачем нам вообще условие if-then-else? неясно...
В итоге в моем модуле этот код сократился до такого:

class function TBaseEasingFunctions.elasticEaseIn(aStartValue, aDiffValue, aUnitValue: Single): Single;
begin
  if (aDiffValue = 0) or (aUnitValue = 0) or (aUnitValue = 1) then
  begin
    if (aDiffValue = 0) then result := aStartValue; //Divide by zero protect
    if (aUnitValue = 0) then result := aStartValue;
    if (aUnitValue = 1) then result := aStartValue + aDiffValue;
    exit;
  end;
  result := (aDiffValue * Power(2, -10 * aUnitValue) * Sin((aUnitValue - 0.25/4)*(2*3.14)/0.25)) + aDiffValue + aStartValue;
end;

Без ошибок, удобней читать, выше производительность и вообще все здорово...
Остальные функции пока не переводил к себе, так как tween'инг elasticEaseIn самый широкоиспользуемый и я начал именно с него. Так что наше дело движется...

В общем, осталось собрать демку с анимационными движениями, описать здесь и выложить на всеобщее обозрение! Как вам такая идея?

Сообщения, схожие по тематике: