in development

mapping the broken city

I was back experimenting with procedural textures when I managed to produce this gem.

brokencity

I took one look at it and thought, that’s a city. There’s roads and blocks and buildings, though it does look as if no one’s been there for a long time. An old city, then, crumbling into ruins. A broken city.

I didn’t have much use for it as a texture, but I liked it enough to want to see more of it, so I decided to make a map of that old, broken city.

Grab the source. Here’s the breakdown.

There are three files: index.html, map.js, and worker.js. The HTML document doesn’t do much beyond hosting the Javascript, so forget about it. The map.js module contains all the UI code, and worker.js is a web worker that handles the actual image generation.

When map.js is loaded, it first runs onInit() to create the canvas, set up the event handlers, and start the web worker. A call to onResize() insures the canvas fills the document area, and kicks off the onDraw() routine, which draws the map image to the canvas. It doesn’t do it all in one go, however.

The map is very large, so it has to be created and displayed in chunks. Each chunk is 256 by 256 pixels. The onDraw() routine checks the viewport (which tracks the current scroll position) to determine which chunks should be visible to the user, and queries a hash table as to which chunks actually exist. Those that do are copied to the canvas at the correct position. Those that don’t are displayed as empty regions, and a request message is posted to the web worker.

The image generation code in the web worker is based on Perlin noise–summing over frequencies of a random function. To create an image, we need a two-dimensional source. To keep things simple, I’ll use a function of the form f(x, y) = a(b(x) + c(y)) where a, b, and c are functions that return a number in the range [0..1] for any real number. This lets me keep the source data in one-dimensional buffers, which are a little easier to manipulate.

Each buffer is populated with random-sized “blocks”, and each block simply repeats a random decimal. These define the regions of the city: dark spots and light spots. Interpolation between each block makes thin transitional areas: roads and bridges. Perlin noise is usually additive, but I found that multiplication provides greater contrast.

Once an image chunk is complete, the web worker posts it back over to the main ui, which handles it in the onChunk() routine. Each chunk is copied to an ImageData object, which is added to the hash table. A call to onDraw() gets the new chunk onto the screen. We also go through the list of available chunks to remove any that are very far away from the current viewport. That way, the memory footprint doesn’t grow too large.

So, that’s how I map the broken city. Happy exploring! If you can make use of the code, feel free to do so.

  1. hi Chris,
    I stumbled on this rusty beauty whilst looking for procedurally generated sci-fi cities for use as a backdrop for a wargaming campaign (a sadly discontinued oldschool RPG called Necromunda). The feel of this map is absolutely perfect for the setting!

    Anyhow i was tinkering with the source code and it gave me some ideas, well more questions than ideas really… i’m only an ambitious Newb when it comes to coding so if you could steer me in the right direction id appreciate it!

    The ideal end-goal would be a very big city map with marked strategic locations and expanding territories (marked by colored zones) that would reflect the progress of a faction during a wargaming campaign (or lack of!).
    its intended for a group of close friends to share and update (without hosting online). with that in mind the following questions arise:

    -is it possible to add markers to the map at specific locations (preferably on a separate layer but synchronized to the movement of the map or is it tricky due to the discarding of chunks beyond chunksize*16 ?

    -likewise how would i go about adding a colored opaque territory overlay, also synchronized with the scrolling of the broken city map.

    i figure i could use the onmove if(mousedown) function along with a large picture (mostly transparent but containing territories and markers) with a higher z value than the broken city map. again im sadly unfamilliar with html or javascript but if you point me in the right direction i can figure out the rest!

    Kind regards,

    Mike

    • Hi Mike,

      Thanks for the kind words! I hope this will be useful to you.

      For the map markers, I would add to the onDraw function in map.js. After the chunks are drawn, you can iterate through a table of marker coordinates, and test each one to see if it occurs in the chunk area. (Look at the lines at the top of onDraw:


      var cx0 = nearestChunk(viewport.x);
      var cy0 = nearestChunk(viewport.y);
      var cx1 = nearestChunk(viewport.x + viewport.width);
      var cy1 = nearestChunk(viewport.y + viewport.height);

      The rectangle (cx0, cy0, cx1, cy1) is the area the chunk represents. Any marker (mx, my) for which mx > cx0 and mx cy0 and my
      context.fillStyle = “blue”;

      if (mx > cx0 && mx cy0 && my

      Google “HTML5 Canvas” for more ideas.

      For the overlay, I think your idea of moving a large image will work fine. You can put the image into an img or div element, set the element to absolute positioning in CSS (look up position: absolute), and set the top and left properties in the onMove function. You will need to center the image at the center of the map–try something like


      img.top = -(img.height / 2) + mouse.x;
      img.left = -(img.width / 2) + mouse.y;

      You will very likely need to play around with that to get it right, though. I hope that’s enough to get you started. Enjoy!

      Chris

      • Fantastic thanks for the quick reply,

        It was actually very difficult to find even a good static top down image of a city that wasn’t hand drawn, fantasy orientated, too elaborate or just plain cartoony. i was delighted to come across this vast pro-gen city…

        anyway keep up the good work, ill let you know how it turns out.

        thanks for the info!

Comments are closed.