microsoft / WindowsCompositionSamples

The Windows Composition Samples have moved here: https://github.com/microsoft/WindowsAppSDK-Samples/tree/main/Samples/SceneGraph
https://github.com/microsoft/WindowsAppSDK-Samples/tree/main/Samples/SceneGraph
MIT License
1.12k stars 287 forks source link

I can't get my Projection Matrix to have a proper perspective for a Rotation animation #339

Closed Massimo37 closed 5 years ago

Massimo37 commented 5 years ago

Im trying to replicate a plane projection rotation in UWP using the composition api. But I'm having trouble with a projection matrix in my composition code. Heres what I have:

private void Perspective(FrameworkElement element, double duration, float rotationDepth = 750f)
{
    var parent = ElementCompositionPreview.GetElementVisual(element.Parent as FrameworkElement);

    var width = (float)element.ActualWidth;
    var height = (float)element.ActualHeight;
    var halfWidth = (float)(width / 2.0);
    var halfHeight = (float)(height / 2.0);

    // Initialize the Compositor
    var visual = ElementCompositionPreview.GetElementVisual(element);

    // Create Scoped batch for animations
    var batch = visual.Compositor.CreateScopedBatch(CompositionBatchTypes.Animation);

    // Rotation animation
    var projectionMatrix = new Matrix4x4(1, 0, 0, 0,
                                            0, 1, 0, 0,
                                            0, 0, 1, 1 / rotationDepth,
                                            0, 0, 0, 1);

    // To ensure that the rotation occurs through the center of the visual rather than the
    // left edge, pre-multiply the rotation matrix with a translation that logically shifts
    // the axis to the point of rotation, then restore the original location
    parent.TransformMatrix = Matrix4x4.CreateTranslation(-halfWidth, -halfHeight, 0) *
                            projectionMatrix *
                            Matrix4x4.CreateTranslation(halfWidth, halfHeight, 0);

    // Rotate along the Y-axis
    visual.RotationAxis = new Vector3(0, 1, 0);
    visual.CenterPoint = new Vector3(halfWidth, halfHeight, 0f);
    visual.RotationAngleInDegrees = 0.0f;

    var rotateAnimation = visual.Compositor.CreateScalarKeyFrameAnimation();

    rotateAnimation.InsertKeyFrame(0.0f, 90);
    rotateAnimation.InsertKeyFrame(1f, 0);
    rotateAnimation.Duration = TimeSpan.FromMilliseconds(duration);

    visual.StartAnimation("RotationAngleInDegrees", rotateAnimation);

    // Batch is ended an no objects can be added
    batch.End();
}

So this code above will rotate along the Y-axis. In the gif below, you'll see that I have it animating on top of another animation which is driven by a PlaneProjection for comparison:

enter image description here

The "perspective" of both is fine, both in the middle. Now lets change this line of code to switch it for a rotation on the X-axis:

// Rotate along the Y-axis
visual.RotationAxis = new Vector3(0, 1, 0);

Now notice the gif below:

enter image description here

The composition animation seems to rotate with a perspective that`s more towards the right and not perfectly centered. Does my projection matrix need to change?

assassin316 commented 5 years ago

I've added a GitHub sample with the code above and some other code I was testing based on an answer on StackOverflow (which I couldn't get to work):

StackOverflow https://stackoverflow.com/questions/58081943/i-cant-get-my-projection-matrix-to-have-a-proper-perspective-for-a-rotation-ani

GitHub https://github.com/assassin316/Perspective

assassin316 commented 5 years ago

I'll loop in @robmikh because his code from #32 helped me get this far. 👍

robmikh commented 5 years ago

The issue has to do with the transform you're manipulating on the parent. The StackPanel that gets picked up by element.Parent has an existing transform for layout purposes (e.g. VerticalAlignment="Center"), which can cause issues. This is another flavor of XAML-Composition interop friction, which you can read more about the general case here and here.

However this is certainly a strange variant, it had me stumped for a bit! I'm not entirely sure what's going on, but it has to do with manipulating the transform of your StackPanel. Thankfully, the usual remedies apply here. To get your code working, I modified your MainPage.xaml:

<StackPanel VerticalAlignment="Center">
    <Canvas Width="300" Height="250" Margin="0,0,0,40">
        <Border Width="300"
            Height="250"
            Background="LightSeaGreen"
            x:Name="Persp">
            <TextBlock Text="Composition"
                   FontSize="28"
                   HorizontalAlignment="Center"
                   VerticalAlignment="Center"
                   FontWeight="Bold"
                   Foreground="White" />
        </Border>
    </Canvas>

    <Border Width="300"
            Height="250"
            Background="LightCoral"
            x:Name="Proj">
        <Border.RenderTransform>
            <CompositeTransform />
        </Border.RenderTransform>
        <Border.Projection>
            <PlaneProjection CenterOfRotationX="0.5" CenterOfRotationY="0.5" />
        </Border.Projection>

        <TextBlock Text="PlaneProjection"
                   FontSize="28"
                   HorizontalAlignment="Center"
                   VerticalAlignment="Center"
                   FontWeight="Bold"
                   Foreground="White" />
    </Border>
</StackPanel>

By manipulating the transform of the Canvas instead of the StackPanel, you should be able to avoid interference.

Let me know if that works for you!

assassin316 commented 5 years ago

That was the fix!! Thank you @robmikh 👍 I can definitely work with that :)

robmikh commented 5 years ago

Glad it worked out :)