Adobe Acrobat 3D, JavaScript i animacje. Część 1.

No Comments

No i stało się. Stanąłem przed koniecznością oprogramowania animacji modeli trójwymiarowych w Acrobacie 3D. Na szczęście chodziło “tylko” o opracowanie kodu, gdyż z grafiką 3D chwilowo jestem na bakier (czytaj: jedynym programem, który udało mi się choć trochę zrozumieć, byl Google SketchUp). Z Acrobatem 3D, siłą rzeczy – również.
Zagadnienie było proste: otrzymuję model utworzony w Adobe Acrobat 3D Toolkit (plik .u3d), zawierający kilka animacji. Animacje pokazują przejścia pomiędzy różnymi wersjami produktu. Klient zaś ma widzieć ciąg animacji, pokazujący przejście od aktualnie pokazywanego, do wybranego przez siebie wariantu. Skomplikowane? Jeszcze nie…

Dysklajmer, czy jakmutam
Nie uważam się za guru w dziedzinie oprogramowania Acrobata 3D w JavaScript, więc jeśli ktoś zna łatwiejsze/szybsze rozwiązanie podanego poniżej problemu, chętnie się czegoś nowego nauczę :)

Warto na początek zaopatrzyć się w darmowy mini eBook od Adobe, “Controlling Animations”. Posiada on załączone dwa pliki z kodem JavaScript:

  • AnimationController – klasa kontrolera animacji, całkiem przydatna
  • Document JavaScript – stąd przyda nam się tylko pierwsza funkcja

Po zapisaniu gdzieś pliku z klasą AnimationController, należy dołączyć na końcu deklaracje naszych animacji. Służy do tego następujący kod:

var Animation1 = new AnimationController(scene.animations.getByIndex(0));
var Animation2 = new AnimationController(scene.animations.getByIndex(1));
// (...)
var AnimationN = new AnimationController(scene.animations.getByIndex(N));

EDIT: jak się okazało, niektóre wersje Acrobata Pro (lub Readera) lubią mylić indeksy animacji. Dlatego polecam inny sposób przypisania animacji:

var Animation1 = new AnimationController(scene.animations.getByName("Sequence-1"));
var Animation2 = new AnimationController(scene.animations.getByName("Sequence-2"));
// (...)
var AnimationN = new AnimationController(scene.animations.getByName("Sequence-N"));

Zabawę czas zacząć. Tworzymy nowego PDFa. Importujemy do niego nasz model 3D (Tools->Advanced Editing->3D Tool).
W oknie, które się pojawi, wybieramy plik z modelem (3D Model) oraz plik z kontrolerem animacji (Default Script). Aby kontroler działał prawidłowo, należy jeszcze wybrac Default Animation Style: None.

Teraz, dla ułatwienia dostępu do animacji, ładujemy do pliku PDF deklarację funkcji Context() z drugiego pliku dołączonego do wspomnianego PDFa oraz funkcję inicjalizacji 3D, którą nie-pamiętam-skąd wziąłem :)
Czyli wybieramy Advanced->Document Processing->Document JavaScripts, wpisujemy dowolną nazwę nowego skryptu i wybieramy Edit. Następnie wpisujemy naszą krótką funkcję:

function Context()
{
    return getAnnots3D(0)[0].context3D;
}
 
// TU BĘDĄ DEKLARACJE NASTĘPNYCH FUNKCJI

W powyższym miejscu będziemy również wpisywać wszystkie deklaracje, inicjalizacje zmiennych etc. Dlatego kiedy podane będzie źródło funkcji, czy obiektu (w JavaScript i tak wyglądają tak samo :)), będzie umieszczane właśnie w miejscu powyższego komentarza.

Pod spodem zaś dopisujemy lekko zmienioną w stosunku do oryginału funkcję inicjalizacji i uruchomienie interwału, który ją odpala:

function initialize()
{
  // sprawdzanie, czy obiekt 3D jest zainicjalizowany
  if(waitingFor3DActivated)
  {
    var a3d = getAnnots3D(0)[0];
    if(a3d.activated)
    {
      // obiekt zainicjalizowany, teraz można już na nim pracować
      waitingFor3DActivated = false;
      app.clearInterval(timeout);
      context3D = a3d.context3D;
      // TU BĘDZIE INICJALIZACJA ZMIENNYCH I OBIEKTÓW
    }
  }
}
 
// uruchamianie sprawdzania inicjalizacji obiektu 3D
timeout = app.setInterval("initialize()", 1000 );
var waitingFor3DActivated = true;

W pierwotnej wersji funkcja uruchamiała się 10 razy, po czym, jeśli obiekt nie był do tego czasu aktywny, interwał był usuwany. Szczerze mówiąc, nie wiem, czemu. Zwłaszcza, że korzystniej jest we właściwościach modelu ustawić Enable 3D When: The page containing the annotation is opened.

Teraz do animacji za pośrednictwem AnimationController możemy się dostać w ten sposób:

Context().Animation1.play();

Dobrze, podsumujmy, co osiągnęliśmy do tej pory. Stworzyliśmy nowy dokument PDF, zaimportowaliśmy go, dołączyliśmy skrypty umożliwiające korzystanie z dobrodziejstw utworzonych wcześniej animacji. Co teraz?

Zastanówmy się, co chcemy uzyskać. W moim przypadku animacje były krótkie i pokazywały przekształcenia jednego produktu pomiędzy kilkoma wariantami. Można to przykładowo opisać jako:

Produkt: długopis.
Warianty:

