1/07/2007

Zaawansowane modele DirectX Managed i text

Na życzenie promotora zabrałem się za wprowadzanie etykietowania wierzchołków. Przyjąłem więc następujące założenia:
  • etykieta powinna być wyświetlana po prawej stronie wierzchołka
  • etykieta powinna być zawsze czytelna (niezależnie od obrotu grafu powinna być skierowana przodem do użytkownika)
Aby sprostać założeniom nie wystarczył zwykły Microsoft.DirectX.Direct3D.Font, gdyż choć potrafi on rysować tekst, ale nie można go przesuwać w przestrzeni. Po chwili poszukiwania znalazłem całkiem sympatyczną metodę FontToText w klasie Microsoft.DirectX.Direct3D.Mesh (z Microsoft.DirectX.Direct3DX.dll). Na podstawie System.Drawing.Font oraz stringa generuje nam całkiem ładny napis, który idealnie się nadaje jako etykieta.
Dolna lewa część napisu znajduje się w punkcie (0,0,0), więc aby dopiąć swego musiałem jeszcze tylko poobracać, poprzesuwać i namalować (przy pomocy metody samego Mesh'a).
Bawiłem się wcześniej trochę OpenGL'em i dlatego cała sprawa wyglądała dość prosto. Okazuje się jednak, że w DirectX trochę inaczej wyglądają zabawy z macierzami. Otóż sam DX'owy Matrix, choć umożliwia obroty to jednak wykonuje je zawsze wg. bezwzględnego punku (0,0,0), czyli wykonanie operacji: (tranlacja * obrót * transacja * -obrót) da identyczny wynik jak (translacja * translacja) i ni jak nie da się w prosty sposób po transacji obrócić nowo powstałego układu współrzędnych.
Trochę posiedziałem w dokumentacji i znalazłem metodę TranslateLocal w klasie MatrixStack, która całkiem dobrze sobie ze sprawą poradziła. Tylko, że zamiast:
  • matrix.rotate
  • matrix.translate
  • matrix.push
  • matrix.rotate
  • matrix.translate
  • matrix.pop
Jakby to miało miejsce w OpenGL kod musi wyglądać tak:
MatrixStack stack=new MatrixStack();
_device.VertexFormat = text.VertexFormat;
_device.Transform.World = DirectX.Matrix.Translation(new DirectX.Vector3(X,Y,Z));
_device.Transform.World *= DirectX.Matrix.RotationX(_worldTransformation.X) * DirectX.Matrix.RotationY(_worldTransformation.Y) * DirectX.Matrix.RotationZ(_worldTransformation.Z);
_device.Transform.World *= DirectX.Matrix.Translation(0.3f, -0.4f, 0);

stack.LoadMatrix(_device.Transform.World);
stack.MultiplyMatrixLocal(DirectX.Matrix.RotationX(-_worldTransformation.X));
stack.MultiplyMatrixLocal(DirectX.Matrix.RotationY(-_worldTransformation.Y));
stack.ScaleLocal(_textFont.Size/10, _textFont.Size/10, _textFont.Size/10);
_device.Transform.World = stack.Top;
text.DrawSubset(0);

Niby nie wielka różnica, ale zawsze.
Dodatkowo jest jeszcze kilka rzeczy odnośnie Mesh'a. Tekst, który zostanie przez niego wygenerowany będzie biały, aby to zmienić trzeba się pobawić materiałami i światłem, co nie bardzo mi pasowało (etykiety będą białe - też ładnie to wygląda). Rozmiar tekstu reguluje się przy pomocy skalowania, ponieważ Mesh jest nie czuły na Font.Size. Ważniejszą rzeczą jest jednak to, że przy wywołaniu funkcji DrawSubset zmieniany jest format wierzchołków w device podanym w konstruktorze i trzeba pamiętać, aby go ponownie ustawić, w przeciwnym razie nic więcej nam się dalej nie narysuje.

12/26/2006

Święta i ich atrybuty

Święta to niby czas w którym nie ma za dużo do roboty. Można spokojnie nadrobić zaległości z różnych dziedzin, miedzy innymi te w pisaniu inżynierki. Ale czy na pewno?
Jakiś tydzień temu włączył mi się straszny leń i jakoś za nic nie mogę się zabrać. Żeby nie było, że bezczynnie siedziałem, bo nie lubię takiego wrażenia sprawiać, napiszę co nieco o takiej miłej rzeczy jaką są csharpowe atrybuty.
Zastanawiałem się w jaki sposób można je wykorzystać w mojej pracy i chyba znalazłem dość ciekawe zastosowanie. Używam ich w dwóch miejscach.
Po pierwsze jest to oznaczenie funkcji, które mogą być uruchamiane w procesie przetwarzania wiadomości otrzymanych (te wysyłane w zawartości wiadomości).
Po drugie z lenistwa i to chyba jest ciekawszy przykład. Nie chciało mi się do GUI wklepywać obsługi każdego generatora grafów, więc postanowiłem, że stworze sobie atrybut, który będzie opisywać mi każdy generator. Na jego podstawie tworzę panel, który daje możliwość wyboru generatora, podania parametrów (dowolna ilość) i sprawdza poprawność wprowadzonych danych (bo przecież każdy może się pomylić :P). Na początku (generatory GNP, GNK, GNF) sprawa z walidacją danych była prosta int.Parse lub float.Parse, niestety potem okazało się, że jest również generator typu GNC co znacznie skomplikowało sprawę. Jest to jedyny generator, który jako parametr przyjmuje tablice. Wprowadziłem więc dodatkowy parametr do atrybutu którym jest nazwa funkcji która ma być wywołana przy walidacji i uruchomieniu generatora. Potem trochę operacji na typach i refleksji i po sprawie. Całość zajęła mi pewnie więcej czasu niż wyklikanie okienek, ale to mało istotne.
No dobra dosyć przydługich wstępów, teraz o atrybutach. Przedstawię sprawę w punktach:
1. Tworzymy standardową klasę, której konstruktor może, ale nie musi mieć parametry. Tu uwaga bo okazuje się, że parametrami mogą być tylko wyrażenia stałe, wyrażenia typeof oraz typy tablicowe.
2. W tej klasie piszemy jakieś miłe getery do tego co potrzebne
3. Dodajemy do klasy dziedziczenie po System.Attribute
4. Przed definicją klasy ustawiamy atrybut AttributeUsage, tu możemy określić sobie do czego stosowalny jest nasz atrybut (intelisense jak zwykle nie zawiedzie), czy jeden element może mieć wiele atrybutów tej klasy oraz czy atrybut może być dziedziczony.
Na tym etapie mamy już gotową klasę atrybutu, ustawienie atrybutu to prosta sprawa, ale jak potem z niego skorzystać? Kawałek kodu będzie si w tym miejscu:

//Pobieramy klasę
Type generatorsClass = typeof(Generators);
//Pobieramy metody statyczne (generatory to metody statyczne)
MethodInfo[] generators = generatorsClass.GetMethods(BindingFlags.Static | BindingFlags.Public);
foreach (MethodInfo info in generators)
{
try
{

//Pobieramy atrybuty metody typu takiego jak nas interesuje (pierwszy parametr) oraz nie dziedziczone (drugi parametr) w rezultacie dostajemy tablicę atrybutów o długości zależnej od ilości zdefiniowanych atrybutów danego typu
GeneratorAttribute[] methodAttributes = (GeneratorAttribute[])info.GetCustomAttributes(typeof(GeneratorAttribute), false);
//Robimy coś z atrybutem
}
catch { }
}


To byłoby na tyle. Całkiem prosta sprawa, a może czasem pomóc.

12/16/2006

cholerny SVN

Przy próbie ujednolicenia nazewnicwa w kodzie i strukturze katalogów, dziwnie i animagicznie posypał się svn - katalogi zaczęły ginąć tak z repozytorium, jak z mojego dysku. Wkurwiony lekko, bo efekty kilkugodzinnej pracy poszły wpizdu, całość przerzuciłem na CVSa. Teoretycznie więc mamy teraz dwa działające repozytoria ;)

