I am working on a map editor for the OSRS cache. It is rendered with OpenGL and based on the GPU Plugin from Runelite. I hope to keep a steady pace on this project and will post the code when it is in a more friendly state. The plan is to make this completely free and open source to help the community build better content and learn together.
MVP Release: https://github.com/tpetrychyn/osrs-map-editor/releases/tag/v0.1.1
Source Code: https://github.com/tpetrychyn/osrs-map-editor
Only tested working with OpenJDK 14!
Progress updates
I have been focusing on my personal life, namely I landed a new job which took a couple months to sort out. Today I finally overcame a huge roadblock that had stumped me numerous times: modifying tiles in realtime. My limited knowledge of OpenGL made it very difficult for me to wrap my head around updating data from CPU->GPU repeatedly. The final solution involves uploading the changed vertexes to the GPU before every frame and pushing VAO changes that tells the GPU to recompute the changed tiles. Essentially this allows me to modify tiles on the GPU with updated data without having to modify the vertex buffer.
The result of this work is the ability to sculpt terrain! Additional work was devoted to having "sticky/contoured" objects stick to the ground when it is modified. It is already quite fun to play around with the editor and build simple scenes.
Video example of terrain sculpting
Unfortunately I don't think this update marks my full return to the project but I will continue to pick away at features when I get free time and motivation.
This sprint, the focus has been on getting a releasable MVP. This meant fixing many bugs and adding screens for users to be able to interact properly with the software. Previously, I would simply change a variable in the code if I wanted to load a different region or cache directory. However that is not possible for end users.
MVP Release
Today I have pushed myself to complete my goal of a release and I have succeeded. On the Github repository is a release jar file that anyone is free to download and try. Given my limited Gradle/Kotlin experience the jar file is large at 60MB, and has software-breaking errors on Java 1.8. I was only able to get it to run properly with OpenJDK 14.
Check it out here: https://github.com/tpetrychyn/osrs-map-editor/releases/tag/0.0.1
Cache Loader
The goal of the cache loader is to allow anyone, with no background in RSPS, to be able to load the map editor and start looking around. This is why the cache loader features a listing of all available cache versions from Runestats, ready for download.
Region Chooser
The region chooser allows a user to type a cache region and the map editor will load that region. The radius input allows you to load multiple regions at once. Unfortunately the performance decreases exponentially as regions increase, as well as newer areas or noisy areas of the game have a noticeable lower performance. However I do not believe a typical workflow will involve loading more than a handful of regions at once.
I have been making slow progress over the past month, just working on the editor when I get a bit of inspiration as to not force myself and burn myself out.
Kotlin Rewrite & Exporting
Since last update I have rewritten the entire application in Kotlin, included Displee's Cache Tool, and split the editor out of OpenOSRS so it is a simpler standalone application. As a result the load time of the application has gone down from 8 seconds to 1 second. Largely because of the increased control over what actually gets loaded from the cache thanks to Displee's Cache Tool.
Progress has also been made on actually saving the map modifications back to the cache meaning I can actually see the edits reflected in the official client.
Piscatoris Hunter Area - demonstrating cleared objects, working rotation, and height levels
Client Edge Cases
Two notable bugs/edge cases I had to handle were rotation & tile height types.
Rotation:
The client has a special case for objects on 45 degree angles wherein their object type actually gets changed (i.e. from type 10: game object to type 11: diagonal game object).
Special tile heights:
The client also handles tile heights in multiple ways, notably tiles can either by "absolute" height or "perlin noise" height. Absolute height is for areas like Otto's Grotto where there are sheer drops and mountains. Less extreme areas like Piscatoris Hunter Area pack the tile heights in a different type instructing the client to apply perlin noise which smooths out the area.
See: https://pastebin.com/5j5Jc8Rx
Modular Editor
Lastly, I always had an early vision that this editor should feel like a professional design tool, drawing inspiration from Photoshop and Blender. I finally stumbled upon a working solution to have movable tool panes and panels. This was a challenge due to the fact that support for JOGL (the Java OpenGL library) and JavaFX to integrate is only recently being worked on. I wanted JOGL to render in a heavyweight frame to maintain it's performance, however that meant it always drew itself to the screen and disregarded JavaFX's layering. A library called DockFX, combined with some custom resizing and positioning code has now enabled all the tools in the editor to be moved around and docked wherever you may like allowing for a personal and performant designer experience.
An advantage of this is it forces all the widgets to be modular making for cleaner code and makes adding new tools faster.
OSRS did a livestream where they showed off their in-house editor which is going to provide a lot of insight in to how this editor should work: https://www.youtube.com/watch?v=hK7FwrU41C8
Brief update but wanted to post the picture of the UI progress; definitely will change.
In other news, I completely refactored the project to use Google Guice dependency injection. I also began modularizing the views using Model-View-Controller pattern. The code was getting quite spaghetti and unruly before today so it was a much needed cleanup and will make feature progress much smoother going forward.
Model Viewer/Picker
The biggest new features are The Object picker and the beginnings of placing objects!
Video of the object picker: https://i.gyazo.com/2455b05b84f16bc65184e1da6d980425.mp4
Video of placing objects in scene: https://i.gyazo.com/5c66e924119ae589e5090b071bb632da.mp4
The object picker is largely proof of concept and will need to be improved with features such as searching for models. I also have a medium-term goal of importing OBJs (I am already using OBJ format to display things in the picker as a temporary solution to my failure to understand how osrs models are packed..)
It is funny I've found myself building features and then checking RSPSi to see how they built them and seeing that I've largely made the same design decisions. Regardless, this is not meant to be a competitor to RSPSi, I am just having fun learning and hope I can share something useful with the community.
Performance improvements
Bit of a bigger stretch between updates here. I got majorly distracted on performance improvements. I recruited the help of The Cherno's discord server (Go watch his series: OpenGL Playlist - The Cherno) and they helped me crack down on a handful of bottlenecks.
The first biggest improvement was how I calculate the mouse hover, I am reading the pixel under the mouse every frame and even though I was using Pixel Buffer Objects, I needed to use 4 PBOs to prevent a "traffic jam" where I am still reading from PBO[0] by the time the GPU has drawn to PBO[1,2,3] and circled back around.
The second biggest improvement was related to selecting objects - I want to be able to select a group of objects to be able to move or clone an area. I was pushing a list of "selectedIds" to the GPU and I was performing an array contains loop in the vertex shader. The new method is to use a Shader Storage Buffer Object with enough bytes to store every single unique id in the scene. When I select an object I simply set that index in the SSBO to 1/true and then I can perform an O(1) lookup in the vertex shader.
The CPU time per frame went from 16ms (potential max 60fps) to 0.3ms (potential max 300fps) in Priff 5x5 region, and I am getting 110fps - this means the remaining bottlenecks are largely in the GPU/shaders unfortunately. Regardless, desert 5x5 areas render over 300fps and with a single chunk loaded I get over 2000 fps.
Animated models work
While I had animated models working previously, I was having the CPU calculate the models and then push the correct model frame to the GPU every cycle. This was one of the major performance bottlenecks. I spent a lot of time refactoring animations to be performed completely by shaders, however I seem to have only found slight improvements. My three thought of solutions are:
1. Upload the animated model frame from CPU->GPU every frame -- seemed very slow
2. Push all frames at load time and then use alpha to show the current frame -- slight improvement, still not great
3. Push the base model at load time and do the anim calculations all in shader -- lot of work, not sure if the transforms will be fast anyway
Perhaps someone can suggest a better method?
Video of all objects loading & selecting: https://i.gyazo.com/a7648e04551867fe5b120c3acfd1a014.mp4
After dealing with random frame stutters for a couple days I realized the way I was showing hover and selected objects was done poorly. I moved the hover state to the GPU and simply send the id of the entity that is hovered or picked. I also did a very hacky test of loading every single object in the location to see the performance implications. Unfortunately, I have discovered a place like Karamja just has too many trees resulting in a crazy number of triangles and the FPS drops to 60 on my GTX 1060. I hope I will eventually stumble on an improvement. The good news is other locations like the desert still render over 144fps Vsync while loading 1024x1024 tiles.
I also did very early experimentation with selecting a section of tiles. I plan to add some sort of selection filter so you can drag and select all trees or all walls in an area and eventually move, copy or remove them.
Demonstration of how snappy the object picking is: https://i.gyazo.com/ad0811ea3d3bff87babddea5b728b5e0.mp4
Starting to work on providing info about what is hovered.
An early debug picture where I was outputting the unique tile/object ids as color. The greens in each column are unique values from eachother.
This task really wracked my brain. The goal this week was to be able to select objects in the world and hover over them without a huge performance hit. The way the OSRS client does so is that every tile in the region transforms the mouse coordinates and checks if the mouse lays within it's bounds. This does not scale to 1024x1024 tiles as that is over 1million comparisons every frame.
I set out to use traditional Projection, model, view transforms to project screen space in to world space but my matrix math knowledge is really lacking. I stumbled upon an interesting technique called color picking. Essentially you render to a background frame with a unique color for each object. Then you simply get the color of the pixel under the mouse and comapre it to your map of colors.
My final solution works as follows:
1. Pack the X, Y and objectType into an integer -- | 14bits X | 14bits Y | 4 bits objType | (really 13bits for x because signed int).
2. Pass this integer in to the GPU with the vertexBuffer object
3. Write out this integer to a second output of the fragment shader
4. Use a PixelBufferObject to async read the second output from the fragment shader.
I have spent a couple weeks on the project so far. A lot of the time spent has been learning OpenGL from scratch and reverse engineering the OSRS client thanks to the various deobs and renames - namely the OpenOSRS deob.
Initial Screens:
As you can see it currently runs at a buttery smooth 200fps and I am setting a goal to not allow the FPS to dip much below this mark for a scene size of 5x5 regions.
The current loading scheme is to read and parse a single region (64x64 tiles) at a time, the same way they are packed in the cache, and build them in to SceneRegion objects. I then take any number of regions and pack them in a NxM grid in a Scene object which finally gets pushed to the GPU to be rendered. Basically this loads like Minecraft chunks. This differs from the client because the client loads 104x104 tiles around the player which actually has to read in 9 regions for any given chunk. This gives the player a better experience but seemed too arbitrary when trying to do tile specific map editing.
Animations!
[video]https://i.gyazo.com/1735f6ab4f071c0883bb709fc7aca8df.mp4[/video]
I am drawing inspiration from the very short scenes of the official Runescape map editor that can be seen in this video:
If anyone knows of any more pictures of videos of the Runescape map editor please let me know.
I am open to suggestions on features and tools to pack in to this editor.
MVP Release: https://github.com/tpetrychyn/osrs-map-editor/releases/tag/v0.1.1
Source Code: https://github.com/tpetrychyn/osrs-map-editor
Only tested working with OpenJDK 14!
Progress updates
Spoiler for September 16, 2020 - Modifying tiles in realtime:
I have been focusing on my personal life, namely I landed a new job which took a couple months to sort out. Today I finally overcame a huge roadblock that had stumped me numerous times: modifying tiles in realtime. My limited knowledge of OpenGL made it very difficult for me to wrap my head around updating data from CPU->GPU repeatedly. The final solution involves uploading the changed vertexes to the GPU before every frame and pushing VAO changes that tells the GPU to recompute the changed tiles. Essentially this allows me to modify tiles on the GPU with updated data without having to modify the vertex buffer.
The result of this work is the ability to sculpt terrain! Additional work was devoted to having "sticky/contoured" objects stick to the ground when it is modified. It is already quite fun to play around with the editor and build simple scenes.
Video example of terrain sculpting
Unfortunately I don't think this update marks my full return to the project but I will continue to pick away at features when I get free time and motivation.
Spoiler for June 22, 2020 - Cache Downloader, Region Choosing, Productionalizing:
This sprint, the focus has been on getting a releasable MVP. This meant fixing many bugs and adding screens for users to be able to interact properly with the software. Previously, I would simply change a variable in the code if I wanted to load a different region or cache directory. However that is not possible for end users.
MVP Release
Today I have pushed myself to complete my goal of a release and I have succeeded. On the Github repository is a release jar file that anyone is free to download and try. Given my limited Gradle/Kotlin experience the jar file is large at 60MB, and has software-breaking errors on Java 1.8. I was only able to get it to run properly with OpenJDK 14.
Check it out here: https://github.com/tpetrychyn/osrs-map-editor/releases/tag/0.0.1
Cache Loader
The goal of the cache loader is to allow anyone, with no background in RSPS, to be able to load the map editor and start looking around. This is why the cache loader features a listing of all available cache versions from Runestats, ready for download.

Region Chooser
The region chooser allows a user to type a cache region and the map editor will load that region. The radius input allows you to load multiple regions at once. Unfortunately the performance decreases exponentially as regions increase, as well as newer areas or noisy areas of the game have a noticeable lower performance. However I do not believe a typical workflow will involve loading more than a handful of regions at once.

Spoiler for June 11, 2020 - Rewrite, cleanup:
I have been making slow progress over the past month, just working on the editor when I get a bit of inspiration as to not force myself and burn myself out.
Kotlin Rewrite & Exporting
Since last update I have rewritten the entire application in Kotlin, included Displee's Cache Tool, and split the editor out of OpenOSRS so it is a simpler standalone application. As a result the load time of the application has gone down from 8 seconds to 1 second. Largely because of the increased control over what actually gets loaded from the cache thanks to Displee's Cache Tool.
Progress has also been made on actually saving the map modifications back to the cache meaning I can actually see the edits reflected in the official client.

Piscatoris Hunter Area - demonstrating cleared objects, working rotation, and height levels
Client Edge Cases
Two notable bugs/edge cases I had to handle were rotation & tile height types.
Rotation:
The client has a special case for objects on 45 degree angles wherein their object type actually gets changed (i.e. from type 10: game object to type 11: diagonal game object).
Special tile heights:
The client also handles tile heights in multiple ways, notably tiles can either by "absolute" height or "perlin noise" height. Absolute height is for areas like Otto's Grotto where there are sheer drops and mountains. Less extreme areas like Piscatoris Hunter Area pack the tile heights in a different type instructing the client to apply perlin noise which smooths out the area.
See: https://pastebin.com/5j5Jc8Rx
Modular Editor
Lastly, I always had an early vision that this editor should feel like a professional design tool, drawing inspiration from Photoshop and Blender. I finally stumbled upon a working solution to have movable tool panes and panels. This was a challenge due to the fact that support for JOGL (the Java OpenGL library) and JavaFX to integrate is only recently being worked on. I wanted JOGL to render in a heavyweight frame to maintain it's performance, however that meant it always drew itself to the screen and disregarded JavaFX's layering. A library called DockFX, combined with some custom resizing and positioning code has now enabled all the tools in the editor to be moved around and docked wherever you may like allowing for a personal and performant designer experience.

