Monday, 5 December 2011

RadioButton groups

A workaround from Martin for RadioButton groups.

There’s a bug in WPF (still) around RadioButton groups which doesn’t immediately manifest but will eventually seem to strike.

There’s a slightly confusing summary of issues here : http://social.msdn.microsoft.com/forums/en-US/wpf/thread/8eb8280a-19c4-4502-8260-f74633a9e2f2/

Suffice to say if you have 2 (or more) radio buttons and put them in the same group, they will reliably update each others status for a while as you’d expect. At some point (in my case I found this reliably happens after changing Document tabs a few times, or going forwards & back again in a Wizard workflow), the bindings stop working and you end up unable to check either RadioButton anymore.

I’ve messed around with various approaches – a couple are suggested in the post above.
The simplest option appears however to simply drop the GroupName=”x” from the XAML and simply do the “single-selection” logic yourself in the backing properties on your VM.

i.e. :
<RadioButton ssform:FormItem.LabelContent="{Binding Source={StaticResource ViewModel}, Path=Strings.V_Coupon, Mode=OneTime}"
                            Content="{Binding Source={StaticResource ViewModel}, Path=Strings.V_eCoupon, Mode=OneTime}"
                            IsEnabled="{Binding Source={StaticResource ViewModel}, Path=IsCouponATrigger}"
                            IsChecked="{Binding Source={StaticResource ViewModel}, Path=IsECouponATrigger, Mode=TwoWay}" />
<RadioButton Content="{Binding Source={StaticResource ViewModel}, Path=Strings.V_PrintCoupon, Mode=OneTime}"
                            IsEnabled="{Binding Source={StaticResource ViewModel}, Path=IsCouponATrigger}"
                            IsChecked="{Binding Source={StaticResource ViewModel}, Path=IsPrintedCouponATrigger, Mode=TwoWay}" />

and
public bool IsPrintedCouponATrigger
{
   get { return PromoBase.UIChoices.IsPrintedCouponATrigger; }
   set
   {
      if (value != PromoBase.UIChoices.IsPrintedCouponATrigger)
      {
         PromoBase.UIChoices.IsPrintedCouponATrigger = value;
         IsECouponATrigger = !value;
         OnPropertyChanged("IsPrintedCouponATrigger");
      }
   }
}
public bool IsECouponATrigger
{
   get { return PromoBase.UIChoices.IsECouponATrigger; }
   set
   {
      if (value != PromoBase.UIChoices.IsECouponATrigger)
      {
         PromoBase.UIChoices.IsECouponATrigger = value;
         IsPrintedCouponATrigger = !value;
         OnPropertyChanged("IsECouponATrigger");
      }
   }
}
Note the check that the value has actually changed in the setter – without this, you get into a nice infinite loop of PropertyChange notifications as each changes the other in turn!

Annoying bug, but simple enough to work around once you know about it..

Saturday, 3 December 2011

3D VisualBrush and TextureCoordinates

This post starts with the last example from the 3D Towards a Solid post.
The previous examples all used a single colour for the Material applied to a surface.
This example replaces the Brush with a StackPanel containing a TextBlock and Button.
<Grid Background="AliceBlue">
   <Viewport3D>
      <ModelVisual3D>
         <ModelVisual3D.Content>
            <Model3DGroup>
            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>
                     <MeshGeometry3D.Positions>-0.5, -0.5, -0.5  0.5, -0.5, -0.5  0.5, 0.5, -0.5  -0.5, 0.5, -0.5</MeshGeometry3D.Positions>
                     <MeshGeometry3D.TriangleIndices>1 0 3 3 2 1</MeshGeometry3D.TriangleIndices>
                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
               <GeometryModel3D.Material>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Maroon</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.Material>
               <GeometryModel3D.BackMaterial>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Blue</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.BackMaterial>
            </GeometryModel3D>
            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>
                     <MeshGeometry3D.Positions>-0.5, -0.5, +0.5  0.5, -0.5, +0.5  0.5, 0.5, +0.5  -0.5, 0.5, +0.5</MeshGeometry3D.Positions>
                     <MeshGeometry3D.TriangleIndices>0 1 2 2 3 0</MeshGeometry3D.TriangleIndices>
                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
               <GeometryModel3D.Material>
                  <DiffuseMaterial>

                     <DiffuseMaterial.Brush>
                        <VisualBrush>
                           <VisualBrush.Visual>
                              <StackPanel>
                                 <TextBlock FontSize="10pt"
                                            Margin="2">Hello, from the Front!</TextBlock>
                                 <Button Margin="2">A Button</Button>
                              </StackPanel>
                           </VisualBrush.Visual>
                        </VisualBrush>
                     </DiffuseMaterial.Brush>

                  </DiffuseMaterial>
               </GeometryModel3D.Material>
               <GeometryModel3D.BackMaterial>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Blue</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.BackMaterial>
            </GeometryModel3D>
            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>
                     <MeshGeometry3D.Positions>-0.5, -0.5, -0.5  -0.5, -0.5, 0.5  -0.5, 0.5, 0.5  -0.5, 0.5, -0.5</MeshGeometry3D.Positions>
                     <MeshGeometry3D.TriangleIndices>0 1 2 2 3 0</MeshGeometry3D.TriangleIndices>
                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
               <GeometryModel3D.Material>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Maroon</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.Material>
               <GeometryModel3D.BackMaterial>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Blue</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.BackMaterial>
            </GeometryModel3D>
            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>
                     <MeshGeometry3D.Positions>0.5, -0.5, -0.5  0.5, -0.5, 0.5  0.5, 0.5, 0.5  0.5, 0.5, -0.5</MeshGeometry3D.Positions>
                     <MeshGeometry3D.TriangleIndices>1 0 3 3 2 1</MeshGeometry3D.TriangleIndices>
                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
               <GeometryModel3D.Material>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Maroon</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.Material>
               <GeometryModel3D.BackMaterial>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Blue</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.BackMaterial>
            </GeometryModel3D>
            </Model3DGroup>
         </ModelVisual3D.Content>
         <ModelVisual3D.Transform>
            <RotateTransform3D>
               <RotateTransform3D.Rotation>
                  <AxisAngleRotation3D  x:Name="Rotate">
                     <AxisAngleRotation3D.Axis>0,1,0</AxisAngleRotation3D.Axis>
                     <AxisAngleRotation3D.Angle>0</AxisAngleRotation3D.Angle>
                  </AxisAngleRotation3D>
               </RotateTransform3D.Rotation>
            </RotateTransform3D>
         </ModelVisual3D.Transform>
      </ModelVisual3D>
      <ModelVisual3D>
         <ModelVisual3D.Content>
            <AmbientLight Color="White" />
         </ModelVisual3D.Content>
      </ModelVisual3D>
      <Viewport3D.Camera>
         <PerspectiveCamera>
            <PerspectiveCamera.LookDirection>0,0,-1</PerspectiveCamera.LookDirection>
            <PerspectiveCamera.Position>0,0,2</PerspectiveCamera.Position>
            <PerspectiveCamera.FieldOfView>90</PerspectiveCamera.FieldOfView>
         </PerspectiveCamera>
      </Viewport3D.Camera>
   </Viewport3D>
   <Grid.Triggers>
      <EventTrigger RoutedEvent="Grid.MouseDown">
         <BeginStoryboard>
            <Storyboard>
               <DoubleAnimation From="0"
                                To="360"
                                BeginTime="0:0:0"
                                Duration="0:0:4"
                                Storyboard.TargetName="Rotate"
                                Storyboard.TargetProperty="Angle" />
            </Storyboard>
         </BeginStoryboard>
      </EventTrigger>
   </Grid.Triggers>
