Skip to content

Instantly share code, notes, and snippets.

@andrewroberts
Created February 14, 2015 17:24
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save andrewroberts/b71fb499d11d9d2f25d1 to your computer and use it in GitHub Desktop.
Save andrewroberts/b71fb499d11d9d2f25d1 to your computer and use it in GitHub Desktop.
Generates terrain values for a 257x257 Google sheet
function onOpen() {
SpreadsheetApp
.getUi()
.createMenu('Terrain')
.addItem('Generate terrain map', 'createTerrain')
.addToUi();
}
// From https://github.com/hunterloftis/playfuljs-demos/blob/gh-pages/terrain/index.html
var MAP_SIZE = 8;
var ROUGHNESS = 0.7;
function createTerrain() {
var terrain = new Terrain(MAP_SIZE); // Map array size pow(2, MAP_SIZE)
terrain.generate(ROUGHNESS); // Roughness 0 - 1
terrain.draw();
} // createTerrain()
/**
* Terrain class with an array pow(2,detail) elements long, each with a
* height value. So map should be pow(2, N) + 1. So a 65x65 array would
* be pow(2, 6) + 1, so MAP_SIZE = 6.
*/
function Terrain(detail) {
this.size = Math.pow(2, detail) + 1;
this.max = this.size - 1;
this.map = new Float32Array(this.size * this.size);
}
Terrain.prototype.get = function(x, y) {
if (x < 0 || x > this.max || y < 0 || y > this.max) return -1;
return this.map[x + this.size * y];
};
Terrain.prototype.set = function(x, y, val) {
this.map[x + this.size * y] = val;
};
Terrain.prototype.generate = function(roughness) {
var self = this;
this.set(0, 0, self.max);
this.set(this.max, 0, self.max / 2);
this.set(this.max, this.max, 0);
this.set(0, this.max, self.max / 2);
divide(this.max);
function divide(size) {
var x, y, half = size / 2;
var scale = roughness * size;
if (half < 1) return;
for (y = half; y < self.max; y += size) {
for (x = half; x < self.max; x += size) {
square(x, y, half, Math.random() * scale * 2 - scale);
}
}
for (y = 0; y <= self.max; y += half) {
for (x = (y + half) % size; x <= self.max; x += size) {
diamond(x, y, half, Math.random() * scale * 2 - scale);
}
}
divide(size / 2);
}
function average(values) {
var valid = values.filter(function(val) { return val !== -1; });
var total = valid.reduce(function(sum, val) { return sum + val; }, 0);
return total / valid.length;
}
function square(x, y, size, offset) {
var ave = average([
self.get(x - size, y - size), // upper left
self.get(x + size, y - size), // upper right
self.get(x + size, y + size), // lower right
self.get(x - size, y + size) // lower left
]);
self.set(x, y, ave + offset);
}
function diamond(x, y, size, offset) {
var ave = average([
self.get(x, y - size), // top
self.get(x + size, y), // right
self.get(x, y + size), // bottom
self.get(x - size, y) // left
]);
self.set(x, y, ave + offset);
}
};
Terrain.prototype.draw = function() {
var self = this;
var sheet = SpreadsheetApp.getActiveSheet();
var rowIndex;
var numberOfRows = sheet.getMaxRows();
var columnIndex;
var numberOfColumns = sheet.getMaxColumns();
var newRow = [];
var data = [];
var height;
for (rowIndex = 0; rowIndex < numberOfRows; rowIndex++) {
for (columnIndex = 0; columnIndex < numberOfColumns; columnIndex++) {
height = this.map[(rowIndex * numberOfRows) + columnIndex];
newRow.push(height);
}
data.push(newRow.slice());
newRow.length = 0;
}
var range = sheet.getRange(1, 1, numberOfRows, numberOfColumns);
range.setValues(data);
};
function Float32Array(size) {
this.array = [];
var index;
for (index = 0; index < size; index++) {
this.array.push(0);
}
return this.array;
} // Float32Array()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment