model viewer: add support for texture rendering on terrain

This commit is contained in:
Adam
2017-01-15 13:06:11 -05:00
parent ffa9a820f1
commit 4ed2260da6
5 changed files with 338 additions and 9 deletions

View File

@@ -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;
}
}

View File

@@ -66,6 +66,11 @@
<artifactId>commons-cli</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>junit</groupId>

View File

@@ -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<Integer, Texture> 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)
{

View File

@@ -0,0 +1,125 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* 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;
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* 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;
}
}