</Grid>
At this point the front of the cube will be transparent and the TextBlock and Button will not appear. We need to add TextCoordinates to map the VisualBrush to the Mesh.
<Grid Background="AliceBlue">
   <Viewport3D>
      <ModelVisual3D>
         <ModelVisual3D.Content>
            <Model3DGroup>
            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>
                     <MeshGeometry3D.Positions>-0.5, -0.5, -0.5  0.5, -0.5, -0.5  0.5, 0.5, -0.5  -0.5, 0.5, -0.5</MeshGeometry3D.Positions>
                     <MeshGeometry3D.TriangleIndices>1 0 3 3 2 1</MeshGeometry3D.TriangleIndices>
                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
               <GeometryModel3D.Material>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Maroon</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.Material>
               <GeometryModel3D.BackMaterial>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Blue</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.BackMaterial>
            </GeometryModel3D>
            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>
                     <MeshGeometry3D.Positions>-0.5, -0.5, +0.5  0.5, -0.5, +0.5  0.5, 0.5, +0.5  -0.5, 0.5, +0.5</MeshGeometry3D.Positions>
                     <MeshGeometry3D.TriangleIndices>0 1 2 2 3 0</MeshGeometry3D.TriangleIndices>

                     <MeshGeometry3D.TextureCoordinates>0,1 1,1 1,0 0,0</MeshGeometry3D.TextureCoordinates>

                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
               <GeometryModel3D.Material>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>
                        <VisualBrush>
                           <VisualBrush.Visual>
                              <StackPanel>
                                 <TextBlock FontSize="10pt"
                                            Margin="2">Hello, from the Front!</TextBlock>
                                 <Button Margin="2">A Button</Button>
                              </StackPanel>
                           </VisualBrush.Visual>
                        </VisualBrush>
                     </DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.Material>
               <GeometryModel3D.BackMaterial>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Blue</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.BackMaterial>
            </GeometryModel3D>
            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>
                     <MeshGeometry3D.Positions>-0.5, -0.5, -0.5  -0.5, -0.5, 0.5  -0.5, 0.5, 0.5  -0.5, 0.5, -0.5</MeshGeometry3D.Positions>
                     <MeshGeometry3D.TriangleIndices>0 1 2 2 3 0</MeshGeometry3D.TriangleIndices>
                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
               <GeometryModel3D.Material>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Maroon</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.Material>
               <GeometryModel3D.BackMaterial>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Blue</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.BackMaterial>
            </GeometryModel3D>
            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>
                     <MeshGeometry3D.Positions>0.5, -0.5, -0.5  0.5, -0.5, 0.5  0.5, 0.5, 0.5  0.5, 0.5, -0.5</MeshGeometry3D.Positions>
                     <MeshGeometry3D.TriangleIndices>1 0 3 3 2 1</MeshGeometry3D.TriangleIndices>
                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
               <GeometryModel3D.Material>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Maroon</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.Material>
               <GeometryModel3D.BackMaterial>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Blue</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.BackMaterial>
            </GeometryModel3D>
            </Model3DGroup>
         </ModelVisual3D.Content>
         <ModelVisual3D.Transform>
            <RotateTransform3D>
               <RotateTransform3D.Rotation>
                  <AxisAngleRotation3D  x:Name="Rotate">
                     <AxisAngleRotation3D.Axis>0,1,0</AxisAngleRotation3D.Axis>
                     <AxisAngleRotation3D.Angle>0</AxisAngleRotation3D.Angle>
                  </AxisAngleRotation3D>
               </RotateTransform3D.Rotation>
            </RotateTransform3D>
         </ModelVisual3D.Transform>
      </ModelVisual3D>
      <ModelVisual3D>
         <ModelVisual3D.Content>
            <AmbientLight Color="White" />
         </ModelVisual3D.Content>
      </ModelVisual3D>
      <Viewport3D.Camera>
         <PerspectiveCamera>
            <PerspectiveCamera.LookDirection>0,0,-1</PerspectiveCamera.LookDirection>
            <PerspectiveCamera.Position>0,0,2</PerspectiveCamera.Position>
            <PerspectiveCamera.FieldOfView>90</PerspectiveCamera.FieldOfView>
         </PerspectiveCamera>
      </Viewport3D.Camera>
   </Viewport3D>
   <Grid.Triggers>
      <EventTrigger RoutedEvent="Grid.MouseDown">
         <BeginStoryboard>
            <Storyboard>
               <DoubleAnimation From="0"
                                To="360"
                                BeginTime="0:0:0"
                                Duration="0:0:4"
                                Storyboard.TargetName="Rotate"
                                Storyboard.TargetProperty="Angle" />
            </Storyboard>
         </BeginStoryboard>
      </EventTrigger>
   </Grid.Triggers>
