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 самый широкоиспользуемый и я начал именно с него. Так что наше дело движется...

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

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

6 коммент.:

  1. Ждем демку с нетерпением

    ОтветитьУдалить
  2. откровенно скажу, недавно при решении задачи дважды заполнил массив одинаковыми значениями, при этом циклы выглядели по разному. такие вещи можно попросту упустить когда обращаешь внимание на что-то другое. потому при повторном просмотре кода все это правится и сводится к нормальному виду.
    по-моему писать класс, заточенный под собственные нужды это отличная идея, особенно если взять во внимание код из готового проекта))
    приведенный участок кода уж слишком богат на "капканы" :) Может автор куда-то спешил или работал в потоке, потом его кто-то отвлек, он сбился и...

    p.s
    может стоит объединить
    if (aDiffValue = 0) then result := aStartValue;
    if (aUnitValue = 0) then result := aStartValue;
    в
    if (aDiffValue = 0 or aUnitValue = 0) then result := aStartValue;
    ?
    у тебя дважды вычисляется каждое условие(с начала все три вместе, потом по отдельности), можно ведь как-то обойтись одним сравнением? это не принципиально и врятли даст прирост к скорости, только сделает код менее читабельным, но все же любопытно :)
    Демку кроме исполняемой еще и в видео-формате, пожалуйста!))

    ОтветитьУдалить
  3. soofX, ок, постараюсь в начале следующей недели выложить))

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

    может он и сбился, просто вроде во второй ревизии он комментарии добавлял - странно, что не проверил основной код, который выполняется постоянно...

    по поводу условий и увеличения производительности... конечно, можно сделать так:
    if(aDiffValue = 0)then
    Exit(aStartValue);
    и так по всем условиям...
    но меня такие конструкции расстраивают, делают лично для меня код менее читабельным...

    ОтветитьУдалить
  4. if(aDiffValue = 0)then
    result := aStartValue;
    почему бы не объединить это условие вместе с
    if(aUnitValue = 0)then
    result := aStartValue;
    вот о чем я :)
    да и мне скорее любопытно как ты относишься к таким конструкциям
    if (aDiffValue = 0 or aUnitValue = 0) then result := aStartValue;
    чем то, даст ли это прирост производительности)

    ОтветитьУдалить
  5. Aero, сразу виден программист java))) мелочь, а выдает тебя с головой - отошел от делфи...
    скобки в выражении (aDiffValue = 0 or aUnitValue = 0) выставлены для делфи неверно ;) а в java, вроде, как раз так и расставляются))

    по поводу применимости конструкции - да, перепишу как ты предложил, нормальная такая конструкция, то есть, если быть формальным: мое отношение "положительное"))

    ОтветитьУдалить
  6. Делфи я запускаю в основном, что бы руками написать структуру, которая уже есть в Java.

    ОтветитьУдалить