Textures origin in strange position

Started by Bob A Red Dino, March 22, 2012, 22:27:23

Previous topic - Next topic

Bob A Red Dino

I'm been working with OpenGL for a a few weeks now, so I'm still very new to it.  From what read and seen, the origin for anything on the screen is supposed to be the lower left corner.  When I add textures to the display, they start in seemingly random positions.  I've tried removing all transformations from my code, and the problem persists.  I'm assuming that I made a stupid mistake somewhere, but I can't seem to find it.  Here's all the code that has anything to do with the display, if you want to to post anything else just ask.  Thanks in advance.

Game.java:
package game;

import java.util.Arrays;
import game.graphic.MainMenuGui;
import game.graphic.Window;
import game.io.InputHandler;
import game.io.Settings;
import org.lwjgl.Sys;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.GL11;

public class Game {

	private String[] threadNames = { "Game Thread", "Clock Thread" };
	private Thread[] threads = new Thread[threadNames.length];
	public boolean[] threadsRunning = new boolean[threads.length];
	public static Game game = new Game();
	public Settings settings;
	public Window window;
	public InputHandler inputHandler;

	private long startTime;
	public int runningTime;
	public int prevRunningTime;
	private int prevFrames;
	private int totalFrames;
	private int peakFPS;
	public boolean commandsToRun;

	public Game() {
		settings = new Settings();
		window = new Window(settings);
		inputHandler = new InputHandler(settings);
	}

	public static void main(String[] args) {
		game.threads[0] = game.new GameThread();
		game.threads[0].start();
	}

	private void gameLoop() throws Throwable {
		window.updateScreen();
		inputHandler.checkInputs();
		calcFPS();
	}

	private void loadGame() throws Throwable {
		initThreads(1);
		settings.loadSettings();
		window.loadScreen();
		displaySystemInfo();
		GL11.glFlush();
		window.setCurrentGui(new MainMenuGui(window, window.texRender));
	}

	private void runGame() {
		try {
			Arrays.fill(threadsRunning, true);
			loadGame();
			while(threadsRunning[0]) {
				try {
					gameLoop();
				}
				catch(OutOfMemoryError e) {
					System.gc();
				}
			}
		}
		catch(Throwable throwable) {
			crashGame(throwable);
		}
		finally {
			stopGame();
		}
	}
	
	private void initThreads(int num) {
		threads[num] = new MinorThread(num);
		threads[num].setDaemon(true);
		threads[num].start();
	}

	public void beginShutdown() {
		Arrays.fill(threadsRunning, false);
	}

	private void stopGame() {
		settings.changeSetting("windowWidth", (short)window.getWindowWidth());
		settings.changeSetting("windowHeight", (short)window.getWindowHeight());
		settings.changeSetting("windowedFullscreen", (short)window.getFrameExtendedState());
		settings.saveSettings();
		Display.destroy();
		System.gc();
		System.exit(1);
	}

	private void crashGame(Throwable throwable) {
		throwable.printStackTrace();
		System.out.println("I crashed!");
		System.exit(0);
	}

	private void displaySystemInfo() {
		System.out.println("=====System Info=====");
		System.out.println("OS: " + System.getProperty("os.name"));
		System.out.println("GPU: " + GL11.glGetString(GL11.GL_VENDOR) + " " + GL11.glGetString(GL11.GL_RENDERER));
		System.out.println("Java Version: " + System.getProperty("java.version"));
		System.out.println("LWJGL Version: " + Sys.getVersion());
	}

	private void calcFPS() {
		totalFrames++;
		float loopTime = runningTime - prevRunningTime;
		float loopFrames = totalFrames - prevFrames;
		if(loopTime >= 100) {
			int fps = (int)(loopFrames / (loopTime / 1000));
			peakFPS = Math.max(fps, peakFPS);
			window.setFrameTitle("FPS: " + fps + "  Peak FPS: " + peakFPS);
			prevRunningTime = runningTime;
			prevFrames = totalFrames;
		}
	}

	private class GameThread extends Thread {

		public GameThread() {
			super(threadNames[0]);
		}

		public void run() {
			game.runGame();
		}
	}

