public class DdsLoader {
	public static ImageSet load(InputStream dds) throws IOException {
		byte[] ddsFile = readDdsFile(dds);
		
		// Check the first 4 bytes.
		int magicTest = readDoubleWord(ddsFile, 0);
		if(magicTest != MagicNumbers.DDS_MAGIC_NUMBER) {
			throw new DdsFileMalformedException("The Magic number is missing from the file.");
		}
		
		if(ddsFile.length < DdsHeader.SIZE + 4) {
			throw new DdsFileMalformedException("The data is way too small to store actual information.");
		}
		
		// Collect info from the DDS file.
		DdsHeader ddsHeader = new DdsHeader(ddsFile);
		Dds10Header dds10Header = getDds10Header(ddsFile, ddsHeader);
		Dimensions ddsDimensions = getDimensions(ddsHeader);
		UncheckedImageFormat ddsFormat = getFormat(ddsHeader, dds10Header);
		
		// Get image counts.
		int numArrays = (dds10Header.arraySize > 1) ? dds10Header.arraySize : 1;
		int numFaces = (dds10Header.miscFlag & Dds10MiscFlags.RESOURCE_MISC_TEXTURECUBE) != 0 ? 6 : 1;
		int numMipmaps = (ddsHeader.flags & DdsFlags.MIPMAPCOUNT) != 0 ? ddsHeader.mipmapCount : 1;
		
		int baseOffset = getOffsetToData(ddsHeader);
		
		// Build the image creator.
		ImageCreator imageCreator = new ImageCreator(new ImageFormat(ddsFormat), ddsDimensions, numMipmaps, numArrays, numFaces);
		int cumulativeOffset = baseOffset;
		
		for(int arrayIx = 0; arrayIx < numArrays; arrayIx++) {
			for(int faceIx = 0; faceIx < numFaces; faceIx++) {
				for(int mipmapLevel = 0; mipmapLevel < numMipmaps; mipmapLevel++) {
					int mipmapLevelSize = calcMipmapSize(ddsFormat, ddsDimensions, mipmapLevel);
					
					// Get specific data from ddsFile.
					byte[] mipmapLevelData = Arrays.copyOfRange(ddsFile, cumulativeOffset, cumulativeOffset + mipmapLevelSize);
					
					// Set data for the current mipmap level in imageCreator.
					imageCreator.setImageData(mipmapLevelData, true, mipmapLevel, arrayIx, faceIx);
					
					// Advance offset to read next mipmapLevelData from ddsFile.
					cumulativeOffset += mipmapLevelSize;
				}
			}
		}
		
		return imageCreator.createImage();
	}
	
	private static class DdsFileMalformedException extends RuntimeException {
		private static final long serialVersionUID = -1523221969465221880L;
		
		private DdsFileMalformedException(String message) {
			super(message);
		}
	}
	
	private static class DdsFileUnsupportedException extends RuntimeException {
		private static final long serialVersionUID = 377383320427260974L;
		
		private DdsFileUnsupportedException(String filename, String message) {
			super(filename + ": " + message);
		}
	}
	
	private static class MagicNumbers {
		static final int DDS_MAGIC_NUMBER = 0x20534444; // "DDS "
		static final int DDS10_FOUR_CC = 0x30314458; // "DX10"
		
		static final int DDSFOURCC_DXT1 = 0x31545844; // "DXT1"
		static final int DDSFOURCC_DXT3 = 0x33545844; // "DXT3"
		static final int DDSFOURCC_DXT5 = 0x35545844; // "DXT5"
	}
	
	@SuppressWarnings("unused")
	private static class DdsFlags {
		static final int CAPS = 0x00000001;
		static final int HEIGHT = 0x00000002;
		static final int WIDTH = 0x00000004;
		static final int PITCH = 0x00000008;
		static final int PIXELFORMAT = 0x00001000;
		static final int MIPMAPCOUNT = 0x00020000;
		static final int LINEARSIZE = 0x00080000;
		static final int DEPTH = 0x00800000;
		
	}
	
	private static class Dds10MiscFlags {
		static final int RESOURCE_MISC_TEXTURECUBE = 0x00000004;
	}
	
	@SuppressWarnings("unused")
	private static class Dds10ResourceDimensions {
		static final int TEXTURE1D = 2;
		static final int TEXTURE2D = 3;
		static final int TEXTURE3D = 4;
	}
	
	private static class DdsCaps2 {
		static final int CUBEMAP = 0x00000200;
		static final int CUBEMAP_POSITIVEX = 0x00000400;
		static final int CUBEMAP_NEGATIVEX = 0x00000800;
		static final int CUBEMAP_POSITIVEY = 0x00001000;
		static final int CUBEMAP_NEGATIVEY = 0x00002000;
		static final int CUBEMAP_POSITIVEZ = 0x00004000;
		static final int CUBEMAP_NEGATIVEZ = 0x00008000;
		static final int VOLUME = 0x00200000;
		
		static final int CUBEMAP_ALL = CUBEMAP |
				CUBEMAP_POSITIVEX | CUBEMAP_NEGATIVEX |
				CUBEMAP_POSITIVEY | CUBEMAP_NEGATIVEY |
				CUBEMAP_POSITIVEZ | CUBEMAP_NEGATIVEZ;
	}
	
	private static class DxgiFormat {
		static final int UNKNOWN = 0;
	}
	
	@SuppressWarnings("unused")
	private static class DdsPixelFormat {
		int size;
		int flags;
		int fourCC;
		int rgbBitCount;
		int rBitMask;
		int gBitMask;
		int bBitMask;
		int aBitMask;
	}
	
	@SuppressWarnings("unused")
	private static class DdsPixelFormatFlags {
		static final int ALPHAPIXELS = 0x00000001;
		static final int ALPHA = 0x00000002;
		static final int FOURCC = 0x00000004;
		static final int RGB = 0x00000040;
		static final int YUV = 0x00000200;
		static final int LUMINANCE = 0x00020000;
	}
	
	@SuppressWarnings("unused")
	private static class DdsHeader {
		static final int SIZE = (7 + 11 + 8 + 5) * (Integer.SIZE / 8);
		
		int size;
		int flags;
		int height;
		int width;
		int pitchOrLinearSize;
		int depth;
		int mipmapCount;
		
		int reserved1[] = new int[11];
		
		DdsPixelFormat ddsPixelFormat;
		
		int caps;
		int caps2;
		int caps3;
		int caps4;
		int reserved2;
		
		DdsHeader(byte[] ddsFile) {
			size = readDoubleWord(ddsFile, 4);
			flags = readDoubleWord(ddsFile, 8);
			height = readDoubleWord(ddsFile, 12);
			width = readDoubleWord(ddsFile, 16);
			pitchOrLinearSize = readDoubleWord(ddsFile, 20);
			depth = readDoubleWord(ddsFile, 24);
			mipmapCount = readDoubleWord(ddsFile, 28);
			
			for(int i = 0; i < reserved1.length; i++) {
				reserved1[i] = readDoubleWord(ddsFile, 32 + 4 * i);
			}
			
			ddsPixelFormat = new DdsPixelFormat();
			ddsPixelFormat.size = readDoubleWord(ddsFile, 76);
			ddsPixelFormat.flags = readDoubleWord(ddsFile, 80);
			ddsPixelFormat.fourCC = readDoubleWord(ddsFile, 84);
			ddsPixelFormat.rgbBitCount = readDoubleWord(ddsFile, 88);
			ddsPixelFormat.rBitMask = readDoubleWord(ddsFile, 92);
			ddsPixelFormat.gBitMask = readDoubleWord(ddsFile, 96);
			ddsPixelFormat.bBitMask = readDoubleWord(ddsFile, 100);
			ddsPixelFormat.aBitMask = readDoubleWord(ddsFile, 104);
			
			caps = readDoubleWord(ddsFile, 108);
			caps2 = readDoubleWord(ddsFile, 112);
			caps3 = readDoubleWord(ddsFile, 116);
			caps4 = readDoubleWord(ddsFile, 120);
			reserved2 = readDoubleWord(ddsFile, 124);
		}
	}
	
	@SuppressWarnings("unused")
	private static class Dds10Header {
		static final int SIZE = 5 * (Integer.SIZE / 8);
		
		int dxgiFormat;
		int resourceDimension;
		int miscFlag;
		int arraySize;
		int reserved;
	}
	
	private static byte[] readDdsFile(InputStream ddsInStream) throws IOException {
		assert ddsInStream != null;
		
		ByteArrayOutputStream ddsOutStream = new ByteArrayOutputStream();
		int bytesRead;
		
		byte[] buffer = new byte[4096];
		
		while((bytesRead = ddsInStream.read(buffer)) != -1) {
			ddsOutStream.write(buffer, 0, bytesRead);
		}
		
		return ddsOutStream.toByteArray();
	}
	
	// Read four bytes.
	private static int readDoubleWord(byte bytes[], int startIx) {
		int dw = 0;
		
		// Read 4 bytes.
		for(int i = startIx; i < startIx + 4; i++) {
			dw += (bytes[i] & 0xff) << (8 * (i - startIx));
		}
		
		return dw;
	}
	
	// Will either generate this or return the actual one.
	private static Dds10Header getDds10Header(byte[] ddsFile, DdsHeader ddsHeader) {
		if(ddsHeader.ddsPixelFormat.fourCC == MagicNumbers.DDS10_FOUR_CC) {
			Dds10Header dds10Header = new Dds10Header();
			int offsetToNewHeader = DdsHeader.SIZE + 4;
			
			dds10Header.dxgiFormat = readDoubleWord(ddsFile, offsetToNewHeader);
			dds10Header.resourceDimension = readDoubleWord(ddsFile, offsetToNewHeader + 4);
			dds10Header.miscFlag = readDoubleWord(ddsFile, offsetToNewHeader + 8);
			dds10Header.arraySize = readDoubleWord(ddsFile, offsetToNewHeader + 12);
			dds10Header.reserved = readDoubleWord(ddsFile, offsetToNewHeader + 16);
			
			return dds10Header;
		}
		
		// Compute the header manually. Namely, compute the DXGI_FORMAT for the given data.
		Dds10Header dds10header = new Dds10Header();
		
		// Get dimensionality. Assume 2D unless otherwise stated.
		dds10header.resourceDimension = Dds10ResourceDimensions.TEXTURE2D;
		
		if((ddsHeader.caps2 & DdsCaps2.VOLUME) != 0 && (ddsHeader.flags & DdsFlags.DEPTH) != 0) {
			dds10header.resourceDimension = Dds10ResourceDimensions.TEXTURE3D;
		}
		
		// Get cubemap.
		int cubemapTest = ddsHeader.caps2 & DdsCaps2.CUBEMAP_ALL;
		if(cubemapTest == 0) {
			dds10header.miscFlag = 0;
		} else {
			// All faces must be specified or none. Otherwise unsupported.
			if(cubemapTest != DdsCaps2.CUBEMAP_ALL) {
				throw new DdsFileUnsupportedException("", "All cubemap faces must be specified.");
			}
			
			dds10header.miscFlag = Dds10MiscFlags.RESOURCE_MISC_TEXTURECUBE;
		}
		
		// Array size is... zero?
		dds10header.arraySize = 0;
		
		// Use the old-style format.
		dds10header.dxgiFormat = DxgiFormat.UNKNOWN;
		
		return dds10header;
	}
	
	private static Dimensions getDimensions(DdsHeader ddsHeader) {
		Dimensions ddsDimensions = new Dimensions();
		ddsDimensions.numDimensions = 1;
		ddsDimensions.width = ddsHeader.width;
		
		if((ddsHeader.flags & DdsFlags.HEIGHT) != 0) {
			ddsDimensions.numDimensions = 2;
			ddsDimensions.height = ddsHeader.height;
		}
		
		if((ddsHeader.flags & DdsFlags.DEPTH) != 0) {
			ddsDimensions.numDimensions = 3;
			ddsDimensions.depth = ddsHeader.depth;
		}
		
		return ddsDimensions;
	}
	
	private static UncheckedImageFormat getFormat(DdsHeader ddsHeader, Dds10Header dds10Header) throws DdsFileUnsupportedException {
		if(dds10Header.dxgiFormat != DxgiFormat.UNKNOWN) {
			throw new IllegalArgumentException("Format is not unknown.");
		}
		
		for(OldDdsFormatConv o : s_oldFormatConvert)
			if(doesMatchFormat(o.ddsFmt, ddsHeader))
				return o.uncheckedImageFormat;
		
		throw new DdsFileUnsupportedException("", "Could not use the DDS9's image format.");
	}
	
	private static int getOffsetToData(DdsHeader ddsHeader) {
		int byteOffset = DdsHeader.SIZE + 4;
		
		if(ddsHeader.ddsPixelFormat.fourCC == MagicNumbers.DDS10_FOUR_CC) {
			byteOffset += Dds10Header.SIZE;
		}
		
		return byteOffset;
	}
	
	// Computes the bytesize of a single scanline of an image of the given format, with the given line width.
	// For compressed textures, the value returned is the number of bytes for every 4 scanlines.
	private static int calcLineSize(UncheckedImageFormat ddsFormat, int lineWidth) {
		// This is from the DDS suggestions for line size computations.
		if(ddsFormat.bitDepth == BitDepth.COMPRESSED) {
			int blockSize = 16;
			
			if(ddsFormat.type == PixelDataType.COMPRESSED_BC1
					|| ddsFormat.type == PixelDataType.COMPRESSED_UNSIGNED_BC4
					|| ddsFormat.type == PixelDataType.COMPRESSED_SIGNED_BC4) {
				blockSize = 8;
			}
			
			return ((lineWidth + 3) / 4) * blockSize;
		} else {
			int bytesPerPixel = DdsUtil.calcBytesPerPixel(new ImageFormat(ddsFormat));
			
			return lineWidth * bytesPerPixel;
		}
	}
	
	private static int calcMipmapSize(UncheckedImageFormat ddsFormat, Dimensions ddsDimensions, int mipmapLevel) {
		Dimensions mipmapDimensions = DdsUtil.calcMipmapLevelDimensions(ddsDimensions, mipmapLevel);
		int lineSize = calcLineSize(ddsFormat, mipmapDimensions.width);
		
		int effectiveHeight = 1;
		if(mipmapDimensions.numDimensions > 1) {
			effectiveHeight = mipmapDimensions.height;
			
			if(ddsFormat.bitDepth == BitDepth.COMPRESSED) {
				effectiveHeight = (effectiveHeight + 3) / 4;
			}
		}
		
		int effectiveDepth = 1;
		if(mipmapDimensions.numDimensions > 2) {
			effectiveDepth = mipmapDimensions.depth;
			
			if(ddsFormat.bitDepth == BitDepth.COMPRESSED) {
				effectiveDepth = (effectiveDepth + 3) / 4;
			}
		}
		
		int numLines = effectiveHeight * effectiveDepth;
		
		return lineSize * numLines;
	}
	
