What Have I Done?

(back to main page)

A detailed pixel-art map of a small forest and village with an irregularly-shaped black outline. Outside of the outline, a lower-resolution faded map is visible. In a box on the top right has controls such as "Zoom Way Out" as well as a listing of some coordinates.

I made a website that stitches together maps generated in the video game Minecraft and displays them with various labeled points of interest.

This is another app scaffolded and built with the Vue CLI, written in TypeScript and with some components that use TSX and render functions. It uses the RBush library to spatially index the map elements and the Bezier.js library to help create SVG curves. The data used by the frontend is built by a small Python program that includes a module written in the Cython extension language in order to process binary data from the Minecraft files at very high speeds. This was a surprisingly math- and data-heavy project.

Details on the requisite geometry:

There already exists a lot of software that generates maps from Minecraft worlds, but I didn't want this program to generate new maps - I wanted to extract and display the ones that I had organically created in-game. These maps consisted of various static images that portrayed a world at very different levels of detail, so I needed to separate the map areas that were mapped out at the higher level of detail and thus could be zoomed in on from those that weren't. To do this, I grouped adjacent maps at similar levels of detail into "islands" and displayed the available islands as part of the "zoom in" interaction.

The more interesting problem was stopping users from panning away from a high-detail island and into low-detail territory while they were zoomed in. To do this, the build step generates data for polygons that enclose all the maps of each island and the front end treats the sides of these as collision bounds that interact with an imaginary point at the center of the user's screen, with the goal of keeping this point from escaping the current island (except when "cutting across" a concave corner; the polygons are altered to leave room for that.) To do this, I perform a simple point-in-polygon test to see if a user's interaction kept the center-of-the-screen point inside the polygon and, if not, I find the closest point on the polygon to the one the user was aiming towards and move the map so that the screen is centered on that. (This method of projecting the collision point back onto the polygon means that the component of its movement vector that wasn't responsible for trying to leave the polygon is preserved (i.e. if you try to pan the map diagonally off an island you will slide along its edge instead of just stopping.))

Described in caption below.
As we pan diagonally across this verdant green island, the point at the center of our view (red) is successfully pulled across a concave corner but then collides with the collision polygon (also red), eventually halting the panning motion.

I also wanted to display organically curved paths on the map that went from point to point without having to manually draw them. To do this, I connected the points with imaginary line segments, calculated the slopes of the tangents of the corners where those line segments meet, and created Bezier curves that had the same slopes at the same points but interpolated them smoothly in between.

Described in caption below.
This simple animation shows how a smooth curve can be derived from a sequence of line segments (which can be derived from a sequence of points) just by keeping the slopes of the tangents constant. In other words, the slopes of the tangents of the corners become the slope of the final curve at those same points. To achieve this, the control points of the final cubic Bezier curve should be at the ends of the dashed line segments here.