wpfwindows-phone-8windows-runtimeaffinetransformviewport3d

Cube in planeprojection. Fitting the top


I am busy migrating a WPF solution to a Windows Store App. That solution uses ViewPort3D which you don't have in App development so I am trying to convert it using PlaneProjection.

The objective is to implement a cube that can rotate around one axis with a storyboard. I have no problems with the sides of the cube but the top rectangle is giving me a headache.

I have made a testset to visualize my problem. If you create a new blanc Windows Store App (NOT Phone) and name it PlaneTest then paste the XAML over your entire MainPage.xaml and place the C# class in the MainPage.xaml.cs.

XAML:

<Page
    x:Class="PlaneTest.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:PlaneTest"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="500"/>
            <ColumnDefinition Width="700"/>
        </Grid.ColumnDefinitions>   
        <Grid.Resources>
            <Style TargetType="Slider">
                <Setter Property="Width" Value="400"/>
                <Setter Property="Height" Value="23"/>
            </Style>
            <Style TargetType="TextBlock">
                <Setter Property="Margin" Value="0,5,0,0"/>
            </Style>
        </Grid.Resources>
        <StackPanel Orientation="Vertical" Width="450" Grid.Column="0">
            <TextBlock Text="CUBE SLIDERS" Margin="25"></TextBlock>
            <TextBlock Text="Rotation X = 23. CUBE ROTATES AROUND THIS AXIS."></TextBlock>
            <Slider Name="RotateX" Minimum="-90" Maximum="90" Value="23"></Slider>
            <TextBlock Text="Rotation Y = -25"></TextBlock>
            <Slider Name="RotateY" Minimum="-90" Maximum="90" Value="-25"></Slider>
            <TextBlock Text="Rotation Z = -90"></TextBlock>
            <Slider Name="RotateZ" Minimum="-90" Maximum="90" Value="-90"></Slider>

            <TextBlock Text="TOP RECTANGLE" Margin="25,50,0,0"></TextBlock>
            <TextBlock Text="Rotation X"></TextBlock>
            <Slider Name="TopRotateX" Minimum="-180" Maximum="180"></Slider>
            <TextBlock Text="Rotation Y"></TextBlock>
            <Slider Name="TopRotateY" Minimum="-180" Maximum="180"></Slider>
            <TextBlock Text="Rotation Z"></TextBlock>
            <Slider Name="TopRotateZ" Minimum="-180" Maximum="180"></Slider>

            <TextBlock Text="Center Of Rotation X" Margin="0,40,0,0"></TextBlock>
            <Slider Name="CenterOfXRotation" Value="0.5" Minimum="0" Maximum="1" StepFrequency="0.1"></Slider>
            <TextBlock Text="Center Of Rotation Y"></TextBlock>
            <Slider Name="CenterOfYRotation" Value="0.5" Minimum="0" Maximum="1" StepFrequency="0.1"></Slider>
            <TextBlock Text="Center Of Rotation Z"></TextBlock>
            <Slider Name="CenterOfZRotation" Value="200" Minimum="-200" Maximum="200"></Slider>

            <TextBlock Text="GlobalOffSet X" Margin="0,40,0,0"></TextBlock>
            <Slider Name="GlobalXOffSet"  Minimum="-200" Maximum="200" ></Slider>
            <TextBlock Text="GlobalOffSet Y"></TextBlock>
            <Slider Name="GlobalYOffSet" Width="400" Height="23" Minimum="-200" Maximum="200"></Slider>
            <TextBlock Text="GlobalOffSet Z"></TextBlock>
            <Slider Name="GlobalZOffSet" Minimum="-200" Maximum="200"></Slider>
         </StackPanel>     

        <Grid x:Name="LayoutRoot"  Grid.Column="1">
            <Grid.Resources>
                <Style TargetType="Rectangle">
                    <Setter Property="Height" Value="400"/>
                    <Setter Property="Width" Value="400"/>
                </Style>
                <local:RotationXOffset x:Key="rotationXOffset"/>
            </Grid.Resources>

            <Rectangle Fill="#a0FF0000" Name="Side1">
               <Rectangle.Projection>
                   <PlaneProjection CenterOfRotationZ="200" 
                                RotationX="{Binding ElementName=RotateX, Path=Value, Converter={StaticResource rotationXOffset}, ConverterParameter='-90'}" 
                                RotationY="{Binding ElementName=RotateY, Path=Value}"
                                RotationZ="{Binding ElementName=RotateZ,Path=Value}"/>
                    </Rectangle.Projection>
            </Rectangle>

            <Rectangle Fill="#a00000FF" Name="Side2">
                <Rectangle.Projection>
                    <PlaneProjection CenterOfRotationZ="200"
                                RotationX="{Binding ElementName=RotateX, Path=Value, Converter={StaticResource rotationXOffset}, ConverterParameter='-0'}" 
                                RotationY="{Binding ElementName=RotateY, Path=Value}"
                                RotationZ="{Binding ElementName=RotateZ, Path=Value}"/>
                </Rectangle.Projection>
            </Rectangle>

            <Rectangle Fill="#a0EFFF30" Name="Side3">
                <Rectangle.Projection>
                    <PlaneProjection CenterOfRotationZ="200"                          
                                RotationX="{Binding ElementName=RotateX, Path=Value, Converter={StaticResource rotationXOffset}, ConverterParameter='90'}" 
                                RotationY="{Binding ElementName=RotateY, Path=Value}"
                                RotationZ="{Binding ElementName=RotateZ, Path=Value}"/>
                </Rectangle.Projection>
            </Rectangle>

            <Rectangle Fill="#af00FF00" Name="Side4">
                <Rectangle.Projection>
                    <PlaneProjection CenterOfRotationZ="200"
                                RotationX="{Binding ElementName=RotateX, Path=Value, Converter={StaticResource rotationXOffset}, ConverterParameter='180'}" 
                                RotationY="{Binding ElementName=RotateY, Path=Value}"
                                RotationZ="{Binding ElementName=RotateZ, Path=Value}"/>
                </Rectangle.Projection>
            </Rectangle>

            <Rectangle Fill="Orange"  Name="Top">
                <Rectangle.Projection>
                    <PlaneProjection
                               CenterOfRotationX="{Binding ElementName=CenterOfXRotation, Path=Value}"
                               CenterOfRotationY="{Binding ElementName=CenterOfYRotation, Path=Value}"
                               CenterOfRotationZ="{Binding ElementName=CenterOfZRotation, Path=Value}"
                               GlobalOffsetX="{Binding ElementName=GlobalXOffSet, Path=Value}"
                               GlobalOffsetY="{Binding ElementName=GlobalYOffSet, Path=Value}"
                               GlobalOffsetZ="{Binding ElementName=GlobalZOffSet, Path=Value}"
                               RotationX="{Binding ElementName=TopRotateX, Path=Value}" 
                               RotationY="{Binding ElementName=TopRotateY, Path=Value}" 
                               RotationZ="{Binding ElementName=TopRotateZ, Path=Value}" />
                </Rectangle.Projection>
            </Rectangle>
        </Grid>  
    </Grid>