	private class MinorThread extends Thread {

		private int threadNum;

		public MinorThread(int num) {
			super(threadNames[num]);
			threadNum = num;
		}

		private void threadLoop() {
			switch(threadNum) {
				case 1:
					runningTime = (int)(System.nanoTime() / 1000000 - startTime);
					break;
			}
		}

		public void run() {
			init();
			while(threadsRunning[threadNum])
				threadLoop();
		}

		private void init() {
			switch(threadNum) {
				case 1:
					startTime = System.nanoTime() / 1000000;
					break;
			}
		}
	}
}


Window.java:
package game.graphic;

import java.awt.Canvas;
import java.awt.Frame;
import java.awt.Image;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import javax.swing.ImageIcon;
import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import static org.lwjgl.opengl.GL11.*;
import game.Game;
import game.io.Settings;

public class Window implements WindowListener {

	private Settings settings;
	public TextureRender texRender;
	private Frame frame;
	private Canvas canvas;
	private Gui currentGui;
	private Gui lastGui;
	private int windowWidth;
	private int windowHeight;
	private boolean fullscreen;
	private boolean frameChanged;

	private int DEFAULT_WINDOW_WIDTH = 1024;
	private int DEFAULT_WINDOW_HEIGHT = 632;

	public Window(Settings settings) {
		this.settings = settings;
		texRender = new TextureRender();
	}

	public void updateScreen() throws Throwable {
		windowWidth = canvas.getWidth();
		windowHeight = canvas.getHeight();
		glViewport(0, 0, windowWidth, windowHeight);
		if(currentGui != lastGui) {
			glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
			currentGui.loadGui();
		}
		else if(frameChanged)
			currentGui.updateGui();
		Display.update();
		frameChanged = false;
		lastGui = currentGui;
	}

	public void loadScreen() throws Throwable {
		if(settings.getSettingValue("windowedFullscreen") == 0) {
			windowWidth = settings.getSettingValue("windowWidth");
			windowHeight = settings.getSettingValue("windowHeight");
		}
		else {
			windowWidth = DEFAULT_WINDOW_WIDTH;
			windowHeight = DEFAULT_WINDOW_HEIGHT;
		}
		initComponents();
		windowWidth = canvas.getWidth();
		windowWidth = canvas.getHeight();
		Display.setParent(canvas);
		Display.setDisplayMode(new DisplayMode(windowWidth, windowHeight));
		Display.setInitialBackground(1f, 1f, 1f);
		Display.create();
		glOrtho(0, windowWidth, 0, windowHeight, 1, -1);
		glLoadIdentity();
		currentGui = new StartupGui(this, texRender);
		updateScreen();
	}

	private void initComponents() {
		frame = new Frame();
		frame.setExtendedState(settings.getSettingValue("windowedFullscreen"));
		frame.setSize(windowWidth + 16, windowHeight + 38);
		frame.setLocationRelativeTo(null);
		frame.addWindowListener(this);
		Image icon = new ImageIcon(getClass().getResource("/resources/image/ui/icon.png")).getImage();
		frame.setIconImage(icon);
		canvas = new Canvas();
		canvas.setFocusable(true);
		canvas.requestFocus();
		canvas.setIgnoreRepaint(true);
		frame.add(canvas);
		frame.setVisible(true);
	}

	public void setCurrentGui(Gui gui) {
		currentGui = gui;
	}

	public void toggleFullscreen() {
		settings.changeSetting("windowWidth", (short)windowWidth);
		settings.changeSetting("windowHeight", (short)windowHeight);
		try {
			fullscreen = !fullscreen;
			Display.setDisplayMode(fullscreen ? Display.getDesktopDisplayMode() : new DisplayMode(windowWidth, windowHeight));
			Display.setFullscreen(fullscreen);
			frame.setVisible(!fullscreen);
		}
		catch(LWJGLException e) {
			System.out.println("Could not toggle fullscreen mode!");
		}
	}

	public int getWindowWidth() {
		return windowWidth;
	}

	public int getWindowHeight() {
		return windowHeight;
	}

	public int getFrameExtendedState() {
		return frame.getExtendedState();
	}

	public void setFrameTitle(String title) {
		frame.setTitle(title);
	}

	public void windowClosing(WindowEvent e) {
		Game.game.beginShutdown();
	}

	public void windowOpened(WindowEvent e) {}

	public void windowClosed(WindowEvent e) {}

	public void windowIconified(WindowEvent e) {}

	public void windowDeiconified(WindowEvent e) {}

	public void windowActivated(WindowEvent e) {}

	public void windowDeactivated(WindowEvent e) {}
}


StartupGui.java:
package game.graphic;

import static org.lwjgl.opengl.GL11.*;
import java.io.IOException;

public class StartupGui extends Gui {

	private Window window;
	private TextureRender texRender;
	private Texture logo;

	public StartupGui(Window window, TextureRender texRender) throws IOException {
		this.window = window;
		this.texRender = texRender;
	}

	public void updateGui() throws Throwable {
		glPushMatrix();
		glBegin(GL_QUADS);
		{
			glTexCoord2f(0, 0);
			glVertex2f(0, 0);

			glTexCoord2f(0, logo.getHeight());
			glVertex2f(0, logo.getHeight());

			glTexCoord2f(logo.getWidth(), logo.getHeight());
			glVertex2f(logo.getWidth(), logo.getHeight());

			glTexCoord2f(logo.getWidth(), 0);
			glVertex2f(logo.getWidth(), 0);
		}
		glEnd();
		glPopMatrix();
	}

	public void loadGui() throws Throwable {
		logo = texRender.getTexture("ui/icon.png");
		logo.bind();
		glMatrixMode(GL_PROJECTION);
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		glEnable(GL_TEXTURE_2D);
		glEnable(GL_BLEND);
		glPushMatrix();
		glBegin(GL_QUADS);
		{
			glTexCoord2f(0, 0);
			glVertex2f(0, 0);

			glTexCoord2f(0, logo.getHeight());
			glVertex2f(0, logo.getHeight());

			glTexCoord2f(logo.getWidth(), logo.getHeight());
			glVertex2f(logo.getWidth(), logo.getHeight());

			glTexCoord2f(logo.getWidth(), 0);
			glVertex2f(logo.getWidth(), 0);
		}
		glEnd();
		glPopMatrix();
	}
}


MainMenuGui.java:
package game.graphic;

import static org.lwjgl.opengl.GL11.*;

public class MainMenuGui extends Gui {

	private Window window;
	private TextureRender texRender;

	public MainMenuGui(Window window, TextureRender texRender) throws Throwable {
		this.window = window;
		this.texRender = texRender;
	}

	public void updateGui() throws Throwable {
		drawBackground();
	}

	public void loadGui() throws Throwable {
		drawBackground();
	}

	private void drawBackground() throws Throwable {
		Texture bg = texRender.getTexture("ui/bg.png");
		bg.bind();
		glMatrixMode(GL_PROJECTION);
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		glEnable(GL_TEXTURE_2D);
		glEnable(GL_BLEND);
		glPushMatrix();
		for(int i = 0; i * bg.getImageWidth() < window.getWindowWidth() / 5; i++) {
			for(int j = 0; j * bg.getImageHeight() < window.getWindowHeight() / 5; j++) {
				float x = i * bg.getWidth();
				float y = -j * bg.getHeight();
				glBegin(GL_QUADS);
				{
					glTexCoord2f(x, y);
					glVertex2f(x, y);

					glTexCoord2f(x, y + bg.getHeight());
					glVertex2f(x, y + bg.getHeight());

					glTexCoord2f(x + bg.getWidth(), y + bg.getHeight());
					glVertex2f(x + bg.getWidth(), y + bg.getHeight());

					glTexCoord2f(x + bg.getWidth(), y);
					glVertex2f(x + bg.getWidth(), y);
				}
				glEnd();
			}
		}
		glPopMatrix();
	}
}


TextureRender.java:
package game.graphic;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import java.io.IOException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import java.util.HashMap;
import java.util.Hashtable;
import javax.swing.ImageIcon;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;

public class TextureRender {