</Grid>
To apply the texture to the "back" of the we need to "flip" the TextureCoordinates horizontally around the Y axis to match the way we previously changed the TriangleIndices.
<Grid Background="AliceBlue">
   <Viewport3D>
      <ModelVisual3D>
         <ModelVisual3D.Content>
            <Model3DGroup>
            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>
                     <MeshGeometry3D.Positions>-0.5, -0.5, -0.5  0.5, -0.5, -0.5  0.5, 0.5, -0.5  -0.5, 0.5, -0.5</MeshGeometry3D.Positions>
                     <MeshGeometry3D.TriangleIndices>1 0 3 3 2 1</MeshGeometry3D.TriangleIndices>

                     <MeshGeometry3D.TextureCoordinates>1,1 0,1 0,0 1,0</MeshGeometry3D.TextureCoordinates>

                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
               <GeometryModel3D.Material>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>
                        <VisualBrush>
                           <VisualBrush.Visual>
                              <StackPanel>
                                 <TextBlock FontSize="10pt"
                                            Margin="2">Hello, from the Back!</TextBlock>
                                 <Button Margin="2">A Button</Button>
                              </StackPanel>
                           </VisualBrush.Visual>
                        </VisualBrush>
                     </DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.Material>
               <GeometryModel3D.BackMaterial>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Blue</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.BackMaterial>
            </GeometryModel3D>
            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>
                     <MeshGeometry3D.Positions>-0.5, -0.5, +0.5  0.5, -0.5, +0.5  0.5, 0.5, +0.5  -0.5, 0.5, +0.5</MeshGeometry3D.Positions>
                     <MeshGeometry3D.TriangleIndices>0 1 2 2 3 0</MeshGeometry3D.TriangleIndices>
                     <MeshGeometry3D.TextureCoordinates>0,1 1,1 1,0 0,0</MeshGeometry3D.TextureCoordinates>
                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
               <GeometryModel3D.Material>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>
                        <VisualBrush>
                           <VisualBrush.Visual>
                              <StackPanel>
                                 <TextBlock FontSize="10pt"
                                            Margin="2">Hello, from the Front!</TextBlock>
                                 <Button Margin="2">A Button</Button>
                              </StackPanel>
                           </VisualBrush.Visual>
                        </VisualBrush>
                     </DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.Material>
               <GeometryModel3D.BackMaterial>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Blue</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.BackMaterial>
            </GeometryModel3D>
            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>
                     <MeshGeometry3D.Positions>-0.5, -0.5, -0.5  -0.5, -0.5, 0.5  -0.5, 0.5, 0.5  -0.5, 0.5, -0.5</MeshGeometry3D.Positions>
                     <MeshGeometry3D.TriangleIndices>0 1 2 2 3 0</MeshGeometry3D.TriangleIndices>
                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
               <GeometryModel3D.Material>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Maroon</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.Material>
               <GeometryModel3D.BackMaterial>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Blue</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.BackMaterial>
            </GeometryModel3D>
            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>
                     <MeshGeometry3D.Positions>0.5, -0.5, -0.5  0.5, -0.5, 0.5  0.5, 0.5, 0.5  0.5, 0.5, -0.5</MeshGeometry3D.Positions>
                     <MeshGeometry3D.TriangleIndices>1 0 3 3 2 1</MeshGeometry3D.TriangleIndices>
                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
               <GeometryModel3D.Material>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Maroon</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.Material>
               <GeometryModel3D.BackMaterial>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Blue</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.BackMaterial>
            </GeometryModel3D>
            </Model3DGroup>
         </ModelVisual3D.Content>
         <ModelVisual3D.Transform>
            <RotateTransform3D>
               <RotateTransform3D.Rotation>
                  <AxisAngleRotation3D  x:Name="Rotate">
                     <AxisAngleRotation3D.Axis>0,1,0</AxisAngleRotation3D.Axis>
                     <AxisAngleRotation3D.Angle>0</AxisAngleRotation3D.Angle>
                  </AxisAngleRotation3D>
               </RotateTransform3D.Rotation>
            </RotateTransform3D>
         </ModelVisual3D.Transform>
      </ModelVisual3D>
      <ModelVisual3D>
         <ModelVisual3D.Content>
            <AmbientLight Color="White" />
         </ModelVisual3D.Content>
      </ModelVisual3D>
      <Viewport3D.Camera>
         <PerspectiveCamera>
            <PerspectiveCamera.LookDirection>0,0,-1</PerspectiveCamera.LookDirection>
            <PerspectiveCamera.Position>0,0,2</PerspectiveCamera.Position>
            <PerspectiveCamera.FieldOfView>90</PerspectiveCamera.FieldOfView>
         </PerspectiveCamera>
      </Viewport3D.Camera>
   </Viewport3D>
   <Grid.Triggers>
      <EventTrigger RoutedEvent="Grid.MouseDown">
         <BeginStoryboard>
            <Storyboard>
               <DoubleAnimation From="0"
                                To="360"
                                BeginTime="0:0:0"
                                Duration="0:0:4"
                                Storyboard.TargetName="Rotate"
                                Storyboard.TargetProperty="Angle" />
            </Storyboard>
         </BeginStoryboard>
      </EventTrigger>
   </Grid.Triggers>
</Grid>

3D Towards a Solid

