procedure TGameWindow.ChangeZoom(aNewZoom: Integer; NoRedraw: Boolean = false);
var
OSHorz, OSVert: Single;
DoZoomOnCursor: Boolean;
procedure SetCursorToCenter();
var
MousePos, ImgCenter: TPoint;
ImgTopLeft, ImgBottomRight: TPoint;
begin
// Clip the Mouse position to the Image rectangle
MousePos := Mouse.CursorPos;
ImgTopLeft := Img.ClientToScreen(Point(0, 0));
ImgBottomRight := Img.ClientToScreen(Point(Img.Width, Img.Height));
MousePos.X := Max(Min(Mouse.CursorPos.X, ImgBottomRight.X), ImgTopLeft.X);
MousePos.Y := Max(Min(Mouse.CursorPos.Y, ImgBottomRight.Y), ImgTopLeft.Y);
// Get center of the image on the screen
ImgCenter := Point(Trunc((ImgTopLeft.X + ImgBottomRight.X) / 2), Trunc((ImgTopLeft.Y + ImgBottomRight.Y) / 2));
// Move the image location
Img.OffsetHorz := Img.OffsetHorz - (MousePos.X - ImgCenter.X);
Img.OffsetVert := Img.OffsetVert - (MousePos.Y - ImgCenter.Y);
end;
procedure ResetCenterToCursor();
var
MousePos, ImgCenter: TPoint;
ImgTopLeft, ImgBottomRight: TPoint;
begin
// Clip the Mouse position to the Image rectangle
MousePos := Mouse.CursorPos;
ImgTopLeft := Img.ClientToScreen(Point(0, 0));
ImgBottomRight := Img.ClientToScreen(Point(Img.Width, Img.Height));
MousePos.X := Max(Min(Mouse.CursorPos.X, ImgBottomRight.X), ImgTopLeft.X);
MousePos.Y := Max(Min(Mouse.CursorPos.Y, ImgBottomRight.Y), ImgTopLeft.Y);
// Get center of the image on the screen
ImgCenter := Point(Trunc((ImgTopLeft.X + ImgBottomRight.X) / 2), Trunc((ImgTopLeft.Y + ImgBottomRight.Y) / 2));
// Move the image location
Img.OffsetHorz := Img.OffsetHorz + (MousePos.X - ImgCenter.X);
Img.OffsetVert := Img.OffsetVert + (MousePos.Y - ImgCenter.Y);
end;
begin
aNewZoom := Max(Min(fMaxZoom, aNewZoom), 1);
if (aNewZoom = fInternalZoom) and not NoRedraw then
Exit;
DoZoomOnCursor := (aNewZoom > fInternalZoom);
Img.BeginUpdate;
SkillPanel.Image.BeginUpdate;
try
// If scrolling in, move the image to center on the cursor position.
// We will ensure that this is a valid position later on.
if DoZoomOnCursor then SetCursorToCenter;
// Switch to top left coordinates, not the center of the image.
OSHorz := Img.OffsetHorz - (Img.Width / 2);
OSVert := Img.OffsetVert - (Img.Height / 2);
OSHorz := (OSHorz * aNewZoom) / fInternalZoom;
OSVert := (OSVert * aNewZoom) / fInternalZoom;
Img.Scale := aNewZoom;
fInternalZoom := aNewZoom;
// Change the Img size and update everything accordingly.
ApplyResize(true);
//// If scrolling in, we wish to keep the pixel below the cursor constant.
//// Therefore we have to move the current center back to the cursor position
if DoZoomOnCursor then ResetCenterToCursor;
// Move back to center coordinates.
OSHorz := OSHorz + (Img.Width / 2);
OSVert := OSVert + (Img.Height / 2);
// Ensure that the offset doesn't move part of the visible area outside of the level area.
Img.OffsetHorz := Min(Max(OSHorz, MinScroll), MaxScroll);
Img.OffsetVert := Min(Max(OSVert, MinVScroll), MaxVScroll);
SetRedraw(rdRedraw);
CheckResetCursor(true);
finally
Img.EndUpdate;
SkillPanel.Image.EndUpdate;
end;
end;
WillLem found this code section and PM'd me. I wrote back; my replay is below the horizontal line here. The answer is from end of March 2023.
Hi,
I need more information about those names and will have to look at the full source repo. I won't have time this week [March 2023] for that. But if you have time, I have some ideas for promising attacks.
For a start, let's clarify:
What is "image":
The entire land, before rendering a section of it?
The section of the land that we are going to render?
The target bitmap onto which we draw during rendering?
What is "cursor position":
On the screen?
On the land? (I.e., it's what I call mouse-on-land? Have we really already recompute backwards from screen coordinates to land coordinates?)
// If scrolling in, move the image to center on the cursor position.
[...]
// Get center of the image on the screen
ImgCenter := Point(Trunc((ImgTopLeft.X + ImgBottomRight.X) / 2), Trunc((ImgTopLeft.Y +
Center? On first skimming, this looks like a dubious approach. Remember what we want to do: Preserve mouse-on-land before and after zooming. To do that, there is no obvious need to compute the center of anything.
... unless that's NL's preferred way of storing scrolling positions, because we need
some reference coordinate for the scrolling position. Typical choices are indeed the center of the visible section of the land, or the top-left corner of the visible land. Does NL store the scrolling position via center of visible area or top-left of visible area? If it's center, fine. If it's top-left, we should be suspicious of computing the center in your shown source.
//// If scrolling in, we wish to keep the pixel below the cursor constant.
//// Therefore we have to move the current center back to the cursor position
I assume "pixel below the cursor" means the land pixel.
Then I don't see why (move the current center back to the cursor position) accomplishes (keep the land pixel below the cursor constant).
Idea to investigate: What does the code assume to draw that conclusion? The distance on the land between mouse-on-land to center-of-rendering-area-on-land doesn't stay the same before and after zooming. Explicitly:
A = mouse on land before zooming
B = (center of rendering area) on land before zooming
C = mouse on land after zooming
D = (center of rendering area) on land after zooming
Then your qouted code looks like it wrongly assumes A − B = C − D.
From NL user experience, the code behaves worse the deeper we zoom in. Maybe (before zooming) in the wrong assumptions is really (at 1x zoom), even though I don't see how that would work in your quoted source.
Draw some examples on paper.
-- Simon