	private HashMap<String, Texture> table = new HashMap<String, Texture>();
	private IntBuffer textureIDBuffer = BufferUtils.createIntBuffer(1);
	private ColorModel glAlphaColorModel;
	private ColorModel glColorModel;

	public TextureRender() {
		glAlphaColorModel = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[] { 8, 8, 8, 8 }, true, false,
				ComponentColorModel.TRANSLUCENT, DataBuffer.TYPE_BYTE);
		glColorModel = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), false, false,
				ComponentColorModel.OPAQUE, DataBuffer.TYPE_BYTE);
	}

	private int createTextureID() {
		GL11.glGenTextures(textureIDBuffer);
		return textureIDBuffer.get(0);
	}

	public Texture getTexture(String resourceName) throws IOException {
		Texture tex = table.get(resourceName);
		if(tex != null)
			return tex;
		tex = getTexture(resourceName, GL11.GL_TEXTURE_2D, GL11.GL_RGBA, GL11.GL_LINEAR, GL11.GL_LINEAR);
		table.put(resourceName, tex);
		return tex;
	}

	public Texture getTexture(String resourceName, int target, int dstPixelFormat, int minFilter, int magFilter) throws IOException {
		int srcPixelFormat;
		int textureID = createTextureID();
		Texture texture = new Texture(target, textureID);
		GL11.glBindTexture(target, textureID);
		BufferedImage bufferedImage = loadImage(resourceName);
		texture.setWidth(bufferedImage.getWidth());
		texture.setHeight(bufferedImage.getHeight());
		if(bufferedImage.getColorModel().hasAlpha())
			srcPixelFormat = GL11.GL_RGBA;
		else
			srcPixelFormat = GL11.GL_RGB;
		ByteBuffer textureBuffer = convertImageData(bufferedImage, texture);
		if(target == GL11.GL_TEXTURE_2D) {
			GL11.glTexParameteri(target, GL11.GL_TEXTURE_MIN_FILTER, minFilter);
			GL11.glTexParameteri(target, GL11.GL_TEXTURE_MAG_FILTER, magFilter);
		}
		GL11.glTexImage2D(target, 0, dstPixelFormat, get2Fold(bufferedImage.getWidth()), get2Fold(bufferedImage.getHeight()), 0, srcPixelFormat,
				GL11.GL_UNSIGNED_BYTE, textureBuffer);
		return texture;
	}
	
	private static int get2Fold(int fold) {
		int ret = 2;
		while(ret < fold)
			ret *= 2;
		return ret;
	}

	private ByteBuffer convertImageData(BufferedImage bufferedImage, Texture texture) {
		ByteBuffer imageBuffer;
		WritableRaster raster;
		BufferedImage texImage;
		int texWidth = 2;
		int texHeight = 2;
		while(texWidth < bufferedImage.getWidth())
			texWidth *= 2;
		while(texHeight < bufferedImage.getHeight())
			texHeight *= 2;
		texture.setTextureHeight(texHeight);
		texture.setTextureWidth(texWidth);
		if(bufferedImage.getColorModel().hasAlpha()) {
			raster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, texWidth, texHeight, 4, null);
			texImage = new BufferedImage(glAlphaColorModel, raster, false, new Hashtable<String, Object>());
		}
		else {
			raster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, texWidth, texHeight, 3, null);
			texImage = new BufferedImage(glColorModel, raster, false, new Hashtable<String, Object>());
		}
		Graphics g = texImage.getGraphics();
		g.setColor(new Color(0f, 0f, 0f, 0f));
		g.fillRect(0, 0, texWidth, texHeight);
		g.drawImage(bufferedImage, 0, 0, null);
		byte[] data = ((DataBufferByte)texImage.getRaster().getDataBuffer()).getData();
		imageBuffer = ByteBuffer.allocateDirect(data.length);
		imageBuffer.order(ByteOrder.nativeOrder());
		imageBuffer.put(data, 0, data.length);
		imageBuffer.flip();
		return imageBuffer;
	}

	private BufferedImage loadImage(String ref) throws IOException {
		URL url = getClass().getResource("/resources/image/" + ref);
		if(url == null)
			throw new IOException("Cannot find: " + ref);
		Image img = new ImageIcon(url).getImage();
		BufferedImage bufferedImage = new BufferedImage(img.getWidth(null), img.getHeight(null), BufferedImage.TYPE_INT_ARGB);
		Graphics g = bufferedImage.getGraphics();
		g.drawImage(img, 0, 0, null);
		g.dispose();
		return bufferedImage;
	}
}


