• Nie Znaleziono Wyników

Budowa aplikacji w technologii .NET wykład 13 – Grafika 3D

N/A
N/A
Protected

Academic year: 2021

Share "Budowa aplikacji w technologii .NET wykład 13 – Grafika 3D"

Copied!
49
0
0

Pełen tekst

(1)

Budowa aplikacji w technologii .NET

wykład 13 – Grafika 3D

Grafika 3D w aplikacjach:

• DirectX lub OpenGL

• złożony model programistyczny i wymagania sprzętowe ograniczają ich użycie w tworzeniu interfejsu aplikacji

Grafika 3D w WPF:

• Nowy model grafiki trójwymiarowej.

• Oparcie budowy obiektów 3D o język znaczników i znane elementy: geometrie, pędzle, transformacje, animacje, wiązanie danych.

• Klasy pomocnicze zapewniają dodatkową funkcjonalność, np. obracanie myszą, hit-testy.

• Nie nadaje się do wymagających aplikacji (np. gier).

• Ręczne definiowanie złożonych scen jest mało praktyczne i podatne na błędy.

(2)

Podstawy

Cztery podstawowe składniki:

• Viewport – widok, przechowuje zawartość 3D

• Obiekty 3D

• Źródła światła – oświetlają scenę 3D lub jej fragment

• Kamera – zapewnia punkt obserwacyjny

Viewport3D

• Element interfejsu, umieszczany w oknie, a zarazem kontener na scenę 3D.

Własność Children zawiera obiekty sceny (w tym źródła oświetlenia)

• Własność Camera

Obiekty trójwymiarowe

• Dziedziczą z System.Windows.Media.Media3D.Visual3D

(3)

Visual3D – bazowa dla wszystkich obiektów 3D, możemy z niej dziedziczyć lub użyć ModelVisual3D i zdefiniować geometrię obiektu. Te obiekty wrzucamy do viewportu.

Geometry3D – analogicznie do Geometry do obrazów 2D – reprezentuje siatkę obiektu. Dziedziczy z niej MeshGeometry3D.

GeometryModel3D opakowuje geometrię 3D, dodając do niej dane o materiale (kolorze, teksturze), następnie jest używany do wypełnienia Visual3D.

Transform3D – klasy RotateTransform3D, ScaleTransform3D,

TranslateTransform3D, Transform3DGroup, MatrixTransform3D odpowiadają dwuwymiarowym transformacjom.

<Viewport3D>

<ModelVisual3D>

<ModelVisual3D.Content>

<GeometryModel3D>

<GeometryModel3D.Geometry>

<MeshGeometry3D ...>

</GeometryModel3D.Geometry>

</GeometryModel3D>

</ModelVisual3D.Content>

</ModelVisual3D>

</Viewport3D>

(4)

Geometria

• Tworzenie obiektu 3D rozpoczyna się od budowy jego geometrii. Służy do tego klasa MeshGeometry3D.

MeshGeometry3D reprezentuje siatkę obiektu (mesh). Siatka złożona jest z trójkątów – to najprostszy sposób definiowania powierzchni (wystarczą trzy punkty) i są powszechnie wykorzystywane w grafice 3D. Wszelkie inne obiekty są definiowane jako złożenie odpowiedniej liczby trójkątów.

Właściwości klasy MeshGeometry3D:

Positions – kolekcja wszystkich punktów siatki (wierzchołków trójkątów). Często jeden punkt jest wierzchołkiem kilku trójkątów, np: sześcian wymaga 12 trójkątów, ale tylko 8 wierzchołków).

TriangleIndices – definicja trójkątów. Każdy trójkąt kolekcji jest reprezentowany przez trzy indeksy punktów z kolekcji Positions.

Normals – to wektory prostopadłe do powierzchni (a raczej prostopadłe do stycznej do powierzchni), definiowane dla wszystkich wierzchołków siatki, są używane do obliczeń oświetlenia.

TextureCoordinates – określa, jak tekstura 2D ma być mapowana na obiekt 3D.

Dla każdego punktu kolekcji Positions dostarcza punkt 2D.

(5)

Jednostki nie są ważne – pozycja kamery i transformacje określą finalny rozmiar obiektu.

Układ współrzędnych – prawoskrętny (oś x w prawo, y do góry, z w kierunku patrzącego).

<MeshGeometry3D Positions="-1,0,0 0,1,0 1,0,0"

TriangleIndices="0,2,1" />

Nie musimy definiować normalnych i tekstur (jeśli wypełnienie to SolidColorBrush).

• Kolejność definiowania punktów nie ma znaczenia, ale znaczenie ma kolejność podawania indeksów wierzchołków. Kolejność musi być przeciwna do ruchu wskazówek zegara – określa to jednoznacznie przód i tył trójkąta (każdy może być wypełniony inną teksturą, często tył w ogóle nie jest rysowany).

(6)

GeometryModel3D

