Skip to content

Commit

Permalink
Noisy edges and lava are for rendering and aren't inherently part of …
Browse files Browse the repository at this point in the history
…the polygon-level map, so move them out to separate modules.
  • Loading branch information
amitp committed Sep 4, 2010
1 parent 0c8e9d1 commit 7ff42c7
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 116 deletions.
28 changes: 28 additions & 0 deletions Lava.as
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Randomly place lava on high elevation dry land.
// Author: amitp@cs.stanford.edu
// License: MIT

package {
import graph.*;

public class Lava {
static public var FRACTION_LAVA_FISSURES:Number = 0.2; // 0 to 1, probability of fissure

// The lava array marks the edges that hava lava.
public var lava:Array = []; // edge index -> Boolean

// Lava fissures are at high elevations where moisture is low
public function createLava(map:voronoi_set, randomDouble:Function):void {
var edge:Edge;
for each (edge in map.edges) {
if (!edge.river && !edge.d0.water && !edge.d1.water
&& edge.d0.elevation > 0.8 && edge.d1.elevation > 0.8
&& edge.d0.moisture < 0.3 && edge.d1.moisture < 0.3
&& randomDouble() < FRACTION_LAVA_FISSURES) {
lava[edge.index] = true;
}
}
}
}
}

88 changes: 88 additions & 0 deletions NoisyEdges.as
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Annotate each edge with a noisy path, to make maps look more interesting.
// Author: amitp@cs.stanford.edu
// License: MIT

package {
import flash.geom.Point;
import graph.*;
import de.polygonal.math.PM_PRNG;

public class NoisyEdges {
static public var NOISY_LINE_TRADEOFF:Number = 0.5; // low: jagged vedge; high: jagged dedge

public var path0:Array = []; // edge index -> Vector.<Point>
public var path1:Array = []; // edge index -> Vector.<Point>

public function NoisyEdges() {
}

// Build noisy line paths for each of the Voronoi edges. There are
// two noisy line paths for each edge, each covering half the
// distance: path0 is from v0 to the midpoint and path1 is from v1
// to the midpoint. When drawing the polygons, one or the other
// must be drawn in reverse order.
public function buildNoisyEdges(map:voronoi_set, lava:Lava, random:PM_PRNG):void {
var p:Center, edge:Edge;
for each (p in map.centers) {
for each (edge in p.edges) {
if (edge.d0 && edge.d1 && edge.v0 && edge.v1 && !path0[edge.index]) {
var f:Number = NOISY_LINE_TRADEOFF;
var t:Point = Point.interpolate(edge.v0.point, edge.d0.point, f);
var q:Point = Point.interpolate(edge.v0.point, edge.d1.point, f);
var r:Point = Point.interpolate(edge.v1.point, edge.d0.point, f);
var s:Point = Point.interpolate(edge.v1.point, edge.d1.point, f);

var minLength:int = 10;
if (edge.d0.biome != edge.d1.biome) minLength = 3;
if (edge.d0.ocean && edge.d1.ocean) minLength = 100;
if (edge.d0.coast || edge.d1.coast) minLength = 1;
if (edge.river || lava.lava[edge.index]) minLength = 1;

path0[edge.index] = buildNoisyLineSegments(random, edge.v0.point, t, edge.midpoint, q, minLength);
path1[edge.index] = buildNoisyLineSegments(random, edge.v1.point, s, edge.midpoint, r, minLength);
}
}
}
}


// Helper function: build a single noisy line in a quadrilateral A-B-C-D,
// and store the output points in a Vector.
static public function buildNoisyLineSegments(random:PM_PRNG, A:Point, B:Point, C:Point, D:Point, minLength:Number):Vector.<Point> {
var points:Vector.<Point> = new Vector.<Point>();

function subdivide(A:Point, B:Point, C:Point, D:Point):void {
if (A.subtract(C).length < minLength || B.subtract(D).length < minLength) {
return;
}

// Subdivide the quadrilateral
var p:Number = random.nextDoubleRange(0.2, 0.8); // vertical (along A-D and B-C)
var q:Number = random.nextDoubleRange(0.2, 0.8); // horizontal (along A-B and D-C)

// Midpoints
var E:Point = Point.interpolate(A, D, p);
var F:Point = Point.interpolate(B, C, p);
var G:Point = Point.interpolate(A, B, q);
var I:Point = Point.interpolate(D, C, q);

// Central point
var H:Point = Point.interpolate(E, F, q);

// Divide the quad into subquads, but meet at H
var s:Number = 1.0 - random.nextDoubleRange(-0.4, +0.4);
var t:Number = 1.0 - random.nextDoubleRange(-0.4, +0.4);

subdivide(A, Point.interpolate(G, B, s), H, Point.interpolate(E, D, t));
points.push(H);
subdivide(H, Point.interpolate(F, C, s), C, Point.interpolate(I, D, t));
}

points.push(A);
subdivide(A, B, C, D);
points.push(C);
return points;
}
}
}