	private static OldDdsFormatConv s_oldFormatConvert[] = {
			new OldDdsFormatConv(new UncheckedImageFormat(
					PixelDataType.NORM_UNSIGNED_INTEGER,
					PixelComponents.COLOR_RGBA, ComponentOrder.RGBA,
					BitDepth.PER_COMP_8, 1), new OldDdsFmtMatch(
					DdsPixelFormatFlags.RGB | DdsPixelFormatFlags.ALPHAPIXELS,
					32, 0xff, 0xff00, 0xff0000, 0xff000000, 0)),
			
			new OldDdsFormatConv(new UncheckedImageFormat(
					PixelDataType.NORM_UNSIGNED_INTEGER,
					PixelComponents.COLOR_RGBA, ComponentOrder.BGRA,
					BitDepth.PACKED_32_BIT_8888_REV, 1), new OldDdsFmtMatch(
					DdsPixelFormatFlags.RGB | DdsPixelFormatFlags.ALPHAPIXELS,
					32, 0xff0000, 0xff00, 0xff, 0xff000000, 0)),
			
			new OldDdsFormatConv(new UncheckedImageFormat(
					PixelDataType.NORM_UNSIGNED_INTEGER,
					PixelComponents.COLOR_RGBX, ComponentOrder.RGBA,
					BitDepth.PER_COMP_8, 1), new OldDdsFmtMatch(
					DdsPixelFormatFlags.RGB, 32, 0xff, 0xff00, 0xff0000, 0, 0)),
			
			new OldDdsFormatConv(new UncheckedImageFormat(
					PixelDataType.NORM_UNSIGNED_INTEGER,
					PixelComponents.COLOR_RGB, ComponentOrder.RGBA,
					BitDepth.PER_COMP_8, 1), new OldDdsFmtMatch(
					DdsPixelFormatFlags.RGB, 24, 0xff, 0xff00, 0xff0000, 0, 0)),
			
			new OldDdsFormatConv(new UncheckedImageFormat(
					PixelDataType.NORM_UNSIGNED_INTEGER,
					PixelComponents.COLOR_RGB, ComponentOrder.RGBA,
					BitDepth.PER_COMP_8, 1), new OldDdsFmtMatch(
					DdsPixelFormatFlags.RGB, 24, 0xff, 0xff00, 0xff0000, 0, 0)),
			
			new OldDdsFormatConv(new UncheckedImageFormat(
					PixelDataType.NORM_UNSIGNED_INTEGER,
					PixelComponents.COLOR_RGB, ComponentOrder.BGRA,
					BitDepth.PER_COMP_8, 1), new OldDdsFmtMatch(
					DdsPixelFormatFlags.RGB, 24, 0xff0000, 0xff00, 0xff, 0, 0)),
			
			new OldDdsFormatConv(new UncheckedImageFormat(
					PixelDataType.NORM_UNSIGNED_INTEGER,
					PixelComponents.COLOR_RGB, ComponentOrder.RGBA,
					BitDepth.PACKED_16_BIT_565, 1), new OldDdsFmtMatch(
					DdsPixelFormatFlags.RGB, 16, 0xf800, 0x7e0, 0x1f, 0, 0)),
			
			new OldDdsFormatConv(new UncheckedImageFormat(
					PixelDataType.NORM_UNSIGNED_INTEGER,
					PixelComponents.COLOR_RGBA, ComponentOrder.BGRA,
					BitDepth.PACKED_16_BIT_1555_REV, 1),
					new OldDdsFmtMatch(DdsPixelFormatFlags.RGB, 16, 0x7c00,
							0x3e0, 0x1f, 0x8000, 0)),
			
			new OldDdsFormatConv(new UncheckedImageFormat(
					PixelDataType.NORM_UNSIGNED_INTEGER,
					PixelComponents.COLOR_RGBA, ComponentOrder.BGRA,
					BitDepth.PACKED_16_BIT_4444_REV, 1), new OldDdsFmtMatch(
					DdsPixelFormatFlags.RGB, 16, 0xf00, 0xf0, 0xf, 0xf000, 0)),
			
			new OldDdsFormatConv(new UncheckedImageFormat(
					PixelDataType.COMPRESSED_BC1, PixelComponents.COLOR_RGB,
					ComponentOrder.COMPRESSED, BitDepth.COMPRESSED, 1),
					new OldDdsFmtMatch(DdsPixelFormatFlags.FOURCC, 0, 0, 0, 0,
							0, MagicNumbers.DDSFOURCC_DXT1)),
			
			new OldDdsFormatConv(new UncheckedImageFormat(
					PixelDataType.COMPRESSED_BC2, PixelComponents.COLOR_RGBA,
					ComponentOrder.COMPRESSED, BitDepth.COMPRESSED, 1),
					new OldDdsFmtMatch(DdsPixelFormatFlags.FOURCC, 0, 0, 0, 0,
							0, MagicNumbers.DDSFOURCC_DXT3)),
			
			new OldDdsFormatConv(new UncheckedImageFormat(
					PixelDataType.COMPRESSED_BC3, PixelComponents.COLOR_RGBA,
					ComponentOrder.COMPRESSED, BitDepth.COMPRESSED, 1),
					new OldDdsFmtMatch(DdsPixelFormatFlags.FOURCC, 0, 0, 0, 0,
							0, MagicNumbers.DDSFOURCC_DXT5)),
			
			new OldDdsFormatConv(new UncheckedImageFormat(
					PixelDataType.NORM_UNSIGNED_INTEGER,
					PixelComponents.COLOR_RG, ComponentOrder.RGBA,
					BitDepth.PER_COMP_16, 1), new OldDdsFmtMatch(
					DdsPixelFormatFlags.RGB, 32, 0xffff, 0xffff0000, 0, 0, 0)),
			
			new OldDdsFormatConv(new UncheckedImageFormat(
					PixelDataType.NORM_UNSIGNED_INTEGER,
					PixelComponents.COLOR_RG, ComponentOrder.RGBA,
					BitDepth.PER_COMP_8, 1), new OldDdsFmtMatch(
					DdsPixelFormatFlags.RGB, 16, 0xffff, 0xffff0000, 0, 0, 0)),
			
			new OldDdsFormatConv(new UncheckedImageFormat(
					PixelDataType.NORM_UNSIGNED_INTEGER,
					PixelComponents.COLOR_RED, ComponentOrder.RGBA,
					BitDepth.PER_COMP_16, 1), new OldDdsFmtMatch(
					DdsPixelFormatFlags.LUMINANCE, 16, 0xffff, 0, 0, 0, 0)),
			
			new OldDdsFormatConv(new UncheckedImageFormat(
					PixelDataType.NORM_UNSIGNED_INTEGER,
					PixelComponents.COLOR_RED, ComponentOrder.RGBA,
					BitDepth.PER_COMP_8, 1), new OldDdsFmtMatch(
					DdsPixelFormatFlags.LUMINANCE, 8, 0xff, 0, 0, 0, 0)),
			
			new OldDdsFormatConv(new UncheckedImageFormat(
					PixelDataType.NORM_UNSIGNED_INTEGER,
					PixelComponents.COLOR_RG, ComponentOrder.RGBA,
					BitDepth.PER_COMP_16, 1), new OldDdsFmtMatch(
					DdsPixelFormatFlags.LUMINANCE
							| DdsPixelFormatFlags.ALPHAPIXELS, 16, 0xffff, 0,
					0, 0xffff0000, 0)),
			
			new OldDdsFormatConv(new UncheckedImageFormat(
					PixelDataType.NORM_UNSIGNED_INTEGER,
					PixelComponents.COLOR_RG, ComponentOrder.RGBA,
					BitDepth.PER_COMP_8, 1), new OldDdsFmtMatch(
					DdsPixelFormatFlags.LUMINANCE
							| DdsPixelFormatFlags.ALPHAPIXELS, 8, 0xff, 0, 0,
					0xff00, 0)), };
	
	private static class OldDdsFmtMatch {
		int flags;
		int bitDepth;
		int rBitmask;
		int gBitmask;
		int bBitmask;
		int aBitmask;
		int fourCC;
		
		OldDdsFmtMatch(int flags, int bitDepth, int rBitmask, int gBitmask,
				int bBitmask, int aBitmask, int fourCC) {
			this.flags = flags;
			this.bitDepth = bitDepth;
			this.rBitmask = rBitmask;
			this.gBitmask = gBitmask;
			this.bBitmask = bBitmask;
			this.aBitmask = aBitmask;
			this.fourCC = fourCC;
		}
	}
	
	private static class OldDdsFormatConv {
		UncheckedImageFormat uncheckedImageFormat;
		OldDdsFmtMatch ddsFmt;
		
		OldDdsFormatConv(UncheckedImageFormat uncheckedImageFormat, OldDdsFmtMatch ddsFmt) {
			this.uncheckedImageFormat = uncheckedImageFormat;
			this.ddsFmt = ddsFmt;
		}
	}
	
	private static boolean doesMatchFormat(OldDdsFmtMatch ddsFmt, DdsHeader ddsHeader) {
		if((ddsHeader.ddsPixelFormat.flags & ddsFmt.flags) == 0) {
			return false;
		}
		
		if((ddsFmt.flags & DdsPixelFormatFlags.FOURCC) != 0) {
			// None of the bit counts matter. Just check the fourCC
			if(ddsFmt.fourCC != ddsHeader.ddsPixelFormat.fourCC) {
				return false;
			}
		} else {
			// Check the bitcounts, not the fourCC.
			if(ddsHeader.ddsPixelFormat.rgbBitCount != ddsFmt.bitDepth) {
				return false;
			}
			
			if((ddsFmt.rBitmask & ddsHeader.ddsPixelFormat.rBitMask) != ddsFmt.rBitmask) {
				return false;
			}
			
			if((ddsFmt.gBitmask & ddsHeader.ddsPixelFormat.gBitMask) != ddsFmt.gBitmask) {
				return false;
			}
			
			if((ddsFmt.bBitmask & ddsHeader.ddsPixelFormat.bBitMask) != ddsFmt.bBitmask) {
				return false;
			}
			
			if((ddsFmt.aBitmask & ddsHeader.ddsPixelFormat.aBitMask) != ddsFmt.aBitmask) {
				return false;
			}
		}
		
		return true;
	}
}
 public abstract class GLProgram {
	private int fps;
	
	/**
	 * Initializes the application in fullscreen mode.
	 * 
	 * @param vsync Enables/disables the vertical-sync feature, where the rendering is in sync with the monitor's refresh rate.
	 *        With v-sync off, there is no framerate cap and the gameloop will run as fast as the hardware can handle.
	 *        A framerate can be set with the <code>setFPS(int fps)</code> method.
	 */
	public GLProgram(boolean vsync) {
		try {
			Display.setFullscreen(true);
			Display.setVSyncEnabled(vsync);
		} catch(Exception exc) {
			exc.printStackTrace();
		}
	}
	
	/**
	 * Initializes a windowed application. The framerate is set to 60 and can be modified using <code>setFPS(int fps)</code>.
	 * 
	 * @param name The title of the window.
	 * @param width The width of the window.
	 * @param height The height of the window.
	 * @param resizable Enables/disables the ability to resize the window.
	 */
	public GLProgram(String name, int width, int height, boolean resizable) {
		Display.setTitle(name);
		
		try {
			Display.setDisplayMode(new DisplayMode(width, height));
		} catch(Exception exc) {
			exc.printStackTrace();
		}
		
		Display.setResizable(resizable);
		
		fps = 60;
	}
	
	/**
	 * Sets the framerate of the game loop.
	 * 
	 * @param fps The desired framerate, in frames per second. It must be a positive integer or 0.
	 *        Specifying 0 means no rendering cap. Specifying a negative value will result in undefined behavior.
	 */
	public void setFPS(int fps) {
		this.fps = fps;
	}
	
	/**
	 * Returns the framerate of the game loop.
	 * 
	 * @return The framerate, in frames per second.
	 */
	public int getFPS() {
		return fps;
	}
	
	/**
	 * Initializes the context with default settings, which is generally the latest OpenGL version supported and compatibility
	 * profile if supported. For Mac OS X, the core profile must be specifically requested therefore this method is not
	 * sufficient.
	 * 
	 * This method does not return until the game loop ends.
	 * 
	 * Equivalent to <code>run(false)</code>.
	 */
	public final void run() {
		run(false);
	}
	
	/**
	 * Initializes the context depending on the value of <code>core</code>. Supplying <code>false</code> is equivalent to
	 * simply calling <code>run()</code>, while <code>true</code> requests the Core profile if available.
	 * 
	 * This method does not return until the game loop ends.
	 * 
	 * @param core <code>True</code> requests the Core profile, while <code>false</code> keeps default settings.
	 */
	public final void run(boolean core) {
		run(core, new PixelFormat());
	}
	
	/**
	 * Initializes the context depending on the value of <code>core</code> and the supplied PixelFormat.
	 * 
	 * This method does not return until the game loop ends.
	 * 
	 * @param core <code>True</code> requests the Core profile, while <code>false</code> keeps default settings.
	 * @param format The PixelFormat specifying the buffers.
	 */
	public final void run(boolean core, PixelFormat format) {
		run(format, core ? new ContextAttribs(3, 2).withProfileCore(true) : null);
	}
	
	/**
	 * Initializes the context requesting the specified OpenGL version in the format: <code>major.minor</code>
	 * 
	 * This method does not return until the game loop ends.
	 * 
	 * @param major
	 * @param minor
	 */
	public final void run(int major, int minor) {
		run(major, minor, false);
	}
	
	/**
	 * Initializes the context depending on the value of <code>core</code> and the specified OpenGL version in the format: <code>major.minor</code>
	 * 
	 * This method does not return until the game loop ends.
	 * 
	 * @param major
	 * @param minor
	 * @param core <code>True</code> requests the Core profile, while <code>false</code> requests the Compatibility profile.
	 */
	public final void run(int major, int minor, boolean core) {
		run(major, minor, core, new PixelFormat());
	}
	
	/**
	 * Initializes the context depending on the value of <code>core</code>, the supplied PixelFormat, and the specified
	 * OpenGL version in the format: <code>major.minor</code>
	 * 
	 * This method does not return until the game loop ends.
	 * 
	 * @param major
	 * @param minor
	 * @param core <<code>True</code> requests the Core profile, while <code>false</code> requests the Compatibility profile.
	 * @param format The PixelFormat specifying the buffers.
	 */
	public final void run(int major, int minor, boolean core, PixelFormat format) {
		run(format, core ? new ContextAttribs(major, minor).withProfileCore(core) : new ContextAttribs(major, minor));
	}
	
	/**
	 * Initializes the context with default settings and the supplied PixelFormat.
	 * 
	 * This method does not return until the game loop ends.
	 * 
	 * @param format The PixelFormat specifying the buffers.
	 */
	public final void run(PixelFormat format) {
		run(format, null);
	}
	
	/**
	 * Initializes the context with its attributes supplied.
	 * 
	 * This method does not return until the game loop ends.
	 * 
	 * @param attribs The context attributes.
	 */
	public final void run(ContextAttribs attribs) {
		run(new PixelFormat(), attribs);
	}
	
	/**
	 * Initializes the context with its attributes and PixelFormat supplied.
	 * 
	 * This method does not return until the game loop ends.
	 * 
	 * @param format The PixelFormat specifying the buffers.
	 * @param attribs The context attributes.
	 */
	public final void run(PixelFormat format, ContextAttribs attribs) {
		try {
			Display.create(format, attribs);
		} catch(Exception exc) {
			exc.printStackTrace();
			System.exit(1);
		}
		
		gameLoop();
	}
	
	private void gameLoop() {
		try {
			init();
			
			Utils.checkGLError("init");
			
			resized();
			
			Utils.checkGLError("resized");
			
			long lastTime, lastFPS;
			lastTime = lastFPS = System.nanoTime();
			int frames = 0;
			
			while(!shouldStop()) {
				long deltaTime = System.nanoTime() - lastTime;
				lastTime += deltaTime;
				
				if(Display.wasResized())
					resized();
				
				while(Keyboard.next()) {
					if(Keyboard.getEventKeyState())
						keyPressed(Keyboard.getEventKey(), Keyboard.getEventCharacter());
					else
						keyReleased(Keyboard.getEventKey(), Keyboard.getEventCharacter());
				}
				
				update(deltaTime);
				
				Utils.checkGLError("update");
				
				render();
				
				Utils.checkGLError("render");
				
				Display.update();
				
				frames++;
				if(System.nanoTime() - lastFPS >= 1e9) {
					System.out.println("FPS: ".concat(String.valueOf(frames)));
					lastFPS += 1e9;
					frames = 0;
				}
				
				Display.sync(fps);
			}
		} catch(Throwable exc) {
			exc.printStackTrace();
		} finally {
			destroy();
		}
	}
	
	/**
	 * Returns the width of the window.
	 * 
	 * @return The width of the window.
	 */
	public int getWidth() {
		return Display.getWidth();
	}
	
	/**
	 * Returns the height of the window.
	 * 
	 * @return The height of the window.
	 */
	public int getHeight() {
		return Display.getHeight();
	}
	
	/**
	 * Called at most once after one of the <code>run</code> methods are called. This method
	 * must be implemented by user code.
	 */
	public abstract void init();
	
	/**
	 * Called when the window is resized. This method updates the <code>glViewport</code> but may be overridden
	 * with custom code. Make sure to call <code>super.resized()</code> if overriding, or remember to manually update
	 * the <code>glViewport</code>!
	 */
	public void resized() {
		glViewport(0, 0, getWidth(), getHeight());
	}
	
	/**
	 * Consistently polled once per frame to test whether the game loop should stop. This
	 * method checks if either the window's close button has been clicked, ALT-F4 has been pressed, or if the ESCAPE key is pressed.
	 * 
	 * @return Returns <code>true</code> if the game loop should stop, otherwise <code>false</code>.
	 */
	public boolean shouldStop() {
		return Display.isCloseRequested() || Keyboard.isKeyDown(Keyboard.KEY_ESCAPE);
	}
	
	/**
	 * Called when a key has been pressed.
	 * 
	 * @param key The @see org.lwjgl.input.Keyboard keycode of the pressed key.
	 * @param c The literal character of the pressed key.
	 */
	public void keyPressed(int key, char c) {}
	
	/**
	 * Called when a key has been released.
	 *
	 * @param key The @see org.lwjgl.input.Keyboard keycode of the released key.
	 * @param c The literal character of the released key.
	 */
	public void keyReleased(int key, char c) {}
	
	/**
	 * Called once per frame and given the elapsed time since the last call
	 * to this method.
	 * 
	 * @param deltaTime The elapsed time since the last call to this method, in nanoseconds.
	 */
	public void update(long deltaTime) {}
	
	/**
	 * Called once per frame and must be implemented by user code. Render operations should
	 * occur here.
	 */
	public abstract void render();
	
	/**
	 * Destroys the context.
	 */
	public void destroy() {
		Display.destroy();
	}
	
	/**
	 * Returns the entire contents of the file in a String. The file should be specified as relative to the user class
	 * that extends GLProgram.
	 * 
	 * @param file The file path, relative to the user class extending GLProgram.
	 * @return The entire contents of the file.
	 */
	protected String readFromFile(String file) {
		try {
			return Utils.readFully(getClass().getResourceAsStream(file));
		} catch(Exception exc) {
			throw new RuntimeException("Failure reading file " + file, exc);
		}
	}
}
 public class Mesh {
	private int vao;
	private HashMap<String,Integer> vaoMap;
	
	private ArrayList<RenderCmd> renderCommands;
	
	public Mesh(URL url) throws IOException, XmlPullParserException {
		renderCommands = new ArrayList<>();
		
		ArrayList<Attribute> attributes = new ArrayList<>();
		
		ByteBuffer attributeData = BufferUtils.createByteBuffer(0), indexData = BufferUtils.createByteBuffer(0);
		
		class VAO {
			String name;
			ArrayList<Integer> sources;
			
			VAO(String name) {
				this.name = name;
				
				sources = new ArrayList<>();
			}
		}
		
		ArrayList<VAO> vaos = null;
		
		try(InputStream is = url.openStream()) {
			XmlPullParser xml = XmlPullParserFactory.newInstance().newPullParser();
			xml.setInput(is, "UTF-8");
			
			xml.next();
			
			xml.require(XmlPullParser.START_TAG, null, "mesh");
			
			do {
				switch(xml.nextTag()) {
					case XmlPullParser.START_TAG:
						switch(xml.getName()) {
							case "attribute": {
								String index = xml.getAttributeValue(null, "index");
								String type = xml.getAttributeValue(null, "type");
								String size = xml.getAttributeValue(null, "size");
								
								if(index == null)
									throw new IllegalArgumentException("<attribute> missing 'index'");
								if(type == null)
									throw new IllegalArgumentException("<attribute> missing 'type'");
								if(size == null)
									throw new IllegalArgumentException("<attribute> missing 'size'");
								
								Attribute attrib = new Attribute(Integer.parseInt(index), type, Integer.parseInt(size));
								attributes.add(attrib);
								
								xml.next();
								xml.require(XmlPullParser.TEXT, null, null);
								
								attributeData = attrib.storeData(attributeData, clean(xml.getText().trim().replace("\r\n", " ").replace('\n', ' ').split(" ")));
								
								xml.next();
								xml.require(XmlPullParser.END_TAG, null, "attribute");
								
								break;
							}
							case "indices": {
								String primitive = xml.getAttributeValue(null, "cmd");
								String type = xml.getAttributeValue(null, "type");
								
								if(primitive == null)
									throw new IllegalArgumentException("<indices> missing 'cmd'");
								if(type == null)
									throw new IllegalArgumentException("<indices> missing 'type'");
								
								RenderCmd cmd = new RenderCmd(primitive, type);
								renderCommands.add(cmd);
								
								xml.next();
								xml.require(XmlPullParser.TEXT, null, null);
								
								indexData = cmd.storeData(indexData, clean(xml.getText().trim().replace("\r\n", " ").replace('\n', ' ').split(" ")));
								
								xml.next();
								xml.require(XmlPullParser.END_TAG, null, "indices");
								
								break;
							}
							case "vao": {
								if(vaos == null)
									vaos = new ArrayList<>();
								
								String name = xml.getAttributeValue(null, "name");
								
								if(name == null)
									throw new IllegalArgumentException("<vao> missing 'name'");
								
								VAO vao = new VAO(name);
								vaos.add(vao);
								
								while(xml.nextTag() == XmlPullParser.START_TAG) {
									xml.require(XmlPullParser.START_TAG, null, "source");
									
									String attrib = xml.getAttributeValue(null, "attrib");
									
									if(attrib == null)
										throw new IllegalArgumentException("<source> missing 'attrib'");
									
									vao.sources.add(Integer.parseInt(attrib));
									xml.nextTag();
									xml.require(XmlPullParser.END_TAG, null, "source");
								}
								
								xml.require(XmlPullParser.END_TAG, null, "vao");
								
								break;
							}
							case "arrays": {
								String primitive = xml.getAttributeValue(null, "cmd");
								String start = xml.getAttributeValue(null, "start");
								String count = xml.getAttributeValue(null, "count");
								
								if(primitive == null)
									throw new IllegalArgumentException("<arrays> missing 'cmd'");
								if(start == null)
									throw new IllegalArgumentException("<arrays> missing 'start'");
								if(count == null)
									throw new IllegalArgumentException("<arrays> missing 'count'");
								
								RenderCmd cmd = new RenderCmd(primitive, Integer.parseInt(start), Integer.parseInt(count));
								renderCommands.add(cmd);
								
								xml.next();
								xml.require(XmlPullParser.END_TAG, null, "arrays");
								
								break;
							}
							default:
								throw new IllegalArgumentException("Invalid TAG name: " + xml.getName());
						}
						
						break;
				}
			} while(xml.next() != XmlPullParser.END_DOCUMENT);
		}
		
		if(attributes.size() == 0)
			throw new IllegalArgumentException("There must be at least 1 set of attributes.");
		if(renderCommands.size() == 0)
			throw new IllegalArgumentException("There must be at least 1 render command.");
		
		attributeData.flip();
		indexData.flip();
		
		int vbo1 = glGenBuffers();
		glBindBuffer(GL_ARRAY_BUFFER, vbo1);
		glBufferData(GL_ARRAY_BUFFER, (ByteBuffer)BufferUtils.createByteBuffer(attributeData.capacity()).put(attributeData).flip(), GL_STATIC_DRAW);
		glBindBuffer(GL_ARRAY_BUFFER, 0);
		
		int vbo2 = -1;
		if(indexData.hasRemaining()) {
			vbo2 = glGenBuffers();
			glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo2);
			glBufferData(GL_ELEMENT_ARRAY_BUFFER, (ByteBuffer)BufferUtils.createByteBuffer(indexData.capacity()).put(indexData).flip(), GL_STATIC_DRAW);
			glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
		}
		
		vao = glGenVertexArrays();
		glBindVertexArray(vao);
		
		glBindBuffer(GL_ARRAY_BUFFER, vbo1);
		for(Attribute attrib : attributes) {
			glEnableVertexAttribArray(attrib.index);
			glVertexAttribPointer(attrib.index, attrib.size, attrib.type.dataType, attrib.type.normalized, 0, attrib.offset);
		}
		
		if(vbo2 >= 0)
			glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo2);
		
		glBindVertexArray(0);
		glBindBuffer(GL_ARRAY_BUFFER, 0);
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
		
		if(vaos != null) {
			vaoMap = new HashMap<>();
			
			for(VAO vao : vaos) {
				int v = glGenVertexArrays();
				glBindVertexArray(v);
				
				vaoMap.put(vao.name, v);
				
				glBindBuffer(GL_ARRAY_BUFFER, vbo1);
				for(Attribute attrib : attributes) {
					if(vao.sources.contains(attrib.index)) {
						glEnableVertexAttribArray(attrib.index);
						glVertexAttribPointer(attrib.index, attrib.size, attrib.type.dataType, attrib.type.normalized, 0, attrib.offset);
					}
				}
				
				if(vbo2 >= 0)
					glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo2);
				
				glBindVertexArray(0);
				glBindBuffer(GL_ARRAY_BUFFER, 0);
				glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
			}
		}
	}
	
	private String[] clean(String[] data) {
		ArrayList<String> clean = new ArrayList<>();
		for(String s : data)
			if(!(s = s.trim()).isEmpty())
				clean.add(s);
		return clean.toArray(new String[clean.size()]);
	}
	
	public void render() {
		glBindVertexArray(vao);
		for(RenderCmd cmd : renderCommands)
			cmd.render();
		glBindVertexArray(0);
	}
	
	public void render(String name) {
		if(vaoMap == null)
			throw new IllegalArgumentException("vaoMap is null, must call render() method.");
		
		if(!vaoMap.containsKey(name))
			throw new IllegalArgumentException(name + " could not be found.");
		
		glBindVertexArray(vaoMap.get(name));
		for(RenderCmd cmd : renderCommands)
			cmd.render();
		glBindVertexArray(0);
	}
	
	private static class RenderCmd {
		private boolean isIndexedCmd;
		private int primitive;
		private int start;
		private int count;
		private RenderCommandType type;
		
		public RenderCmd(String primitive, String type) {
			isIndexedCmd = true;
			this.primitive = getPrimitive(primitive);
			this.type = RenderCommandType.getRenderType(type);
		}
		
		public RenderCmd(String primitive, int start, int count) {
			isIndexedCmd = false;
			this.primitive = getPrimitive(primitive);
			this.start = start;
			this.count = count;
		}
		
		public ByteBuffer storeData(ByteBuffer b, String[] data) {
			start = b.position();
			count = data.length;
			
			ByteBuffer b2 = BufferUtils.createByteBuffer(start + count * type.size);
			b2.put((ByteBuffer)b.flip());
			type.store(b2, data);
			
			return b2;
		}
		
		private static int getPrimitive(String name) {
			switch(name) {
				case "triangles":
					return GL_TRIANGLES;
				case "tri-fan":
					return GL_TRIANGLE_FAN;
				case "tri-strip":
					return GL_TRIANGLE_STRIP;
				case "lines":
					return GL_LINES;
				case "line-strip":
					return GL_LINE_STRIP;
				case "line-loop":
					return GL_LINE_LOOP;
				case "points":
					return GL_POINTS;
				default:
					throw new IllegalArgumentException("Invalid primitive name: " + name);
			}
		}
		
		public void render() {
			if(isIndexedCmd)
				glDrawElements(primitive, count, type.dataType, start);
			else
				glDrawArrays(primitive, start, count);
		}
	}
	
	private enum RenderCommandType {
		UBYTE("ubyte", GL_UNSIGNED_BYTE, 1) {
			public void store(ByteBuffer b, String[] data) {
				for(String s : data)
					b.put((byte)Long.parseLong(s));
			}
		},
		USHORT("ushort", GL_UNSIGNED_SHORT, 2) {
			public void store(ByteBuffer b, String[] data) {
				for(String s : data)
					b.putShort((short)Long.parseLong(s));
			}
		},
		UINT("uint", GL_UNSIGNED_INT, 4) {
			public void store(ByteBuffer b, String[] data) {
				for(String s : data)
					b.putInt((int)Long.parseLong(s));
			}
		};
		
		private String name;
		private int dataType;
		private int size;
		
		private RenderCommandType(String name, int type, int size) {
			this.name = name;
			this.dataType = type;
			this.size = size;
		}
		
		public abstract void store(ByteBuffer b, String[] data);
		
		public static RenderCommandType getRenderType(String name) {
			for(RenderCommandType rt : values()) {
				if(rt.name.equals(name))
					return rt;
			}
			
			throw new IllegalArgumentException("Unsupported render command type!");
		}
	}
	
	private class Attribute {
		private int index;
		private AttributeType type;
		private int size;
		private int offset;
		
		public Attribute(int index, String type, int size) {
			this.index = index;
			this.size = size;
			
			this.type = AttributeType.getAttributeType(type);
		}
		
		public ByteBuffer storeData(ByteBuffer b, String[] data) {
			offset = b.position();
			
			ByteBuffer b2 = BufferUtils.createByteBuffer(offset + data.length * type.size);
			b2.put((ByteBuffer)b.flip());
			type.store(b2, data);
			
			return b2;
		}
	}
	
	private enum AttributeType {
		FLOAT("float", false, GL_FLOAT, 4),
		INT("int", false, GL_INT, 4), UINT("uint", false, GL_UNSIGNED_INT, 4), NORM_INT("norm-int", true, GL_INT, 4), NORM_UINT("norm-uint", true, GL_UNSIGNED_INT, 4),
		SHORT("short", false, GL_SHORT, 2), USHORT("ushort", false, GL_UNSIGNED_SHORT, 2), NORM_SHORT("norm-short", true, GL_SHORT, 2), NORM_USHORT("norm-ushort", true, GL_UNSIGNED_SHORT, 2),
		BYTE("byte", false, GL_BYTE, 1), UBYTE("ubyte", false, GL_UNSIGNED_BYTE, 1), NORM_BYTE("norm-byte", true, GL_BYTE, 1), NORM_UBYTE("norm-ubyte", true, GL_UNSIGNED_BYTE, 1);
		
		private String name;
		private boolean normalized;
		private int dataType;
		private int size;
		
		private AttributeType(String name, boolean normalized, int type, int size) {
			this.name = name;
			this.normalized = normalized;
			this.dataType = type;
			this.size = size;
		}
		
		public void store(ByteBuffer b, String[] data) {
			switch(dataType) {
				case GL_FLOAT:
					for(String s : data)
						b.putFloat(Float.parseFloat(s));
					break;
				case GL_INT:
				case GL_UNSIGNED_INT:
					for(String s : data)
						b.putInt((int)Long.parseLong(s));
					break;
				case GL_SHORT:
				case GL_UNSIGNED_SHORT:
					for(String s : data)
						b.putShort((short)Long.parseLong(s));
					break;
				case GL_BYTE:
				case GL_UNSIGNED_BYTE:
					for(String s : data)
						b.put((byte)Long.parseLong(s));
					break;
			}
		}
		
		public static AttributeType getAttributeType(String name) {
			for(AttributeType at : values()) {
				if(at.name.equals(name))
					return at;
			}
			
			throw new IllegalArgumentException("Unsupported attribute type!");
		}
	}
}
 public enum MouseButton {
	LEFT_BUTTON,
	MIDDLE_BUTTON,
	RIGHT_BUTTON;
		
	public static MouseButton getButton(int button) {
		switch(button) {
			case 0:
				return MouseButton.LEFT_BUTTON;
			case 1:
				return MouseButton.RIGHT_BUTTON;
			case 2:
				return MouseButton.MIDDLE_BUTTON;
			case -1:
				return null;
			default:
				throw new IllegalArgumentException("Invalid button: " + button);
		}
	}
}
 public static class ObjectData {
	public Vector3 position;
	public Quaternion orientation;
		
	public ObjectData(ObjectData data) {
		position = data.position.copy();
		orientation = data.orientation.copy();
	}
		
	public ObjectData(Vector3 v, Quaternion q) {
		position = v.copy();
		orientation = q.copy();
	}
}
 public static class ObjectPole extends Pole {
	private enum Axis {
		AXIS_X,
		AXIS_Y,
		AXIS_Z
	}
		
	private enum RotateMode {
		DUAL_AXIS,
		BIAXIAL,
		SPIN
	}
		
	private Vector3[] axisVectors = {
			new Vector3(1, 0, 0),
			new Vector3(0, 1, 0),
			new Vector3(0, 0, 1)
	};
		
	private ViewProvider view;
	private ObjectData po;
	private ObjectData initialPo;
		
	private float rotateScale;
	private MouseButton actionButton;
		
	private RotateMode rotateMode;
	private boolean isDragging;
		
	private Vector2 prevMousePos;
	private Vector2 startDragMousePos;
	private Quaternion startDragOrient;
		
	public ObjectPole(ObjectData initialData, float rotateScale, MouseButton actionButton, ViewProvider lookAtProvider) {
		this.view = lookAtProvider;
		this.po = new ObjectData(initialData);
		this.initialPo = initialData;
		this.rotateScale = rotateScale;
		this.actionButton = actionButton;
	}
		
	public Matrix4 calcMatrix() {
		Matrix4 translateMat = new Matrix4().clearToIdentity();
		translateMat.putColumn(3, po.position, 1);
		return translateMat.mult(po.orientation.toMatrix());
	}
		
	public void setRotationScale(float rotateScale) {
		this.rotateScale = rotateScale;
	}
		
	public float getRotationScale() {
		return rotateScale;
	}
		
	public ObjectData getPosOrient() {
		return po;
	}
		
	public boolean isDragging() {
		return isDragging;
	}
		
	public void reset() {
		if(!isDragging)
			po = new ObjectData(initialPo);
	}
		
	private Quaternion calcRotationQuat(Axis axis, float angle) {
		return calcRotationQuat(axis.ordinal(), angle);
	}
		
	private Quaternion calcRotationQuat(int axis, float angle) {
		return Utils.angleAxisDeg(angle, axisVectors[axis]);
	}
		
	private void rotateWorldDegrees(Quaternion rot, boolean fromInitial) {
		if(!isDragging)
			fromInitial = false;
			
		po.orientation = rot.copy().mult(fromInitial ? startDragOrient : po.orientation).normalize();
	}
		
	public void rotateViewDegrees(Quaternion rot, boolean fromInitial) {
		if(!isDragging)
			fromInitial = false;
			
		if(view == null)
			rotateWorldDegrees(rot, fromInitial);
		else {
			Quaternion viewQuat = view.calcMatrix().toQuaternion();
			Quaternion inViewQuat = viewQuat.copy().conjugate();
			po.orientation = inViewQuat.mult(rot).mult(viewQuat).mult(fromInitial ? startDragOrient : po.orientation).normalize();
		}
	}
		
	@Override
	public void mouseMove(Vector2 position) {
		if(isDragging) {
			Vector2 diff = position.copy().sub(prevMousePos);
				
			switch(rotateMode) {
				case DUAL_AXIS:
					Quaternion rot = calcRotationQuat(Axis.AXIS_Y, diff.x() * rotateScale);
					rot = calcRotationQuat(Axis.AXIS_X, diff.y() * rotateScale).mult(rot).normalize();
					rotateViewDegrees(rot, false);
					break;
				case BIAXIAL: {
					Vector2 initDiff = position.copy().sub(startDragMousePos);
						
					Axis axis;
					float degAngle;
					if(Math.abs(initDiff.x()) > Math.abs(initDiff.y())) {
						axis = Axis.AXIS_Y;
						degAngle = initDiff.x() * rotateScale;
					} else {
						axis = Axis.AXIS_X;
						degAngle = initDiff.y() * rotateScale;
					}
						
					rotateViewDegrees(calcRotationQuat(axis, degAngle), true);
				}
					break;
				case SPIN:
					rotateViewDegrees(calcRotationQuat(Axis.AXIS_Z, -diff.x() * rotateScale), false);
					break;
			}
				
			prevMousePos = position.copy();
		}
	}
		
	@Override
	public void mouseClick(MouseButton button, boolean isPressed, MouseModifier modifiers, Vector2 position) {
		if(isPressed) {
			if(!isDragging) {
				if(button == actionButton) {
					if(modifiers == MouseModifier.KEY_ALT)
						rotateMode = RotateMode.SPIN;
					else if(modifiers == MouseModifier.KEY_CTRL)
						rotateMode = RotateMode.BIAXIAL;
					else
						rotateMode = RotateMode.DUAL_AXIS;
						
					prevMousePos = position.copy();
						
					startDragMousePos = position.copy();
						
					startDragOrient = po.orientation.copy();
						
					isDragging = true;
				}
			}
		} else {
			if(isDragging) {
				if(button == actionButton) {
					mouseMove(position);
						
					isDragging = false;
				}
			}
		}
	}
		
	@Override
	public void mouseWheel(int direction, MouseModifier modifiers) {}
		
	@Override
	public void charPress(long deltaTime) {}
}
 public static class ViewData {
	public Vector3 targetPos;
	public Quaternion orient;
	public float radius;
	public float degSpinRotation;
		
	public ViewData(ViewData data) {
		targetPos = data.targetPos.copy();
		orient = data.orient.copy();
		radius = data.radius;
		degSpinRotation = data.degSpinRotation;
	}
		
	public ViewData(Vector3 v, Quaternion q, float r, float d) {
		targetPos = v;
		orient = q.copy();
		radius = r;
		degSpinRotation = d;
	}
}
 public static class ViewPole extends ViewProvider {
	private enum TargetOffsetDir {
		DIR_UP,
		DIR_DOWN,
		DIR_FORWARD,
		DIR_BACKWARD,
		DIR_RIGHT,
		DIR_LEFT
	}
		
	private enum RotateMode {
		DUAL_AXIS_ROTATE,
		BIAXIAL_ROTATE,
		XZ_AXIS_ROTATE,
		Y_AXIS_ROTATE,
		SPIN_VIEW_AXIS
	}
		
	private Vector3[] offsets = {
			new Vector3(0, 1, 0),
			new Vector3(0, -1, 0),
			new Vector3(0, 0, -1),
			new Vector3(0, 0, 1),
			new Vector3(1, 0, 0),
			new Vector3(-1, 0, 0)
	};
		
	private ViewData currView;
	private ViewScale viewScale;
		
	private ViewData initialView;
	private MouseButton actionButton;
	private boolean rightKeyboardCtrls;
		
	private boolean isDragging;
	private RotateMode rotateMode;
		
	private float degStarDragSpin;
	private Vector2 startDragMouseLoc;
	private Quaternion startDragOrient;
		
	public ViewPole(ViewData initialView, ViewScale viewScale, MouseButton actionButton) {
		this(initialView, viewScale, actionButton, false);
	}
		
	public ViewPole(ViewData initialView, ViewScale viewScale, MouseButton actionButton, boolean rightKeyboardCtrls) {
		this.currView = new ViewData(initialView);
		this.viewScale = new ViewScale(viewScale);
		this.initialView = initialView;
		this.actionButton = actionButton;
		this.rightKeyboardCtrls = rightKeyboardCtrls;
	}
		
	@Override
	public Matrix4 calcMatrix() {
		Matrix4 mat = new Matrix4().clearToIdentity();
			
		mat.translate(0, 0, -currView.radius);
			
		Quaternion fullRotation = Utils.angleAxisDeg(currView.degSpinRotation, new Vector3(0, 0, 1)).mult(currView.orient);
		mat.mult(fullRotation.toMatrix());
			
		mat.translate(currView.targetPos.copy().mult(-1));
			
		return mat;
	}
		
	public void reset() {
		if(!isDragging)
			currView = new ViewData(initialView);
	}
		
	public void setRotationScale(float rotateScale) {
		viewScale.rotationScale = rotateScale;
	}
		
	public float getRotationScale() {
		return viewScale.rotationScale;
	}
		
	public ViewData getView() {
		return currView;
	}
		
	public boolean isDragging() {
		return isDragging;
	}
		
	public void processXChange(int diffX) {
		float degAngleDiff = diffX * viewScale.rotationScale;
			
		currView.orient = startDragOrient.copy().mult(Utils.angleAxisDeg(degAngleDiff, new Vector3(0, 1, 0)));
	}
		
	public void processYChange(int diffY) {
		float degAngleDiff = diffY * viewScale.rotationScale;
			
		currView.orient = Utils.angleAxisDeg(degAngleDiff, new Vector3(1, 0, 0)).mult(startDragOrient);
	}
		
	public void processXYChange(int diffX, int diffY) {
		float degXAngleDiff = diffX * viewScale.rotationScale;
		float degYAngleDiff = diffY * viewScale.rotationScale;
			
		currView.orient = startDragOrient.copy().mult(Utils.angleAxisDeg(degXAngleDiff, new Vector3(0, 1, 0)));
		currView.orient = Utils.angleAxisDeg(degYAngleDiff, new Vector3(1, 0, 0)).mult(currView.orient);
	}
		
	public void processSpinAxis(int diffX) {
		float degSpinDiff = diffX * viewScale.rotationScale;
		currView.degSpinRotation = degSpinDiff + degStarDragSpin;
	}
		
	public void beginDragRotate(Vector2 start, RotateMode rotMode) {
		rotateMode = rotMode;
			
		startDragMouseLoc = start.copy();
			
		degStarDragSpin = currView.degSpinRotation;
			
		startDragOrient = currView.orient.copy();
			
		isDragging = true;
	}
		
	public void onDragRotate(Vector2 position) {
		int diffX = (int)(position.x() - startDragMouseLoc.x());
		int diffY = -(int)(position.y() - startDragMouseLoc.y());
			
		switch(rotateMode) {
			case DUAL_AXIS_ROTATE:
				processXYChange(diffX, diffY);
				break;
			case BIAXIAL_ROTATE:
				if(Math.abs(diffX) > Math.abs(diffY))
					processXChange(diffX);
				else
					processYChange(diffY);
				break;
			case XZ_AXIS_ROTATE:
				processXChange(diffX);
				break;
			case Y_AXIS_ROTATE:
				processYChange(diffY);
				break;
			case SPIN_VIEW_AXIS:
				processSpinAxis(diffX);
				break;
		}
	}
		
	public void endDragRotate(Vector2 end, boolean keepResults) {
		if(keepResults)
			onDragRotate(end);
		else
			currView.orient = startDragOrient.copy();
			
		isDragging = false;
	}
		
	public void moveCloser(boolean largeStep) {
		if(largeStep)
			currView.radius -= viewScale.largeRadiusDelta;
		else
			currView.radius -= viewScale.smallRadiusDelta;
			
		if(currView.radius < viewScale.minRadius)
			currView.radius = viewScale.minRadius;
	}
		
	public void moveAway(boolean largeStep) {
		if(largeStep)
			currView.radius += viewScale.largeRadiusDelta;
		else
			currView.radius += viewScale.smallRadiusDelta;
			
		if(currView.radius > viewScale.maxRadius)
			currView.radius = viewScale.maxRadius;
	}
		
	@Override
	public void mouseMove(Vector2 position) {
		if(isDragging)
			onDragRotate(position);
	}
		
	@Override
	public void mouseClick(MouseButton button, boolean isPressed, MouseModifier modifiers, Vector2 position) {
		if(isPressed) {
			if(!isDragging) {
				if(button == actionButton) {
					if(modifiers == MouseModifier.KEY_CTRL)
						beginDragRotate(position, RotateMode.BIAXIAL_ROTATE);
					else if(modifiers == MouseModifier.KEY_ALT)
						beginDragRotate(position, RotateMode.SPIN_VIEW_AXIS);
					else
						beginDragRotate(position, RotateMode.DUAL_AXIS_ROTATE);
				}
			}
		} else {
			if(isDragging) {
				if(button == actionButton) {
					if(rotateMode == RotateMode.DUAL_AXIS_ROTATE ||
							rotateMode == RotateMode.SPIN_VIEW_AXIS ||
							rotateMode == RotateMode.BIAXIAL_ROTATE)
						endDragRotate(position, true);
				}
			}
		}
	}
		
	@Override
	public void mouseWheel(int direction, MouseModifier modifiers) {
		if(direction > 0)
			moveCloser(modifiers != MouseModifier.KEY_SHIFT);
		else
			moveAway(modifiers != MouseModifier.KEY_SHIFT);
	}
		
	private void offsetTargetPos(TargetOffsetDir dir, float worldDistance) {
		offsetTargetPos(offsets[dir.ordinal()].copy().mult(worldDistance));
	}
		
	private void offsetTargetPos(Vector3 cameraOffset) {
		currView.targetPos.add(calcMatrix().toQuaternion().conjugate().mult(cameraOffset));
	}
		
	@Override
	public void charPress(long deltaTime) {
		boolean isShiftPressed = Keyboard.isKeyDown(Keyboard.KEY_LSHIFT) || Keyboard.isKeyDown(Keyboard.KEY_RSHIFT);
			
		float offset = (isShiftPressed ? viewScale.smallPosOffset : viewScale.largePosOffset) * 10 * deltaTime / (float)1e9;
			
		if(rightKeyboardCtrls) {
			if(Keyboard.isKeyDown(Keyboard.KEY_I))
				offsetTargetPos(TargetOffsetDir.DIR_FORWARD, offset);
			if(Keyboard.isKeyDown(Keyboard.KEY_K))
				offsetTargetPos(TargetOffsetDir.DIR_BACKWARD, offset);
			if(Keyboard.isKeyDown(Keyboard.KEY_L))
				offsetTargetPos(TargetOffsetDir.DIR_RIGHT, offset);
			if(Keyboard.isKeyDown(Keyboard.KEY_J))
				offsetTargetPos(TargetOffsetDir.DIR_LEFT, offset);
			if(Keyboard.isKeyDown(Keyboard.KEY_O))
				offsetTargetPos(TargetOffsetDir.DIR_UP, offset);
			if(Keyboard.isKeyDown(Keyboard.KEY_U))
				offsetTargetPos(TargetOffsetDir.DIR_DOWN, offset);
		}
		else {
			if(Keyboard.isKeyDown(Keyboard.KEY_W))
				offsetTargetPos(TargetOffsetDir.DIR_FORWARD, offset);
			if(Keyboard.isKeyDown(Keyboard.KEY_S))
				offsetTargetPos(TargetOffsetDir.DIR_BACKWARD, offset);
			if(Keyboard.isKeyDown(Keyboard.KEY_D))
				offsetTargetPos(TargetOffsetDir.DIR_RIGHT, offset);
			if(Keyboard.isKeyDown(Keyboard.KEY_A))
				offsetTargetPos(TargetOffsetDir.DIR_LEFT, offset);
			if(Keyboard.isKeyDown(Keyboard.KEY_E))
				offsetTargetPos(TargetOffsetDir.DIR_UP, offset);
			if(Keyboard.isKeyDown(Keyboard.KEY_Q))
				offsetTargetPos(TargetOffsetDir.DIR_DOWN, offset);
		}
	}
}
 public static class ViewScale {
	public float minRadius;
	public float maxRadius;
	public float largeRadiusDelta;
	public float smallRadiusDelta;
	public float largePosOffset;
	public float smallPosOffset;
	public float rotationScale;
		
	public ViewScale(float min, float max, float large, float small, float largePos, float smallPos, float rot) {
		minRadius = min;
		maxRadius = max;
		largeRadiusDelta = large;
		smallRadiusDelta = small;
		largePosOffset = largePos;
		smallPosOffset = smallPos;
		rotationScale = rot;
	}
		
	public ViewScale(ViewScale v) {
		minRadius = v.minRadius;
		maxRadius = v.maxRadius;
		largeRadiusDelta = v.largeRadiusDelta;
		smallRadiusDelta = v.smallRadiusDelta;
		largePosOffset = v.largePosOffset;
		smallPosOffset = v.smallPosOffset;
		rotationScale = v.rotationScale;
	}
}
 public class ShaderProgram {
	private int program;
	
	public ShaderProgram(String vertexShader, String fragmentShader) {
		this(vertexShader, null, fragmentShader, null);
	}
	
	public ShaderProgram(String vertexShader, String fragmentShader, Map<Integer,String> attributes) {
		this(vertexShader, null, fragmentShader, attributes);
	}
	
	public ShaderProgram(String vertexShader, String geometryShader, String fragmentShader) {
		this(vertexShader, geometryShader, fragmentShader, null);
	}
	
	public ShaderProgram(String vertexShader, String geometryShader, String fragmentShader, Map<Integer,String> attributes) {
		int vs = compileShader(vertexShader, GL_VERTEX_SHADER);
		int gs = compileShader(geometryShader, GL_GEOMETRY_SHADER);
		int fs = compileShader(fragmentShader, GL_FRAGMENT_SHADER);
		
		program = glCreateProgram();
		glAttachShader(program, vs);
		if(gs != -1)
			glAttachShader(program, gs);
		glAttachShader(program, fs);
		
		if(attributes != null)
			for(int i : attributes.keySet())
				glBindAttribLocation(program, i, attributes.get(i));
		
		glLinkProgram(program);
		
		String infoLog = glGetProgramInfoLog(program, glGetProgrami(program, GL_INFO_LOG_LENGTH));
		
		if(glGetProgrami(program, GL_LINK_STATUS) == GL_FALSE)
			throw new RuntimeException("Failure in linking program. Error log:\n" + infoLog);
		else {
			System.out.print("Linking program successful.");
			if(infoLog != null && !(infoLog = infoLog.trim()).isEmpty())
				System.out.println(" Log:\n" + infoLog);
			else
				System.out.println();
		}
		
		glDetachShader(program, vs);
		if(gs != -1)
			glDetachShader(program, gs);
		glDetachShader(program, fs);
		
		glDeleteShader(vs);
		if(gs != -1)
			glDeleteShader(gs);
		glDeleteShader(fs);
	}
	
	private int compileShader(String source, int type) {
		if(source == null)
			return -1;
		
		int shader = glCreateShader(type);
		glShaderSource(shader, source);
		
		glCompileShader(shader);
		
		String infoLog = glGetShaderInfoLog(shader, glGetShaderi(shader, GL_INFO_LOG_LENGTH));
		
		if(glGetShaderi(shader, GL_COMPILE_STATUS) == GL_FALSE)
			throw new RuntimeException("Failure in compiling " + getName(type) + " shader. Error log:\n" + infoLog);
		else {
			System.out.print("Compiling " + getName(type) + " shader successful.");
			if(infoLog != null && !(infoLog = infoLog.trim()).isEmpty())
				System.out.println(" Log:\n" + infoLog);
			else
				System.out.println();
		}
		
		return shader;
	}
	
	private String getName(int shaderType) {
		if(shaderType == GL_VERTEX_SHADER)
			return "vertex";
		if(shaderType == GL_GEOMETRY_SHADER)
			return "geometry";
		if(shaderType == GL_FRAGMENT_SHADER)
			return "fragment";
		
		throw new IllegalArgumentException("Invalid shaderType, must be either GL_VERTEX_SHADER, GL_GEOMETRY_SHADER, or GL_FRAGMENT_SHADER");
	}
	
	public int getProgram() {
		return program;
	}
	
	public int getUniformLocation(String name) {
		return glGetUniformLocation(program, name);
	}
	
	public int getUniformBlockIndex(String name) {
		return glGetUniformBlockIndex(program, name);
	}
	
	public void begin() {
		glUseProgram(program);
	}
	
	public void end() {
		glUseProgram(0);
	}
	
	public void destroy() {
		glDeleteProgram(program);
	}
}
 public class Timer {
	public enum Type {
		LOOP,
		SINGLE,
		INFINITE
	}
	
	private Type type;
	private float secDuration;
	
	private boolean hasUpdated;
	private boolean isPaused;
	
	private float absPrevTime;
	private float secAccumTime;
	
	private long elapsedTime;
	
	public Timer() {
		this(Type.INFINITE, 1);
	}
	
	public Timer(Type type, float duration) {
		this.type = type;
		this.secDuration = duration;
		
		if(type != Type.INFINITE && duration <= 0)
			throw new IllegalArgumentException("duration cannot be less than or equal to 0");
	}
	
	public void reset() {
		hasUpdated = false;
		secAccumTime = 0;
	}
	
	public boolean togglePause() {
		isPaused = !isPaused;
		return isPaused;
	}
	
	public boolean isPaused() {
		return isPaused;
	}
	
	public void setPause(boolean pause) {
		isPaused = pause;
	}
	
	public boolean update(long deltaTime) {
		elapsedTime += deltaTime;
		
		float currTime = elapsedTime / (float)1e9;
		
		if(!hasUpdated) {
			absPrevTime = currTime;
			hasUpdated = true;
		}
		
		if(isPaused) {
			absPrevTime = currTime;
			return false;
		}
		
		float delta = currTime - absPrevTime;
		secAccumTime += delta;
		
		absPrevTime = currTime;
		
		if(type == Type.SINGLE)
			return secAccumTime > secDuration;
		
		return false;
	}
	
	public void rewind(float secRewind) {
		secAccumTime -= secRewind;
		if(secAccumTime < 0)
			secAccumTime = 0;
	}
	
	public void fastForward(float secFF) {
		secAccumTime += secFF;
	}
	
	public float getAlpha() {
		switch(type) {
			case LOOP:
				return (secAccumTime % secDuration) / secDuration;
			case SINGLE:
				return Utils.clamp(secAccumTime / secDuration, 0, 1);
			case INFINITE:
			default:
				return -1;
		}
	}
	
	public float getProgression() {
		switch(type) {
			case LOOP:
				return secAccumTime % secDuration;
			case SINGLE:
				return Utils.clamp(secAccumTime, 0, secDuration);
			case INFINITE:
			default:
				return -1;
		}
	}
	
	public float getTimeSinceStart() {
		return secAccumTime;
	}
}
 public enum Type {
	LOOP,
	SINGLE,
	INFINITE
}
 public class UniformBlockArray<T extends UniformBlockArray.UniformBlockObject> {
	private final FloatBuffer storage;
	private final int blockOffset;
	private final int arrayCount;
	
	private int maxPosition;
	
	public UniformBlockArray(int uniformBlockObjectSize, int arrayCount) {
		this.arrayCount = arrayCount;
		
		int uniformBufferAlignSize = glGetInteger(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT);
		
		blockOffset = uniformBlockObjectSize + uniformBufferAlignSize - uniformBlockObjectSize % uniformBufferAlignSize;
		
		storage = BufferUtils.createFloatBuffer(blockOffset * arrayCount / 4);
	}
	
	public int size() {
		return arrayCount;
	}
	
	public int getArrayOffset() {
		return blockOffset;
	}
	
	public void setBlockMember(int index, T member) {
		storage.position(index * blockOffset / 4);
		storage.put(member.toBuffer());
		
		if(storage.position() > maxPosition)
			maxPosition = storage.position();
	}
	
	public int createBufferObject() {
		int bufferObject = glGenBuffers();
		glBindBuffer(GL_UNIFORM_BUFFER, bufferObject);
		glBufferData(GL_UNIFORM_BUFFER, (FloatBuffer)storage.position(maxPosition).flip(), GL_STATIC_DRAW);
		glBindBuffer(GL_UNIFORM_BUFFER, 0);
		
		return bufferObject;
	}
	
	public static interface UniformBlockObject {
		public FloatBuffer toBuffer();
	}
}
 public static interface UniformBlockObject {
	public FloatBuffer toBuffer();
}
 public class Utils {
	public static boolean ENABLE_ERROR_CHECKING = true;
	
	public static void checkGLError(String event) {
		int error;
		if(ENABLE_ERROR_CHECKING && (error = glGetError()) != GL_NO_ERROR)
			throw new RuntimeException("OpenGL Error during " + event + ": " + gluErrorString(error));
	}
	
	public static Quaternion angleAxisDeg(float angle, Vector3 vec) {
		return new Quaternion((float)Math.toRadians(angle), vec);
	}
	
	public static float clamp(float value, float low, float high) {
		return Math.min(Math.max(value, low), high);
	}
	
	public static float mix(float f1, float f2, float a) {
		return f1 + (f2 - f1) * a;
	}
	
	public static Matrix4 lookAt(Vector3 eye, Vector3 center, Vector3 up) {
		Vector3 f = center.copy().sub(eye).normalize();
		up = up.copy().normalize();
		
		Vector3 s = f.cross(up);
		Vector3 u = s.cross(f);
		
		return new Matrix4(new float[] {
				s.x(), u.x(), -f.x(), 0,
				s.y(), u.y(), -f.y(), 0,
				s.z(), u.z(), -f.z(), 0,
				0, 0, 0, 1
		}).translate(eye.copy().mult(-1));
	}
	
	public static void updateMousePoles(ViewPole... viewPoles) {
		updateMousePoles(viewPoles, null);
	}
	
	public static void updateMousePoles(ObjectPole... objectPoles) {
		updateMousePoles(null, objectPoles);
	}
	
	public static void updateMousePoles(ViewPole viewPole, ObjectPole objectPole) {
		updateMousePoles(viewPole == null ? null : new ViewPole[] { viewPole }, objectPole == null ? null : new ObjectPole[] { objectPole });
	}
	
	public static void updateMousePoles(ViewPole[] viewPoles, ObjectPole[] objectPoles) {
		while(Mouse.next()) {
			MouseButton button = MouseButton.getButton(Mouse.getEventButton());
			if(button != null) {
				boolean pressed = Mouse.getEventButtonState();
				if(viewPoles != null)
					for(ViewPole v : viewPoles)
						v.mouseClick(button, pressed, getModifier(), new Vector2(Mouse.getX(), Mouse.getY()));
				if(objectPoles != null)
					for(ObjectPole o : objectPoles)
						o.mouseClick(button, pressed, getModifier(), new Vector2(Mouse.getX(), Mouse.getY()));
			} else {
				int dwheel = Mouse.getDWheel();
				
				if(dwheel != 0) {
					if(viewPoles != null)
						for(ViewPole v : viewPoles)
							v.mouseWheel(dwheel, getModifier());
					if(objectPoles != null)
						for(ObjectPole o : objectPoles)
							o.mouseWheel(dwheel, getModifier());
				} else {
					if(viewPoles != null)
						for(ViewPole v : viewPoles)
							v.mouseMove(new Vector2(Mouse.getX(), Mouse.getY()));
					if(objectPoles != null)
						for(ObjectPole o : objectPoles)
							o.mouseMove(new Vector2(Mouse.getX(), Mouse.getY()));
				}
			}
		}
	}
	
	private static MouseModifier getModifier() {
		if(Keyboard.isKeyDown(Keyboard.KEY_LSHIFT) || Keyboard.isKeyDown(Keyboard.KEY_RSHIFT))
			return MouseModifier.KEY_SHIFT;
		
		if(Keyboard.isKeyDown(Keyboard.KEY_LCONTROL) || Keyboard.isKeyDown(Keyboard.KEY_RCONTROL))
			return MouseModifier.KEY_CTRL;
		
		if(Keyboard.isKeyDown(Keyboard.KEY_LMENU))
			return MouseModifier.KEY_ALT;
		
		return null;
	}
	
	public static String readFully(InputStream is) {
		try(BufferedReader reader = new BufferedReader(new InputStreamReader(is, "UTF-8"))) {
			StringBuilder s = new StringBuilder();
			String l;
			
			while((l = reader.readLine()) != null)
				s.append(l).append('\n');
			
			return s.toString();
		} catch(Exception exc) {
			throw new RuntimeException("Failure reading input stream", exc);
		}
	}
	
	public static Quaternion parseQuaternion(String s) {
		String[] comp = StringUtil.split(s, ' ');
		if(comp.length != 4)
			throw new IllegalArgumentException("invalid Quaternion");
		
		Quaternion quat = new Quaternion();
		quat.x(Float.parseFloat(comp[0]));
		quat.y(Float.parseFloat(comp[1]));
		quat.z(Float.parseFloat(comp[2]));
		quat.w(Float.parseFloat(comp[3]));
		
		return quat;
	}
	
	public static Vector4 parseVector4(String s) {
		String[] comp = StringUtil.split(s, ' ');
		if(comp.length != 4)
			throw new IllegalArgumentException("invalid Vector4");
		
		Vector4 vec = new Vector4();
		vec.x(Float.parseFloat(comp[0]));
		vec.y(Float.parseFloat(comp[1]));
		vec.z(Float.parseFloat(comp[2]));
		vec.w(Float.parseFloat(comp[3]));
		
		return vec;
	}
	
	public static Vector3 parseVector3(String s) throws NumberFormatException {
		String[] comp = StringUtil.split(s, ' ');
		if(comp.length != 3)
			throw new IllegalArgumentException("invalid Vector3");
		
		Vector3 vec = new Vector3();
		vec.x(Float.parseFloat(comp[0]));
		vec.y(Float.parseFloat(comp[1]));
		vec.z(Float.parseFloat(comp[2]));
		
		return vec;
	}
	
	public static Vector2 parseVector2(String s) throws NumberFormatException {
		String[] comp = StringUtil.split(s, ' ');
		if(comp.length != 2)
			throw new IllegalArgumentException("invalid Vector2");
		
		Vector2 vec = new Vector2();
		vec.x(Float.parseFloat(comp[0]));
		vec.y(Float.parseFloat(comp[1]));
		
		return vec;
	}
}
 public class Matrix3 {
	private float[] matrix;
	
	public Matrix3() {
		matrix = new float[9];
	}
	
	public Matrix3(float[] m) {
		this();
		put(m);
	}
	
	public Matrix3(Matrix3 m) {
		this();
		put(m);
	}
	
	public Matrix3(Matrix4 m) {
		this();
		put(m);
	}
	
	public Matrix3 clear() {
		Arrays.fill(matrix, 0);
		return this;
	}
	
	public Matrix3 clearToIdentity() {
		return clear().put(0, 1).put(4, 1).put(8, 1);
	}
	
	public float get(int index) {
		return matrix[index];
	}
	
	public Matrix3 put(int index, float f) {
		matrix[index] = f;
		return this;
	}
	
	public Matrix3 putColumn(int index, Vector3 v) {
		put(index * 3 + 0, v.x());
		put(index * 3 + 1, v.y());
		put(index * 3 + 2, v.z());
		return this;
	}
	
	public Matrix3 put(float[] m) {
		if(m.length < matrix.length)
			throw new IllegalArgumentException("float array must have at least " + matrix.length + " values.");
		
		System.arraycopy(m, 0, matrix, 0, matrix.length);
		
		return this;
	}
	
	public Matrix3 put(Matrix3 m) {
		return put(m.matrix);
	}
	
	public Matrix3 put(Matrix4 m) {
		for(int a = 0; a < 3; a++) {
			put(a * 3 + 0, m.get(a * 4 + 0));
			put(a * 3 + 1, m.get(a * 4 + 1));
			put(a * 3 + 2, m.get(a * 4 + 2));
		}
		
		return this;
	}
	
	public Matrix3 mult(float f) {
		for(int a = 0; a < matrix.length; a++)
			put(a, get(a) * f);
		
		return this;
	}
	
	public Matrix3 mult(float[] m) {
		float[] newm = new float[matrix.length];
		
		for(int a = 0; a < matrix.length; a += 3) {
			newm[a + 0] = get(0) * m[a] + get(3) * m[a + 1] + get(6) * m[a + 2];
			newm[a + 1] = get(1) * m[a] + get(4) * m[a + 1] + get(7) * m[a + 2];
			newm[a + 2] = get(2) * m[a] + get(5) * m[a + 1] + get(8) * m[a + 2];
		}
		
		put(newm);
		
		return this;
	}
	
	public Matrix3 mult(Matrix3 m) {
		return mult(m.matrix);
	}
	
	public Vector3 mult(Vector3 vec) {
		Vector3 v = new Vector3();
		
		v.x(get(0) * vec.x() + get(3) * vec.y() + get(6) * vec.z());
		v.x(get(1) * vec.x() + get(4) * vec.y() + get(7) * vec.z());
		v.x(get(2) * vec.x() + get(5) * vec.y() + get(8) * vec.z());
		
		return v;
	}
	
	public Matrix3 transpose() {
		float old = get(1);
		put(1, get(3));
		put(3, old);
		
		old = get(2);
		put(2, get(6));
		put(6, old);
		
		old = get(5);
		put(5, get(7));
		put(7, old);
		
		return this;
	}
	
	public float determinant() {
		return +get(0) * get(4) * get(8) + get(3) * get(7) * get(2) + get(6) * get(1) * get(5)
				- get(2) * get(4) * get(6) - get(5) * get(7) * get(0) - get(8) * get(1) * get(3);
	}
	
	public Matrix3 inverse() {
		Matrix3 inv = new Matrix3();
		
		inv.put(0, +(get(4) * get(8) - get(5) * get(7)));
		inv.put(1, -(get(3) * get(8) - get(5) * get(6)));
		inv.put(2, +(get(3) * get(7) - get(4) * get(6)));
		
		inv.put(3, -(get(1) * get(8) - get(2) * get(7)));
		inv.put(4, +(get(0) * get(8) - get(2) * get(6)));
		inv.put(5, -(get(0) * get(7) - get(1) * get(6)));
		
		inv.put(6, +(get(1) * get(5) - get(2) * get(4)));
		inv.put(7, -(get(0) * get(5) - get(2) * get(3)));
		inv.put(8, +(get(0) * get(4) - get(1) * get(3)));
		
		return put(inv.transpose().mult(1 / determinant()));
	}
	
	private final static FloatBuffer direct = BufferUtils.createFloatBuffer(9);
	
	public FloatBuffer toBuffer() {
		direct.clear();
		direct.put(matrix);
		direct.flip();
		return direct;
	}
}
 public class Matrix4 {
	private float[] matrix;
	
	public Matrix4() {
		matrix = new float[16];
	}
	
	public Matrix4(float[] m) {
		this();
		set(m);
	}
	
	public Matrix4(Matrix3 m) {
		this();
		set(m);
	}
	
	public Matrix4(Matrix4 m) {
		this();
		set(m);
	}
	
	public Matrix4 copy() {
		return new Matrix4(this);
	}
	
	public Matrix4 clear() {
		Arrays.fill(matrix, 0);
		return this;
	}
	
	public Matrix4 clearToIdentity() {
		return clear().put(0, 1)
				.put(5, 1)
				.put(10, 1)
				.put(15, 1);
	}
	
	public Matrix4 clearToOrtho(float left, float right, float bottom, float top, float near, float far) {
		return clear().put(0, 2 / (right - left))
				.put(5, 2 / (top - bottom))
				.put(10, -2 / (far - near))
				.put(12, -(right + left) / (right - left))
				.put(13, -(top + bottom) / (top - bottom))
				.put(14, -(far + near) / (far - near))
				.put(15, 1);
	}
	
	public Matrix4 clearToPerspective(float fovRad, float width, float height, float near, float far) {
		float fov = 1 / (float)Math.tan(fovRad / 2);
		return clear().put(0, fov / (width / height))
				.put(5, fov).put(10, (far + near) / (near - far))
				.put(14, (2 * far * near) / (near - far))
				.put(11, -1);
	}
	
	public Matrix4 clearToPerspectiveDeg(float fov, float width, float height, float near, float far) {
		return clearToPerspective((float)Math.toRadians(fov), width, height, near, far);
	}
	
	public float get(int index) {
		return matrix[index];
	}
	
	public Vector4 getColumn(int index) {
		return new Vector4(get(index * 4 + 0), get(index * 4 + 1), get(index * 4 + 2), get(index * 4 + 3));
	}
	
	public Matrix4 put(int index, float f) {
		matrix[index] = f;
		return this;
	}
	
	public Matrix4 putColumn(int index, Vector4 v) {
		put(index * 4 + 0, v.x());
		put(index * 4 + 1, v.y());
		put(index * 4 + 2, v.z());
		put(index * 4 + 3, v.z());
		return this;
	}
	
	public Matrix4 putColumn(int index, Vector3 v) {
		put(index * 4 + 0, v.x());
		put(index * 4 + 1, v.y());
		put(index * 4 + 2, v.z());
		return this;
	}
	
	public Matrix4 putColumn(int index, Vector3 v, float w) {
		put(index * 4 + 0, v.x());
		put(index * 4 + 1, v.y());
		put(index * 4 + 2, v.z());
		put(index * 4 + 3, w);
		return this;
	}
	
	public Matrix4 set(float[] m) {
		if(m.length < matrix.length)
			throw new IllegalArgumentException("float array must have at least " + matrix.length + " values.");
		
		System.arraycopy(m, 0, matrix, 0, matrix.length);
		
		return this;
	}
	
	public Matrix4 set(Matrix4 m) {
		return set(m.matrix);
	}
	
	public Matrix4 set(Matrix3 m) {
		for(int a = 0; a < 3; a++) {
			matrix[a * 4 + 0] = m.get(a * 3 + 0);
			matrix[a * 4 + 1] = m.get(a * 3 + 1);
			matrix[a * 4 + 2] = m.get(a * 3 + 2);
		}
		
		return this;
	}
	
	public Matrix4 mult(float f) {
		for(int a = 0; a < matrix.length; a++)
			put(a, get(a) * f);
		
		return this;
	}
	
	public Matrix4 mult(float[] m) {
		float[] newm = new float[matrix.length];
		
		for(int a = 0; a < matrix.length; a += 4) {
			newm[a + 0] = get(0) * m[a] + get(4) * m[a + 1] + get(8) * m[a + 2] + get(12) * m[a + 3];
			newm[a + 1] = get(1) * m[a] + get(5) * m[a + 1] + get(9) * m[a + 2] + get(13) * m[a + 3];
			newm[a + 2] = get(2) * m[a] + get(6) * m[a + 1] + get(10) * m[a + 2] + get(14) * m[a + 3];
			newm[a + 3] = get(3) * m[a] + get(7) * m[a + 1] + get(11) * m[a + 2] + get(15) * m[a + 3];
		}
		
		set(newm);
		
		return this;
	}
	
	public Matrix4 mult(Matrix4 m) {
		return mult(m.matrix);
	}
	
	public Vector4 mult(Vector4 vec) {
		Vector4 v = new Vector4();
		
		v.x(get(0) * vec.x() + get(4) * vec.y() + get(8) * vec.z() + get(12) * vec.w());
		v.y(get(1) * vec.x() + get(5) * vec.y() + get(9) * vec.z() + get(13) * vec.w());
		v.z(get(2) * vec.x() + get(6) * vec.y() + get(10) * vec.z() + get(14) * vec.w());
		v.w(get(3) * vec.x() + get(7) * vec.y() + get(11) * vec.z() + get(15) * vec.w());
		
		return v;
	}
	
	public Matrix4 transpose() {
		float old = get(1);
		put(1, get(4));
		put(4, old);
		
		old = get(2);
		put(2, get(8));
		put(8, old);
		
		old = get(3);
		put(3, get(12));
		put(12, old);
		
		old = get(7);
		put(7, get(13));
		put(13, old);
		
		old = get(11);
		put(11, get(14));
		put(14, old);
		
		old = get(6);
		put(6, get(9));
		put(9, old);
		
		return this;
	}
	
	public Matrix4 translate(float x, float y, float z) {
		float[] m = new float[matrix.length];
		
		m[0] = 1;
		m[5] = 1;
		m[10] = 1;
		m[15] = 1;
		
		m[12] = x;
		m[13] = y;
		m[14] = z;
		
		return mult(m);
	}
	
	public Matrix4 translate(Vector3 vec) {
		return translate(vec.x(), vec.y(), vec.z());
	}
	
	public Matrix4 scale(float f) {
		return scale(f, f, f);
	}
	
	public Matrix4 scale(float x, float y, float z) {
		float[] m = new float[matrix.length];
		
		m[0] = x;
		m[5] = y;
		m[10] = z;
		m[15] = 1;
		
		return mult(m);
	}
	
	public Matrix4 scale(Vector3 vec) {
		return scale(vec.x(), vec.y(), vec.z());
	}
	
	public Matrix4 rotate(float angle, float x, float y, float z) {
		float cos = (float)Math.cos(angle);
		float sin = (float)Math.sin(angle);
		float oneMinusCos = 1 - cos;
		
		Vector3 v = new Vector3(x, y, z).normalize();
		
		float[] m = new float[matrix.length];
		m[0] = v.x() * v.x() + (1 - v.x() * v.x()) * cos;
		m[4] = v.x() * v.y() * oneMinusCos - v.z() * sin;
		m[8] = v.x() * v.z() * oneMinusCos + v.y() * sin;
		
		m[1] = v.y() * v.x() * oneMinusCos + v.z() * sin;
		m[5] = v.y() * v.y() + (1 - v.y() * v.y()) * cos;
		m[9] = v.y() * v.z() * oneMinusCos - v.x() * sin;
		
		m[2] = v.z() * v.x() * oneMinusCos - v.y() * sin;
		m[6] = v.z() * v.y() * oneMinusCos + v.x() * sin;
		m[10] = v.z() * v.z() + (1 - v.z() * v.z()) * cos;
		
		m[15] = 1;
		
		return mult(m);
	}
	
	public Matrix4 rotate(float angle, Vector3 vec) {
		return rotate(angle, vec.x(), vec.y(), vec.z());
	}
	
	public Matrix4 rotateDeg(float angle, float x, float y, float z) {
		return rotate((float)Math.toRadians(angle), x, y, z);
	}
	
	public Matrix4 rotateDeg(float angle, Vector3 vec) {
		return rotate((float)Math.toRadians(angle), vec);
	}
	
	public float determinant() {
		float a = get(5) * get(10) * get(15) + get(9) * get(14) * get(7) + get(13) * get(6) * get(11) - get(7) * get(10) * get(13) - get(11) * get(14) * get(5) - get(15) * get(6) * get(9);
		float b = get(1) * get(10) * get(15) + get(9) * get(14) * get(3) + get(13) * get(2) * get(11) - get(3) * get(10) * get(13) - get(11) * get(14) * get(1) - get(15) * get(2) * get(9);
		float c = get(1) * get(6) * get(15) + get(5) * get(14) * get(3) + get(13) * get(2) * get(7) - get(3) * get(6) * get(13) - get(7) * get(14) * get(1) - get(15) * get(2) * get(5);
		float d = get(1) * get(6) * get(11) + get(5) * get(10) * get(3) + get(9) * get(2) * get(7) - get(3) * get(6) * get(9) - get(7) * get(10) * get(1) - get(11) * get(2) * get(5);
		
		return get(0) * a - get(4) * b + get(8) * c - get(12) * d;
	}
	
	public Matrix4 inverse() {
		Matrix4 inv = new Matrix4();
		
		inv.put(0, +(get(5) * get(10) * get(15) + get(9) * get(14) * get(7) + get(13) * get(6) * get(11) - get(7) * get(10) * get(13) - get(11) * get(14) * get(5) - get(15) * get(6) * get(9)));
		inv.put(1, -(get(4) * get(10) * get(15) + get(8) * get(14) * get(7) + get(12) * get(6) * get(11) - get(7) * get(10) * get(12) - get(11) * get(14) * get(4) - get(15) * get(6) * get(8)));
		inv.put(2, +(get(4) * get(9) * get(15) + get(8) * get(13) * get(7) + get(12) * get(5) * get(11) - get(7) * get(9) * get(12) - get(11) * get(13) * get(4) - get(15) * get(5) * get(8)));
		inv.put(3, -(get(4) * get(9) * get(14) + get(8) * get(13) * get(6) + get(12) * get(5) * get(10) - get(6) * get(9) * get(12) - get(10) * get(13) * get(4) - get(14) * get(5) * get(8)));
		
		inv.put(4, -(get(1) * get(10) * get(15) + get(9) * get(14) * get(3) + get(13) * get(2) * get(11) - get(3) * get(10) * get(13) - get(11) * get(14) * get(1) - get(15) * get(2) * get(9)));
		inv.put(5, +(get(0) * get(10) * get(15) + get(8) * get(14) * get(3) + get(12) * get(2) * get(11) - get(3) * get(10) * get(12) - get(11) * get(14) * get(0) - get(15) * get(2) * get(8)));
		inv.put(6, -(get(0) * get(9) * get(15) + get(8) * get(13) * get(3) + get(12) * get(1) * get(11) - get(3) * get(9) * get(12) - get(11) * get(13) * get(0) - get(15) * get(1) * get(8)));
		inv.put(7, +(get(0) * get(9) * get(14) + get(8) * get(13) * get(2) + get(12) * get(1) * get(10) - get(2) * get(9) * get(12) - get(10) * get(13) * get(0) - get(14) * get(1) * get(8)));
		
		inv.put(8, +(get(1) * get(6) * get(15) + get(5) * get(14) * get(3) + get(13) * get(2) * get(7) - get(3) * get(6) * get(13) - get(7) * get(14) * get(1) - get(15) * get(2) * get(5)));
		inv.put(9, -(get(0) * get(6) * get(15) + get(4) * get(14) * get(3) + get(12) * get(2) * get(7) - get(3) * get(6) * get(12) - get(7) * get(14) * get(0) - get(15) * get(2) * get(4)));
		inv.put(10, +(get(0) * get(5) * get(15) + get(4) * get(13) * get(3) + get(12) * get(1) * get(7) - get(3) * get(5) * get(12) - get(7) * get(13) * get(0) - get(15) * get(1) * get(4)));
		inv.put(11, -(get(0) * get(5) * get(14) + get(4) * get(13) * get(2) + get(12) * get(1) * get(6) - get(2) * get(5) * get(12) - get(6) * get(13) * get(0) - get(14) * get(1) * get(4)));
		
		inv.put(12, -(get(1) * get(6) * get(11) + get(5) * get(10) * get(3) + get(9) * get(2) * get(7) - get(3) * get(6) * get(9) - get(7) * get(10) * get(1) - get(11) * get(2) * get(5)));
		inv.put(13, +(get(0) * get(6) * get(11) + get(4) * get(10) * get(3) + get(8) * get(2) * get(7) - get(3) * get(6) * get(8) - get(7) * get(10) * get(0) - get(11) * get(2) * get(4)));
		inv.put(14, -(get(0) * get(5) * get(11) + get(4) * get(9) * get(3) + get(8) * get(1) * get(7) - get(3) * get(5) * get(8) - get(7) * get(9) * get(0) - get(11) * get(1) * get(4)));
		inv.put(15, +(get(0) * get(5) * get(10) + get(4) * get(9) * get(2) + get(8) * get(1) * get(6) - get(2) * get(5) * get(8) - get(6) * get(9) * get(0) - get(10) * get(1) * get(4)));
		
		return set(inv.transpose().mult(1 / determinant()));
	}
	
	public Quaternion toQuaternion() {
		float x = get(0) - get(5) - get(10);
		float y = get(5) - get(0) - get(10);
		float z = get(10) - get(0) - get(5);
		float w = get(0) + get(5) + get(10);
		
		int biggestIndex = 0;
		float biggest = w;
		
		if(x > biggest) {
			biggest = x;
			biggestIndex = 1;
		}
		
		if(y > biggest) {
			biggest = y;
			biggestIndex = 2;
		}
		
		if(z > biggest) {
			biggest = z;
			biggestIndex = 3;
		}
		
		float biggestVal = (float)(Math.sqrt(biggest + 1) * 0.5);
		float mult = 0.25f / biggestVal;
		
		Quaternion res = new Quaternion();
		
		switch(biggestIndex) {
			case 0:
				res.w(biggestVal);
				res.x((get(6) - get(9)) * mult);
				res.y((get(8) - get(2)) * mult);
				res.z((get(1) - get(4)) * mult);
				break;
			case 1:
				res.w((get(6) - get(9)) * mult);
				res.x(biggestVal);
				res.y((get(1) + get(4)) * mult);
				res.z((get(8) + get(2)) * mult);
				break;
			case 2:
				res.w((get(8) - get(2)) * mult);
				res.x((get(1) + get(4)) * mult);
				res.y(biggestVal);
				res.z((get(6) + get(9)) * mult);
				break;
			case 3:
				res.w((get(1) - get(4)) * mult);
				res.x((get(8) + get(2)) * mult);
				res.y((get(6) + get(9)) * mult);
				res.z(biggestVal);
				break;
		}
		
		return res;
	}
	
	private final static FloatBuffer direct = BufferUtils.createFloatBuffer(16);
	
	public FloatBuffer toBuffer() {
		direct.clear();
		direct.put(matrix);
		direct.flip();
		return direct;
	}
}
 public class MatrixStack {
	private Stack<Matrix4> stack;
	private Matrix4 current;
	
	public MatrixStack() {
		current = new Matrix4().clearToIdentity();
		stack = new Stack<>();
	}
	
	public Matrix4 getTop() {
		return current;
	}
	
	public void setTop(Matrix4 m) {
		current = m;
	}
	
	public void pushMatrix() {
		stack.push(current);
		current = new Matrix4(current);
	}
	
	public void popMatrix() {
		current = stack.pop();
	}
}
 public class Quaternion {
	private float x, y, z, w;
	
	public Quaternion() {
		reset();
	}
	
	public Quaternion(float x, float y, float z, float w) {
		set(x, y, z, w);
	}
	
	public Quaternion(float angle, Vector3 vec) {
		float s = (float)Math.sin(angle / 2);
		
		x = vec.x() * s;
		y = vec.y() * s;
		z = vec.z() * s;
		w = (float)Math.cos(angle / 2);
	}
	
	public Quaternion(Quaternion q) {
		set(q);
	}
	
	public Quaternion copy() {
		return new Quaternion(this);
	}
	
	public float x() {
		return x;
	}
	
	public Quaternion x(float x) {
		this.x = x;
		return this;
	}
	
	public float y() {
		return y;
	}
	
	public Quaternion y(float y) {
		this.y = y;
		return this;
	}
	
	public float z() {
		return z;
	}
	
	public Quaternion z(float z) {
		this.z = z;
		return this;
	}
	
	public float w() {
		return w;
	}
	
	public Quaternion w(float w) {
		this.w = w;
		return this;
	}
	
	public Quaternion set(float x, float y, float z, float w) {
		this.x = x;
		this.y = y;
		this.z = z;
		this.w = w;
		return this;
	}
	
	public Quaternion set(Quaternion q) {
		return set(q.x, q.y, q.z, q.w);
	}
	
	public Quaternion reset() {
		x = y = z = 0;
		w = 1;
		return this;
	}
	
	public float length() {
		return (float)Math.sqrt(x * x + y * y + z * z + w * w);
	}
	
	public Quaternion normalize() {
		float length = length();
		x /= length;
		y /= length;
		z /= length;
		w /= length;
		return this;
	}
	
	public float dot(Quaternion q) {
		return x * q.x + y * q.y + z * q.z + w * q.w;
	}
	
	public Quaternion add(float x, float y, float z, float w) {
		this.x += x;
		this.y += y;
		this.z += z;
		this.w += w;
		
		return this;
	}
	
	public Quaternion add(Quaternion q) {
		return add(q.x, q.y, q.z, q.w);
	}
	
	public Quaternion sub(float x, float y, float z, float w) {
		this.x -= x;
		this.y -= y;
		this.z -= z;
		this.w -= w;
		
		return this;
	}
	
	public Quaternion sub(Quaternion q) {
		return sub(q.x, q.y, q.z, q.w);
	}
	
	public Quaternion mult(float f) {
		this.x *= f;
		this.y *= f;
		this.z *= f;
		this.w *= f;
		
		return this;
	}
	
	public Vector3 mult(Vector3 v) {
		Vector3 uv, uuv;
		Vector3 quatVector = new Vector3(x, y, z);
		
		uv = quatVector.cross(v);
		uuv = quatVector.cross(uv);
		
		uv.mult(w * 2);
		uuv.mult(2);
		
		return new Vector3(v).add(uv).add(uuv);
	}
	
	public Quaternion mult(Quaternion q) {
		float xx = w * q.x + x * q.w + y * q.z - z * q.y;
		float yy = w * q.y + y * q.w + z * q.x - x * q.z;
		float zz = w * q.z + z * q.w + x * q.y - y * q.x;
		float ww = w * q.w - x * q.x - y * q.y - z * q.z;
		
		x = xx;
		y = yy;
		z = zz;
		w = ww;
		
		return this;
	}
	
	public Quaternion conjugate() {
		x *= -1;
		y *= -1;
		z *= -1;
		
		return this;
	}
	
	public Matrix4 toMatrix() {
		float[] m = {
				1 - 2 * y * y - 2 * z * z, 2 * x * y + 2 * w * z, 2 * x * z - 2 * w * y, 0,
				2 * x * y - 2 * w * z, 1 - 2 * x * x - 2 * z * z, 2 * y * z + 2 * w * x, 0,
				2 * x * z + 2 * w * y, 2 * y * z - 2 * w * x, 1 - 2 * x * x - 2 * y * y, 0,
				0, 0, 0, 1,
		};
		
		return new Matrix4(m);
	}
}
 public class Vector3 implements Vector<Vector3> {
	private float x, y, z;
	
	public Vector3() {
		set(0, 0, 0);
	}
	
	public Vector3(float v) {
		this(v, v, v);
	}
	
	public Vector3(float x, float y, float z) {
		set(x, y, z);
	}
	
	public Vector3(Vector2 vec) {
		set(vec);
	}
	
	public Vector3(Vector2 vec, float z) {
		set(vec, z);
	}
	
	public Vector3(Vector3 vec) {
		set(vec);
	}
	
	public Vector3(Vector4 vec) {
		set(vec);
	}
	
	@Override
	public Vector3 copy() {
		return new Vector3(this);
	}
	
	public float x() {
		return x;
	}
	
	public Vector3 x(float x) {
		this.x = x;
		return this;
	}
	
	public float y() {
		return y;
	}
	
	public Vector3 y(float y) {
		this.y = y;
		return this;
	}
	
	public float z() {
		return z;
	}
	
	public Vector3 z(float z) {
		this.z = z;
		return this;
	}
	
	@Override
	public boolean equals(Object o) {
		if(o instanceof Vector3) {
			Vector3 v = (Vector3)o;
			return x == v.x && y == v.y && z == v.z;
		}
		
		return false;
	}
	
	public Vector3 set(float x, float y, float z) {
		this.x = x;
		this.y = y;
		this.z = z;
		return this;
	}
	
	public Vector3 set(Vector2 vec) {
		return set(vec.x(), vec.y(), 0);
	}
	
	public Vector3 set(Vector2 vec, float z) {
		return set(vec.x(), vec.y(), z);
	}
	
	public Vector3 set(Vector3 vec) {
		return set(vec.x, vec.y, vec.z);
	}
	
	public Vector3 set(Vector4 vec) {
		return set(vec.x(), vec.y(), vec.z());
	}
	
	public Vector3 reset() {
		x = y = z = 0;
		return this;
	}
	
	@Override
	public float length() {
		return (float)Math.sqrt(x * x + y * y + z * z);
	}
	
	public Vector3 normalize() {
		float length = length();
		x /= length;
		y /= length;
		z /= length;
		return this;
	}
	
	public float dot(Vector3 vec) {
		return x * vec.x + y * vec.y + z * vec.z;
	}
	
	public Vector3 cross(Vector3 vec) {
		return new Vector3(y * vec.z - vec.y * z, z * vec.x - vec.z * x, x * vec.y - vec.x * y);
	}
	
	public Vector3 add(float x, float y, float z) {
		this.x += x;
		this.y += y;
		this.z += z;
		return this;
	}
	
	@Override
	public Vector3 add(Vector3 vec) {
		return add(vec.x, vec.y, vec.z);
	}
	
	public Vector3 sub(float x, float y, float z) {
		this.x -= x;
		this.y -= y;
		this.z -= z;
		return this;
	}
	
	@Override
	public Vector3 sub(Vector3 vec) {
		return sub(vec.x, vec.y, vec.z);
	}
	
	@Override
	public Vector3 mult(float f) {
		return mult(f, f, f);
	}
	
	public Vector3 mult(float x, float y, float z) {
		this.x *= x;
		this.y *= y;
		this.z *= z;
		return this;
	}
	
	@Override
	public Vector3 mult(Vector3 vec) {
		return mult(vec.x, vec.y, vec.z);
	}
	
	@Override
	public Vector3 divide(float f) {
		return divide(f, f, f);
	}
	
	public Vector3 divide(float x, float y, float z) {
		this.x /= x;
		this.y /= y;
		this.z /= z;
		return this;
	}
	
	@Override
	public Vector3 divide(Vector3 vec) {
		return divide(vec.x, vec.y, vec.z);
	}
	
	private final static FloatBuffer direct = BufferUtils.createFloatBuffer(3);
	
	@Override
	public FloatBuffer toBuffer() {
		direct.clear();
		direct.put(x).put(y).put(z);
		direct.flip();
		return direct;
	}
}
 public class Vector4 implements Vector<Vector4> {
	private float x, y, z, w;
	
	public Vector4() {
		set(0, 0, 0, 0);
	}
	
	public Vector4(float v) {
		this(v, v, v, v);
	}
	
	public Vector4(float x, float y, float z, float w) {
		set(x, y, z, w);
	}
	
	public Vector4(Vector2 vec) {
		set(vec);
	}
	
	public Vector4(Vector2 vec, float z, float w) {
		set(vec, z, w);
	}
	
	public Vector4(Vector3 vec) {
		set(vec);
	}
	
	public Vector4(Vector3 vec, float w) {
		set(vec, w);
	}
	
	public Vector4(Vector4 vec) {
		set(vec);
	}
	
	@Override
	public Vector4 copy() {
		return new Vector4(this);
	}
	
	public float x() {
		return x;
	}
	
	public Vector4 x(float x) {
		this.x = x;
		return this;
	}
	
	public float y() {
		return y;
	}
	
	public Vector4 y(float y) {
		this.y = y;
		return this;
	}
	
	public float z() {
		return z;
	}
	
	public Vector4 z(float z) {
		this.z = z;
		return this;
	}
	
	public float w() {
		return w;
	}
	
	public Vector4 w(float w) {
		this.w = w;
		return this;
	}
	
	@Override
	public boolean equals(Object o) {
		if(o instanceof Vector4) {
			Vector4 v = (Vector4)o;
			return x == v.x && y == v.y && z == v.z && w == v.w;
		}
		
		return false;
	}
	
	public Vector4 set(float x, float y, float z, float w) {
		this.x = x;
		this.y = y;
		this.z = z;
		this.w = w;
		return this;
	}
	
	public Vector4 set(Vector2 vec) {
		return set(vec, 0, 0);
	}
	
	public Vector4 set(Vector2 vec, float z, float w) {
		return set(vec.x(), vec.y(), z, w);
	}
	
	public Vector4 set(Vector3 vec) {
		return set(vec, 0);
	}
	
	public Vector4 set(Vector3 vec, float w) {
		return set(vec.x(), vec.y(), vec.z(), w);
	}
	
	public Vector4 set(Vector4 vec) {
		return set(vec.x, vec.y, vec.z, vec.w);
	}
	
	public Vector4 reset() {
		x = y = z = w = 0;
		return this;
	}
	
	@Override
	public float length() {
		return (float)Math.sqrt(x * x + y * y + z * z + w * w);
	}
	
	public Vector4 normalize() {
		float length = length();
		x /= length;
		y /= length;
		z /= length;
		w /= length;
		return this;
	}
	
	public float dot(Vector4 vec) {
		return x * vec.x + y * vec.y + z * vec.z + w * vec.w;
	}
	
	public Vector4 add(float x, float y, float z, float w) {
		this.x += x;
		this.y += y;
		this.z += z;
		this.w += w;
		return this;
	}
	
	@Override
	public Vector4 add(Vector4 vec) {
		return add(vec.x, vec.y, vec.z, vec.w);
	}
	
	public Vector4 sub(float x, float y, float z, float w) {
		this.x -= x;
		this.y -= y;
		this.z -= z;
		this.w -= w;
		return this;
	}
	
	@Override
	public Vector4 sub(Vector4 vec) {
		return sub(vec.x, vec.y, vec.z, vec.w);
	}
	
	@Override
	public Vector4 mult(float f) {
		return mult(f, f, f, f);
	}
	
	public Vector4 mult(float x, float y, float z, float w) {
		this.x *= x;
		this.y *= y;
		this.z *= z;
		this.w *= w;
		return this;
	}
	
	@Override
	public Vector4 mult(Vector4 vec) {
		return mult(vec.x, vec.y, vec.z, vec.w);
	}
	
	@Override
	public Vector4 divide(float f) {
		return divide(f, f, f, f);
	}
	
	public Vector4 divide(float x, float y, float z, float w) {
		this.x /= x;
		this.y /= y;
		this.z /= z;
		this.w /= w;
		return this;
	}
	
	@Override
	public Vector4 divide(Vector4 vec) {
		return divide(vec.x, vec.y, vec.z, vec.w);
	}
	
	private final static FloatBuffer direct = BufferUtils.createFloatBuffer(4);
	
	@Override
	public FloatBuffer toBuffer() {
		direct.clear();
		direct.put(x).put(y).put(z).put(w);
		direct.flip();
		return direct;
	}
}

import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL12.*;
import static org.lwjgl.opengl.GL13.*;
import static org.lwjgl.opengl.GL15.*;
import static org.lwjgl.opengl.GL20.*;
import static org.lwjgl.opengl.GL30.*;
import static org.lwjgl.opengl.GL31.*;
import static org.lwjgl.opengl.GL32.*;
import static org.lwjgl.opengl.GL33.*;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import org.lwjgl.BufferUtils;
import org.lwjgl.input.Keyboard;
import rosick.jglsdk.glimg.DdsLoader;
import rosick.jglsdk.glimg.ImageSet;
import rosick.jglsdk.glimg.ImageSet.Dimensions;
import rosick.jglsdk.glimg.ImageSet.SingleImage;
import com.ra4king.opengl.GLProgram;
import com.ra4king.opengl.util.Mesh;
import com.ra4king.opengl.util.MousePoles.MouseButton;
import com.ra4king.opengl.util.MousePoles.ObjectData;
import com.ra4king.opengl.util.MousePoles.ObjectPole;
import com.ra4king.opengl.util.MousePoles.ViewData;
import com.ra4king.opengl.util.MousePoles.ViewPole;
import com.ra4king.opengl.util.MousePoles.ViewScale;
import com.ra4king.opengl.util.ShaderProgram;
import com.ra4king.opengl.util.Timer;
import com.ra4king.opengl.util.Timer.Type;
import com.ra4king.opengl.util.UniformBlockArray;
import com.ra4king.opengl.util.UniformBlockArray.UniformBlockObject;
import com.ra4king.opengl.util.Utils;
import com.ra4king.opengl.util.math.Matrix3;
import com.ra4king.opengl.util.math.Matrix4;
import com.ra4king.opengl.util.math.MatrixStack;
import com.ra4king.opengl.util.math.Quaternion;
import com.ra4king.opengl.util.math.Vector3;
import com.ra4king.opengl.util.math.Vector4;