• Opakowuje obiekt MeshGeometry3D.

• Posiada trzy właściwości:

Geometry – przyjmuje obiekt reprezentujący kształt (MeshGeometry3D).

Material i BackMaterial – definiują powierzchnię z jakiej zbudowany jest kształt. Określa ona kolor (lub teksturę) obiektu oraz sposób reakcji na oświetlenie.

• WPF udostępnia cztery klasy materiałów:

DiffuseMaterial – płaska, matowa powierzchnia. Rozprasza światło równomiernie we wszystkich kierunkach. Najczęściej używana.

SpecularMaterial – lśniąca, błyszcząca powierzchnia. Naśladuje metal lub szkło. Odbija światło jak lustro (ale nie geometrię).

EmissiveMaterial – świecąca powierzchnia, generuje własne światło (ale nie jest źródłem światła).

MaterialGroup – pozwala na łączenie materiałów (np. SpecularMaterial dodający odblask do DiffuseMaterial).

(7)

<DiffuseMaterial

Brush="Yellow"/> <MaterialGroup>

<DiffuseMaterial Brush="Yellow"/>

<SpecularMaterial Brush="White"/>

</MaterialGroup>

<MaterialGroup>

<DiffuseMaterial Brush="Yellow"/>

<EmissiveMaterial Brush="Red"/>

</MaterialGroup>

(8)

Przykład:

<GeometryModel3D>

<GeometryModel3D.Geometry>

<MeshGeometry3D ... />

</GeometryModel3D.Geometry>

<GeometryModel3D.Material>

<DiffuseMaterial Brush="Yellow"/>

</GeometryModel3D.Material>

</GeometryModel3D>

(Nie określiliśmy BackMaterial, a zatem trójkąt będzie widoczny tylko od przodu.)

(9)

Źródła światła

• Uzyskanie cieniowania wymaga dodania do sceny jednego lub kilku źródeł światła.

• Model oświetlenia w WPF korzysta z wielu uproszczeń: oświetlenie obliczane jest osobno dla każdego obiektu (obiekty nie rzucają na siebie cieni ani odbić),

oświetlenie obliczane jest dla wierzchołków i interpolowane na pozostałej powierzchni trójkąta.

• WPF udostępnia cztery klasy oświetlenia:

DirectionalLight – równoległe promienie padające we wskazanym kierunku (jak światło słoneczne).

AmbientLight – światło rozproszone (zazwyczaj używane w połączeniu z innymi źródłami światła).

PointLight – źródło punktowe, emituje światło z pewnego punktu przestrzeni we wszystkich kierunkach.

SpotLight – źródło stożkowe, emituje światło z punktu, w określonym kierunku.

(10)

Przykład:

<DirectionalLight Color="White" Direction="-1,0,-1" />

(Długość wektora nie ma znaczenia, tylko kierunek.)

Źródła światła dodawane są do viewportu jak obiekty geometrii:

<Viewport3D>

<Viewport3D.Camera>...</Viewport3D.Camera>

<ModelVisual3D>

<ModelVisual3D.Content>

<DirectionalLight Color="White"

Direction="-1,0,-1" />

</ModelVisual3D.Content>

</ModelVisual3D>

<ModelVisual3D>

<ModelVisual3D.Content>

<GeometryModel3D>...</GeometryModel3D>

</ModelVisual3D.Content>

</ModelVisual3D>

</Viewport3D>

(11)

Kamera

• Kamera reprezentuje obserwatora. Znajduje się w pewnym położeniu i jest skierowana w pewnym kierunku. Określa jak scena 3D jest rzutowana na powierzchnię 2D.

• WPF udostępnia trzy klasy kamer:

PerspectiveCamera – rzut perspektywiczny.

OrthographicCamera – rzutowanie równoległe: z punktem rzutowania w nieskończoności.

MatrixCamera – pozwala określić własną macierz rzutowania.

• Należy określić:

położenie kamery (Position)

wektor określający orientację (LookDirection) – najłatwiej określić jako różnicę punktu na który patrzymy i punktu w którym znajduje się kamera (CenterPointOfInterest – CameraPosition)

dodatkowo można podać UpDirection – określa pochylenie kamery

(12)

Przykład:

<Viewport3D>

<Viewport3D.Camera>

<PerspectiveCamera Position="0,0.5,3"

LookDirection="0,0,-1"

UpDirection="0,1,0" />

</Viewport3D.Camera>

...

</Viewport3D>

• Inne przydatne właściwości:

FieldOfView – odpowiednik

ogniskowej, określa kąt widzenia (w OrthographicCamera odpowiednikiem jest Width)

NearPlaneDistance i

FarPlaneDistance – określają minimalną i maksymalną odległość renderowania (domyślnie odpowiednio 0.125 i Double.PositiveInfinity)

(13)

Złożone sceny 3D

Sześcian składa się z 8 punktów i 12 trójkątów (po dwa na ścianę).