</Page>

C#

 class RotationXOffset : IValueConverter
    {
        object IValueConverter.Convert(object value, Type targetType, object parameter, string language)
        {
            return (double)value + Convert.ToDouble(parameter);
        }

        object IValueConverter.ConvertBack(object value, Type targetType, object parameter, string language)
        {
            throw new NotImplementedException();
        }
    }

If you don't thouch the 3 top sliders, try putting the orange top rectangle on top of the cube by using the other sliders. I can't.

I can fit the top when I move the Rotation X most top slider to 0 or 90, but not with any other values. What am I doing wrong? Is it even possible with PlaneProjection? Am I running into this affine transformation limitation? http://www.charlespetzold.com/blog/2009/01/Non-Affine-Transforms-in-Silverlight.html


Solution

  • Meanwhile I was working on an other project now back on this solution I had another look at it.

    With this XAML and the value converter from the question it works as i wanted.

    <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="500"/>
                <ColumnDefinition Width="700"/>
            </Grid.ColumnDefinitions>   
            <Grid.Resources>
                <Style TargetType="Slider">
                    <Setter Property="Width" Value="400"/>
                    <Setter Property="Height" Value="23"/>
                </Style>
                <Style TargetType="TextBlock">
                    <Setter Property="Margin" Value="0,5,0,0"/>
                </Style>
            </Grid.Resources>
            <StackPanel Orientation="Vertical" Width="450" Grid.Column="0">
                <TextBlock Text="CUBE SLIDERS" Margin="25"></TextBlock>
                <TextBlock Text="Rotation X = 23. CUBE ROTATES AROUND THIS AXIS."></TextBlock>
                <Slider Name="RotateX" Minimum="-90" Maximum="90" Value="23"></Slider>
                <TextBlock Text="Rotation Y = -25"></TextBlock>
                <Slider Name="RotateY" Minimum="-90" Maximum="90" Value="-25"></Slider>
                <TextBlock Text="Rotation Z = -90"></TextBlock>
                <Slider Name="RotateZ" Minimum="-90" Maximum="90" Value="-90"></Slider>
            </StackPanel>     
    
            <Grid x:Name="LayoutRoot"  Grid.Column="1">
                <Grid.Resources>
                    <Style TargetType="Rectangle">
                        <Setter Property="Height" Value="400"/>
                        <Setter Property="Width" Value="400"/>
                    </Style>
                    <local:RotationXOffset x:Key="rotationXOffset"/>
                </Grid.Resources>
    
                <Rectangle Fill="#a0FF0000" Name="Side1">
                   <Rectangle.Projection>
                       <PlaneProjection CenterOfRotationZ="200" 
                                    RotationX="{Binding ElementName=RotateX, Path=Value, Converter={StaticResource rotationXOffset}, ConverterParameter='-90'}" 
                                    RotationY="{Binding ElementName=RotateY, Path=Value}"
                                    RotationZ="{Binding ElementName=RotateZ,Path=Value}"/>
                        </Rectangle.Projection>
                </Rectangle>
    
                <Rectangle Fill="#a00000FF" Name="Side2">
                    <Rectangle.Projection>
                        <PlaneProjection CenterOfRotationZ="200"
                                    RotationX="{Binding ElementName=RotateX, Path=Value, Converter={StaticResource rotationXOffset}, ConverterParameter='-0'}" 
                                    RotationY="{Binding ElementName=RotateY, Path=Value}"
                                    RotationZ="{Binding ElementName=RotateZ, Path=Value}"/>
                    </Rectangle.Projection>
                </Rectangle>
    
                <Rectangle Fill="#a0EFFF30" Name="Side3">
                    <Rectangle.Projection>
                        <PlaneProjection CenterOfRotationZ="200"                          
                                    RotationX="{Binding ElementName=RotateX, Path=Value, Converter={StaticResource rotationXOffset}, ConverterParameter='90'}" 
                                    RotationY="{Binding ElementName=RotateY, Path=Value}"
                                    RotationZ="{Binding ElementName=RotateZ, Path=Value}"/>
                    </Rectangle.Projection>
                </Rectangle>
    
                <Rectangle Fill="#af00FF00" Name="Side4">
                    <Rectangle.Projection>
                        <PlaneProjection CenterOfRotationZ="200"
                                    RotationX="{Binding ElementName=RotateX, Path=Value, Converter={StaticResource rotationXOffset}, ConverterParameter='180'}" 
                                    RotationY="{Binding ElementName=RotateY, Path=Value}"
                                    RotationZ="{Binding ElementName=RotateZ, Path=Value}"/>
                    </Rectangle.Projection>
                </Rectangle>
    
            </Grid>  
            <Grid Grid.Column="1">
                <Grid.Projection>
                    <PlaneProjection RotationX="115" CenterOfRotationZ="200"/>
                </Grid.Projection>
                <Rectangle Fill="Orange"  Name="Top" Height="400" Width="400" >
                    <Rectangle.Projection>
                        <PlaneProjection RotationZ="{Binding ElementName=RotateX, Path=Value}"/>
                    </Rectangle.Projection>
                </Rectangle>
            </Grid>
        </Grid>