package com.ra4king.opengl.arcsynthesis.gl33.chapter14.example3;






public class Example14_3 extends GLProgram {
	public static void main(String[] args) {
		new Example14_3().run(true);
	}
	
	private ProgramData[] programs = new ProgramData[ShaderMode.values().length];
	private UnlitProgramData unlit;
	
	private ShaderPair[] shaderPairs = {
			new ShaderPair("PN.vert", "FixedShininess.frag"),
			new ShaderPair("PNT.vert", "TextureShininess.frag"),
			new ShaderPair("PNT.vert", "TextureCompute.frag")
	};
	
	private Mesh objectMesh;
	private Mesh cubeMesh;
	private Mesh planeMesh;
	
	private ViewPole viewPole;
	private ObjectPole objectPole;
	
	private ShaderMode mode = ShaderMode.MODE_FIXED;
	
	private Timer lightTimer = new Timer(Type.LOOP, 6);
	
	private int materialUniformBuffer;
	private int lightUniformBuffer;
	private int projectionUniformBuffer;
	
	private int materialOffset;
	
	private final int NUM_GAUSS_TEXTURES = 4;
	private int[] gaussTextures = new int[NUM_GAUSS_TEXTURES];
	
	private int textureSampler;
	
	private int shineTexture;
	
	private int currentTexture = NUM_GAUSS_TEXTURES - 1;
	private int currentMaterial;
	
