code: /*
   * Copyright (c) 2021 Callum Newlands
   *
   *     This program is free software: you can redistribute it and/or modify
   *     it under the terms of the GNU General Public License as published by
   *     the Free Software Foundation, either version 3 of the License, or
   *     (at your option) any later version.
   *
   *     The additional term of 7.b applies: Requiring preservation of specified
   *     reasonable legal notices or author attributions in that material or in the
   *     Appropriate Legal Notices displayed by works containing it
   *
   *     This program is distributed in the hope that it will be useful,
   *     but WITHOUT ANY WARRANTY; without even the implied warranty of
   *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   *     GNU General Public License for more details.
   *
   *     You should have received a copy of the GNU General Public License
   *     along with this program.  If not, see <https://www.gnu.org/licenses/>.
   */
  
  package modeldata.meshdata;
  
  import java.io.File;
  import java.io.FileInputStream;
  import java.io.IOException;
  import java.nio.ByteBuffer;
  import java.nio.FloatBuffer;
  import java.nio.IntBuffer;
  import java.nio.channels.FileChannel;
  
  import static org.lwjgl.opengl.GL11.GL_RED;
  import static org.lwjgl.opengl.GL11.GL_RGBA;
  import static org.lwjgl.opengl.GL11.glGenTextures;
  import static org.lwjgl.opengl.GL11C.GL_RGB;
  import static org.lwjgl.opengl.GL13C.GL_TEXTURE0;
  import static org.lwjgl.stb.STBImage.stbi_failure_reason;
  import static org.lwjgl.stb.STBImage.stbi_load_from_memory;
  import static org.lwjgl.stb.STBImage.stbi_loadf_from_memory;
  import static org.lwjgl.stb.STBImage.stbi_set_flip_vertically_on_load;
  import static org.lwjgl.system.MemoryStack.stackPush;
  
  import lombok.AllArgsConstructor;
  import org.joml.Vector3f;
  import org.lwjgl.system.MemoryStack;
  import params.ParameterLoader;
  import params.Parameters;
  
  public abstract class Texture {
  
  	static final Parameters parameters = ParameterLoader.getParameters();
  
  	static final int RGBA_NO_OF_COMPONENTS = 4;
  	static final int RGB_NO_OF_COMPONENTS = 3;
  	static final int SINGLE_COMPONENT = 1;
  
  	protected final int handle;
  	protected final int unit;
  	private final Vector3f colour;
  
  	public Texture(final Vector3f colour, final int textureUnit, final int textureWrap) {
  		this.colour = colour;
  		this.handle = glGenTextures();
  		this.unit = GL_TEXTURE0 + textureUnit;
  		this.bind();
  		this.setHints(textureWrap);
  	}
  
  	protected static ByteBuffer readFileToByteBuffer(final String path) throws IOException {
  		ByteBuffer buffer;
  
  		File file = new File(parameters.resourcesRoot + path);
  		if (file.exists() && file.isFile()) {
  			FileInputStream fis = new FileInputStream(file);
  			FileChannel fc = fis.getChannel();
  			buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size());
  			fc.close();
  			fis.close();
  		} else {
  			throw new IOException("Resource: " + file.getAbsolutePath() + " is not a file.");
  		}
  		return buffer;
  	}
  
  	protected abstract void setHints(int textureWrap);
  
  	protected LoadedByteImage loadByteTextureFromFile(final String path) throws IOException {
  		return loadByteTextureFromFile(path, true);
  	}
  
  	protected LoadedByteImage loadByteTextureFromFile(final String path, final boolean flip) throws IOException {
  		ByteBuffer rawImageData;
  		ByteBuffer image;
  
  		rawImageData = readFileToByteBuffer(path);
  
  		int width;
  		int height;
  		int numberOfComponents;
  		try (MemoryStack stack = stackPush()) {
  			IntBuffer widthBuffer = stack.mallocInt(1);
  			IntBuffer heightBuffer = stack.mallocInt(1);
  			IntBuffer numberOfComponentsBuffer = stack.mallocInt(1);
  
  			stbi_set_flip_vertically_on_load(flip);
  
  			image = stbi_load_from_memory(rawImageData, widthBuffer, heightBuffer,
  					numberOfComponentsBuffer, 0);
  			if (image == null) {
  				throw new RuntimeException("Failed to load image: " + stbi_failure_reason());
  			}
  
  			width = widthBuffer.get(0);
  			height = heightBuffer.get(0);
  			numberOfComponents = numberOfComponentsBuffer.get(0);
  		}
  
  		int format;
  		if (numberOfComponents == RGBA_NO_OF_COMPONENTS) {
  			format = GL_RGBA;
  		} else if (numberOfComponents == RGB_NO_OF_COMPONENTS) {
  			format = GL_RGB;
  		} else if (numberOfComponents == SINGLE_COMPONENT) {
  			format = GL_RED;
  		} else {
  			throw new IllegalArgumentException("Unsupported number of colour channels in Texture Class: "
  					+ numberOfComponents);
  		}
  
  		return new LoadedByteImage(image, format, width, height);
  	}
  
  	protected LoadedFloatImage loadFloatTextureFromFile(final String path) throws IOException {
  		return loadFloatTextureFromFile(path, true);
  	}
  
  	protected LoadedFloatImage loadFloatTextureFromFile(final String path, final boolean flip) throws IOException {
  		ByteBuffer rawImageData;
  		FloatBuffer image;
  
  		rawImageData = readFileToByteBuffer(path);
  
  		int width;
  		int height;
  		int numberOfComponents;
  		try (MemoryStack stack = stackPush()) {
  			IntBuffer widthBuffer = stack.mallocInt(1);
  			IntBuffer heightBuffer = stack.mallocInt(1);
  			IntBuffer numberOfComponentsBuffer = stack.mallocInt(1);
  
  			stbi_set_flip_vertically_on_load(flip);
  
  			image = stbi_loadf_from_memory(rawImageData, widthBuffer, heightBuffer, numberOfComponentsBuffer, 0);
  			if (image == null) {
  				throw new RuntimeException("Failed to load image: " + stbi_failure_reason());
  			}
  
  			width = widthBuffer.get(0);
  			height = heightBuffer.get(0);
  			numberOfComponents = numberOfComponentsBuffer.get(0);
  		}
  
  		int format;
  		if (numberOfComponents == RGBA_NO_OF_COMPONENTS) {
  			format = GL_RGBA;
  		} else if (numberOfComponents == RGB_NO_OF_COMPONENTS) {
  			format = GL_RGB;
  		} else if (numberOfComponents == SINGLE_COMPONENT) {
  			format = GL_RED;
  		} else {
  			throw new IllegalArgumentException("Unsupported number of colour channels in Texture Class: "
  					+ numberOfComponents);
  		}
  
  		return new LoadedFloatImage(image, format, width, height);
  	}
  
  	abstract public void bind();
  
  	abstract public void unbind();
  
  	public int getUnitID() {
  		return this.unit - GL_TEXTURE0;
  	}
  
  	public Vector3f getColour() {
  		return colour;
  	}
  
  	@AllArgsConstructor
  	static class LoadedImage {
  		final int format;
  		final int width;
  		final int height;
  	}
  
  	static class LoadedByteImage extends LoadedImage {
  		final ByteBuffer imageData;
  
  		public LoadedByteImage(ByteBuffer imageData, int format, int width, int height) {
  			super(format, width, height);
  			this.imageData = imageData;
  		}
  	}
  
  	static class LoadedFloatImage extends LoadedImage {
  		final FloatBuffer imageData;
  
  		public LoadedFloatImage(FloatBuffer imageData, int format, int width, int height) {
  			super(format, width, height);
  			this.imageData = imageData;
  		}
  	}
  }
  