12/13/2006

Postęp prac i głupie pomysły

Termin oddania inżynierki zbliża się wielkimi krokami. Nad programem pracuję już od jakiegoś czasu i zacząłem się zastanawiać, ile kodu już wygenerowałem. Jak zwykle jednak pomyślałem poco klikać i liczyć ręcznie jeśli może to za mnie zrobić komputer. Prosta sprawa: jedna klasa, mała rekurencja, trochę prostych operacji na plikach i gotowe. I tak zamiast pisać to co powinienem zabrałem się dziarsko za coś co w łącznym rozrachunku wydłużyło proces liczenia linii kodu o 15 minut.
Poco przez godzinę wykonywać żmudną pracę, skoro w trzy można zrobić program, który całą pracę wykona dokładniej i to w dwie minuty? Ale pocieszam się tym, że to nie tylko ja tak mam...

Oto link

12/12/2006

Start

Zatem startuje YAGDT dev-blog. Skąd pomysł? Nie mam pojęcia. Można chyba to traktować jako swego rodzaju kaprys.
YAGDT?
Akronim od Yet Another Graph Drawing Tool - nazwa kodowa mojej i klapera pracy inżynierskiej. Wpadliśmy na genialny pomysł napisania wspaniałego narzędzia rysującego grafy oraz przechowującego je w swego rodzaju rozproszonej bazie danych. Skąd wspaniałość? Bo cały projekt ma się w końcowym efekcie stać całkowicie rozszerzalnym przy pomocy różnego rodzaju wtyczek.
Co tu?
Raczej niekoniecznie będziemy tu wpisywać jakieś szczytne wydarzenia związane z projektem. Myślałem raczej o swoistym logu z tworzenia oraz wyjaśnieniu/przybliżeniu co ciekawszych problemów związanych z pisaniem pracy. Zatem sporo informacji o C# powinno się tu znaleźć. Co będzie dokładnie? Czas pokaże.