	private boolean drawCameraPos, drawLights = true, useInfinity = true;
	
	private final int materialBlockIndex = 0;
	private final int lightBlockIndex = 1;
	private final int projectionBlockIndex = 2;
	
	private final int gaussTexUnit = 0;
	private final int shineTexUnit = 1;
	
	private final float lightAttenuation = 1f / (25f * 25f);
	
	private static final int NUMBER_OF_MATERIALS = 2;
	private static final int NUMBER_OF_LIGHTS = 2;
	
	public Example14_3() {
		super("Example 14.3 - Material Texture", 500, 500, true);
	}
	
	@Override
	public void init() {
		glClearColor(0.75f, 0.75f, 1, 1);
		glClearDepth(1);
		
		ObjectData objectData = new ObjectData(new Vector3(0, 0.5f, 0), new Quaternion());
		ViewData viewData = new ViewData(objectData.position, new Quaternion(0.3826834f, 0, 0, 0.92387953f), 10, 0);
		ViewScale viewScale = new ViewScale(1.5f, 70, 1.5f, 0.5f, 0, 0, 90 / 250f);
		
		viewPole = new ViewPole(viewData, viewScale, MouseButton.LEFT_BUTTON);
		objectPole = new ObjectPole(objectData, 90 / 250f, MouseButton.RIGHT_BUTTON, viewPole);
		
		for(int a = 0; a < shaderPairs.length; a++)
			programs[a] = loadStandardProgram("example14.3." + shaderPairs[a].vertexShader, "example14.3." + shaderPairs[a].fragmentShader);
		
		unlit = loadUnlitProgram("example14.3.Unlit.vert", "example14.3.Unlit.frag");
		
		try {
			objectMesh = new Mesh(getClass().getResource("example14.3.Infinity.xml"));
			cubeMesh = new Mesh(getClass().getResource("example14.3.UnitCube.xml"));
			planeMesh = new Mesh(getClass().getResource("example14.3.UnitPlane.xml"));
		} catch(Exception exc) {
			exc.printStackTrace();
			destroy();
		}
		
		glEnable(GL_CULL_FACE);
		glCullFace(GL_BACK);
		glFrontFace(GL_CW);
		
		glEnable(GL_DEPTH_TEST);
		glDepthMask(true);
		glDepthFunc(GL_LEQUAL);
		glDepthRange(0, 1);
		glEnable(GL_DEPTH_CLAMP);
		
		setupMaterials();
		
		lightUniformBuffer = glGenBuffers();
		glBindBuffer(GL_UNIFORM_BUFFER, lightUniformBuffer);
		glBufferData(GL_UNIFORM_BUFFER, LightBlock.SIZE, GL_DYNAMIC_DRAW);
		
		projectionUniformBuffer = glGenBuffers();
		glBindBuffer(GL_UNIFORM_BUFFER, projectionUniformBuffer);
		glBufferData(GL_UNIFORM_BUFFER, 16 * 4, GL_DYNAMIC_DRAW);
		glBindBuffer(GL_UNIFORM_BUFFER, 0);
		
		glBindBufferRange(GL_UNIFORM_BUFFER, lightBlockIndex, lightUniformBuffer, 0, LightBlock.SIZE);
		glBindBufferRange(GL_UNIFORM_BUFFER, projectionBlockIndex, projectionUniformBuffer, 0, 16 * 4);
		glBindBufferRange(GL_UNIFORM_BUFFER, materialBlockIndex, materialUniformBuffer, 0, MaterialBlock.SIZE);
		
		createGaussianTextures();
		createShininessTexture();
	}
	
