I’ve spent the last few days reworking how I generate caves. That canvas-based map object I mentioned in the last entry really helped me focus on the algorithms, rather than the mechanics of getting data into the map. I’m able to create distinct structures by drunk-walking around a set of randomly-chosen points.
I create each path by making incremental steps and doing a fillRect at each point to plot a small square with a miniscule alpha.
// closure function for drawing a meandering path function drawPath(x, y, tx, ty) { var dx, dy, d; do { dx = rng.get() - rng.get(); dy = rng.get() - rng.get(); d = Math.sqrt(dx * dx + dy * dy); x += 0.5 * dx / d; y += 0.5 * dy / d; dx = tx - x; dy = ty - y; d = Math.sqrt(dx * dx + dy * dy); x += 0.05 * dx / d; y += 0.05 * dy / d; x = SOAR.clamp(x, 0, l); y = SOAR.clamp(y, 0, l); map.context.fillRect(x - 2, y - 2, 4, 4); } while (Math.abs(dx) > 1 || Math.abs(dy) > 1); } ... // generate paths map.context.fillStyle = "rgba(255, 0, 0, 0.05)"; for (i = 1, il = this.area.length; i < il; i++) { drawPath(this.area[i - 1].x, this.area[i - 1].y, this.area[i].x, this.area[i].y); }
Premultiplied alpha insures that I’ll get a nice cumulative effect as the rectangles overlap each other. Instead of the sheer walls I often get with noise algorithms, I can get tunnels, and other natural-looking structures.
The canvas performs alpha blending and color accumulation much faster than if I tried to do it in raw Javascript. It’s a win-win, except when something like this happens.
Oh, dear. Good luck getting through that.
I’m still trying to figure out how to prevent this. A rare occurance, but it only has to happen once to bring a game to a halt. If I make the alpha too large, I wind up with dull flat cave floors instead of exciting curvy tunnels. If I make the rectangle size too big, I’ll get vast open spaces with little suspense about what’s around the next corner.
And this in fact leads to the sole issue I have with using the canvas to generate maps. I have to call getImageData to see the actual pixel data, and the object it returns doesn’t track canvas changes, so if I wanted to use it to fix paths I’d have to generate additional data objects. Wasteful!
Naturally, I could operate on the data object itself, checking and correcting each path by flipping its bits, though that makes me wonder why I’m using the canvas in the first place. I will exhaust other options first.
UPDATE: Worked out a nice solution. In the path drawing loop, I draw a filled circle with a heavier alpha (0.25) and a radius of 2 every time the distance between the current (x, y) and the last time I drew my circle exceeds 1. It looks like this.
dx = lx - x; dy = ly - y; d = Math.sqrt(dx * dx + dy * dy); if (d > 1) { f = map.context.fillStyle; map.context.fillStyle = "rgba(255, 0, 0, 0.25)"; map.context.beginPath(); map.context.arc(x, y, 2, 0, SOAR.PIMUL2, false); map.context.fill(); map.context.fillStyle = f; lx = x; ly = y; }
Stick that in just after the start of the do..while loop. It insures that the path between points is always open just enough to squeeze through.