Texture.java:
package game.graphic;

import org.lwjgl.opengl.GL11;

public class Texture {
	private int target;
	private int textureID;
	private int height;
	private int width;
	private int texWidth;
	private int texHeight;
	private float widthRatio;
	private float heightRatio;

	public Texture(int target, int textureID) {
		this.target = target;
		this.textureID = textureID;
	}

	public void bind() {
		GL11.glBindTexture(target, textureID);
	}

	public void setHeight(int height) {
		this.height = height;
		setHeight();
	}

	public void setWidth(int width) {
		this.width = width;
		setWidth();
	}

	public int getImageHeight() {
		return height;
	}

	public int getImageWidth() {
		return width;
	}

	public float getHeight() {
		return heightRatio;
	}

	public float getWidth() {
		return widthRatio;
	}

	public void setTextureHeight(int texHeight) {
		this.texHeight = texHeight;
		setHeight();
	}

	public void setTextureWidth(int texWidth) {
		this.texWidth = texWidth;
		setWidth();
	}

	private void setHeight() {
		if(texHeight != 0) {
			heightRatio = ((float)height) / texHeight;
		}
	}

	private void setWidth() {
		if(texWidth != 0) {
			widthRatio = ((float)width) / texWidth;
		}
	}
}


Here's an example of what's happening. This is the image loaded from StartupGui.java, without any translations, scaling, rotations, etc.

CodeBunny

I don't have time to look through your code, but are you a) flipping your x-coordinate somehow while drawing, or b) loading the image mirrored horizontally?

matheus23

Maybe your Texture size isn't a power of 2? It should be 2x2, 4x4, 16x16, 32x32, 64x64 ... ;)
My github account and currently active project: https://github.com/matheus23/UniverseEngine

CodeBunny

Actually, that brings up a good point. Is your question about why there is white space around the swords? If so, then yes, Power-of-two textures is the problem. Usually, loading a nPoT image onto a PoT texture results in the image being loaded onto the top-left of the texture (which is what I assumed you were asking about - why it was on the top-right and not the top-left).

Bob A Red Dino

Thanks for the replies.  The texture is 64x64, and I'm not flipping it.  The Texture and TextureRender classes in my program are directly from the Space Invaders example in the tutorials section of lwjgl.org.

CodeBunny

Do you mean texture or source image is 64x64? At a glance it looks like you mean the source image.

It looks like your image is being put onto a texture 2x its size.

matheus23

 Yes, the source Image is 64x64 ... I feel stupid for counting the pixels... I don't really get, why the Texture is bigger than the Image.. I'll take further look on the source now...

EDIT: Just seen your glOrtho(..... (last 2 values:) 1, -1);
I don't think the near value should be bigger, than the far value. Acctually gluOrtho2D() calls glOrtho(), with near/far values -1/1. Maybe that is the problem...
Either, you use gluOrtho2D() without near and far values, or you switch the -1 and 1...
EDIT2: Okey, switching -1 and 1 does not change anything...
My github account and currently active project: https://github.com/matheus23/UniverseEngine

CodeBunny

Where's the code where you load the image onto a texture?

matheus23

My github account and currently active project: https://github.com/matheus23/UniverseEngine

Fool Running

Display.create();
		glOrtho(0, windowWidth, 0, windowHeight, 1, -1);
		glLoadIdentity();

I would guess this is your problem. You are setting up your view (with glOrtho) and then instantly clearing the setup (with glLoadIdentity).
You probably wanted something more like:
        glMatrixMode(GL_PROJECTION);
        glOrtho(0, windowWidth, 0, windowHeight, 1, -1);
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
Programmers will, one day, rule the world... and the world won't notice until its too late.Just testing the marquee option ;D