diff --git a/cache/src/test/java/net/runelite/cache/TextureDumper.java b/cache/src/test/java/net/runelite/cache/TextureDumper.java index 827d667780..4c12df4694 100644 --- a/cache/src/test/java/net/runelite/cache/TextureDumper.java +++ b/cache/src/test/java/net/runelite/cache/TextureDumper.java @@ -70,7 +70,7 @@ public class TextureDumper TextureLoader loader = new TextureLoader(); TextureDefinition texture = loader.load(file.getFileId(), file.getContents()); - Files.write(gson.toJson(texture), new java.io.File(outDir, file.getFileId() + ".json"), Charset.defaultCharset()); + Files.write(gson.toJson(texture), new java.io.File(outDir, texture.getId() + ".json"), Charset.defaultCharset()); ++count; } } diff --git a/model-viewer/pom.xml b/model-viewer/pom.xml index 2c1d79b1d9..f1b544c5a9 100644 --- a/model-viewer/pom.xml +++ b/model-viewer/pom.xml @@ -66,6 +66,11 @@ commons-cli 1.3.1 + + commons-io + commons-io + 2.5 + junit diff --git a/model-viewer/src/main/java/net/runelite/modelviewer/ModelViewer.java b/model-viewer/src/main/java/net/runelite/modelviewer/ModelViewer.java index 7ad6d0029c..5819c19f1d 100644 --- a/model-viewer/src/main/java/net/runelite/modelviewer/ModelViewer.java +++ b/model-viewer/src/main/java/net/runelite/modelviewer/ModelViewer.java @@ -26,17 +26,23 @@ package net.runelite.modelviewer; import com.google.gson.Gson; import java.awt.Color; +import java.awt.image.BufferedImage; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; +import java.nio.ByteBuffer; import java.nio.file.Files; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import javax.imageio.ImageIO; import net.runelite.cache.definitions.ModelDefinition; import net.runelite.cache.definitions.NpcDefinition; import net.runelite.cache.definitions.OverlayDefinition; +import net.runelite.cache.definitions.TextureDefinition; import net.runelite.cache.definitions.UnderlayDefinition; import net.runelite.cache.definitions.loaders.ModelLoader; import net.runelite.cache.models.Vector3f; @@ -50,14 +56,28 @@ import org.apache.commons.compress.utils.IOUtils; import org.lwjgl.opengl.Display; import org.lwjgl.opengl.DisplayMode; import org.lwjgl.opengl.GL11; +import static org.lwjgl.opengl.GL11.GL_NEAREST; +import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D; +import static org.lwjgl.opengl.GL11.GL_TEXTURE_MAG_FILTER; +import static org.lwjgl.opengl.GL11.GL_TEXTURE_MIN_FILTER; +import static org.lwjgl.opengl.GL11.GL_TEXTURE_WRAP_S; +import static org.lwjgl.opengl.GL11.GL_TEXTURE_WRAP_T; +import static org.lwjgl.opengl.GL11.glTexParameteri; +import static org.lwjgl.opengl.GL12.GL_CLAMP_TO_EDGE; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class ModelViewer { - private static final int NUM_UNDERLAYS = 150; - private static final int NUM_OVERLAYS = 174; + private static final Logger logger = LoggerFactory.getLogger(ModelViewer.class); + + private static int NUM_UNDERLAYS = 150; + private static int NUM_OVERLAYS = 174; + private static int NUM_TEXTURES = 61; private static UnderlayDefinition[] underlays = new UnderlayDefinition[NUM_UNDERLAYS]; private static OverlayDefinition[] overlays = new OverlayDefinition[NUM_OVERLAYS]; + private static Map textures = new HashMap<>(); public static void main(String[] args) throws Exception { @@ -256,8 +276,6 @@ public class ModelViewer return; } - GL11.glBegin(GL11.GL_TRIANGLES); - for (int regionX = 0; regionX < Region.X; ++regionX) { for (int regionY = 0; regionY < Region.Y; ++regionY) @@ -303,6 +321,8 @@ public class ModelViewer int overlayId = region.getOverlayId(0, regionX, regionY); Color color = null; + int glTexture = -1; + if (underlayId > 0) { UnderlayDefinition ud = underlays[underlayId - 1]; @@ -320,26 +340,67 @@ public class ModelViewer if (od.getTexture() > -1) { - // textures? + color = Color.WHITE; + + Texture texture = getTexture(od.getTexture()); + glTexture = texture.getOpenglId(); + assert glTexture > -1; + + GL11.glEnable(GL11.GL_TEXTURE_2D); + GL11.glBindTexture(GL11.GL_TEXTURE_2D, glTexture); } } + GL11.glBegin(GL11.GL_TRIANGLES); + if (color != null) { GL11.glColor3f((float) color.getRed() / 255f, (float) color.getGreen() / 255f, (float) color.getBlue() / 255f); } + // triangle 1 + if (glTexture > -1) + { + GL11.glTexCoord2f(0, 0); + } GL11.glVertex3i(x, z1, -y); + if (glTexture > -1) + { + GL11.glTexCoord2f(1, 0); + } GL11.glVertex3i(x + TILE_SCALE, z2, -y); + if (glTexture > -1) + { + GL11.glTexCoord2f(0, 1); + } GL11.glVertex3i(x, z3, -(y + TILE_SCALE)); + // triangle 2 + if (glTexture > -1) + { + GL11.glTexCoord2f(0, 1); + } GL11.glVertex3i(x, z3, -(y + TILE_SCALE)); + if (glTexture > -1) + { + GL11.glTexCoord2f(1, 0); + } GL11.glVertex3i(x + TILE_SCALE, z2, -y); + if (glTexture > -1) + { + GL11.glTexCoord2f(1, 1); + } GL11.glVertex3i(x + TILE_SCALE, z4, -(y + TILE_SCALE)); + + GL11.glEnd(); + + if (glTexture > -1) + { + GL11.glDisable(GL11.GL_TEXTURE_2D); + } + } } - - GL11.glEnd(); } private static void loadUnderlays() throws IOException @@ -359,7 +420,7 @@ public class ModelViewer private static void loadOverlays() throws IOException { - for (int i = 0; i < NUM_UNDERLAYS; ++i) + for (int i = 0; i < NUM_OVERLAYS; ++i) { try (FileInputStream fin = new FileInputStream("overlays/" + i + ".json")) { @@ -372,6 +433,83 @@ public class ModelViewer } } + private static Texture getTexture(int id) + { + Texture texture = textures.get(id); + if (texture != null) + { + return texture; + } + + TextureDefinition td; + try (FileInputStream fin = new FileInputStream("textures/" + id + ".json")) + { + td = new Gson().fromJson(new InputStreamReader(fin), TextureDefinition.class); + } + catch (IOException ex) + { + logger.warn(null, ex); + return null; + } + + try (FileInputStream fin = new FileInputStream("sprite/" + td.getFileIds()[0] + "-0.png")) + { + BufferedImage image = ImageIO.read(fin); + + int width = image.getWidth(); + int height = image.getHeight(); + int[] rgb = new int[width * height]; + + int[] out = image.getRGB(0, 0, width, height, rgb, 0, width); + assert rgb == out; + + ByteBuffer buffer = ByteBuffer.allocateDirect(rgb.length * 4); + for (int i = 0; i < rgb.length; ++i) + { + int pixel = rgb[i]; + + // argb -> rgba + int a = pixel >>> 24; + int r = (pixel >> 16) & 0xff; + int g = (pixel >> 8) & 0xff; + int b = pixel & 0xff; + + buffer.put((byte) r); + buffer.put((byte) g); + buffer.put((byte) b); + buffer.put((byte) a); + } + buffer.position(0); + + int glTexture = GL11.glGenTextures(); + GL11.glBindTexture(GL11.GL_TEXTURE_2D, glTexture); + + //Setup filtering, i.e. how OpenGL will interpolate the pixels when scaling up or down + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + //Setup wrap mode, i.e. how OpenGL will handle pixels outside of the expected range + //Note: GL_CLAMP_TO_EDGE is part of GL12 + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, width, height, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, buffer); + + GL11.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR); // Linear Filtering + GL11.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR); // Linear Filtering + + texture = new Texture(rgb, width, height, glTexture); + textures.put(id, texture); + + return texture; + } + catch (IOException ex) + { + logger.warn(null, ex); + return null; + } + } + // found these two functions here https://www.rune-server.org/runescape-development/rs2-client/tools/589900-rs2-hsb-color-picker.html public static int RGB_to_RS2HSB(int red, int green, int blue) { diff --git a/model-viewer/src/main/java/net/runelite/modelviewer/ShaderManager.java b/model-viewer/src/main/java/net/runelite/modelviewer/ShaderManager.java new file mode 100644 index 0000000000..971ed18d2e --- /dev/null +++ b/model-viewer/src/main/java/net/runelite/modelviewer/ShaderManager.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.modelviewer; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import org.apache.commons.io.IOUtils; +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL20; +import static org.lwjgl.opengl.GL20.GL_INFO_LOG_LENGTH; +import static org.lwjgl.opengl.GL20.glGetProgrami; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ShaderManager +{ + private static final Logger logger = LoggerFactory.getLogger(ShaderManager.class); + + private static final int ERR_LEN = 1024; + + private int program; + private int vertexShader; + private int fragmentShader; + + public void load(InputStream vertexShaderStream, InputStream fragShaderStream) throws IOException + { + program = GL20.glCreateProgram(); + vertexShader = GL20.glCreateShader(GL20.GL_VERTEX_SHADER); + fragmentShader = GL20.glCreateShader(GL20.GL_FRAGMENT_SHADER); + + String vertexShaderStr = IOUtils.toString(new InputStreamReader(vertexShaderStream)); + String fragShaderStr = IOUtils.toString(new InputStreamReader(fragShaderStream)); + + GL20.glShaderSource(vertexShader, vertexShaderStr); + GL20.glCompileShader(vertexShader); + + if (GL20.glGetShader(vertexShader, GL20.GL_COMPILE_STATUS) == GL11.GL_TRUE) + { + GL20.glAttachShader(program, vertexShader); + } + else + { + String err = GL20.glGetShaderInfoLog(vertexShader, ERR_LEN); + logger.warn("Error compiling vertex shader: {}", err); + } + + GL20.glShaderSource(fragmentShader, fragShaderStr); + GL20.glCompileShader(fragmentShader); + + if (GL20.glGetShader(fragmentShader, GL20.GL_COMPILE_STATUS) == GL11.GL_TRUE) + { + GL20.glAttachShader(program, fragmentShader); + } + else + { + String err = GL20.glGetShaderInfoLog(fragmentShader, ERR_LEN); + logger.warn("Error compiling fragment shader: {}", err); + } + + GL20.glLinkProgram(program); + + if (GL20.glGetProgram(program, GL20.GL_LINK_STATUS) == GL11.GL_FALSE) + { + String err = GL20.glGetProgramInfoLog(program, glGetProgrami(program, GL_INFO_LOG_LENGTH)); + logger.warn("Error linking program: {}", err); + } + + GL20.glValidateProgram(program); + } + + public void destroy() + { + GL20.glDeleteShader(vertexShader); + GL20.glDeleteShader(fragmentShader); + GL20.glDeleteProgram(program); + } + + public void use() + { + GL20.glUseProgram(program); + } + + void unuse() + { + GL20.glUseProgram(0); + } + + public int getProgram() + { + return program; + } + + public int getVertexShader() + { + return vertexShader; + } + + public int getFragmentShader() + { + return fragmentShader; + } +} diff --git a/model-viewer/src/main/java/net/runelite/modelviewer/Texture.java b/model-viewer/src/main/java/net/runelite/modelviewer/Texture.java new file mode 100644 index 0000000000..16e1a45c1f --- /dev/null +++ b/model-viewer/src/main/java/net/runelite/modelviewer/Texture.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2016-2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.modelviewer; + +public class Texture +{ + private final int[] rgb; + private final int width; + private final int height; + private final int openglId; + + public Texture(int[] rgb, int width, int height, int openglId) + { + this.rgb = rgb; + this.width = width; + this.height = height; + this.openglId = openglId; + } + + public int[] getRgb() + { + return rgb; + } + + public int getWidth() + { + return width; + } + + public int getHeight() + { + return height; + } + + public int getOpenglId() + { + return openglId; + } +}