<MeshGeometry3D Positions="0,0,0 10,0,0 0,10,0 10,10,0

0,0,10 10,0,10 0,10,10 10,10,10"

TriangleIndices="0,2,1 1,2,3 0,4,2 2,4,6 0,1,4 1,5,4 1,7,5 1,3,7 4,5,6 7,6,5 2,6,3 3,6,7" />

<PerspectiveCamera Position="16,16,21"

LookDirection="-3,-3,-4"

UpDirection="0,1,0" />

...

<DirectionalLight Color="White"

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

...

<DiffuseMaterial Brush="LawnGreen"/>

(14)

Normalne są liczone nie dla trójkątów, a dla punktów – rodzi to problemy, gdy punkt jest współdzielony. Definiując osobno 24 punkty (po cztery na ścianę) normalne będą

prostopadłe do każdej ściany.

<MeshGeometry3D Positions="0,0,0 10,0,0 0,10,0 10,10,0 0,0,0 0,0,10 0,10,0 0,10,10 0,0,0 10,0,0 0,0,10 10,0,10 10,0,0 10,10,10 10,0,10 10,10,0 0,0,10 10,0,10 0,10,10 10,10,10 0,10,0 0,10,10 10,10,0 10,10,10"

TriangleIndices="0,2,1 1,2,3 4,5,6 6,5,7 8,9,10 9,11,10 12,13,14 12,15,13 16,17,18 19,18,17 20,21,22 22,21,23" />

(15)

Możemy też sami ustawić normalne, np. aby uzyskać efekt płynnego przejścia (dobre do naśladowania gładkich struktur).

<MeshGeometry3D Positions="0,0,0 10,0,0 0,10,0 10,10,0

0,0,10 10,0,10 0,10,10 10,10,10"

TriangleIndices="0,2,1 1,2,3 0,4,2 2,4,6 0,1,4 1,5,4 1,7,5 1,3,7 4,5,6 7,6,5 2,6,3 3,6,7"

Normals="0,1,0 0,1,0 1,0,0 1,0,0 0,1,0 0,1,0 1,0,0 1,0,0" />

(16)

Uwaga: ze względu na wydajność, należy ograniczać liczbę oddzielnych siatek oraz obiektów Visual3D. Pomaga w tym Model3DGroup:

<ModelVisual3D>

<ModelVisual3D.Content>

<Model3DGroup x:Name="Scene">

<AmbientLight ... />

<DirectionalLight ... />

<DirectionalLight ... />

<Model3DGroup x:Name="Character01">

<Model3DGroup x:Name="Torso">

<GeometryModel3D>...</GeometryModel3D>

</Model3DGroup>

<Model3DGroup x:Name="Head">...

</Model3DGroup>

<Model3DGroup x:Name="Arms">...

</Model3DGroup>

<Model3DGroup x:Name="Legs">...

</Model3DGroup>

</Model3DGroup>

...

</ModelVisual3D.Content>

</ModelVisual3D>

(17)

Zaawansowane materiały

• DiffuseMaterial może być rysowany również przy użyciu innych pędzli niż SolidColorBrush (LinearGradientBrush, RadialGradientBrush, ImageBrush, VisualBrush).

• Aby z nich skorzystać, należy dostarczyć informację na temat mapowania pędzla 2D na powierzchnię 3D.

• Służy do tego właściwość MeshGeometry.TextureCoordinates: każdemu położeniu w przestrzeni 3D (wierzchołkowi) przypisuje położenie na teksturze (w przestrzeni 2D).

• Użyjmy tekstury z obrazka:

<GeometryModel3D.Material>

<DiffuseMaterial>

<DiffuseMaterial.Brush>

<ImageBrush ImageSource="wood.jpg"></ImageBrush>

</DiffuseMaterial.Brush>

</DiffuseMaterial>

</GeometryModel3D.Material>

(18)

• Nałożenie jej na poniższy kształt nie wystarczy, aby stał się on widoczny.

<MeshGeometry3D Positions="0,0,0 10,0,0 0,10,0 10,10,0 0,0,0 0,0,10 0,10,0 0,10,10 0,0,0 10,0,0 0,0,10 10,0,10 10,0,0 10,10,10 10,0,10 10,10,0 0,0,10 10,0,10 0,10,10 10,10,10 0,10,0 0,10,10 10,10,0 10,10,10"

TriangleIndices="0,2,1 1,2,3 4,5,6 6,5,7 8,9,10 9,11,10 12,13,14 12,15,13 16,17,18 19,18,17 20,21,22 22,21,23"/>

• Wytłuszczona ściana (dwa trójkąty) ma wierzchołki o współrzędnych:

(0,0,0) (0,0,10) (0,10,0) (0,10,10)

• Przypisujemy im odpowiednie współrzędne na teksturze (względne, zatem z przedziału [0,1]):

(1,1) (0,1) (1,0) (0,0)

(19)

• Podobnie postępujemy z pozostałymi:

<MeshGeometry3D ...

TextureCoordinates="0,0 0,1 1,0 1,1 ...

1,1 0,1 1,0 0,0 0,0 1,0 0,1 1,1 1,1 0,0 0,1 1,0 1,1 0,1 1,0 0,0 1,1 0,1 1,0 0,0"/>

W podobny sposób możemy używać innych pędzli, w tym VisualBrush lub pędzli animowanych.

(20)

Przykład – tworzenie geometrii w kodzie:

<Viewport3D>

<Viewport3D.Camera>

<PerspectiveCamera Position="0,2.5,2.5"

LookDirection="0,-1,-1" UpDirection="0,1,0" />

</Viewport3D.Camera>

<ModelVisual3D>

<ModelVisual3D.Content>

<DirectionalLight Color="White"

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

</ModelVisual3D.Content>

</ModelVisual3D>

<ModelVisual3D>

<ModelVisual3D.Content>

<GeometryModel3D x:Name="mymodel">

<GeometryModel3D.Material>

...

</GeometryModel3D.Material>

</GeometryModel3D>

</ModelVisual3D.Content>

</ModelVisual3D>

</Viewport3D>

A w kodzie:

mymodel.Geometry = Create(50, 50, 1);

(21)

// za: http://blogs.msdn.com/b/wpf3d/

public static MeshGeometry3D Create(int tDiv, int pDiv, double radius) {

double dt = 2*Math.PI / tDiv;

double dp = Math.PI / pDiv;

MeshGeometry3D mesh = new MeshGeometry3D();

for (int pi = 0; pi <= pDiv; pi++) {

double phi = pi * dp;

for (int ti = 0; ti <= tDiv; ti++) {

double theta = ti * dt;

mesh.Positions.Add(GetPosition(theta, phi, radius));

mesh.Normals.Add(GetNormal(theta, phi));

mesh.TextureCoordinates.Add(GetTextureCoordinate(theta, phi));

} }

(22)

for (int pi = 0; pi < pDiv; pi++) {

for (int ti = 0; ti < tDiv; ti++) {

int x0 = ti;

int x1 = (ti + 1);

int y0 = pi * (tDiv + 1);

int y1 = (pi + 1) * (tDiv + 1);

mesh.TriangleIndices.Add(x0 + y0);

mesh.TriangleIndices.Add(x0 + y1);

mesh.TriangleIndices.Add(x1 + y0);

mesh.TriangleIndices.Add(x1 + y0);

mesh.TriangleIndices.Add(x0 + y1);

mesh.TriangleIndices.Add(x1 + y1);

} }

mesh.Freeze();

return mesh;

}

(23)

private static Point3D GetPosition(double theta, double phi, double radius) {

double x = radius * Math.Sin(theta) * Math.Sin(phi);

double y = radius * Math.Cos(phi);

double z = radius * Math.Cos(theta) * Math.Sin(phi);

return new Point3D(x, y, z);

}

private static Vector3D GetNormal(double theta, double phi) {

return (Vector3D)GetPosition(theta, phi, 1.0);

}

private static Point GetTextureCoordinate(double theta, double phi) {

Point p = new Point(theta / (2 * Math.PI), phi / (Math.PI));

return p;

}

(24)

• Niestety, stworzenie złożonej sceny 3D w XAMLu nie jest proste.

• Istnieją gotowe narzędzia do budowania złożonych scen 3D w WPF, np.:

◦ ZAM 3D

▪ http://www.erain.com/Products/ZAM3D

◦ Blender

▪ http://blender.org

▪ http://codeplex.com/xamlexporter

◦ Wtyczki do profesjonalnych programów 3D (np. Maya, LightWave)

◦ http://blogs.msdn.com/mswanson/articles/WPFToolsAndControls.aspx

(25)

Animacje 3D

• Najwygodniejszy sposób animowania sceny 3D to transformacje.

• Transformować możemy:

◦ Model3D lub Model3DGroup (pojedynczą siatkę)

◦ ModelVisual3D (całą scenę)

◦ źródło światła

◦ kamerę

Przykład – obracający się sześcian

• Definiujemy transformację obrotu:

<ModelVisual3D.Transform>

<RotateTransform3D CenterX="5" CenterZ="5">

<RotateTransform3D.Rotation>

<AxisAngleRotation3D x:Name="rotate" Axis="0 1 0" />

</RotateTransform3D.Rotation>

</RotateTransform3D>

</ModelVisual3D.Transform>

(26)

• Teraz możemy dodać slidera:

<Slider Grid.Row="1" Minimum="0" Maximum="360"

Orientation="Horizontal"

Value="{Binding ElementName=rotate, Path=Angle}" />

• Lub animację:

<Button Grid.Row="1">

<Button.Content>Go!</Button.Content>

<Button.Triggers>

<EventTrigger RoutedEvent="Button.Click">

<BeginStoryboard>

<Storyboard RepeatBehavior="Forever">

<DoubleAnimation

Storyboard.TargetName="rotate"

Storyboard.TargetProperty="Angle"

To="360" Duration="0:0:2.5"/>

</Storyboard>

</BeginStoryboard>

</EventTrigger>

</Button.Triggers>

</Button>

(27)

Przemieszczając (TranslateTransform) kamerę wzdłuż ścieżki (AnimationUsingPath) lub według klatek (AnimationUsingKeyFrames) można uzyskać efekt poruszającego się obserwatora.

<Storyboard>

<Point3DAnimationUsingKeyFrames Storyboard.TargetName="kamera"

Storyboard.TargetProperty="Position">

<LinearPoint3DKeyFrame Value="21,-6,-6" KeyTime="0:0:3"/>

<LinearPoint3DKeyFrame Value="-6,-6,-11" KeyTime="0:0:6"/>

<LinearPoint3DKeyFrame Value="-11,16,16" KeyTime="0:0:9"/>

<LinearPoint3DKeyFrame Value="16,16,21" KeyTime="0:0:12"/>

</Point3DAnimationUsingKeyFrames>

<Vector3DAnimationUsingKeyFrames Storyboard.TargetName="kamera"

Storyboard.TargetProperty="LookDirection">

<LinearVector3DKeyFrame Value="-20,15,15" KeyTime="0:0:3"/>

<LinearVector3DKeyFrame Value="15,15,20" KeyTime="0:0:6"/>

<LinearVector3DKeyFrame Value="20,-15,-15" KeyTime="0:0:9"/>

<LinearVector3DKeyFrame Value="-15,-15,-20" KeyTime="0:0:12"/>

</Vector3DAnimationUsingKeyFrames>

</Storyboard>

(28)

Wskazówka: warto dodawać komplet transformacji (i nadawać im nazwy przy pomocy x:Name), by następnie móc animować wybrane. W przykładzie umieszczono dwie translacje, bo przesunięcie przed i po obrocie działa inaczej.

<Model3DGroup.Transform>

<Transform3DGroup>

<TranslateTransform3D

OffsetX="0" OffsetY="0" OffsetZ="0"/>

<ScaleTransform3D ScaleX="1" ScaleY="1" ScaleZ="1"/>

<RotateTransform3D>

<RotateTransform3D.Rotation>

<AxisAngleRotation3D Angle="0" Axis="0 1 0"/>

</RotateTransform3D.Rotation>

</RotateTransform3D>

<TranslateTransform3D

OffsetX="0" OffsetY="0" OffsetZ="0"/>

</Transform3DGroup>

</Model3DGroup.Transform>

(29)

Wydajność

• Renderowanie scen 3D wymaga o wiele większej pracy niż renderowanie scen 2-D

• W przypadku animacji sceny 3D, WPF próbuje odświeżać zmienione fragmenty 60 razy na sekundę

• W zależności od skomplikowania sceny, może się zdarzyć, że zasoby pamięciowe karty graficznej się skończą, co doprowadzi do spadku liczby wyświetlanych ramek na sekundę

W jaki sposób poprawić wydajność renderowanych scen 3D?

• Jeżeli nie ma potrzeby przycinania zawartości, która wystaje poza Viewport, należy ustawić właściwość Viewport3D.ClipToBounds na false

• Jeżeli nie ma potrzeby sprawdzania kliknięć w scenie 3-D, należy ustawić właściwość Viewport3D.IsHitTestVisiblena false

• Jeżeli Viewport jest większy niż potrzeba, należy go zmniejszyć

• Jeśli niewygładzone krawędzie kształtów 3D nam nie przeszkadzają – można ustawić w Viewporcie własność dołączoną RenderOptions.EdgeMode na Aliased.

(30)

Tworzenie wydajnych siatek i modeli

• Lepiej stworzyć pojedynczą bardziej skomplikowaną siatkę niż kilka prostych

• Jeżeli istnieje potrzeba wykorzystania różnych materiałów dla jednej siatki, należy zdefiniować obiekt MeshGeometry jednokrotnie (jako zasób), a następnie używać go do tworzenia wielu obiektów GeometryModel3D

• Kiedykolwiek to możliwe, należy otaczać grupę obiektów GeometryModel3D obiektem Model3DGroup i umieścić ten obiekt w pojedynczym obiekcie ModelVisual3D

• Nie należy definiować materiału tylnego w przypadku, gdy użytkownik nigdy nie będzie widział tylnej części obiektu

• Lepiej używać pędzli typu Solid, Gradient, Image niż DrawingBrush i VisualBrush

• Używając DrawingBrush lub VisualBrush do odrysowania statycznej zawartości należy ustawić w pędzlu dołączoną właściwość RenderOptions.CachingHint na wartość Cache

(31)

Hit Testing

• Rozpoznawania obszaru, który kliknięto (lub wskazano) myszą.

• Możemy to zrobić na jeden z dwóch sposobów:

◦ Obsłużyć zdarzenia myszy w viewporcie i posługując się metodą

VisualTreeHelper.HitTest() zlokalizować obiekt, którego dotyczy zdarzenie.

◦ Zastąpić obiekt ModelVisual3D obiektem ModelUIElement3D, który posiada obsługę zdarzeń.

(32)

Sposób pierwszy

• Obsługę zdarzenia dodajemy do viewportu.

<Viewport3D MouseDown="Viewport3D_MouseDown">

...

</Viewport3D>

Sprawdzamy w jaki ModelVisual3D kliknięto.

private void Viewport3D_MouseDown(...) {

Viewport3D viewport = (Viewport3D)sender;

Point location = e.GetPosition(viewport);

HitTestResult hitResult =

VisualTreeHelper.HitTest(viewport, location);

if (hitResult != null && hitResult.VisualHit == kostka) {

// Kliknięto kostkę }

}

(33)

• Jeśli to informacja nie wystarczy – możemy zlokalizować właściwy element GeometryModel3D lub MeshGeometry3D:

RayMeshGeometry3DHitTestResult meshHitResult =

hitResult as RayMeshGeometry3DHitTestResult;

if (meshHitResult != null) {

if (meshHitResult.ModelHit == ...) ...

if (meshHitResult.MeshHit == ...) ...

// punkt 3D w który kliknięto meshHitResult.PointHit...

}

(34)

Drugi sposób

• Pierwszy sposób jest nieco żmudny i wymaga szukania w kodzie elementu, którego dotyczy zdarzenie.

• Innym rozwiązaniem jest zastąpienie obiektu ModelVisual3D obiektem z hierarchii UIElement3D: ModelUIElement3D lub ContainerUIElement3D.

• Dodają one do elementów 3D obsługę myszy, klawiatury, etc. (ale nie layouty).

<Viewport3D x:Name="viewport">

<Viewport3D.Camera>...</Viewport3D.Camera>

<ModelUIElement3D MouseDown="element_MouseDown">

<ModelUIElement3D.Model>

<Model3DGroup>...

</Model3DGroup>

</ModelUIElement3D.Model>

</ModelUIElement3D>

</Viewport3D>

• Jeśli chcemy umieścić kilka elementów umożliwiających interakcję, powinniśmy dodać kilka ModelUIElement3D w jednym ContainerUIElement3D (poza

obiektami ModelUIElement3D może on przechowywać również zwykłe ModelVisual3D).

(35)

Umieszczanie elementów interfejsu na obiektach 3D

Pierwszym sposobem jest wykorzystanie VisualBrush jako tekstury:

◦ kopiuje on tylko wygląd elementu,

◦ brak interakcji z elementem.

Klasa Viewport2DVisual3D pozwala umieścić element na powierzchni 3D (zgodnie z mapowaniem teksturowania):

◦ taki element w pełni zachowuje swoją funkcjonalność.

• Usuwamy jedną ze ścian sześcianu – (12,13,14) (12,15,13).

Zamiast niej dodajemy Viewport2DVisual3D do viewportu.

• Geometria to nasza usunięta ściana.

• Tekstura składa się z tła (imitacja drewna) i fragmentu interfejsu (formularza).

• Jest on w pełni funkcjonalny.

<Viewport2DVisual3D>

<Viewport2DVisual3D.Geometry>

<MeshGeometry3D

Positions="10,0,0 10,10,10 10,0,10 10,10,0"

TriangleIndices="0,1,2 0,3,1"

TextureCoordinates="1,1 0,0 0,1 1,0" />

</Viewport2DVisual3D.Geometry>

(36)

<Viewport2DVisual3D.Material>

<MaterialGroup>

<DiffuseMaterial>

<DiffuseMaterial.Brush>

<ImageBrush ImageSource="wood.jpg"/>

</DiffuseMaterial.Brush>

</DiffuseMaterial>

<DiffuseMaterial

Viewport2DVisual3D.IsVisualHostMaterial="True" />

</MaterialGroup>

</Viewport2DVisual3D.Material>

<Viewport2DVisual3D.Visual>

<Border CornerRadius="10" BorderBrush="DarkGoldenrod"

BorderThickness="1">

<StackPanel Margin="10">

<TextBlock Margin="3">Wprowadź dane</TextBlock>

<TextBox Margin="3"></TextBox>

<Button Margin="3">OK</Button>

</StackPanel>

</Border>

</Viewport2DVisual3D.Visual>

</Viewport2DVisual3D>

(37)
(38)

• http://3dtools.codeplex.com/ – biblioteka narzędzi, oferująca między innymi dekorator viewporta zapewniający poruszanie kamerą przy pomocy myszy:

<Window ...

xmlns:tools="clr-namespace:_3DTools;assembly=3DTools"

Title="3D" Height="300" Width="300">

<Grid>

<tools:TrackballDecorator>

<Viewport3D>

...

</Viewport3D>

</tools:TrackballDecorator>

</Grid>

</Window>

(39)

drukowanie

Podstawowym punktem wyjścia jest dla nas klasa PrintDialog. Nie tylko pokazuje ona opcje drukowania, ale również umożliwia uruchomienie wydruku:

PrintVisual() – do drukowania elementów dziedziczących z System.Windows.Media.Visual

PrintDocument() – do drukowania dokumentów (klasa DocumentPaginator) Drukowanie elementu

• PrintDialog.PrintVisual() pozwala wydrukować dokładnie to, co widać na ekranie.

PrintDialog printDialog = new PrintDialog();

if (printDialog.ShowDialog() == true) {

printDialog.PrintVisual(canvas, "A Simple Drawing");

}

• Pierwszy parametr – element do wydrukowania.

• Drugi parametr – string identyfikujący zadanie drukarki.

(40)

<Window ...>

<Window.CommandBindings>

<CommandBinding Command="Print" Executed="print"/>

</Window.CommandBindings>

<Canvas Name="canvas">

<Path Fill="Yellow" Stroke="Blue"

Canvas.Top="30" Canvas.Left="20" >

<Path.Data>

<GeometryGroup>

<RectangleGeometry Rect="0,0 100,60"/>

<EllipseGeometry Center="90,10"

RadiusX="40" RadiusY="30"/>

</GeometryGroup>

</Path.Data>

</Path>

</Canvas>

</Window>

(41)

Nie ma tu zbyt dużej kontroli nad wydrukiem (ustawienia marginesu, wyrównania, podziału na strony, skalowania). Rozmiar na wydruku jest taki sam, jak rozmiar w oknie.

(42)

Można sobie z tym poradzić dodając transformacje i włączając dopasowanie do rozmiaru strony:

PrintDialog printDialog = new PrintDialog();

if (printDialog.ShowDialog() == true) {

// Magnify the output by a factor of 5.

canvas.LayoutTransform = new ScaleTransform(5, 5);

// Define a margin.

int pageMargin = 5;

// Get the size of the page.

Size pageSize = new Size(printDialog.PrintableAreaWidth - pageMargin * 2, printDialog.PrintableAreaHeight - 20);

// Trigger the sizing of the element.

canvas.Measure(pageSize);

canvas.Arrange(new Rect(pageMargin, pageMargin,

pageSize.Width, pageSize.Height));

// Print the element.

printDialog.PrintVisual(canvas, "A Scaled Drawing");

// Remove the transform.

canvas.LayoutTransform = null;

}

(43)
(44)

Dokument XPS może być używany jako podgląd wydruku: aplikacja drukuje dokument do pliku XPS, aby go później wyświetlić w oknie. Można to też wykorzystać do asynchronicznego drukowania.

(Uwaga: należy pamiętać o dodaniu assembli ReachFramework i System.Printing w References)

Należy stworzyć writera (można użyć metody Path.GetTempFileName() aby uzyskać ścieżkę do pliku tymczasowego):

XpsDocument xpsDocument = new XpsDocument("filename.xps", FileAccess.ReadWrite);

XpsDocumentWriter writer =

XpsDocument.CreateXpsDocumentWriter(xpsDocument);

Następnie metody Write() i WriteAsync() umożliwiają wydrukowanie obiektów graficznych (Visual) lub dokumentów (DocumentPaginator).

DocumentViewer docViewer = new DocumentViewer();

writer.Write(canvas);

docViewer.Document = xpsDocument.GetFixedDocumentSequence();

xpsDocument.Close();

File.Delete("filename.xps");

(45)

Można stworzyć i wyświetlić okienko z podglądem:

Window window = new Window();

window.Content = docViewer;

window.Width = 300;

window.Height = 300;

window.Title = "podgląd wydruku";

window.Show();

(46)

Drukowanie dokumentu

Metoda PrintDocument() z PrintDialog oferuje drukowanie dokumentu. Przyjmuje ona parametr typu DocumentPaginator (zadaniem tej klasy jest dzielenie dokumentu na strony – obiekty klasy DocumentPage – i udostępnianie ich).

PrintDialog printDialog = new PrintDialog();

if (printDialog.ShowDialog() == true) {

printDialog.PrintDocument(

((IDocumentPaginatorSource)docReader.Document).DocumentPaginator, "A Flow Document");

}

Jeśli dokument jest zawarty w kontenerze RichTextBox lub FlowDocumentScrollViewer, paginacja zostanie wykonana prawidłowo. Jeśli jednak drukujemy z

FlowDocumentPageViewer lub FlowDocumentReader, musimy powtórzyć paginację, aby dostosować ją do strony, a nie okna. (Podobnie jest z kolumnami.) (Oczywiście warto zachować te wartości, by przywrócić je, gdy wrócimy do okienka.)

FlowDocument doc = docReader.Document;

doc.PageHeight = printDialog.PrintableAreaHeight;

doc.PageWidth = printDialog.PrintableAreaWidth;

printDialog.PrintDocument(

((IDocumentPaginatorSource)doc).DocumentPaginator, "A Flow Document");

(47)

Kontrola nad paginacją na wydruku

Możemy uzyskać kontrolę nad paginacją pisząc własną klasę DocumentPaginator.

Nie musimy robić paginacji ręcznie (można to zadanie zostawić paginatorowi z dokumentu), ale możemy np. dodać nagłówek i stopkę do każdej strony.

public class HeaderedFlowDocumentPaginator : DocumentPaginator {

// The real paginator (which does all the pagination work).

private DocumentPaginator flowDocumentPaginator;

// Store the FlowDocument paginator from the given document.

public HeaderedFlowDocumentPaginator(FlowDocument document) {

flowDocumentPaginator =

((IDocumentPaginatorSource)document).DocumentPaginator;

}

public override bool IsPageCountValid

{ get { return flowDocumentPaginator.IsPageCountValid; } } public override int PageCount

{ get { return flowDocumentPaginator.PageCount; } } public override Size PageSize

{ get { return flowDocumentPaginator.PageSize; } set { flowDocumentPaginator.PageSize = value; } } public override IDocumentPaginatorSource Source

{ get { return flowDocumentPaginator.Source; } } public override DocumentPage GetPage(int pageNumber) { ... }

(48)

Gdy pobierana jest strona, możemy dodać własne elementy:

public override DocumentPage GetPage(int pageNumber) {

// Pobierz stronę

DocumentPage page = flowDocumentPaginator.GetPage(pageNumber);

// Opakuj ją w Visual

ContainerVisual newVisual = new ContainerVisual();

newVisual.Children.Add(page.Visual);

// Stwórz nagłówek

DrawingVisual header = new DrawingVisual();

using (DrawingContext dc = header.RenderOpen()) {

Typeface typeface = new Typeface("Times New Roman");

FormattedText text = new FormattedText("Page " +

(pageNumber + 1).ToString(), CultureInfo.CurrentCulture, FlowDirection.LeftToRight, typeface, 14, Brushes.Black);

dc.DrawText(text, new Point(96 * 0.25, 96 * 0.25));

}

// Dodaj nagłówek do Visual newVisual.Children.Add(header);

// Stwórz i zwróć nową stronę dokumentu

DocumentPage newPage = new DocumentPage(newVisual);

return newPage;

}

(49)

Aby modyfikować Visual musimy usunąć dokument z kontenera na czas drukowania:

FlowDocument document = docReader.Document;

docReader.Document = null;

HeaderedFlowDocumentPaginator paginator =

new HeaderedFlowDocumentPaginator(document);

printDialog.PrintDocument(paginator, "A Headered Flow Document");

docReader.Document = document;

Drukowanie zakresu stron

Własność PrintDialog.UserPageRangeEnabled na true umożliwi wybór zakresu przez użytkownika (Selection i Current Page są nieobsługiwane). Możemy też ustawić MaxPage i MinPage, aby nadać mu ograniczenie. Następnie odczytujemy własność

PageRangeSelection. Jeśli ma wartość UserPages, to możemy odczytać

PageRange.PageFrom i PageRange.PageTo. Wykorzystanie tej informacji należy do nas.

Cytaty

Powiązane dokumenty

Dany jest trapez równoramienny o krótszej podstawie długości wysokości i ramionach po Oblicz długość dłuższej podstawy tego trapezu.. Zaznacz

Temat lekcji 1) Dowody geometryczne – rachunek kątów, nierówność trójkąta, przystawanie trójkątów. 2,3) Dowody geometryczne – podobieństwo trójkątów, twierdzenie

I wtedy zauważył, że bez sensu jest czekanie, czy nie znajdzie się jakiś kolejny młody gniewny, który ten wzór obali – trzeba dowieść, że tak jest naprawdę.. To

• IsFilled – jeśli true, wnętrze jest wypełniane przy użyciu pędzla Path.Fill PathFigure to kształt który narysowany jest linią składającą się z wielu

Wymaga stworzenia obiektu animacji, ustawienia własności i odpalenia jej przy pomocy metody BeginAnimation() (zadeklarowanej w interfejsie

• Jeśli chcemy aby szablony były używane w obrębie wielu aplikacji należy stworzyć osobny słownik zasobów (Resource Dictionary) – Solution Explorer -&gt; Add -&gt;. New

Na przeciwrozwartokątnej trójkąta rozwartokątnego znajdź punkt, którego odległość od wierzchołka kąta rozwartego jest średnią geometryczną długości odcinków, na jakie

Punkty te połączono między sobą i z wierzchołkami trójkąta nieprzecinającymi się odcinkami tak, iż ”duży” trójkąt podzielono na mniejsze trójkąty.. Udowodnij, że