2 changes: 0 additions & 2 deletions graph/Edge.as
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@ package graph {
public var index:int;
public var v0:Corner, v1:Corner;
public var d0:Center, d1:Center;
public var path0:Vector.<Point>, path1:Vector.<Point>;
public var midpoint:Point;
public var river:Number;
public var lava:Boolean;
};
}
42 changes: 25 additions & 17 deletions mapgen2.as
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ package {
// The map data
public var map:voronoi_set;
public var roads:Roads;
public var lava:Lava;
public var noisyEdges:NoisyEdges;


public function mapgen2() {
Expand Down Expand Up @@ -167,6 +169,8 @@ package {
cancelCommands();

roads = new Roads();
lava = new Lava();
noisyEdges = new NoisyEdges();

commandExecute("Shaping map...",
function():void {
Expand Down Expand Up @@ -201,8 +205,9 @@ package {

commandExecute("Edges...",
function():void {
map.go(6, 7);
roads.createRoads(map);
lava.createLava(map, map.mapRandom.nextDouble);
noisyEdges.buildNoisyEdges(map, lava, map.mapRandom);
drawMap(mapMode);
});
}
Expand Down Expand Up @@ -555,20 +560,23 @@ package {
}

function drawPath0():void {
var path:Vector.<Point> = noisyEdges.path0[edge.index];
graphics.moveTo(p.point.x, p.point.y);
graphics.lineTo(edge.path0[0].x, edge.path0[0].y);
drawPathForwards(graphics, edge.path0);
graphics.lineTo(path[0].x, path[0].y);
drawPathForwards(graphics, path);
graphics.lineTo(p.point.x, p.point.y);
}

function drawPath1():void {
var path:Vector.<Point> = noisyEdges.path1[edge.index];
graphics.moveTo(p.point.x, p.point.y);
graphics.lineTo(edge.path1[0].x, edge.path1[0].y);
drawPathForwards(graphics, edge.path1);
graphics.lineTo(path[0].x, path[0].y);
drawPathForwards(graphics, path);
graphics.lineTo(p.point.x, p.point.y);
}

if (edge.path0 == null || edge.path1 == null) {
if (noisyEdges.path0[edge.index] == null
|| noisyEdges.path1[edge.index] == null) {
// It's at the edge of the map, where we don't have
// the noisy edges computed. TODO: figure out how to
// fill in these edges from the voronoi library.
Expand Down Expand Up @@ -716,10 +724,9 @@ package {
for each (p in map.centers) {
for each (r in p.neighbors) {
edge = map.lookupEdgeFromCenter(p, r);
if (edge.path0 == null || edge.path1 == null) {
// It's at the edge of the map, where we don't have
// the noisy edges computed. TODO: fill these in with
// non-noisy lines.
if (noisyEdges.path0[edge.index] == null
|| noisyEdges.path1[edge.index] == null) {
// It's at the edge of the map
continue;
}
if (p.ocean != r.ocean) {
Expand All @@ -731,20 +738,21 @@ package {
} else if (p.water || r.water) {
// Lake interior – we don't want to draw the rivers here
continue;
} else if (edge.river != 0.0) {
// River edge
graphics.lineStyle(Math.sqrt(edge.river), colors.RIVER);
} else if (edge.lava) {
} else if (lava.lava[edge.index]) {
// Lava flow
graphics.lineStyle(1, colors.LAVA);
} else if (edge.river > 0.0) {
// River edge
graphics.lineStyle(Math.sqrt(edge.river), colors.RIVER);
} else {
// No edge
continue;
}

graphics.moveTo(edge.path0[0].x, edge.path0[0].y);
drawPathForwards(graphics, edge.path0);
drawPathBackwards(graphics, edge.path1);
graphics.moveTo(noisyEdges.path0[edge.index][0].x,
noisyEdges.path0[edge.index][0].y);
drawPathForwards(graphics, noisyEdges.path0[edge.index]);
drawPathBackwards(graphics, noisyEdges.path1[edge.index]);
graphics.lineStyle();
}
}
Expand Down
97 changes: 0 additions & 97 deletions voronoi_set.as
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ package {

public class voronoi_set {
static public var NUM_POINTS:int = 2000;
static public var NOISY_LINE_TRADEOFF:Number = 0.5; // low: jagged vedge; high: jagged dedge
static public var FRACTION_LAVA_FISSURES:Number = 0.2; // 0 to 1, probability of fissure
static public var LAKE_THRESHOLD:Number = 0.3; // 0 to 1, fraction of water corners for water polygon
static public var NUM_LLOYD_ITERATIONS:int = 2;

Expand Down Expand Up @@ -195,18 +193,9 @@ package {
stages.push
(["Decorate map...",
function():void {
createLava();
assignBiomes();
}]);

// For all edges between polygons, build a noisy line path that
// we can reuse while drawing both polygons connected to that
// edge. The noisy lines are constructed in two sections, going
// from the vertex to the midpoint. We don't construct the noisy
// lines from the polygon centers to the midpoints, because
// they're not needed for polygon filling.
stages.push(["Add noise...", buildNoisyEdges]);

for (var i:int = first; i < last; i++) {
timeIt(stages[i][0], stages[i][1]);
}
Expand Down Expand Up @@ -726,23 +715,6 @@ package {
}


// Lava fissures are at high elevations where moisture is low
public function createLava():void {
var edge:Edge, p:Center, s:Center;
for each (p in centers) {
for each (s in p.neighbors) {
edge = lookupEdgeFromCenter(p, s);
if (!edge.river && !p.water && !s.water
&& p.elevation > 0.8 && s.elevation > 0.8
&& p.moisture < 0.3 && s.moisture < 0.3
&& mapRandom.nextDouble() < FRACTION_LAVA_FISSURES) {
edge.lava = true;
}
}
}
}


// Assign a biome type to each polygon. If it has
// ocean/coast/water, then that's the biome; otherwise it depends
// on low/high elevation and low/medium/high moisture. This is
Expand Down Expand Up @@ -783,75 +755,6 @@ package {
}


// Helper function: build a single noisy line in a quadrilateral A-B-C-D,
// and store the output points in a Vector.
static public function buildNoisyLineSegments(random:PM_PRNG, A:Point, B:Point, C:Point, D:Point, minLength:Number):Vector.<Point> {
var points:Vector.<Point> = new Vector.<Point>();

function subdivide(A:Point, B:Point, C:Point, D:Point):void {
if (A.subtract(C).length < minLength || B.subtract(D).length < minLength) {
return;
}

// Subdivide the quadrilateral
var p:Number = random.nextDoubleRange(0.2, 0.8); // vertical (along A-D and B-C)
var q:Number = random.nextDoubleRange(0.2, 0.8); // horizontal (along A-B and D-C)

// Midpoints
var E:Point = Point.interpolate(A, D, p);
var F:Point = Point.interpolate(B, C, p);
var G:Point = Point.interpolate(A, B, q);
var I:Point = Point.interpolate(D, C, q);

// Central point
var H:Point = Point.interpolate(E, F, q);

// Divide the quad into subquads, but meet at H
var s:Number = 1.0 - random.nextDoubleRange(-0.4, +0.4);
var t:Number = 1.0 - random.nextDoubleRange(-0.4, +0.4);

subdivide(A, Point.interpolate(G, B, s), H, Point.interpolate(E, D, t));
points.push(H);
subdivide(H, Point.interpolate(F, C, s), C, Point.interpolate(I, D, t));
}

points.push(A);
subdivide(A, B, C, D);
points.push(C);
return points;
}


// Build noisy line paths for each of the Voronoi edges. There are
// two noisy line paths for each edge, each covering half the
// distance: edge.path0 will be from v0 to the midpoint and
// edge.path1 will be from v1 to the midpoint. When drawing
// the polygons, one or the other must be drawn in reverse order.
public function buildNoisyEdges():void {
var p:Center, edge:Edge;
for each (p in centers) {
for each (edge in p.edges) {
if (edge.d0 && edge.d1 && edge.v0 && edge.v1 && !edge.path0) {
var f:Number = NOISY_LINE_TRADEOFF;
var t:Point = Point.interpolate(edge.v0.point, edge.d0.point, f);
var q:Point = Point.interpolate(edge.v0.point, edge.d1.point, f);
var r:Point = Point.interpolate(edge.v1.point, edge.d0.point, f);
var s:Point = Point.interpolate(edge.v1.point, edge.d1.point, f);

var minLength:int = 10;
if (edge.d0.biome != edge.d1.biome) minLength = 3;
if (edge.d0.ocean && edge.d1.ocean) minLength = 100;
if (edge.d0.coast || edge.d1.coast) minLength = 1;
if (edge.river || edge.lava) minLength = 1;

edge.path0 = buildNoisyLineSegments(mapRandom, edge.v0.point, t, edge.midpoint, q, minLength);
edge.path1 = buildNoisyLineSegments(mapRandom, edge.v1.point, s, edge.midpoint, r, minLength);
}
}
}
}


// Look up a Voronoi Edge object given two adjacent Voronoi
// polygons, or two adjacent Voronoi corners
public function lookupEdgeFromCenter(p:Center, r:Center):Edge {
Expand Down

0 comments on commit 7ff42c7

Please sign in to comment.