	private ProgramData loadStandardProgram(String vertexShader, String fragmentShader) {
		ProgramData data = new ProgramData(new ShaderProgram(readFromFile(vertexShader), readFromFile(fragmentShader)));
		data.modelToCameraMatrixUniform = data.program.getUniformLocation("modelToCameraMatrix");
		data.normalModelToCameraMatrixUniform = data.program.getUniformLocation("normalModelToCameraMatrix");
		
		int materialBlock = data.program.getUniformBlockIndex("Material");
		int lightBlock = data.program.getUniformBlockIndex("Light");
		int projectionBlock = data.program.getUniformBlockIndex("Projection");
		
		glUniformBlockBinding(data.program.getProgram(), materialBlock, materialBlockIndex);
		glUniformBlockBinding(data.program.getProgram(), lightBlock, lightBlockIndex);
		glUniformBlockBinding(data.program.getProgram(), projectionBlock, projectionBlockIndex);
		
		int gaussianTextureUniform = data.program.getUniformLocation("gaussianTexture");
		int shininessTextureUniform = data.program.getUniformLocation("shininessTexture");
		
		data.program.begin();
		glUniform1i(gaussianTextureUniform, gaussTexUnit);
		glUniform1i(shininessTextureUniform, shineTexUnit);
		data.program.end();
		
		return data;
	}
	
	private UnlitProgramData loadUnlitProgram(String vertexShader, String fragmentShader) {
		UnlitProgramData data = new UnlitProgramData(new ShaderProgram(readFromFile(vertexShader), readFromFile(fragmentShader)));
		data.modelToCameraMatrixUniform = data.program.getUniformLocation("modelToCameraMatrix");
		data.objectColorUniform = data.program.getUniformLocation("objectColor");
		
		int projectionBlock = data.program.getUniformBlockIndex("Projection");
		glUniformBlockBinding(data.program.getProgram(), projectionBlock, projectionBlockIndex);
		
		return data;
	}
	
	private void setupMaterials() {
		UniformBlockArray<MaterialBlock> materials = new UniformBlockArray<>(MaterialBlock.SIZE, NUMBER_OF_MATERIALS);
		

		materials.setBlockMember(0, new MaterialBlock(new Vector4(1, 0.673f, 0.043f, 1), new Vector4(1, 0.673f, 0.043f, 1).mult(0.4f), 0.125f));
