Artykuły

A A A
Drukuj Ekportuj do PDF
Opublikowane: 2009.03.12 8:58 | Maciej Zbrzezny | Aktualizacja: 2010.01.21 2:04

Tworzenie napisów w WPF 3D

tagi: WPF
Artykuł jest poświęcony dodawaniu napisów do scen 3D, które są tworzone przy pomocy Windows Presentation Foundation (WPF). Artykuł opisuje dwa podejścia: napis&ów trój- i dwu- wymiarowych.
Jakiś czas temu postanowiłem rozpocząć moją przygodę grafiką 3D generowaną za pomocą Windows Presentation Foundation (WPF). Muszę przyznać, że biorąc pod uwagę prostotę z jaką można tworzyć grafikę 3D jest to na prawdę dobre narzędzie. Bez większych problemów opanowałem generowaną przy pomocy trójkątów w przestrzeni grafikę, tym bardziej, że bardzo wiele artykułów opisuje właśnie te elementy. Schody pojawiły się w momencie, gdy postanowiłem dodać do moich grafik 3D jakieś napisy. Dlatego właśnie tworzeniu napisów w scenach WPF 3D chciałbym poświęcić niniejszy artykuł.

Dwa podejścia przy tworzeniu napisów w scenach WPF 3D

Naszym celem jest więc umieszczenie jakichś napisów w tworzonej scenie 3D. Możemy tutaj wyróżnić dwa podejścia:
  • Tworzenie napisów 3D
  • Tworzenie napisów 2D w oparciu o lokalizacje 3D
Podejście pierwsze wymaga stworzenie (narysowanie) obiektu w przestrzeni a następnie pokrycie go napisem. W tym celu tworzony jest pędzel z napisem, którym maluje się obiekt w przestrzeni. Przykładowa scena zawierająca tak stworzone napisy znajduje się na poniższym rysunku:



Podejście drugie wymaga na warstwie zawierającej Viweport3D umieszczenie warstwy zawierającej obiekt typu Canvas, który jest przeżroczysty, na nim umieszczamy TexBlock'i w odpowiedniej lokalizacji, która związana jest z pewnym punktem w przestrzeni. Podejście takie przedstawione jest na rysunku poniżej:




Oczywiście, każde rozwiązanie ma swoje wady i zalety. Etykiety 3D, bazujące na pierwszym podejściu, są bardziej przydatne, gdy tekst jest elementem sceny. Napisy tworzone przy pomocy drugiego podejścia, są zwykle bardziej czytelne, ale bardziej odróżniają się od sceny i obsługa ich jest bardziej skomplikowana. Rysunek poniżej przedstawia porównanie obydwu metod:



Rozwiązanie w praktyce


Przykładowy kod dołączony do artykułu rysuje układ współrzędnych. Scena może być obracana i położenie kamery może być zmieniane przy użyciu suwaków. Pola wyborów znajdujące się w lewym panelu okna aplikacji pozwalają na włączanie i wyłączanie widzialności poszczególnych elementów sceny (w szczególności etykiet 3D i 2D).
Aplikacja może się na pierwszy rzut oka wydawać skomplikowana, jednak są w niej wykorzystane proste elementy WPF 3D i każdy powinien zrozumieć o co w niej chodzi. W tym artykule chciałbym się skupić na tworzeniu napisów i dwóch podejściach opisanych powyżej.

Napisy przestrzenne (WPF 3D)

Aby zobaczyć jak tworzyć etykiety 3D, zobaczymy jak działa funkcja, która to realizuje: CreateTextLabel3D. Ta funkcja jest odpowiedzialna za wytworzenie obiektu typu ModelVisual3D , zawierającego etykiety 3D. Funkcja ta będzie miała następującą deklarację:
[Kod C#]
/// <summary>
/// Creates a ModelVisual3D containing a text label.
/// </summary>
/// <param name="text">The string to be drawn</param>
/// <param name="textColor">The color of the text.</param>
/// <param name="isDoubleSided">Visible from both sides?</param>
/// <param name="height">Height of the characters</param>
/// <param name="basePoint">The base point of the label</param>
/// <param name="isBasePointCenterPoint">if set to <c>true</c> the base point
/// is center point of the label.</param>
/// <param name="vectorOver">Horizontal direction of the label</param>  
/// <param name="vectorUp">Vertical direction of the label</param>  
/// <returns>Suitable for adding to your Viewport3D</returns>
/// <remarks>Two vectors: vectorOver and vectorUp are creating the surface
/// on which we are drawing the text. Both those vectors are used for
/// calculation of label size, so it is reasonable that each co-ordinate
/// should be 0 or 1. e.g. [1,1,0] or [1,0,1], etc...</remarks>
public static ModelVisual3D CreateTextLabel3D( string text, Brush textColor,
    bool isDoubleSided, double
    height, Point3D basePoint, bool isBasePointCenterPoint, Vector3D vectorOver,
    Vector3D vectorUp);
Jak ona działa?
  1. W pierwszym kroku tworzymy TextBlock , który zawiera naszą etykietę. 

    [Kod C#]

    TextBlock textblock = newTextBlock(newRun(text));

    textblock.Foreground = textColor; // setting the text color

    textblock.FontFamily = newFontFamily("Arial"); // setting the font to be used



  2. W drugim kroku, tworzymy pędzel i materiał TextBlock , który zawiera stworzony w poprzednim kroku.

    [Kod C#]

    DiffuseMaterial mataterialWithLabel = new DiffuseMaterial();

    // Allows the application of a 2-D brush,

    // like a SolidColorBrush or TileBrush, to a diffusely-lit 3-D model.

    // we are creating the brush from the TextBlock

    mataterialWithLabel.Brush = new VisualBrush(textblock);



  3. Teraz czas jest na przygotowanie objektu 3D, który zostanie pokryty tekstem

    [Kod C#]

    //calculation of text width (assumming that characters are square):

    double width = text.Length * height;

    // we need to find the four corners

    // p0: the lower left corner; p1: the upper left

    // p2: the lower right; p3: the upper right

    Point3D p0 = basePoint;

    // when the base point is the center point we have to set it up in different way

    if(isBasePointCenterPoint)

    p0 = basePoint - width / 2 * vectorOver - height / 2 * vectorUp;

    Point3D p1 = p0 + vectorUp * 1 * height;

    Point3D p2 = p0 + vectorOver * width;

    Point3D p3 = p0 + vectorUp * 1 * height + vectorOver * width;

    // we are going to create object in 3D now:

    // this object will be painted using the (text) brush created before

    // the object is rectangle made of two triangles (on each side).

    MeshGeometry3D mg_RestangleIn3D = new MeshGeometry3D();

    mg_RestangleIn3D.Positions = new Point3DCollection();

    mg_RestangleIn3D.Positions.Add(p0); // 0

    mg_RestangleIn3D.Positions.Add(p1); // 1

    mg_RestangleIn3D.Positions.Add(p2); // 2

    mg_RestangleIn3D.Positions.Add(p3); // 3

    // when we want to see the text on both sides:

    if (isDoubleSided)

    {

    mg_RestangleIn3D.Positions.Add(p0); // 4

    mg_RestangleIn3D.Positions.Add(p1); // 5

    mg_RestangleIn3D.Positions.Add(p2); // 6

    mg_RestangleIn3D.Positions.Add(p3); // 7

    }

    mg_RestangleIn3D.TriangleIndices.Add(0);

    mg_RestangleIn3D.TriangleIndices.Add(3);

    mg_RestangleIn3D.TriangleIndices.Add(1);

    mg_RestangleIn3D.TriangleIndices.Add(0);

    mg_RestangleIn3D.TriangleIndices.Add(2);

    mg_RestangleIn3D.TriangleIndices.Add(3);

    // when we want to see the text on both sides:

    if (isDoubleSided)

    {

    mg_RestangleIn3D.TriangleIndices.Add(4);

    mg_RestangleIn3D.TriangleIndices.Add(5);

    mg_RestangleIn3D.TriangleIndices.Add(7);

    mg_RestangleIn3D.TriangleIndices.Add(4);

    mg_RestangleIn3D.TriangleIndices.Add(7);

    mg_RestangleIn3D.TriangleIndices.Add(6);

    }



  4. W kolejnym kroku pokrywamy stworzony przed chwilą obiekt 3D używając materiału stworzonego na początku.
    [Kod C#]
    // texture coordinates must be set to
    // stretch the TextBox brush to cover
    // the full side of the 3D label.
    mg_RestangleIn3D.TextureCoordinates.Add(newPoint(0, 1));
    mg_RestangleIn3D.TextureCoordinates.Add(newPoint(0, 0));
    mg_RestangleIn3D.TextureCoordinates.Add(newPoint(1, 1));
    mg_RestangleIn3D.TextureCoordinates.Add(newPoint(1, 0));
    // when the label is double sided:
    if (isDoubleSided)
    {
    mg_RestangleIn3D.TextureCoordinates.Add(newPoint(1, 1));
    mg_RestangleIn3D.TextureCoordinates.Add(newPoint(1, 0));
    mg_RestangleIn3D.TextureCoordinates.Add(newPoint(0, 1));
    mg_RestangleIn3D.TextureCoordinates.Add(newPoint(0, 0));
    }

  5. Teraz jest czas na stworznie obiektu ModelVisual3D  z napisem

    [Kod C#]
    ModelVisual3D result = newModelVisual3D();

    // we are setting the content:

    // our 3D rectangle object covered with materila that is made of label

    // (TextBox with text)

    result.Content = newGeometryModel3D(mg_RestangleIn3D, mataterialWithLabel); 

    return result;



Napisy płaskie (WPF 2D)

To podejście jest bardziej skomplikowane od poprzedniego, dlatego trudno przygotować funkcję, która jak to miało miejsce w poprzednim przypadku wykonywała wszystkie czynności.

Pierwszym krokiem jest stworzenie dwóch nakładających się warstw:

  • Viewport3D jako warstwy spodniej
  • Canvas jako przeźroczystej warstwy nałożonej
Kod XAML realizujący dwie nakładające się warstwy może wyglądać następująco:

[Kod XAML]

<Grid ClipToBounds="True">

<Viewport3D Name="mainViewport" ClipToBounds="True" Grid.Column="0" Grid.Row="0">

<Viewport3D.Camera>

<PerspectiveCamera

FarPlaneDistance="100"

LookDirection="-11,-10,-9"

UpDirection="0,1,0"

NearPlaneDistance="1"

Position="11,10,9"

FieldOfView="70" />

</Viewport3D.Camera>

<ModelVisual3D>

<ModelVisual3D.Content>

<DirectionalLight

Color="White"

Direction="-2,-3,-1" />

</ModelVisual3D.Content>

</ModelVisual3D>

</Viewport3D>

<Canvas Name="mainViewportCanvas" ClipToBounds="True" Grid.Column="0" Grid.Row="0"></Canvas>

</Grid>




Kolejnym zadaniem jest lokalizacja punktu ze sceny 3D na nałożonej warstwie typu Canvas. Do transformacji lokalizacji można użyć funkcję podobną do poniższej:

Kod C#]

public static Point Get2DPoint(Point3D p3d, Viewport3D vp)

{

bool TransformationResultOK;

Viewport3DVisual vp3Dv = VisualTreeHelper.GetParent(

vp.Children[0]) as Viewport3DVisual;

Matrix3D m = MathUtils.TryWorldToViewportTransform(vp3Dv, out TransformationResultOK);

if (!TransformationResultOK) return new Point(0, 0);

Point3D pb = m.Transform(p3d);

Point p2d = new Point(pb.X, pb.Y);

return p2d;

}



Zauważmy, że ta funkcja używa funkcji  TryWorldToViewportTransform z pakietu 3D Tools (dostępnego tutaj): _3DTools.MathUtils.TryWorldToViewportTransform.


Kolejnym krokiem jest utworzenie i lokalizacja obiektu TextBlock:

Kod C#]

UIElementIModelVisual3D.GetUIElement(ModelVisual3DFilter FilterSettings, Viewport3D DestinationViewport3D)

{

if (FilterSettings.Texts2D)

{

TextBlock tb = new TextBlock();

tb.Text = Description;

Point p2d = Panel3DMath.Get2DPoint(this.Point3D, DestinationViewport3D);

Canvas.SetTop(tb, p2d.Y);

Canvas.SetLeft(tb, p2d.X);

return tb;

}

else

return new UIElement();

}



Ostatecznie dodajemy TextBlock (utworzonego w poprzednim kroku) do naszego obiektu canvas.
[Kod C#]

this.mainViewportCanvas.Children.Add(element.GetUIElement(filter,mainViewport));



Podsumowanie

Mam nadzieję, że czytelników zainteresowały moje dwa podejścia do tworzenia napisów na potrzeby scen 3D tworzonych w WPF. Artykuł powinien pojawić się również na moim blogu, zapraszam do odwiedzania mojego bloga: http://maciej-progtech.blogspot.com/ , na którym można znaleźć również inne ciekawe informacje.

Historia tego artykułu


  • 2009.03.04 - Powstanie angielskiej wersji artykułu (opublikowany na CodeProject )
  • 2009.03.09 - Powstanie polskiej wersji artykułu

Załączniki:


Podobne artykuły

Komentarze 0 Masz uwagi do tej strony? Napisz

Dodaj komentarz

avatar

Zaloguj się lub Zarejestruj się aby wykonać tę czynność.

Autor Maciej Zbrzezny
avatar
 

Nazywam się Maciej Zbrzezny, jestem programistą, który interesuje się i zajmuje również architekturą oprogramowania. Tworzę oprogramowanie wykorzystujące platformę .NET(głównie C#) i OPC.

Załóż konto
CodeGuru to miejsce dla każdego programisty. Przez lata portal rozwijany był siłami społeczności i to właśnie społeczność programistów jest tutaj najważniejsza. CG od wielu lat gromadzi wokół siebie coraz większą grupę pasjonatów. Warto być jej częścią!

Dowiedz się więcej o CodeGuru

Geek Club - Windows Phone

 

MetroOne

Idź na górę strony