This post starts with the last example from the 3D Rotation post. I want to add a second face of a cube, opposite the original surface. First add a Model3DGroup to contain all the GeometryModel3D elements that will form the cube.
<Grid Background="AliceBlue">
   <Viewport3D>
      <ModelVisual3D>
         <ModelVisual3D.Content>

            <Model3DGroup>

            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>
                     <MeshGeometry3D.Positions>-0.5, -0.5, -0.5  0.5, -0.5, -0.5  0.5, 0.5, -0.5  -0.5, 0.5, -0.5</MeshGeometry3D.Positions>
                     <MeshGeometry3D.TriangleIndices>0 1 2 2 3 0</MeshGeometry3D.TriangleIndices>
                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
               <GeometryModel3D.Material>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Maroon</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.Material>
               <GeometryModel3D.BackMaterial>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Blue</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.BackMaterial>
            </GeometryModel3D>

            </Model3DGroup>

         </ModelVisual3D.Content>
         <ModelVisual3D.Transform>
            <RotateTransform3D>
               <RotateTransform3D.Rotation>
                  <AxisAngleRotation3D  x:Name="Rotate">
                     <AxisAngleRotation3D.Axis>0,1,0</AxisAngleRotation3D.Axis>
                     <AxisAngleRotation3D.Angle>0</AxisAngleRotation3D.Angle>
                  </AxisAngleRotation3D>
               </RotateTransform3D.Rotation>
            </RotateTransform3D>
         </ModelVisual3D.Transform>
      </ModelVisual3D>
      <ModelVisual3D>
         <ModelVisual3D.Content>
            <AmbientLight Color="White" />
         </ModelVisual3D.Content>
      </ModelVisual3D>
      <Viewport3D.Camera>
         <PerspectiveCamera>
            <PerspectiveCamera.LookDirection>0,0,-1</PerspectiveCamera.LookDirection>
            <PerspectiveCamera.Position>0,0,2</PerspectiveCamera.Position>
            <PerspectiveCamera.FieldOfView>90</PerspectiveCamera.FieldOfView>
         </PerspectiveCamera>
      </Viewport3D.Camera>
   </Viewport3D>
   <Grid.Triggers>
      <EventTrigger RoutedEvent="Grid.MouseDown">
         <BeginStoryboard>
            <Storyboard>
               <DoubleAnimation From="0"
                                To="360"
                                BeginTime="0:0:0"
                                Duration="0:0:4"
                                Storyboard.TargetName="Rotate"
                                Storyboard.TargetProperty="Angle" />
            </Storyboard>
         </BeginStoryboard>
      </EventTrigger>
   </Grid.Triggers>
</Grid>
And add the second surface exactly the same as the first but with the Z coordinates moved from -0.5 to +0.5.
<Grid Background="AliceBlue">
   <Viewport3D>
      <ModelVisual3D>
         <ModelVisual3D.Content>
            <Model3DGroup>
            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>
                     <MeshGeometry3D.Positions>-0.5, -0.5, -0.5  0.5, -0.5, -0.5  0.5, 0.5, -0.5  -0.5, 0.5, -0.5</MeshGeometry3D.Positions>
                     <MeshGeometry3D.TriangleIndices>0 1 2 2 3 0</MeshGeometry3D.TriangleIndices>
                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
               <GeometryModel3D.Material>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Maroon</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.Material>
               <GeometryModel3D.BackMaterial>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Blue</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.BackMaterial>
            </GeometryModel3D>

            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>
                     <MeshGeometry3D.Positions>-0.5, -0.5, +0.5  0.5, -0.5, +0.5  0.5, 0.5, +0.5  -0.5, 0.5, +0.5</MeshGeometry3D.Positions>
                     <MeshGeometry3D.TriangleIndices>0 1 2 2 3 0</MeshGeometry3D.TriangleIndices>
                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
               <GeometryModel3D.Material>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Maroon</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.Material>
               <GeometryModel3D.BackMaterial>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Blue</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.BackMaterial>
            </GeometryModel3D>

            </Model3DGroup>
         </ModelVisual3D.Content>
         <ModelVisual3D.Transform>
            <RotateTransform3D>
               <RotateTransform3D.Rotation>
                  <AxisAngleRotation3D  x:Name="Rotate">
                     <AxisAngleRotation3D.Axis>0,1,0</AxisAngleRotation3D.Axis>
                     <AxisAngleRotation3D.Angle>0</AxisAngleRotation3D.Angle>
                  </AxisAngleRotation3D>
               </RotateTransform3D.Rotation>
            </RotateTransform3D>
         </ModelVisual3D.Transform>
      </ModelVisual3D>
      <ModelVisual3D>
         <ModelVisual3D.Content>
            <AmbientLight Color="White" />
         </ModelVisual3D.Content>
      </ModelVisual3D>
      <Viewport3D.Camera>
         <PerspectiveCamera>
            <PerspectiveCamera.LookDirection>0,0,-1</PerspectiveCamera.LookDirection>
            <PerspectiveCamera.Position>0,0,2</PerspectiveCamera.Position>
            <PerspectiveCamera.FieldOfView>90</PerspectiveCamera.FieldOfView>
         </PerspectiveCamera>
      </Viewport3D.Camera>
   </Viewport3D>
   <Grid.Triggers>
      <EventTrigger RoutedEvent="Grid.MouseDown">
         <BeginStoryboard>
            <Storyboard>
               <DoubleAnimation From="0"
                                To="360"
                                BeginTime="0:0:0"
                                Duration="0:0:4"
                                Storyboard.TargetName="Rotate"
                                Storyboard.TargetProperty="Angle" />
            </Storyboard>
         </BeginStoryboard>
      </EventTrigger>
   </Grid.Triggers>
