Zoom in at mouse (SLICK2D)

I’ve been working on a RTS game and I wanted to be able to zoom in with the mouse wheel toward the mouse.
In a lot of RTS games, you can hover over a unit, scroll upwards, and end up with the unit in the middle of your screen very zoomed.
That’s what I’m trying to accomplish.


Here's my World.java: [spoiler]
package net.space.render;

import java.util.ArrayList;

import net.space.entity.Entity;
import net.space.main.ReducedSpace;

import org.lwjgl.input.Mouse;
import org.newdawn.slick.Graphics;

public class World {

	public float zX=0, zY=0;

	private float zoomLevel;
	private static final float zoomStep=0.0625f;
	
	private ArrayList<Entity> entities = new ArrayList<Entity>();
	
	
	
	public void draw(Graphics g) {
		g.scale(getZoomLevel(), getZoomLevel());
		g.translate(zX, zY);
		
		for(Entity e:entities) {
			if(e!=null) {
				e.draw(g);
			}
		}
	}
	
	
	
	public float getZoomLevel() {
		return zoomLevel;
	}
	
	public void setZoomLevel(float z) {
		zoomLevel = z;
	}
	
	
	
	public void mouseWheelZoom() {
		
		  int mouseWheel = Mouse.getDWheel();
		  
		  if(mouseWheel==0) {
			  
			  return;
		  }
		  
		  boolean positive = mouseWheel>0;
		  
		  
		  if (positive) {
			  
			  addZoomLevel(zoomStep);
		  }
		  
		  else {

			  addZoomLevel(-zoomStep);
		  }
		  
		  if(mouseWheel != 0) {
			  
			  zX+=(Mouse.getX()*(mouseWheel/240f))/zoomLevel;
			  zY-=((ReducedSpace.h-Mouse.getY())*(mouseWheel/240f))/zoomLevel;
		  }
		  
		  System.out.println("RAWX: "+Mouse.getX()+"\nRAWY: "+Mouse.getY()+"\nEXP: "+mouseWheel+"|"+(mouseWheel/240f)+"\nWHEEL: "+mouseWheel);
		  System.out.println("X: "+zX+"\nY: "+zY);
	}
	
	public void addZoomLevel(float a) {
		
		zoomLevel+=a;
		
		if(zoomLevel>1.2375f) {
			zoomLevel=1.2375f;
		}
		
		if(zoomLevel<.1f) {
			
			zoomLevel=.1f;
		}
	}
	
	public int addEntity(Entity e) {
		entities.add(e);
		return entities.size();
	}
	public void removeEntity(int index) {
		entities.set(index, null);
	}
	public void removeEntity(String name) {
		for(int i=0;i<entities.size();i++) {
			if(entities.get(i).name.matches(name)) {
				removeEntity(i);
				return;
			}
		}
	}
	public Entity getEntity(int index) {
		return entities.get(index);
	}
	public Entity getEntity(String name) {
		for(int i=0;i<entities.size();i++) {
			if(entities.get(i).name.matches(name)) {
				return getEntity(i);
			}
		}
		return null;
	}
}

[/spoiler]


Thanks for any help!

This forum has a very nice search function… :wink:

Mike

I guess my keywords weren’t sufficient. I’ll check that out. Thanks!
I don’t know what LibGDX is, but I would really like to do this without ANOTHER library. I already have Slick2D and LWJGL. Another seems like it shouldn’t be necessary…

You can do it with a simple translate and scale (or is it scale and translate?)

You should be able to get it by changing order of transformations.

I understood that you could do it that way, but I’m having trouble getting the transformations just right. Could you give me any pointers?

You have translation working right?

If you don’t, go and fix it.

If/Once you have translation working, add in a scale before or after the translation. Whatever one works.

Wait, translate BEFORE the scale?

Or after.

I can’t test it now so I’m not sure.

It also depends on when the transformation is performed.

You need to experiment

The results are always weird and unpredictable. I don’t know how to code this :X

No matter if you use lwjgl or slick or java2d or something else it will always be the same idea, just with a little bit different code.

From the thread I posted:

It shouldn’t be too hard to implement the above in any engine of your choice. :slight_smile:

Mike

First you need to have a camera position in your game. The virtual “camera” in a 2D game is simply a rectangle that’s positioned somewhere in your 2D world.
An example - your world is 10,000 * 10,000 pixels big and your drawing surface (the window you paint in or the entire screen in full screen mode) is 1000 * 1000. Then you might define your camera to have a position of (200, 300) and a size of (1000, 1000). Everything inside that rectangle is displayed on the screen.
Most of the time it’s sufficient to just remember the camera position and forget about it’s size because the Graphics object doesn’t need to know if an object is inside the visible area. Drawing stuff outside the screen is not causing any problems. So you can simply always draw all objects.
Additionally to the position you also need to store the zoom level of the camera.

The draw method will then look like this.

public void draw(Graphics g) {
  g.scale(getZoomLevel(), getZoomLevel());
  g.translate(-cameraPos.getX(), -cameraPos.getY());
  
  for(Entity e:entities) {
     if(e!=null) {
        e.draw(g);
     }
  }
}

It’s convinient to use vectors for positions.
So cameraPos should be of type org.newdawn.slick.geom.Vector2f.
So replace

public float zX=0, zY=0;

with

public cameraPos = new Vector2f(0,0);

Also it’s important to remember that a game always has two coordinate systems. So you need to know which vectors are relative to the screen (absolute screen coordinates) and which vectors are relative to the coordinate system of your game.
cameraPos is always relative to the game.
Furthermore org.newdawn.slick.Graphics uses a coordinate system where (0, 0) is the upper left corner of the screen/window.
org.lwjgl.input.Mouse however puts (0, 0) at the lower left corner.
This video here demonstrates that. http://thenewboston.org/watch.php?cat=54&number=10
So you need to convert your coordinates so (0, 0) is always at the upper left corner.
You could do that like this.

static Vector2f getMousePos() {
    return new Vector2f(Mouse.getX(), gameContainer.getHeight()-1-Mouse.getY());
}

Now for the mouseWheelZoom method. I assume you call that every frame?
Try this


public void mouseWheelZoom() {
    int mouseWheel = Mouse.getDWheel();
    if(mouseWheel==0) {
       return;
    }
    if(mouseWheel != 0) {
        Vector2f mousePosAbs = getMousePos();
        Vector2f mousePosRel = mousePosAbs.copy().scale(1/zoomLevel).add(cameraPos);
        if (mouseWheel>0) {
           addZoomLevel(zoomStep);
        } else {
           addZoomLevel(-zoomStep);
        }
        cameraPos = mousePosRel.sub(mousePosAbs.scale(1/zoomLevel));
    }
}

I wasn’t able to test those modifications since I don’t have all of your code but I hope it works.