UWP: Calculate Transformation based on ScrollViewer
Asked Answered
G

1

24

I have a windows universal app where I am rendering a scene with DirectX. I want to make use of the Scrollviewer and therefore I render my scene behind the Scrollviewer and want to calculate the scene transformation based on the Scrollviewer. It works fine so far, especially the translation and scrolling. But when I zoom in, the scene jumps around in two special situations:

  1. The scene had enough space and was centered and now scrolling is required.
  2. The opposite direction.

More or less I use the following code:

float zoom = scrollViewer.ZoomFactor;

float inverseZoom = 1f / scrollViewer.ZoomFactor;

float scaledContentW = Document.Size.X * scrollViewer.ZoomFactor;
float scaledContentH = Document.Size.Y * scrollViewer.ZoomFactor;

float translateX;
float translateY;

if (scaledContentW < scrollViewer.ViewportWidth)
{
    translateX = ((float)scrollViewer.ViewportWidth * inverseZoom - Document.Size.X) * 0.5f;
}
else
{
    translateX = -inverseZoom * (float)scrollViewer.HorizontalOffset;
}

if (scaledContentH < scrollViewer.ViewportHeight)
{
    translateY = ((float)scrollViewer.ViewportHeight * inverseZoom - Document.Size.Y) * 0.5f;
}
else
{
    translateY = -inverseZoom * (float)scrollViewer.VerticalOffset;
}

float visibleX = inverseZoom * (float)scrollViewer.HorizontalOffset;
float visibleY = inverseZoom * (float)scrollViewer.VerticalOffset; ;

float visibleW = Math.Min(Document.Size.X, inverseZoom * (float)scrollViewer.ViewportWidth);
float visibleH = Math.Min(Document.Size.Y, inverseZoom * (float)scrollViewer.ViewportHeight);

Rect2 visibleRect = new Rect2(visibleX, visibleY, visibleW, visibleH);

transform =
    Matrix3x2.CreateTranslation(
        translateX,
        translateY) *
    Matrix3x2.CreateScale(zoom);

You can get an example here: https://github.com/SebastianStehle/Win2DZoomTest

To be sure that my eyes are not broken I was zooming around and have written the translation and zoom values to a file. You can see it here:

https://www.dropbox.com/s/9ak6ohg4zb1mnxa/Test.png?dl=0

The meaning of the columns is the following:

Column 1: The computed zoom value of the transformation matrix (M11) = ScrollViewer.ZoomFactor Column 2: The computed x offset of the matrix (See above) Column 3: The x value of the result of matrix * vector (500, 500), here: Colum1 * 500 + Column2

You see, that the matrix values look good, but when applying the transformation you get this little jump to the right for some milliseconds. One theory was, that the viewport might change because the scrollbar becomes visible. But this is not the case. I also tried fixed values here, made the scrollbars visible and even created a custom template for the scrollviewer with no scrollbars at all.

Btw: This is a cross post, I also asked the question here: https://github.com/Microsoft/Win2D/issues/125

Grape answered 7/8, 2015 at 15:26 Comment(0)
F
6

You see this behavior because when you zoom bigger than the ScrollViewer's size, the zoom center point is moved. To fix this, you just need to subscribe to the ScrollViewer's LayoutUpdated event and inside the handler, manually keep its content in the center.

    private void ScrollViewer_LayoutUpdated(object sender, object e)
    {
        this.ScrollViewer.ChangeView(this.ScrollViewer.ScrollableWidth / 2, this.ScrollViewer.ScrollableHeight / 2, this.ScrollViewer.ZoomFactor, true);
    }

This should fix the jumpy movement on the two drawed Rectangles from Win2D.


Update

Just to prove my point, the jumpy behavior is most likely due to unusual translate x and/or y value change when the content size goes over the size of the ScrollViewer. So I wrote some code to log these values on the screen as shown below -

...
this.Test1.Text += ((float)translateX).ToString() + " ";
this.Test2.Text += ((float)translateY).ToString() + " ";

session.Transform =
    Matrix3x2.CreateTranslation(
        (float)translateX,
        (float)translateY) *
    Matrix3x2.CreateScale((float)zoom);

enter image description here

Now look at the numbers on the image above. What I did was I tried zooming in until the jumpy scene occurred. See the highlighted translate y value? It is slightly greater than its previous value, which is against the declining trend.

So to fix this, you will need to be able to skip these unusual values caused by ScrollViewer.

Ferry answered 20/8, 2015 at 3:59 Comment(12)
With this approach panning does not work anymore and you cannot zoom to the mouse position.Grape
@Grape if you want pan, then only do ChangeView when zooming happens; if you want to zoom to mouse position, then get the mouse point and replace ChangeView's first two params based on mouse point position.Ferry
I dont get it, why should I zoom manually? I dont want to implement custom scrolling. I want to use the default behavior of the scrollviewer and transform a layer under it. This works fine as described below besides one jump to the right (or top) when changing from the "if" to the "else" ;)Grape
Thanks for your help. I found the same issue, you see it in my attached screenshot. The question is: What is the reason? (Bug in ScrollViewer, wrong math) and how to solve it?Grape
Well, like I already explained... It's the nature of the ScrollViewer. When the content grows bigger than itself, its central point will get moved slightly. How to solve this? See the last paragraph of my answer.Ferry
I dont get it. The scrollviewer also needs to transform the content. Therefore there are only 2 options: 1. My calculation is wrong 2. The values from the scrollviewer are wrong I see, that all my values are correct, besides 1 or 2 updates. You dont explain the problem or I dont understand you.Grape
Your calculation is correct. Otherwise it won't even work. Put a xaml Rectangle inside the sv and zoom it. You will see it jump a bit too. Because the central point is not always the middle of the sv. So your 2nd point is almost correct, except the values from sv are not wrong, just slightly off in a couple of cases due to the CENTRAL POINT CHANGE.Ferry
So you say there is not solution?Grape
But how? You can get the same values when you do a translation. And my diagrams show that the values itself might be very uniform (no jump in the vertical offset and the zoom value), but a jump in the resulting transformation.Grape
Did you even read my answer?? The highlighted value is greater than its previous in an overall declining trend. Surly you should be able to pick it up n ignore the transformation??Ferry
The highlighted value is tranalateY, NOT the final transform value in your diagram.Ferry
Sorry, too much stuff in parallel here. I tried your approach and skipped a value whenever the sign between the translation minus the last translation changed. So far it doesnt workGrape

© 2022 - 2024 — McMap. All rights reserved.