</Grid>
We can now see two Maroon/Blue surfaces rotating but we always see the same colour for both surfaces. The original surface needs to point "out" from the centre of the cube. The easiest way would appear to be drawing the Triangles in a different order so their Normals are reversed. Swap 0 and 1, swap 2 and 3 in the TriangleIndices list.
<Grid Background="AliceBlue">
   <Viewport3D>
      <ModelVisual3D>
         <ModelVisual3D.Content>
            <Model3DGroup>
            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>
                     <MeshGeometry3D.Positions>-0.5, -0.5, -0.5  0.5, -0.5, -0.5  0.5, 0.5, -0.5  -0.5, 0.5, -0.5</MeshGeometry3D.Positions>

                     <MeshGeometry3D.TriangleIndices>1 0 3 3 2 1</MeshGeometry3D.TriangleIndices>

                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
               <GeometryModel3D.Material>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Maroon</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.Material>
               <GeometryModel3D.BackMaterial>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Blue</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.BackMaterial>
            </GeometryModel3D>
            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>
                     <MeshGeometry3D.Positions>-0.5, -0.5, +0.5  0.5, -0.5, +0.5  0.5, 0.5, +0.5  -0.5, 0.5, +0.5</MeshGeometry3D.Positions>
                     <MeshGeometry3D.TriangleIndices>0 1 2 2 3 0</MeshGeometry3D.TriangleIndices>
                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
               <GeometryModel3D.Material>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Maroon</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.Material>
               <GeometryModel3D.BackMaterial>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Blue</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.BackMaterial>
            </GeometryModel3D>
            </Model3DGroup>
         </ModelVisual3D.Content>
         <ModelVisual3D.Transform>
            <RotateTransform3D>
               <RotateTransform3D.Rotation>
                  <AxisAngleRotation3D  x:Name="Rotate">
                     <AxisAngleRotation3D.Axis>0,1,0</AxisAngleRotation3D.Axis>
                     <AxisAngleRotation3D.Angle>0</AxisAngleRotation3D.Angle>
                  </AxisAngleRotation3D>
               </RotateTransform3D.Rotation>
            </RotateTransform3D>
         </ModelVisual3D.Transform>
      </ModelVisual3D>
      <ModelVisual3D>
         <ModelVisual3D.Content>
            <AmbientLight Color="White" />
         </ModelVisual3D.Content>
      </ModelVisual3D>
      <Viewport3D.Camera>
         <PerspectiveCamera>
            <PerspectiveCamera.LookDirection>0,0,-1</PerspectiveCamera.LookDirection>
            <PerspectiveCamera.Position>0,0,2</PerspectiveCamera.Position>
            <PerspectiveCamera.FieldOfView>90</PerspectiveCamera.FieldOfView>
         </PerspectiveCamera>
      </Viewport3D.Camera>
   </Viewport3D>
   <Grid.Triggers>
      <EventTrigger RoutedEvent="Grid.MouseDown">
         <BeginStoryboard>
            <Storyboard>
               <DoubleAnimation From="0"
                                To="360"
                                BeginTime="0:0:0"
                                Duration="0:0:4"
                                Storyboard.TargetName="Rotate"
                                Storyboard.TargetProperty="Angle" />
            </Storyboard>
         </BeginStoryboard>
      </EventTrigger>
   </Grid.Triggers>
</Grid>
Finally, add the remaining two vertical surfaces of the cube. The "left" surface uses the same TriangleIndices as the "front" whereas the "right" surface uses the same TriangleIndices as the "back".
<Grid Background="AliceBlue">
   <Viewport3D>
      <ModelVisual3D>
         <ModelVisual3D.Content>
            <Model3DGroup>
            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>
                     <MeshGeometry3D.Positions>-0.5, -0.5, -0.5  0.5, -0.5, -0.5  0.5, 0.5, -0.5  -0.5, 0.5, -0.5</MeshGeometry3D.Positions>
                     <MeshGeometry3D.TriangleIndices>1 0 3 3 2 1</MeshGeometry3D.TriangleIndices>
                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
               <GeometryModel3D.Material>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Maroon</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.Material>
               <GeometryModel3D.BackMaterial>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Blue</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.BackMaterial>
            </GeometryModel3D>
            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>
                     <MeshGeometry3D.Positions>-0.5, -0.5, +0.5  0.5, -0.5, +0.5  0.5, 0.5, +0.5  -0.5, 0.5, +0.5</MeshGeometry3D.Positions>
                     <MeshGeometry3D.TriangleIndices>0 1 2 2 3 0</MeshGeometry3D.TriangleIndices>
                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
               <GeometryModel3D.Material>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Maroon</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.Material>
               <GeometryModel3D.BackMaterial>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Blue</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.BackMaterial>
            </GeometryModel3D>
            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>

                     <MeshGeometry3D.Positions>-0.5, -0.5, -0.5  -0.5, -0.5, 0.5  -0.5, 0.5, 0.5  -0.5, 0.5, -0.5</MeshGeometry3D.Positions>
                     <MeshGeometry3D.TriangleIndices>0 1 2 2 3 0</MeshGeometry3D.TriangleIndices>

                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
               <GeometryModel3D.Material>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Maroon</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.Material>
               <GeometryModel3D.BackMaterial>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Blue</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.BackMaterial>
            </GeometryModel3D>
            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>

                     <MeshGeometry3D.Positions>0.5, -0.5, -0.5  0.5, -0.5, 0.5  0.5, 0.5, 0.5  0.5, 0.5, -0.5</MeshGeometry3D.Positions>
                     <MeshGeometry3D.TriangleIndices>1 0 3 3 2 1</MeshGeometry3D.TriangleIndices>

                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
               <GeometryModel3D.Material>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Maroon</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.Material>
               <GeometryModel3D.BackMaterial>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Blue</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.BackMaterial>
            </GeometryModel3D>
            </Model3DGroup>
         </ModelVisual3D.Content>
         <ModelVisual3D.Transform>
            <RotateTransform3D>
               <RotateTransform3D.Rotation>
                  <AxisAngleRotation3D  x:Name="Rotate">
                     <AxisAngleRotation3D.Axis>0,1,0</AxisAngleRotation3D.Axis>
                     <AxisAngleRotation3D.Angle>0</AxisAngleRotation3D.Angle>
                  </AxisAngleRotation3D>
               </RotateTransform3D.Rotation>
            </RotateTransform3D>
         </ModelVisual3D.Transform>
      </ModelVisual3D>
      <ModelVisual3D>
         <ModelVisual3D.Content>
            <AmbientLight Color="White" />
         </ModelVisual3D.Content>
      </ModelVisual3D>
      <Viewport3D.Camera>
         <PerspectiveCamera>
            <PerspectiveCamera.LookDirection>0,0,-1</PerspectiveCamera.LookDirection>
            <PerspectiveCamera.Position>0,0,2</PerspectiveCamera.Position>
            <PerspectiveCamera.FieldOfView>90</PerspectiveCamera.FieldOfView>
         </PerspectiveCamera>
      </Viewport3D.Camera>
   </Viewport3D>
   <Grid.Triggers>
      <EventTrigger RoutedEvent="Grid.MouseDown">
         <BeginStoryboard>
            <Storyboard>
               <DoubleAnimation From="0"
                                To="360"
                                BeginTime="0:0:0"
                                Duration="0:0:4"
                                Storyboard.TargetName="Rotate"
                                Storyboard.TargetProperty="Angle" />
            </Storyboard>
         </BeginStoryboard>
      </EventTrigger>
   </Grid.Triggers>
