3D Shuttle Effect? UWP Can Do It Too

3D Shuttle Effect? UWP Can Do It Too

After coming home for dinner, taking care of the kids, and playing Age of Empires 4, I suddenly remembered that I haven't used UWP in a long time. A bold thought emerged: 'I have the mighty UWP, I can definitely achieve a 3D shuttle effect!'

Last updated 2/21/2022 2:31 PM
dino.c
6 min read
Category
.NET
Tags
.NET C# UWP

Yesterday, ChokCoco created a cool 3D warp effect. You can check it out here:

3D Warp Effect? Easily Done with CSS

This effect is amazing. He even asked if I could implement it in WPF, and since I've never used WPF's 3D before, my first reaction was "This is too hard for me."

When I got home, I had dinner, played with the kids, and played some Age of Empires 4. Suddenly, I remembered I hadn't used UWP in a long time. A burst of confidence hit me—"UWP can totally handle this 3D warp effect."

So I went ahead and built this animation.

In summary, the principle behind implementing 3D warp is using CSS's perspective to create a sense of depth. perspective defines the distance between the observer and the z=0 plane, giving elements with 3D transforms a perspective effect. The smaller the value, the deeper the perspective.

For more details on using perspective, check this doc:

perspective - CSS (Cascading Style Sheets) _ MDN

Correspondingly, UWP provides the PerspectiveTransform3D class. Its Depth property has a similar effect: the smaller the Depth, the deeper the perspective, and the more distorted objects intersecting the plane become:

Once I understood the principle, I started working right away. First, in XAML, I set the Grid's size to 300 and set the PerspectiveTransform3D's Depth to a very small value:

<Grid Height="300" Width="300">
  <Grid.Transform3D>
    <media3D:PerspectiveTransform3D Depth="2" />
  </Grid.Transform3D>
</Grid>

Then I got a starry sky image from ChokCoco and placed it in the Grid:

Next, I set the CompositeTransform3D's RotationY to -90, which distorted the image:

<media3D:CompositeTransform3D RotationY="-90" />

Then I set TranslateZ="100" to stretch the image outward:

<media3D:CompositeTransform3D RotationY="-90" TranslateZ="100" />

After getting one direction right, I applied similar steps to all directions, just changing the rotation, translation, and center point. This gave me a static 3D warp image:

<media3D:CompositeTransform3D
  x:Name="TransformLeft"
  x:Key="TransformLeft"
  RotationY="-90"
  TranslateZ="100"
/>
<media3D:CompositeTransform3D
  x:Name="TransformUp"
  x:Key="TransformUp"
  RotationX="90"
  TranslateZ="50"
/>
<media3D:CompositeTransform3D
  x:Name="TransformRight"
  x:Key="TransformRight"
  RotationY="90"
  CenterX="300"
  TranslateZ="50"
/>
<media3D:CompositeTransform3D
  x:Name="TransformDown"
  x:Key="TransformDown"
  RotationX="-90"
  CenterY="300"
  TranslateZ="50"
/>

<Grid
  Background="{StaticResource ImageBackground}"
  Transform3D="{StaticResource TransformLeft}"
/>
<Grid
  Background="{StaticResource ImageBackground}"
  Transform3D="{StaticResource TransformUp}"
/>
<Grid
  Background="{StaticResource ImageBackground}"
  Transform3D="{StaticResource TransformRight}"
/>
<Grid
  Background="{StaticResource ImageBackground}"
  Transform3D="{StaticResource TransformDown}"
/>

The next step was to animate these four images. That was simple—just use a basic DoubleAnimation to change TranslateZ from 10 to 200:

<Storyboard x:Name="Move" x:Key="Move" RepeatBehavior="Forever">
  <DoubleAnimation
    Storyboard.TargetName="TransformLeft"
    Storyboard.TargetProperty="TranslateZ"
    From="10"
    To="200"
    Duration="0:0:8"
  />
  <DoubleAnimation
    Storyboard.TargetName="TransformUp"
    Storyboard.TargetProperty="TranslateZ"
    From="10"
    To="200"
    Duration="0:0:8"
  />
  <DoubleAnimation
    Storyboard.TargetName="TransformRight"
    Storyboard.TargetProperty="TranslateZ"
    From="10"
    To="200"
    Duration="0:0:8"
  />
  <DoubleAnimation
    Storyboard.TargetName="TransformDown"
    Storyboard.TargetProperty="TranslateZ"
    From="10"
    To="200"
    Duration="0:0:8"
  />
</Storyboard>

At this point, the basic animation was working, but it didn't loop seamlessly. So I first encapsulated the result into a control:

Then I added an opacity animation:

<Storyboard x:Name="Fade" RepeatBehavior="Forever">
  <DoubleAnimationUsingKeyFrames
    Storyboard.TargetName="Root"
    Storyboard.TargetProperty="Opacity"
    Duration="0:0:8"
  >
    <LinearDoubleKeyFrame KeyTime="0:0:0" Value="0" />
    <LinearDoubleKeyFrame KeyTime="0:0:2" Value="1" />
    <LinearDoubleKeyFrame KeyTime="0:0:4.8" Value="1" />
    <LinearDoubleKeyFrame KeyTime="0:0:8" Value="0" />
  </DoubleAnimationUsingKeyFrames>
</Storyboard>

By stacking two GalaxyShuttleControl instances and controlling their animation start times, I masked the issue of mismatched start and end animations:

public TimeSpan Delay { get; set; }

private async void GalaxyShettleControl_Loaded(object sender, RoutedEventArgs e)
{
    await Task.Delay(Delay);
    Move.Begin();
    Fade.Begin();
}
<galaxyshuttles:GalaxyShuttleControl Delay="0:0:4" />
<galaxyshuttles:GalaxyShuttleControl/>

And that's how the 3D warp effect was achieved.

Finally, one thing was missing: ChokCoco's animation includes hueRotate to continuously change colors. In UWP, you can use HueRotationEffect for this, but its Angle value ranges from 0 to 2 * Math.PI. To animate it, you could use the Windows Community Toolkit's PipelineVisualFactory and AnimationSet, which are capable of handling complex effects and animations—overkill for this case, but still:

<media:UIElementExtensions.VisualFactory>
    <media:PipelineVisualFactory>
        <media:HueRotationEffect x:Name="HueRotationEffect" IsAnimatable="True"/>
    </media:PipelineVisualFactory>
</media:UIElementExtensions.VisualFactory>
<animations:Explicit.Animations>
    <animations:AnimationSet x:Name="HueAnimation" >
        <animations:AnimationScope>
            <animations:HueRotationEffectAnimation From="0" To="6.28318530718" Target="{Binding ElementName=HueRotationEffect}"  Repeat="Forever" Duration="0:0:28"/>
        </animations:AnimationScope>
    </animations:AnimationSet>
</animations:Explicit.Animations>

The final result looks like this:

After satisfying my curiosity, I stopped—no need to generate my own images. Implementing a 3D warp animation isn't that hard; the hardest part was ChokCoco's creative idea. I look forward to more animations from him that I can "copy" and play with.

Finally, I should note that while MAUI or WinUI3 could probably do this, and they sound more trendy, they haven't released their official versions yet. So for now, I'll stick with UWP.

Source code: https://github.com/DinoChan/uwp_design_and_animation_lab

Keep Exploring

Related Reading

More Articles