An advantage of this is it forces all the widgets to be modular making for cleaner code and makes adding new tools faster.
OSRS did a livestream where they showed off their in-house editor which is going to provide a lot of insight in to how this editor should work: https://www.youtube.com/watch?v=hK7FwrU41C8
Spoiler for May 1, 2020 - Fleshing out UI:
Brief update but wanted to post the picture of the UI progress; definitely will change.

In other news, I completely refactored the project to use Google Guice dependency injection. I also began modularizing the views using Model-View-Controller pattern. The code was getting quite spaghetti and unruly before today so it was a much needed cleanup and will make feature progress much smoother going forward.
Spoiler for April 28, 2020 - Object Placing:
Model Viewer/Picker
The biggest new features are The Object picker and the beginnings of placing objects!
Video of the object picker: https://i.gyazo.com/2455b05b84f16bc65184e1da6d980425.mp4
Video of placing objects in scene: https://i.gyazo.com/5c66e924119ae589e5090b071bb632da.mp4
The object picker is largely proof of concept and will need to be improved with features such as searching for models. I also have a medium-term goal of importing OBJs (I am already using OBJ format to display things in the picker as a temporary solution to my failure to understand how osrs models are packed..)
It is funny I've found myself building features and then checking RSPSi to see how they built them and seeing that I've largely made the same design decisions. Regardless, this is not meant to be a competitor to RSPSi, I am just having fun learning and hope I can share something useful with the community.
Performance improvements
Bit of a bigger stretch between updates here. I got majorly distracted on performance improvements. I recruited the help of The Cherno's discord server (Go watch his series: OpenGL Playlist - The Cherno) and they helped me crack down on a handful of bottlenecks.
The first biggest improvement was how I calculate the mouse hover, I am reading the pixel under the mouse every frame and even though I was using Pixel Buffer Objects, I needed to use 4 PBOs to prevent a "traffic jam" where I am still reading from PBO[0] by the time the GPU has drawn to PBO[1,2,3] and circled back around.
The second biggest improvement was related to selecting objects - I want to be able to select a group of objects to be able to move or clone an area. I was pushing a list of "selectedIds" to the GPU and I was performing an array contains loop in the vertex shader. The new method is to use a Shader Storage Buffer Object with enough bytes to store every single unique id in the scene. When I select an object I simply set that index in the SSBO to 1/true and then I can perform an O(1) lookup in the vertex shader.
The CPU time per frame went from 16ms (potential max 60fps) to 0.3ms (potential max 300fps) in Priff 5x5 region, and I am getting 110fps - this means the remaining bottlenecks are largely in the GPU/shaders unfortunately. Regardless, desert 5x5 areas render over 300fps and with a single chunk loaded I get over 2000 fps.
Animated models work
While I had animated models working previously, I was having the CPU calculate the models and then push the correct model frame to the GPU every cycle. This was one of the major performance bottlenecks. I spent a lot of time refactoring animations to be performed completely by shaders, however I seem to have only found slight improvements. My three thought of solutions are:
1. Upload the animated model frame from CPU->GPU every frame -- seemed very slow
2. Push all frames at load time and then use alpha to show the current frame -- slight improvement, still not great
3. Push the base model at load time and do the anim calculations all in shader -- lot of work, not sure if the transforms will be fast anyway
Perhaps someone can suggest a better method?
Spoiler for April 23, 2020 - More objects & better picking:
Video of all objects loading & selecting: https://i.gyazo.com/a7648e04551867fe5b120c3acfd1a014.mp4
After dealing with random frame stutters for a couple days I realized the way I was showing hover and selected objects was done poorly. I moved the hover state to the GPU and simply send the id of the entity that is hovered or picked. I also did a very hacky test of loading every single object in the location to see the performance implications. Unfortunately, I have discovered a place like Karamja just has too many trees resulting in a crazy number of triangles and the FPS drops to 60 on my GTX 1060. I hope I will eventually stumble on an improvement. The good news is other locations like the desert still render over 144fps Vsync while loading 1024x1024 tiles.
I also did very early experimentation with selecting a section of tiles. I plan to add some sort of selection filter so you can drag and select all trees or all walls in an area and eventually move, copy or remove them.
Spoiler for April 20, 2020 - Object Picking:
Demonstration of how snappy the object picking is: https://i.gyazo.com/ad0811ea3d3bff87babddea5b728b5e0.mp4

