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.