1. z zatyczką i wkładem
2. z zatyczką, bez wkładu
3. z wkładem, bez zatyczki
4. z wkładem, bez zatyczki, z klipsem do kieszeni
5. bez wkładu, bez zatyczki

Stąd animacje muszą być tak skonstruowane, aby za pomocą skończonej ich liczby można było pokazać płynne przejście pomiędzy dwoma dowolnymi różnymi od siebie wariantami produktu.

Przykładowo, posiadamy animacje:

1. zdejmowanie/zakładanie (w zależności od kierunku odtwarzania) zatyczki
2. wyjmowanie/wkładanie (w zależności od kierunku odtwarzania) wkładu
3. odłączanie/dołączanie (w zależności od kierunku odtwarzania) klipsa

Stąd jeśli pokazujemy wariant 1 (zatyczka, wkład), a chcemy przejść do wariantu 4 (wkład, klips), musimy uruchomić animację 1 (zdejmowanie zatyczki), a następnie animację 3 (dołączanie klipsa). Myślę, że jest to w miarę zrozumiałe.

Jak to miało ogólnie wyglądać:

  • użytkownik otwiera dokument PDF. Model 3D aktywuje się automatycznie (należy we właściwościach modelu wybrać Enable 3D When: The page containing the annotation is opened)
  • model pokazuje “wyjściowy” wariant produktu
  • użytkownik klika przycisk “Pokaż inne warianty”, rozwija się menu wyświetlające listę wszystkich dostępnych wariantów (aktualnie wybrany również, tylko w postaci nieaktywnej)
  • po wybraniu z menu innego wariantu, wyświetla się sekwencja animacji, pokazująca przejście od aktualnego wariantu do wybranego.

Moje założenia były takie, że należy określić najkrótszą drogę pomiędzy dwoma wariantami, kierując się wyłącznie liczbą przeskoków (wariantów pośrednich) pomiędzy nimi. Czyli długość animacji była czynnikiem pomijalnym.

Cóż było robić. Pisanie czas zacząć :)

Najpierw tworzymy obiekt animacji, zawierający interesujące nas właściwości:

function extAnim(myID, myStart, myEnd, rev)
{
  this.animID    = myID;
  this.animStart = myStart;
  this.animEnd   = myEnd;
  this.reversed  = !!rev;
 
  // metoda zwracajaca animacje w kierunku przeciwnym
  this.reverse = function()
  {
    return new extAnim(this.animID, this.animEnd, this.animStart, true);
  }
}

Następnie należałoby zgromadzić wszystkie animacje w jakiś uporządkowany sposób w jednym miejscu. Posłuży do tego obiekt listy animacji:

// funkcja pomocnicza
function in_array(what, where) 
{
  var isthere = false, key;
  for (key in where)
    if (where[key] == what) 
    {
      isthere = true;
      break;
    }
  return isthere;
}
function animList()
{
  // lista animacji
  this.anims  = new Array();
  this.points = new Array();
 
  // dodawanie animacji do listy
  this.add = function(x)
  {
    this.anims.push(x);
    // od razu odwracanie
    this.anims.push(x.reverse());
 
    // dodaj poczatek i koniec animacji do listy punktow animacji,
    // jesli jeszcze tam sie nie znajduja
    if(!in_array(x.animStart, this.points))
      this.points.push(x.animStart);
    if(!in_array(x.animEnd, this.points))
      this.points.push(x.animEnd);
    // sortuj liste punktow animacji
    this.points.sort();
  };
 
  // zwrocenie ilosci animacji
  this.count = function() 
  { 
    return this.anims.length;
  };
 
  // zwracanie animacji na podstawie punktu poczatkowego i koncowego
  this.getAnim = function(getStart, getEnd)
  {
    for(var a = 0; a < this.anims.length; a++)
      if(this.anims[a].animStart == getStart && this.anims[a].animEnd == getEnd)
        // dlatego dodajemy animacje podwojnie - wersje normalna i odwrocona
        return Array(this.anims[a].animID, this.anims[a].reversed, a);
  };
}

Warto zauważyć, że dodawanie pojedynczej animacji spowoduje też dodanie jej duplikatu z odwróconym kierunkiem. Przyda się to później przy wyszukiwaniu drogi pomiędzy dwoma wariantami produktu.

Następnie możemy utworzyć naszą listę animacji. Jest to utrudnione koniecznością pewnego przewidywania możliwych wariantów, niemniej jednak i tak jest to łatwiejsze, niż tworzenie ogromnej liczby animacji, aby wziąć pod uwagę wszystkie możliwe kombinacje. Posiłkując się wcześniejszym przykładem produktu i animacji, wychodzi coś takiego:

// inicjalizacja nowej listy animacji
var myList = new animList();
 
// dodawanie kolejnych animacji do listy
myList.add(new extAnim(Context().Animation1, 
                       'zatyczka, wklad', 
                       'wklad'));
myList.add(new extAnim(Context().Animation1, 
                       'zatyczka', 
                       'bez zatyczki i wkladu'));
myList.add(new extAnim(Context().Animation2, 
                       'zatyczka, wklad', 
                       'zatyczka'));
myList.add(new extAnim(Context().Animation2, 
                       'wklad', 
                       'bez zatyczki i wkladu'));
myList.add(new extAnim(Context().Animation3, 
                       'wklad', 
                       'wklad, klips'));

Nie było tak źle, prawda?

Następnym razem przeliczymy ścieżkę pomiędzy dwoma wariantami i uruchomimy kilka animacji jedna po drugiej. W trzecim, poskładamy wszystko w całość.

Leave a Reply

Your email address will not be published. Required fields are marked *