</Grid>

3D Rotation

This post starts with the simple 3D model example from the first 3D post but with the camera moved back to its original position.
<Grid Background="AliceBlue">
   <Viewport3D>
      <ModelVisual3D>
         <ModelVisual3D.Content>
            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>
                     <MeshGeometry3D.Positions>-0.5, -0.5, -0.5  0.5, -0.5, -0.5  0.5, 0.5, -0.5  -0.5, 0.5, -0.5</MeshGeometry3D.Positions>
                     <MeshGeometry3D.TriangleIndices>0 1 2 2 3 0</MeshGeometry3D.TriangleIndices>
                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
               <GeometryModel3D.Material>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Maroon</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.Material>
               <GeometryModel3D.BackMaterial>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Blue</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.BackMaterial>
            </GeometryModel3D>
         </ModelVisual3D.Content>
      </ModelVisual3D>
      <ModelVisual3D>
         <ModelVisual3D.Content>
            <AmbientLight Color="White" />
         </ModelVisual3D.Content>
      </ModelVisual3D>
      <Viewport3D.Camera>
         <PerspectiveCamera>
            <PerspectiveCamera.LookDirection>0,0,-1</PerspectiveCamera.LookDirection>
            <PerspectiveCamera.Position>0,0,2</PerspectiveCamera.Position>
            <PerspectiveCamera.FieldOfView>90</PerspectiveCamera.FieldOfView>
         </PerspectiveCamera>
      </Viewport3D.Camera>
   </Viewport3D>
</Grid>
We want to rotate the Model around the Y axis, represented by the "1" in the definition of AxisAngleRotation3D.Axis below. Because the Model has a negative Z coordinate (-0.5) the Z component will gradually increase until the angle of rotation is 180 degrees at which point the Z coordinate becomes 0.5. Because the Z coordinate is closer to use the Model will appear larger.
<Grid Background="AliceBlue">
   <Viewport3D>
      <ModelVisual3D>
         <ModelVisual3D.Content>
            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>
                     <MeshGeometry3D.Positions>-0.5, -0.5, -0.5  0.5, -0.5, -0.5  0.5, 0.5, -0.5  -0.5, 0.5, -0.5</MeshGeometry3D.Positions>
                     <MeshGeometry3D.TriangleIndices>0 1 2 2 3 0</MeshGeometry3D.TriangleIndices>
                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
               <GeometryModel3D.Material>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Maroon</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.Material>
               <GeometryModel3D.BackMaterial>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Blue</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.BackMaterial>
            </GeometryModel3D>
         </ModelVisual3D.Content>

         <ModelVisual3D.Transform>
            <RotateTransform3D>
               <RotateTransform3D.Rotation>
                  <AxisAngleRotation3D>
                     <AxisAngleRotation3D.Axis>0,1,0</AxisAngleRotation3D.Axis>
                     <AxisAngleRotation3D.Angle>0</AxisAngleRotation3D.Angle>
                  </AxisAngleRotation3D>
               </RotateTransform3D.Rotation>
            </RotateTransform3D>
         </ModelVisual3D.Transform>

      </ModelVisual3D>
      <ModelVisual3D>
         <ModelVisual3D.Content>
            <AmbientLight Color="White" />
         </ModelVisual3D.Content>
      </ModelVisual3D>
      <Viewport3D.Camera>
         <PerspectiveCamera>
            <PerspectiveCamera.LookDirection>0,0,-1</PerspectiveCamera.LookDirection>
            <PerspectiveCamera.Position>0,0,2</PerspectiveCamera.Position>
            <PerspectiveCamera.FieldOfView>90</PerspectiveCamera.FieldOfView>
         </PerspectiveCamera>
      </Viewport3D.Camera>
   </Viewport3D>
</Grid>
Starting with an Angle of 0 (zero) degrees gives the same result as previously. Increasing the Angle to more than 90 degrees the reverse side of the Model comes into view.
<Grid Background="AliceBlue">
   <Viewport3D>
      <ModelVisual3D>
         <ModelVisual3D.Content>
            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>
                     <MeshGeometry3D.Positions>-0.5, -0.5, -0.5  0.5, -0.5, -0.5  0.5, 0.5, -0.5  -0.5, 0.5, -0.5</MeshGeometry3D.Positions>
                     <MeshGeometry3D.TriangleIndices>0 1 2 2 3 0</MeshGeometry3D.TriangleIndices>
                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
               <GeometryModel3D.Material>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Maroon</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.Material>
               <GeometryModel3D.BackMaterial>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Blue</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.BackMaterial>
            </GeometryModel3D>
         </ModelVisual3D.Content>
         <ModelVisual3D.Transform>
            <RotateTransform3D>
               <RotateTransform3D.Rotation>
                  <AxisAngleRotation3D>
                     <AxisAngleRotation3D.Axis>0,1,0</AxisAngleRotation3D.Axis>

                     <AxisAngleRotation3D.Angle>120</AxisAngleRotation3D.Angle>

                  </AxisAngleRotation3D>
               </RotateTransform3D.Rotation>
            </RotateTransform3D>
         </ModelVisual3D.Transform>
      </ModelVisual3D>
      <ModelVisual3D>
         <ModelVisual3D.Content>
            <AmbientLight Color="White" />
         </ModelVisual3D.Content>
      </ModelVisual3D>
      <Viewport3D.Camera>
         <PerspectiveCamera>
            <PerspectiveCamera.LookDirection>0,0,-1</PerspectiveCamera.LookDirection>
            <PerspectiveCamera.Position>0,0,2</PerspectiveCamera.Position>
            <PerspectiveCamera.FieldOfView>90</PerspectiveCamera.FieldOfView>
         </PerspectiveCamera>
      </Viewport3D.Camera>
   </Viewport3D>
