(I've seen some similar-ish questions to mine, but they do not really apply to my case.)
I am scripting on a custom canvas for a program plugin using Lua. Drawn onto the canvas is a grid of squares, and the "zoom" is controlled by controlling the width of the squares. There are 7 zoom levels. (If you zoom out, it goes to the next level up; zoom in, it goes to the next level down.) The camera is an xy point that is just offsets from 0,0.
Here is my code sample:
-- Each index is a different zoom level. Lua indexes start at 1. If the user tries zooming past one of the ends, it will stay at said end (clamp).
local squareSizes = {8, 12, 16, 20, 24, 28, 32}
local squareSizeIndex = 3 -- This value is changed externally whenever the user zooms.
local cameraOffset = {x=0, y=0} -- When the user moves/pan's the screen, this value is affected.
local function drawSquare(x, y, size)
-- Square drawing placeholder - this function will draw a square at a specified xy location and with the specified size (width and height).
end
local function Update(squares, mousePos) -- Mouse pos is in the same format as the camera variable (eg: mousePos.x is the x pos of the mouse)
for p, _ in pairs(squares) do -- p is the location a square should be drawn at. p changes based on the squareSize, because number of pixels required to get to the next p (or the square size) is smaller/bigger.
local x = p.x + cameraOffset.x
local y = p.y + cameraOffset.y
drawSquare(x, y, squareSizes[squareSizeIndex])
end
end
Each square is being drawn at p + camera. If I zoom in/out, the canvas doesn't zoom in or out centered around the mouse—it centers around 0,0 instead.
The only two things changing are p and the square size. How can I implement zooming into the mouse position while retaining my grid system?
After some tinkering, I have got the code to work. Here is my solution/process:
Before the canvas is zoomed, the mouse is a certain distance away from the camera (for example the x distance would be mousePos.x - cameraOffset.x). The goal of zooming the canvas is to retain this distance. If we divide this by the current square size (squareSizes[squareSizeIndex]), then we get how many squares away the camera is from the mouse.
local xSquaresAwayFromPoint = (point.x - cameraOffset.x) / squareSizes[squareSizeIndex]
local ySquaresAwayFromPoint = (point.y - cameraOffset.y) / squareSizes[squareSizeIndex]
After we increment/step the square size index, we can then multiply the value by the new square size.
-- Increment the square size index. The step is how much we want to increment the squareSizeIndex. Can be a negative value.
squareSizeIndex = clamp(squareSizeIndex + step, 1, 7)
-- Get the offset based on the number of squares
local xOffset = math.floor(squareSizes[squareSizeIndex] * xSquaresAwayFromPoint)
local yOffset = math.floor(squareSizes[squareSizeIndex] * ySquaresAwayFromPoint)
That gets us the offset from the camera, so we subtract the offset from the camera to get the new camera position.
cameraOffset.x = point.x - xOffset
cameraOffset.y = point.y - yOffset
Here is the full code:
local squareSizes = {8, 12, 16, 20, 24, 28, 32}
local squareSizeIndex = 3
local cameraOffset = {x=0, y=0}
local function clamp(x, min, max)
-- Clamp number placeholder
end
local function squareSize()
-- This is just a getter for squareSizes[squareSizeIndex]
return clamp(squareSizes[squareSizeIndex], 1, 7)
end
local function drawSquare(x, y, size)
-- Square drawing placeholder
end
local function zoomCanvas(point, step)
-- Get how many squares away the point is from the camera
local xSquaresAwayFromPoint = (point.x - cameraOffset.x) / squareSize()
local ySquaresAwayFromPoint = (point.y - cameraOffset.y) / squareSize()
-- Increment the square size index
squareSizeIndex = clamp(squareSizeIndex + step, 1, 7)
-- Get the offset based on the number of squares
local xOffset = math.floor(squareSize() * xSquaresAwayFromPoint)
local yOffset = math.floor(squareSize() * ySquaresAwayFromPoint)
-- Offset the camera
cameraOffset.x = point.x - xOffset
cameraOffset.y = point.y - yOffset
end
local function Update(squares, step, mousePos) -- Step is the amount to increment the zoom index by. Can be positive or negative.
zoomCanvas(mousePos, step)
for p, _ in pairs(squares) do
local x = p.x + cameraOffset.x
local y = p.y + cameraOffset.y
drawSquare(x, y, squareSize())
end
end