Zooming smoothly

BlitzMax Forums/BlitzMax Programming/Zooming smoothly

OK, I've stared at it for a bit, and I think I can see what you're doing.
If you consider the part of the world that is drawn on the screen as a box in space, what you're doing is shrinking the box (linearly!) down until it fits the square (150,150)->(170,170).

But then to draw the screen, you convert the co-ordinates of your points using a function that isn't linear! If you take the line
Local rx1:Int = (x1-ZX1)*800/(ZX2-ZX1)

and expand it out, the bit depends on t (the time, starting at t=0 and ending at t=1) behaves like 1/t, which is where you're getting the acceleration from.

So, we need some new thinking.
We want to stretch a rectangle with top-left corner (x1,y1) and bottom-right corner (x2,y2) so that every point in the rectangle moves towards its final position at a constant speed.
Importantly, (x1,y1) and (x2,y2) must not move in the virtual space, just when they are projected onto the screen.
So we will make a function Zoom(x,y,t) that, given a point (x,y) in the virtual space and the time t, returns a point (px,py) on the screen at which to draw that point.
Minor niggle here that it's difficult to return two things at once in BMax, so we'll actually need two functions, ZoomX(x,t) and ZoomY(y,t).

We need ZoomX(x1,0) = x1 and ZoomX(x1,1) = 0. We also need ZoomX(x2,0)=x2 and ZoomX(x2,1)=800.

A note about linear interpolation, which I think you understand because you had it half-right in your code, but I might as well make it explicit:
To interpolate a value X linearly between two values A and B, so that at time t=0, X = A ,and at time t=1, X = B, set
X = A * (1 - t) + B * t


So, try:
Function ZoomX:Double(x:Double,t:Double,x1:Double,x2:Double)
	Local zx1:Double=x1*(1-t)	'this is the position of x1 on the screen at time t.
						'note that this is linear, and has zx1(t=0) = x1 and zx1(t=1) = 0
						'we could also work out zx2, for the point x2, but we don't need to.
					
	Local width:Double=(x2-x1)*(1-t)+800*t	'this gives us how wide, in pixels, the rectangle is 
										'on the screen at time t. Note that it is linear and 
										'we have width(t=0) = x2 - x1 and width(t=1) = 800.
								
	Local nx:Double=(x-x1)/(x2-x1)	'We want to know where the point x is in the rectangle as
								'a proportion of the width. Note that this number does not
								'depend on t, so remains constant throughout.
	
	Local px:Double=zx1+nx*width		'finally, this gives us the position on the screen of our point.
								'Note that if x=x1 then nx=0 so px = zx1 as expected, and if
								'x=x2 then nx=1 so we will get px = x2*(1-t)+800*t = zx2.
								
	Return px
	
End Function


The function for ZoomY looks much the same, but with Ys instead of Xs and 600 instead of 800.

Now, we can rewrite the LinearZoom function and put it back in the program:
SuperStrict

Graphics 800,600

While Not KeyHit(KEY_ESCAPE) And Not AppTerminate()
	Cls
	DrawLine 150,150,170,170
	DrawLine 150,170,170,150
	DrawText "Press L for linear zoom",10,500
	Flip
	If KeyHit(KEY_L) Then LinearZoom(150,150,170,170)
Wend


Function ZoomX:Double(x:Double,t:Double,x1:Double,x2:Double)
	Local zx1:Double=x1*(1-t)	'this is the position of x1 on the screen at time t.
						'note that this is linear, and has zx1(t=0) = x1 and zx1(t=1) = 0
						'we could also work out zx2, for the point x2, but we don't need to.
					
	Local width:Double=(x2-x1)*(1-t)+800*t	'this gives us how wide, in pixels, the rectangle is 
										'on the screen at time t. Note that it is linear and 
										'we have width(t=0) = x2 - x1 and width(t=1) = 800.
								
	Local nx:Double=(x-x1)/(x2-x1)	'We want to know where the point x is in the rectangle as
								'a proportion of the width. Note that this number does not
								'depend on t, so remains constant throughout.
	
	Local px:Double=zx1+nx*width		'finally, this gives us the position on the screen of our point.
								'Note that if x=x1 then nx=0 so px = zx1 as expected, and if
								'x=x2 then nx=1 so we will get px = x2*(1-t)+800*t = zx2.
								
	Return px
	
End Function

Function ZoomY:Double(y:Double,t:Double,y1:Double,y2:Double)
	Local zy1:Double=y1*(1-t)	'this is the position of y1 on the screen at time t.
						'note that this is linear, and has zy1(t=0) = y1 and zy1(t=1) = 0
						'we could also work out zy2, for the point y2, but we don't need to.
					
	Local width:Double=(y2-y1)*(1-t)+600*t	'this gives us how wide, in pixels, the rectangle is 
										'on the screen at time t. Note that it is linear and 
										'we have width(t=0) = y2 - y1 and width(t=1) = 600.
								
	Local ny:Double=(y-y1)/(y2-y1)	'We want to know where the point y is in the rectangle as
								'a proportion of the width. Note that this number does not
								'depend on t, so remains constant throughout.
	
	Local py:Double=zy1+ny*width		'finally, this gives us the position on the screen of our point.
								'Note that if y=y1 then ny=0 so py = zy1 as expected, and if
								'y=y2 then ny=1 so we will get py = y2*(1-t)+600*t = zy2.
								
	Return py
	
End Function


Function LinearZoom(ex1:Double,ey1:Double,ex2:Double,ey2:Double)
	For Local i:Int = 0 To 300
		Local t:Double = Double(i)/Double(300)
		Line(150,150,170,170,t,ex1,ey1,ex2,ey2)
		Line(150,170,170,150,t,ex1,ey1,ex2,ey2)
		Flip
		Cls
	Next
	WaitKey()
End Function

Function Line(x1:Double,y1:Double,x2:Double,y2:Double,t:Double,ex1:Double,ey1:Double,ex2:Double,ey2:Double)
	Local zx1:Double=ZoomX(x1,t,ex1,ex2)
	Local zy1:Double=ZoomY(y1,t,ey1,ey2)
	Local zx2:Double=ZoomX(x2,t,ex1,ex2)
	Local zy2:Double=ZoomY(y2,t,ey1,ey2)


	DrawLine zx1,zy1,zx2,zy2
End Function


Note that I pass the ex1,ey1, etc. around all the time because I didn't want to confuse matters with new globals, but if you're always doing the same zoom you could just make them globals and set them at the start of the process.