</Grid>
The rotation can be animated by applying a DoubleAnimation to the RotateTransform.
Reset the Angle to zero, name the AxisAngleRotation3D and add a Grid.Trigger to start the animation.
<Grid Background="AliceBlue">
   <Viewport3D>
      <ModelVisual3D>
         <ModelVisual3D.Content>
            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>
                     <MeshGeometry3D.Positions>-0.5, -0.5, -0.5  0.5, -0.5, -0.5  0.5, 0.5, -0.5  -0.5, 0.5, -0.5</MeshGeometry3D.Positions>
                     <MeshGeometry3D.TriangleIndices>0 1 2 2 3 0</MeshGeometry3D.TriangleIndices>
                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
               <GeometryModel3D.Material>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Maroon</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.Material>
               <GeometryModel3D.BackMaterial>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Blue</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.BackMaterial>
            </GeometryModel3D>
         </ModelVisual3D.Content>
         <ModelVisual3D.Transform>
            <RotateTransform3D>
               <RotateTransform3D.Rotation>
                  <AxisAngleRotation3D  x:Name="Rotate">
                     <AxisAngleRotation3D.Axis>0,1,0</AxisAngleRotation3D.Axis>

                     <AxisAngleRotation3D.Angle>0</AxisAngleRotation3D.Angle>

                  </AxisAngleRotation3D>
               </RotateTransform3D.Rotation>
            </RotateTransform3D>
         </ModelVisual3D.Transform>
      </ModelVisual3D>
      <ModelVisual3D>
         <ModelVisual3D.Content>
            <AmbientLight Color="White" />
         </ModelVisual3D.Content>
      </ModelVisual3D>
      <Viewport3D.Camera>
         <PerspectiveCamera>
            <PerspectiveCamera.LookDirection>0,0,-1</PerspectiveCamera.LookDirection>
            <PerspectiveCamera.Position>0,0,2</PerspectiveCamera.Position>
            <PerspectiveCamera.FieldOfView>90</PerspectiveCamera.FieldOfView>
         </PerspectiveCamera>
      </Viewport3D.Camera>
   </Viewport3D>

   <Grid.Triggers>
      <EventTrigger RoutedEvent="Grid.MouseDown">
         <BeginStoryboard>
            <Storyboard>
               <DoubleAnimation From="0"
                                To="360"
                                BeginTime="0:0:0"
                                Duration="0:0:4"
                                Storyboard.TargetName="Rotate"
                                Storyboard.TargetProperty="Angle" />
            </Storyboard>
         </BeginStoryboard>
      </EventTrigger>
   </Grid.Triggers>

</Grid>

3D Foundations

To use 3D in WPF we need a minimum of an object, somewhere to draw it, a camera to see it and some light to illuminate the scene. Which in the following examples roughly correspond to GeometryModel3D, Viewport3D, PerspectiveCamera and AmbientLight respectively.
Starting with the Model and Viewport the bare minimum to describe a square is:
<Grid Background="AliceBlue">
   <Viewport3D>
      <ModelVisual3D>
         <ModelVisual3D.Content>
            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>
                     <MeshGeometry3D.Positions>-0.5, -0.5, -0.5  0.5, -0.5, -0.5  0.5, 0.5, -0.5  -0.5, 0.5, -0.5</MeshGeometry3D.Positions>
                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
            </GeometryModel3D>
         </ModelVisual3D.Content>
      </ModelVisual3D>
   </Viewport3D>
</Grid>
Each Mesh position is a triplet of X, Y and Z. Positive X is UP. Positive Y is RIGHT. Positive Z is OUT of the screen.
The Mesh triplets are numbered from 0 (zero) starting in BottomLeft and working anti-clockwise.

3 ------------ 2
|              |
|              |
|              |
0 ------------ 1
These form the indices used to cover the surface in triangles. Each triangle is drawn anti-clockwise to keep the "Normals" pointing in the same direction. I'm not going to mention Normals again.
<Grid Background="AliceBlue">
   <Viewport3D>
      <ModelVisual3D>
         <ModelVisual3D.Content>
            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>
                     <MeshGeometry3D.Positions>-0.5, -0.5, -0.5  0.5, -0.5, -0.5  0.5, 0.5, -0.5  -0.5, 0.5, -0.5</MeshGeometry3D.Positions>

                     <MeshGeometry3D.TriangleIndices>0 1 2 2 3 0</MeshGeometry3D.TriangleIndices>

                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
            </GeometryModel3D>
         </ModelVisual3D.Content>
      </ModelVisual3D>
   </Viewport3D>
</Grid>
The model has two surfaces and each can have its own material. The front surface, Material, has been coloured Maroon.
<Grid Background="AliceBlue">
   <Viewport3D>
      <ModelVisual3D>
         <ModelVisual3D.Content>
            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>
                     <MeshGeometry3D.Positions>-0.5, -0.5, -0.5  0.5, -0.5, -0.5  0.5, 0.5, -0.5  -0.5, 0.5, -0.5</MeshGeometry3D.Positions>
                     <MeshGeometry3D.TriangleIndices>0 1 2 2 3 0</MeshGeometry3D.TriangleIndices>
                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
         
               <GeometryModel3D.Material>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Maroon</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.Material>

            </GeometryModel3D>
         </ModelVisual3D.Content>
      </ModelVisual3D>
   </Viewport3D>
</Grid>
At this point nothing can be seen on screen because there is no camera.
<Grid Background="AliceBlue">
   <Viewport3D>
      <ModelVisual3D>
         <ModelVisual3D.Content>
            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>
                     <MeshGeometry3D.Positions>-0.5, -0.5, -0.5  0.5, -0.5, -0.5  0.5, 0.5, -0.5  -0.5, 0.5, -0.5</MeshGeometry3D.Positions>
                     <MeshGeometry3D.TriangleIndices>0 1 2 2 3 0</MeshGeometry3D.TriangleIndices>
                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
               <GeometryModel3D.Material>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Maroon</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.Material>
            </GeometryModel3D>
         </ModelVisual3D.Content>
      </ModelVisual3D>
         
      <Viewport3D.Camera>
         <PerspectiveCamera>
            <PerspectiveCamera.LookDirection>0,0,-1</PerspectiveCamera.LookDirection>
            <PerspectiveCamera.Position>0,0,2</PerspectiveCamera.Position>
            <PerspectiveCamera.FieldOfView>90</PerspectiveCamera.FieldOfView>
         </PerspectiveCamera>
      </Viewport3D.Camera>

   </Viewport3D>
</Grid>
At this point we should see a Black square which is the Maroon surface of the model in an unlit scene. Next we add some AmbientLight.
<Grid Background="AliceBlue">
   <Viewport3D>
      <ModelVisual3D>
         <ModelVisual3D.Content>
            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>
                     <MeshGeometry3D.Positions>-0.5, -0.5, -0.5  0.5, -0.5, -0.5  0.5, 0.5, -0.5  -0.5, 0.5, -0.5</MeshGeometry3D.Positions>
                     <MeshGeometry3D.TriangleIndices>0 1 2 2 3 0</MeshGeometry3D.TriangleIndices>
                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
               <GeometryModel3D.Material>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Maroon</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.Material>
            </GeometryModel3D>
         </ModelVisual3D.Content>
      </ModelVisual3D>
      
      <ModelVisual3D>
         <ModelVisual3D.Content>
            <AmbientLight Color="White" />
         </ModelVisual3D.Content>
      </ModelVisual3D>
      
      <Viewport3D.Camera>
         <PerspectiveCamera>
            <PerspectiveCamera.LookDirection>0,0,-1</PerspectiveCamera.LookDirection>
            <PerspectiveCamera.Position>0,0,2</PerspectiveCamera.Position>
            <PerspectiveCamera.FieldOfView>90</PerspectiveCamera.FieldOfView>
         </PerspectiveCamera>
      </Viewport3D.Camera>
   </Viewport3D>
</Grid>
We should now be able to see a Maroon surface. The Model's reverse surface, BackMaterial, will be coloured Blue.
<Grid Background="AliceBlue">
   <Viewport3D>
      <ModelVisual3D>
         <ModelVisual3D.Content>
            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>
                     <MeshGeometry3D.Positions>-0.5, -0.5, -0.5  0.5, -0.5, -0.5  0.5, 0.5, -0.5  -0.5, 0.5, -0.5</MeshGeometry3D.Positions>
                     <MeshGeometry3D.TriangleIndices>0 1 2 2 3 0</MeshGeometry3D.TriangleIndices>
                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
               <GeometryModel3D.Material>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Maroon</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.Material>
      
               <GeometryModel3D.BackMaterial>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Blue</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.BackMaterial>

            </GeometryModel3D>
         </ModelVisual3D.Content>
      </ModelVisual3D>
      <ModelVisual3D>
         <ModelVisual3D.Content>
            <AmbientLight Color="White" />
         </ModelVisual3D.Content>
      </ModelVisual3D>
      <Viewport3D.Camera>
         <PerspectiveCamera>
            <PerspectiveCamera.LookDirection>0,0,-1</PerspectiveCamera.LookDirection>
            <PerspectiveCamera.Position>0,0,2</PerspectiveCamera.Position>
            <PerspectiveCamera.FieldOfView>90</PerspectiveCamera.FieldOfView>
         </PerspectiveCamera>
      </Viewport3D.Camera>
   </Viewport3D>
</Grid>
Finally, reposition the camera and the direction it is looking in order to see the other side of the model. We should now see the blue surface.
<Grid Background="AliceBlue">
   <Viewport3D>
      <ModelVisual3D>
         <ModelVisual3D.Content>
            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>
                     <MeshGeometry3D.Positions>-0.5, -0.5, -0.5  0.5, -0.5, -0.5  0.5, 0.5, -0.5  -0.5, 0.5, -0.5</MeshGeometry3D.Positions>
                     <MeshGeometry3D.TriangleIndices>0 1 2 2 3 0</MeshGeometry3D.TriangleIndices>
                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
               <GeometryModel3D.Material>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Maroon</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.Material>
               <GeometryModel3D.BackMaterial>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Blue</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.BackMaterial>
            </GeometryModel3D>
         </ModelVisual3D.Content>
      </ModelVisual3D>
      <ModelVisual3D>
         <ModelVisual3D.Content>
            <AmbientLight Color="White" />
         </ModelVisual3D.Content>
      </ModelVisual3D>
      <Viewport3D.Camera>
         <PerspectiveCamera>
      
            <PerspectiveCamera.LookDirection>0,0,1</PerspectiveCamera.LookDirection>
            <PerspectiveCamera.Position>0,0,-2</PerspectiveCamera.Position>

            <PerspectiveCamera.FieldOfView>90</PerspectiveCamera.FieldOfView>
         </PerspectiveCamera>
      </Viewport3D.Camera>
   </Viewport3D>
</Grid>