Skip to content

Instantly share code, notes, and snippets.

@ralfw
Last active February 3, 2017 10:05
Show Gist options
  • Save ralfw/a2545bc61a5fb852959884bc3999a2e8 to your computer and use it in GitHub Desktop.
Save ralfw/a2545bc61a5fb852959884bc3999a2e8 to your computer and use it in GitHub Desktop.
Diamond-Square Solution
using System;
using System.Linq;
namespace terraingenBlog
{
public class TerrainGenerator {
public static void Interpolate(Terrain terrain, float offset, float amplitude) {
Interpolate(terrain, offset, amplitude, Random_numbers_between_minus_1_and_1());
}
public static void Interpolate(Terrain terrain, float offset, float amplitude, Func<float> randomValue)
{
var sh = new ShapeHierarchy(terrain.N);
var calc = new Calculator(offset, amplitude, randomValue);
foreach (var s in sh.Shapes) {
var vertex_values = s.Vertexes.Select(c => terrain[c.Y, c.X]);
var terrain_value = calc.Process(vertex_values.ToArray());
terrain[s.Center.Y, s.Center.X] = terrain_value;
}
}
static Func<float> Random_numbers_between_minus_1_and_1() {
var rnd = new Random();
return () => (float)rnd.Next(-100, 100) / 100.0f;
}
}
}
using System;
namespace terraingenBlog
{
public class Terrain {
private float[,] matrix;
public Terrain(int n) {
this.N = n;
var size = (int)Math.Pow(2,n) + 1;
matrix = new float[size, size];
}
public Terrain(int n, float leftTop, float rightTop, float rightBottom, float leftBottom) : this(n) {
matrix[0, 0] = leftTop;
matrix[0, Size - 1] = rightTop;
matrix[Size - 1, Size - 1] = rightBottom;
matrix[Size - 1, 0] = leftBottom;
}
public int N { get; }
public int Size => matrix.GetLength(0);
public float this[int y, int x] {
get { return matrix[y, x]; }
set { matrix[y, x] = value; }
}
public float[,] ToArray() => matrix;
}
}
using System;
using System.Linq;
namespace terraingenBlog
{
class Calculator {
readonly float offset;
readonly float amplitude;
readonly Func<float> randomValue;
public Calculator(float offset, float amplitude, Func<float> randomValue) {
this.offset = offset;
this.amplitude = amplitude;
this.randomValue = randomValue;
}
public float Process(float[] values) {
var result = values.Average();
return Add_jitter(result);
}
float Add_jitter(float value) {
return value + this.offset + this.amplitude * this.randomValue();
}
}
}
using System;
using System.Linq;
using System.Collections.Generic;
namespace terraingenBlog
{
class Shape
{
public Coordinate Center;
public Coordinate[] Vertexes;
public override string ToString()
{
var vertexes = string.Join(",", Vertexes);
return $"{Center}@({vertexes})";
}
}
struct Coordinate
{
public Coordinate(int y, int x) { Y = y; X = x; }
public int Y, X;
public override string ToString()
{
return $"[{Y},{X}]";
}
}
class ShapeHierarchy {
int n;
public ShapeHierarchy(int n) { this.n = n; }
public IEnumerable<Shape> Shapes { get {
var size = (int)Math.Pow(2.0, this.n) + 1;
var r = (int)Math.Pow(2.0, (double)(n - 1));
return Enumerate_shapes(size, r);
}
}
IEnumerable<Shape> Enumerate_shapes(int size, int r) {
if (r < 1) return new Shape[0];
return Enumerate_squares(size, r)
.Concat(Enumerate_diamonds(size, r))
.Concat(Enumerate_shapes(size, r / 2));
}
static IEnumerable<Shape> Enumerate_squares(int size, int r) {
for (var y = r; y < size; y += 2*r)
for (var x = r; x < size; x += 2*r) {
yield return new Shape {
Center = new Coordinate(y, x),
Vertexes = new[]{
new Coordinate(y-r, x-r), // north-west
new Coordinate(y-r, x+r), // north-east
new Coordinate(y+r, x+r), // south-east
new Coordinate(y+r, x-r) // south-west
}
};
}
}
static IEnumerable<Shape> Enumerate_diamonds(int size, int r) {
for (var y = 0; y < size; y += 2 * r)
for (var x = r; x < size; x += 2 * r) {
yield return new Shape {
Center = new Coordinate(y, x),
Vertexes = Enumerate_diamond_vertexes(y,x,r,size).ToArray()
};
};
for (var y = r; y < size; y += 2 * r)
for (var x = 0; x < size; x += 2 * r) {
yield return new Shape {
Center = new Coordinate(y, x),
Vertexes = Enumerate_diamond_vertexes(y, x, r, size).ToArray()
};
};
}
static IEnumerable<Coordinate> Enumerate_diamond_vertexes(int y, int x, int r, int size) {
int dy, dx;
dy = y-r; dx = x; if (Is_valid_coordinate(dy, dx, size)) yield return new Coordinate(dy, dx); // north
dy = y; dx = x+r; if (Is_valid_coordinate(dy, dx, size)) yield return new Coordinate(dy, dx); // east
dy = y+r; dx = x; if (Is_valid_coordinate(dy, dx, size)) yield return new Coordinate(dy, dx); // south
dy = y; dx = x-r; if (Is_valid_coordinate(dy, dx, size)) yield return new Coordinate(dy, dx); // west
}
static bool Is_valid_coordinate(int y, int x, int size) => y >= 0 && y < size && x >= 0 && x < size;
}
}
using System;
using NUnit.Framework;
using System.Linq;
namespace terraingenBlog
{
[TestFixture]
public class test_Acceptance {
[Test]
public void Run_without_jitter() {
var t = new Terrain(2, 1.0f, 2.0f, 3.0f, 4.0f);
TerrainGenerator.Interpolate(t, 0.0f, 0.0f, () => 0.0f);
Assert_matrix_equality(new float[,] {
{1.00f, 1.60f, 1.83f, 2.01f, 2.00f},
{1.82f, 1.96f, 2.13f, 2.21f, 2.24f},
{2.50f, 2.50f, 2.50f, 2.50f, 2.50f},
{3.18f, 3.04f, 2.88f, 2.79f, 2.76f},
{4.00f, 3.40f, 3.17f, 2.99f, 3.00f}
}, t.ToArray());
}
[Test]
public void Run_with_constant_jitter()
{
var t = new Terrain(2, 3.0f, 7.0f, 5.0f, 1.0f);
TerrainGenerator.Interpolate(t, 3.0f, 1.0f, () => 1.0f);
Assert_matrix_equality(new float[,] {
{ 3.00f, 12.08f, 10.00f, 13.97f, 7.00f},
{11.42f, 11.25f, 14.54f, 12.92f, 14.19f},
{ 8.00f, 13.42f, 8.00f, 14.92f, 10.67f},
{10.47f, 10.42f, 13.79f, 12.08f, 13.25f},
{ 1.00f, 10.69f, 8.67f, 12.58f, 5.00f}
}, t.ToArray());
}
[Test]
public void RobertCMartin_example()
{
var t = new Terrain(2, 0.0f, 0.0f, 0.0f, 0.0f);
TerrainGenerator.Interpolate(t, 4.0f, 2.0f, () => 1.0f);
Assert_matrix_equality(new float[,] {
{ 0.00f, 12.50f, 8.00f, 12.50f, 0.00f},
{12.50f, 11.50f, 15.25f, 11.50f, 12.50f},
{ 8.00f, 15.25f, 6.00f, 15.25f, 8.00f},
{12.50f, 11.50f, 15.25f, 11.50f, 12.50f},
{ 0.00f, 12.50f, 8.00f, 12.50f, 0.00f}
}, t.ToArray());
}
/* This test helper method is useful to check for same size of matrixes,
* but mostly to compare the cell values with a delta. Assert.AreEqual() does not provide
* delta comparison for arrays.
*/
private void Assert_matrix_equality(float[,] expected, float[,] result) {
Assert.IsTrue(expected.GetLength(0) == result.GetLength(0), "Matrix heights differ!");
Assert.IsTrue(expected.GetLength(1) == result.GetLength(1), "Matrix width differ!");
for (var y = 0; y < result.GetLength(0); y++)
for (var x = 0; x < result.GetLength(1); x++)
Assert.AreEqual(expected[y,x], result[y,x], 0.01, $"Differing values at [{y},{x}]!");
}
}
}
using System;
using NUnit.Framework;
using System.Linq;
namespace terraingenBlog
{
[TestFixture]
public class test_Calculator {
[Test]
public void No_jitter() {
var sut = new Calculator(0, 0, () => 0.0f);
Assert.AreEqual(2.0f, sut.Process(new[] { 1.0f, 2.0f, 3.0f }));
Assert.AreEqual(2.5f, sut.Process(new[] { 1.0f, 2.0f, 3.0f, 4.0f }));
}
[Test]
public void With_jitter() {
var sut = new Calculator(6, 2, () => 1.0f);
Assert.AreEqual(8.0f, sut.Process(new[] { 0.0f }));
sut = new Calculator(6, 2, () => -1.0f);
Assert.AreEqual(4.0f, sut.Process(new[] { 0.0f }));
}
}
}
using System;
using NUnit.Framework;
using System.Linq;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
namespace terraingenBlog
{
[TestFixture]
public class test_ShapeHierarchy {
[Test]
public void Size3() {
var sut = new ShapeHierarchy(1);
var shapes = sut.Shapes.ToArray();
Assert.AreEqual(5, shapes.Length);
Assert.AreEqual("[1,1]@([0,0],[0,2],[2,2],[2,0])", shapes[0].ToString());
Assert.AreEqual("[0,1]@([0,2],[1,1],[0,0])", shapes[1].ToString());
Assert.AreEqual("[2,1]@([1,1],[2,2],[2,0])", shapes[2].ToString());
Assert.AreEqual("[1,0]@([0,0],[1,1],[2,0])", shapes[3].ToString());
Assert.AreEqual("[1,2]@([0,2],[2,2],[1,1])", shapes[4].ToString());
}
[Test]
public void Size5() {
var sut = new ShapeHierarchy(2);
var shapes = sut.Shapes.ToArray();
Assert.AreEqual(21, shapes.Length);
Assert.AreEqual("[2,2]@([0,0],[0,4],[4,4],[4,0])", shapes[0].ToString());
Assert.AreEqual("[2,4]@([0,4],[4,4],[2,2])", shapes[4].ToString());
Assert.AreEqual("[1,1]@([0,0],[0,2],[2,2],[2,0])", shapes[5].ToString());
Assert.AreEqual("[0,1]@([0,2],[1,1],[0,0])", shapes[9].ToString());
}
}
}
using System;
using NUnit.Framework;
using System.Linq;
namespace terraingenBlog
{
[TestFixture]
public class test_Terrain {
[Test]
public void Terrains_are_squares_of_size_2n1() {
var t = new Terrain(1);
var m = t.ToArray();
Assert.IsTrue(m.GetLength(0) == m.GetLength(1));
Assert.AreEqual(t.Size, m.GetLength(0));
var size = new Terrain(3).Size;
Assert.AreEqual(9, size);
}
[Test]
public void Terrains_are_initialized() {
var t = new Terrain(3, 1.0f, 2.0f, 3.0f, 4.0f);
Assert.AreEqual(1.0f, t[0, 0]);
Assert.AreEqual(2.0f, t[0, t.Size-1]);
Assert.AreEqual(3.0f, t[t.Size-1, t.Size-1]);
Assert.AreEqual(4.0f, t[t.Size-1, 0]);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment