Home | Course Index | << Prev. | Next >> | Complete Code | PDF Version of this Page |
![]() |
Course 3D_WPF: 3D-Computer Graphics with C# + WPF
|
![]() |
![]() Let me know what you think |
![]() ![]() ![]() ![]() ![]() |
Guidance for Visual C# 2010 Express:
Main Menu after start of VC# 2010 : File → New Project... → WPF Application → Name: dice1 → OK.
Replace the default code of MainWindow.xaml and of MainWindow.xaml.cs by the following codes:
MainWindow.xaml: <Window x:Class="dice1.MainWindow" x:Name="window" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="dice1" Width="400" Height="650"> <Window.Resources> <MeshGeometry3D x:Key="face_prototype" Positions= "-0.5 0.5 0, -0.5 -0.5 0, 0.5 -0.5 0, 0.5 0.5 0" TextureCoordinates="0 0, 0 1, 1 1, 1 0" TriangleIndices= "0 1 3, 1 2 3"/> <ImageBrush x:Key="frontBrush" ImageSource="http://www.miszalok.de/C_3D_WPF/C3_Dice/Images/front.bmp" /> <DiffuseMaterial x:Key="frontMaterial" Brush="{StaticResource frontBrush }"/> </Window.Resources> <StackPanel Orientation="Vertical"> <Viewport3D x:Name="viewport"> <ModelVisual3D x:Name="model"> <ModelVisual3D.Content> <Model3DGroup x:Name="group"> <GeometryModel3D x:Name="front" Geometry ="{StaticResource face_prototype}" Material ="{StaticResource frontMaterial}" BackMaterial="{StaticResource frontMaterial}"/> <DirectionalLight x:Name="directionalLight" Color="#ffffff" Direction="-1 -1 -1" /> </Model3DGroup><!--end of <Model3DGroup x:Name="group">--> </ModelVisual3D.Content> </ModelVisual3D><!--end of <ModelVisual3D x:Name="model">--> <Viewport3D.Camera> <PerspectiveCamera x:Name="camera" Position= " 1.5 1.5 1.5" LookDirection="-1 -1 -1" UpDirection= " 0 1 0"/> </Viewport3D.Camera> </Viewport3D> </StackPanel><!--end of <StackPanel Orientation="Vertical">--> </Window> MainWindow.xaml.cs: using System; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Media.Media3D; namespace dice1 { public partial class MainWindow : Window { public MainWindow() //constructor { InitializeComponent(); } protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo) { viewport.Width = window.ActualWidth; viewport.Height = window.ActualHeight; } } }
Experiments in MainWindow.xaml: (Restore the original values after the experiments.)
1. | Distort the MeshGeometry3D x:Key="face_prototype" by moving its first point from Positions="-0.5 0.5 0, to Positions="-1.2 0.5 0,. |
Trapezoid |
2. | Distort the MeshGeometry3D x:Key="face_prototype" by moving its first texture coordinate from TextureCoordinates="0 0, to TextureCoordinates="0.4 0,. |
Clipped image |
3. | Shift the PerspectiveCamera to Position="0.0 0.0 1.5" with LookDirection="0 0 -1". | Right angled central view |
4. | Shift the PerspectiveCamera to Position="0.0 0.0 -1.5" with LookDirection=" 0 0 1". | Right angled rear view |
5. | Restore all original values and try out other camera Positons with adequate LookDirections. |
Experiments in MainWindow.xaml.cs: (Restore the original values after any experiment.)
1. | Reduce viewport.Width = window.ActualWidth; to window.ActualWidth/2. | Half size |
2. | Reduce viewport.Height = window.ActualHeight; to window.ActualHeight/2. | Same size |
3. | Drag the window borders at run time and observe the effects of the viewport's changing width and height. | Viewport-width is more important than height. |
![]() ![]() |
Changes in MainWindow.xaml:
Insert 5 additional ImageBrushes into the <Windows.Resources>-tag below the line:
<ImageBrush x:Key="rightBrush" ImageSource="http://www.miszalok.de/C_3D_WPF/C3_Dice/Images/right.bmp" /> <ImageBrush x:Key="backBrush" ImageSource="http://www.miszalok.de/C_3D_WPF/C3_Dice/Images/back.bmp" /> <ImageBrush x:Key="leftBrush" ImageSource="http://www.miszalok.de/C_3D_WPF/C3_Dice/Images/left.bmp" /> <ImageBrush x:Key="topBrush" ImageSource="http://www.miszalok.de/C_3D_WPF/C3_Dice/Images/top.bmp" /> <ImageBrush x:Key="bottomBrush" ImageSource="http://www.miszalok.de/C_3D_WPF/C3_Dice/Images/bottom.bmp"/>
Insert 5 additional DiffuseMaterials into the <Windows.Resources>-tag below the line:
<DiffuseMaterial x:Key="rightMaterial" Brush="{StaticResource rightBrush }"/> <DiffuseMaterial x:Key="backMaterial" Brush="{StaticResource backBrush }"/> <DiffuseMaterial x:Key="leftMaterial" Brush="{StaticResource leftBrush }"/> <DiffuseMaterial x:Key="topMaterial" Brush="{StaticResource topBrush }"/> <DiffuseMaterial x:Key="bottomMaterial" Brush="{StaticResource bottomBrush}"/>
Insert 5 additional GeometryModel3Ds into the <Model3DGroup x:Name="group">-tag below the line:
<GeometryModel3D x:Name="right" Geometry ="{StaticResource face_prototype}" Material ="{StaticResource rightMaterial}" BackMaterial="{StaticResource rightMaterial}"/> <GeometryModel3D x:Name="back" Geometry ="{StaticResource face_prototype}" Material ="{StaticResource backMaterial}" BackMaterial="{StaticResource backMaterial}"/> <GeometryModel3D x:Name="left" Geometry ="{StaticResource face_prototype}" Material ="{StaticResource leftMaterial}" BackMaterial="{StaticResource leftMaterial}"/> <GeometryModel3D x:Name="top" Geometry ="{StaticResource face_prototype}" Material ="{StaticResource topMaterial}" BackMaterial="{StaticResource topMaterial}"/> <GeometryModel3D x:Name="bottom" Geometry ="{StaticResource face_prototype}" Material ="{StaticResource bottomMaterial}" BackMaterial="{StaticResource bottomMaterial}"/>
Changes in MainWindow.xaml.cs:
Keep the using lines but replace the complete namespace dice1:
namespace dice1 { public partial class MainWindow : Window { Matrix3D matrix_front = new Matrix3D(), matrix_right = new Matrix3D(), matrix_back = new Matrix3D(), matrix_left = new Matrix3D(), matrix_top = new Matrix3D(), matrix_bottom = new Matrix3D(); public MaainWindow() //constructor { InitializeComponent(); matrix_front .Rotate( new Quaternion( new Vector3D(0,1,0), 0 ) ); //do nothing matrix_right .Rotate( new Quaternion( new Vector3D(0,1,0), 90 ) ); //turn right matrix_back .Rotate( new Quaternion( new Vector3D(0,1,0), 180 ) ); //turn around matrix_left .Rotate( new Quaternion( new Vector3D(0,1,0), -90 ) ); //turn left matrix_top .Rotate( new Quaternion( new Vector3D(1,0,0), -90 ) ); //turn up matrix_bottom.Rotate( new Quaternion( new Vector3D(1,0,0), 90 ) ); //turn down matrix_front .Translate( new Vector3D( 0.0, 0.0, 0.5 ) ); //shift ahead matrix_right .Translate( new Vector3D( 0.5, 0.0, 0.0 ) ); //shift right matrix_back .Translate( new Vector3D( 0.0, 0.0,-0.5 ) ); //shift left matrix_left .Translate( new Vector3D(-0.5, 0.0, 0.0 ) ); //shift back matrix_top .Translate( new Vector3D( 0.0, 0.5, 0.0 ) ); //shift upwards matrix_bottom.Translate( new Vector3D( 0.0,-0.5, 0.0 ) ); //shift downwards front .Transform = new MatrixTransform3D( matrix_front ); right .Transform = new MatrixTransform3D( matrix_right ); back .Transform = new MatrixTransform3D( matrix_back ); left .Transform = new MatrixTransform3D( matrix_left ); top .Transform = new MatrixTransform3D( matrix_top ); bottom.Transform = new MatrixTransform3D( matrix_bottom); } protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo) { viewport.Width = window.ActualWidth; viewport.Height = window.ActualHeight; } } }
![]() ![]() |
Changes in MainWindow.xaml:
Insert a Style-definition in <Window.Resources> below the line
<DiffuseMaterial x:Key="bottomMaterial" Brush="{StaticResource bottomBrush}"/>
<Style TargetType="{x:Type CheckBox}"><!--Set a property and an event handler for all CheckBoxes--> <Setter Property="IsChecked" Value="True"/> <EventSetter Event="Click" Handler="on_checkbox_clicked"/> </Style>
Insert 6 CheckBoxes just below the <StackPanel Orientation="Vertical">-tag above the line <Viewport3D x:Name="viewport">:
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0 10 0 10"> <CheckBox x:Name="cb_front" Content="front "/> <CheckBox x:Name="cb_right" Content="right "/> <CheckBox x:Name="cb_back" Content="back "/> <CheckBox x:Name="cb_left" Content="left "/> <CheckBox x:Name="cb_top" Content="top "/> <CheckBox x:Name="cb_bottom" Content="bottom"/> </StackPanel><!--end of <StackPanel ... Margin="0 10 0 10">-->
Changes in MainWindow.xaml.cs:
Above last two braces insert the common event handler for all 6 CheckBoxes:
private void on_checkbox_clicked(object sender, EventArgs e) { switch( ((CheckBox)sender).Name ) { case "cb_front": if ( cb_front .IsChecked == false ) group.Children.Remove( front ); else group.Children.Insert( 0, front ); break; case "cb_right": if ( cb_right .IsChecked == false ) group.Children.Remove( right ); else group.Children.Insert( 0, right ); break; case "cb_back": if ( cb_back .IsChecked == false ) group.Children.Remove( back ); else group.Children.Insert( 0, back ); break; case "cb_left": if ( cb_left .IsChecked == false ) group.Children.Remove( left ); else group.Children.Insert( 0, left ); break; case "cb_top": if ( cb_top .IsChecked == false ) group.Children.Remove( top ); else group.Children.Insert( 0, top ); break; case "cb_bottom": if ( cb_bottom.IsChecked == false ) group.Children.Remove( bottom ); else group.Children.Insert( 0, bottom ); break; } model.Content = group; }
Try out the CheckBoxes.
![]() ![]() |
Changes in MainWindow.xaml:
Insert two more Style-definitions in <Window.Resources> below </Style>
<Style TargetType="{x:Type TextBlock}"><!--Set two properties for all TextBlocks--> <Setter Property="FontSize" Value="10"/> <Setter Property="HorizontalAlignment" Value="Center"/> </Style> <Style TargetType="{x:Type Slider}"><!--Set 3 properties and an event handler for all Sliders--> <Setter Property="Minimum" Value="-1.5"/> <Setter Property="Maximum" Value=" 1.5"/> <Setter Property="Value" Value=" 1.5"/> <EventSetter Event="ValueChanged" Handler="on_slider_value_changed"/> </Style>
Insert 3 Slider-controls below the line:
</StackPanel><!--end of <StackPanel ... Margin="0 10 0 10">-->.
<StackPanel Orientation="Vertical" Margin="3"> <TextBlock Text="Camera moves parallel to the X-axis"/> <Slider x:Name="camera_X_axis_move_slider"/> <TextBlock Text="Camera moves parallel to the Y-axis"/> <Slider x:Name="camera_Y_axis_move_slider"/> <TextBlock Text="Camera moves parallel to the Z-axis"/> <Slider x:Name="camera_Z_axis_move_slider"/> </StackPanel><!--end of <StackPanel Orientation="Vertical" Margin="3">-->
Changes in MainWindow.xaml.cs:
Above the last two braces insert the common event handler for all 3 Sliders:
private void on_slider_value_changed(object sender, EventArgs e) { try { double x = camera_X_axis_move_slider.Value; double y = camera_Y_axis_move_slider.Value; double z = camera_Z_axis_move_slider.Value; camera.Position = new Point3D( x, y, z ); camera.LookDirection = -(Vector3D)camera.Position; //always look to (0,0,0) camera.LookDirection.Normalize(); //vector length = 1 directionalLight.Direction = camera.LookDirection; //light comes from the camera } catch {} }
Change the event handler
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo) { viewport.Width = window.ActualWidth; viewport.Height = window.ActualHeight - 8*camera_X_axis_move_slider.ActualHeight; }
Experiments: (Restore the original values after the experiments.)
1. | In MainWindow.xaml change all three Slider-Minima from -1.5 to -3 and Slider-Maxima from 1.5 to 3. |
More freedom to move the camera |
2. | Move all Sliders in mid positions ≈ 0. | Inside the dice |
3. | Put the x-Slider in mid position ≈ 0. Put the y-Slider in maximum position ≈ 1.5. The camera is now above the top face. Move the z-Slider slowly from left to right. |
Observe how the camera.LookDirection reverses. |
![]() ![]() |
Changes in MainWindow.xaml.cs:
Insert a subroutine call into the constructor public MainWindow() below the line { InitializeComponent();:
corrugate();
Insert the corresponding subroutine above the last two braces of MainWindow.xaml.cs:
private void corrugate() { const int nn = 60; //face width 1.0 will be divided into nn vertical stripes Point3D [] p = new Point3D [ 2*nn + 2 ]; //a stripe has 4 vertices Point [] t = new Point [ 2*nn + 2 ]; //texture coordinates int[] index = new int[ 6*nn ]; //2 triangles times 3 vertices per stripe double x, dx = 1.0 / nn; //dx = stripe width int i, j; double frequency = 8, amplitude = 0.02; for ( i=0, x=0; i < p.Length; i+=2, x+=dx ) //compute point and texture coordinates { p[i].X = p[i+1].X = x - 0.5; //face starts at x=-0.5 and ends at x = 0.5 t[i].X = t[i+1].X = x; //texture starts at x= 0.0 and ends at x = 1.0 p[i].Y = 0.5; p[i+1].Y = -0.5; //face top and bottom t[i].Y = 0; t[i+1].Y = 1; //texture top and bottom p[i].Z = p[i+1].Z = amplitude * Math.Sin( x*frequency*Math.PI ); } for ( i=0, j=0; i < 6*nn; i+=6, j+=2 ) //2 triangles for each stripe { index[i ] = j; //1. vertex of 1. triangle index[i+1] = j+1; //2. vertex of 1. triangle index[i+2] = j+2; //3. vertex of 1. triangle index[i+3] = j+1; //1. vertex of 2. triangle index[i+4] = j+3; //2. vertex of 2. triangle index[i+5] = j+2; //3. vertex of 2. triangle } MeshGeometry3D face_prototype = (MeshGeometry3D)this.FindResource( "face_prototype" ); face_prototype.Positions = new Point3DCollection ( p ); face_prototype.TextureCoordinates = new PointCollection ( t ); face_prototype.TriangleIndices = new Int32Collection ( index ); }
Experiments in MainWindow.xaml.cs: (Restore the original values after the experiments.)
1. | Change the no. of stripes const int nn = 60; from 60 to 1, 3, 10 etc. | Flat or askew faces |
2. | Change double frequency = 8 to 12, 20 etc. | More waves |
3. | Change amplitude = 0.02; to 0, 0.01, 0.03 etc. | Flatter or steeper waves |
top of page:![]() |