Starting to work on providing info about what is hovered.

An early debug picture where I was outputting the unique tile/object ids as color. The greens in each column are unique values from eachother.
This task really wracked my brain. The goal this week was to be able to select objects in the world and hover over them without a huge performance hit. The way the OSRS client does so is that every tile in the region transforms the mouse coordinates and checks if the mouse lays within it's bounds. This does not scale to 1024x1024 tiles as that is over 1million comparisons every frame.
I set out to use traditional Projection, model, view transforms to project screen space in to world space but my matrix math knowledge is really lacking. I stumbled upon an interesting technique called color picking. Essentially you render to a background frame with a unique color for each object. Then you simply get the color of the pixel under the mouse and comapre it to your map of colors.
My final solution works as follows:
1. Pack the X, Y and objectType into an integer -- | 14bits X | 14bits Y | 4 bits objType | (really 13bits for x because signed int).
2. Pass this integer in to the GPU with the vertexBuffer object
3. Write out this integer to a second output of the fragment shader
4. Use a PixelBufferObject to async read the second output from the fragment shader.
Spoiler for April 16, 2020 - First update:
I have spent a couple weeks on the project so far. A lot of the time spent has been learning OpenGL from scratch and reverse engineering the OSRS client thanks to the various deobs and renames - namely the OpenOSRS deob.
Initial Screens:

As you can see it currently runs at a buttery smooth 200fps and I am setting a goal to not allow the FPS to dip much below this mark for a scene size of 5x5 regions.
The current loading scheme is to read and parse a single region (64x64 tiles) at a time, the same way they are packed in the cache, and build them in to SceneRegion objects. I then take any number of regions and pack them in a NxM grid in a Scene object which finally gets pushed to the GPU to be rendered. Basically this loads like Minecraft chunks. This differs from the client because the client loads 104x104 tiles around the player which actually has to read in 9 regions for any given chunk. This gives the player a better experience but seemed too arbitrary when trying to do tile specific map editing.
Animations!
[video]https://i.gyazo.com/1735f6ab4f071c0883bb709fc7aca8df.mp4[/video]
I am drawing inspiration from the very short scenes of the official Runescape map editor that can be seen in this video:
Spoiler for Official Runescape Map Editor clip:
If anyone knows of any more pictures of videos of the Runescape map editor please let me know.
I am open to suggestions on features and tools to pack in to this editor.
Last edited: