function ge(id) {
return document.getElementById(id);
}
// Lazy implementation of fractal landscape generation (diamond-square algorithm)
// AUTHOR: deNULL (me@denull.ru)
// TODO: Make a nice wrapper (done), add Voronoi diagram modulation, optimize something
DiamondSquare = function(size, roughness, seed) {
// public fields
this.size = size;
this.roughness = roughness;
this.seed = (seed ? seed : Math.random());
var opCount = 0;
// private field
var data = new Array();
// public methods
this.value = function(x, y, v) {
x = parseInt(x);
y = parseInt(y);
if (typeof(v) != 'undefined')
val(x, y, v);
else
return val(x, y);
}
this.clear = function() {
data = new Array();
}
this.opCount = function(v) {
if (typeof(v) != 'undefined')
opCount = v;
else
return opCount;
}
// private methods
function val(x, y, v) {
if (typeof(v) != 'undefined')
data[x + '_' + y] = Math.max(0.0, Math.min(1.0, v));
else {
if (x <= 0 || x >= size || y <= 0 || y >= size) return 0.0;
if (data[x + '_' + y] == null) {
opCount++;
base = 1;
while (((x & base) == 0) && ((y & base) == 0))
base <<= 1;
if (((x & base) != 0) && ((y & base) != 0))
squareStep(x, y, base);
else
diamondStep(x, y, base);
}
return data[x + '_' + y];
}
}
function randFromPair(x, y) {
for (var i = 0; i < 80; i++) {
var xm7 = x % 7;
var xm13 = x % 13;
var xm1301081 = x % 1301081;
var ym8461 = y % 8461;
var ym105467 = y % 105467;
var ym105943 = y % 105943;
//y = (i < 40 ? seed : x);
y = x + seed;
x += (xm7 + xm13 + xm1301081 + ym8461 + ym105467 + ym105943);
}
return (xm7 + xm13 + xm1301081 + ym8461 + ym105467 + ym105943) / 1520972.0;
}
function displace(v, blockSize, x, y) {
return (v + (randFromPair(x, y, seed) - 0.5) * blockSize * 2 / size * roughness);
}
function squareStep(x, y, blockSize) {
if (data[x + '_' + y] == null) {
val(x, y,
displace((val(x - blockSize, y - blockSize) +
val(x + blockSize, y - blockSize) +
val(x - blockSize, y + blockSize) +
val(x + blockSize, y + blockSize)) / 4, blockSize, x, y));
}
}
function diamondStep(x, y, blockSize) {
if (data[x + '_' + y] == null) {
val(x, y,
displace((val(x - blockSize, y) +
val(x + blockSize, y) +
val(x, y - blockSize) +
val(x, y + blockSize)) / 4, blockSize, x, y));
}
}
}
var rough, seed, waterLevel, sz, vwSz, xofs, yofs, land;
function redrawMap(id) {
var stTime = (new Date()).getTime();
var display = document.getElementById('map' + id);
var stage = display.getContext('2d');
var dispSz = display.width;
var curSz = (id == 1 ? sz : vwSz);
var curXOfs = (id == 1 ? 0 : xofs);
var curYOfs = (id == 1 ? 0 : yofs);
var scale = curSz / dispSz;
var baseOp = 0;
var img = stage.createImageData(dispSz, dispSz);
land.opCount(0);
for (var i = 0; i < dispSz; i++) {
for (var j = 0; j < dispSz; j++) {
baseOp++;
var v = land.value(i * scale + curXOfs, j * scale + curYOfs);
v *= v;
var idx = (i + j * (dispSz)) * 4;
if (v < waterLevel) {
img.data[idx] = 0;
img.data[idx + 1] = 0;
img.data[idx + 2] = parseInt(v * 255.0) + 128;
} else {
img.data[idx] = parseInt(v * 255.0);
img.data[idx + 1] = parseInt(v * 255.0);
img.data[idx + 2] = parseInt(v * 255.0);
}
img.data[idx + 3] = 255;
}
}
stage.putImageData(img, 0, 0);
stage.fillStyle = 'rgba(255,255,255,0.7)';
stage.fillRect(4, 4, 168, 36);
stage.fillStyle = 'black';
stage.fillText('(' + curXOfs + ', ' + curYOfs + '), ' + curSz + 'x' + curSz, 8, 15);
stage.fillText('канва ' + dispSz + 'x' + dispSz + ', масштаб 1:' + scale, 8, 25);
var enTime = (new Date()).getTime();
stage.fillText('действий: ' + land.opCount() + ', ' + (enTime - stTime) + 'мс', 8, 35);
}
function updateParams() {
sz = parseInt(ge('sz').value);
xofs = parseInt(ge('xofs').value);
yofs = parseInt(ge('yofs').value);
vwSz = parseInt(ge('vwSz').value);
var nseed = parseInt(ge('seed').value);
var nrough = parseFloat(ge('rough').value);
var nwaterLevel = parseFloat(ge('waterLevel').value);
if (nseed != seed || nrough != rough || nwaterLevel != waterLevel) {
seed = nseed;
rough = nrough;
waterLevel = nwaterLevel;
land = new DiamondSquare(sz, rough, seed);
redrawMap(1);
}
redrawMap(2);
}
function posHelpers() {
var _sz = parseInt(ge('sz').value);
var _xofs = parseInt(ge('xofs').value) / (_sz / 512);
var _yofs = parseInt(ge('yofs').value) / (_sz / 512);
var _vwSz = parseInt(ge('vwSz').value) / (_sz / 512);
var wnd = ge('wnd');
wnd.style.left = _xofs;
wnd.style.top = _yofs;
wnd.style.width = _vwSz;
wnd.style.height = _vwSz;
wnd = ge('wndNW');
wnd.style.left = _xofs;
wnd.style.top = _yofs;
wnd = ge('wndNE');
wnd.style.left = _xofs + _vwSz - 6;
wnd.style.top = _yofs;
wnd = ge('wndSE');
wnd.style.left = _xofs + _vwSz - 6;
wnd.style.top = _yofs + _vwSz - 6;
wnd = ge('wndSW');
wnd.style.left = _xofs;
wnd.style.top = _yofs + _vwSz - 6;
}
var dragging = false;
var dX, dY, did;
var sx, sy, ssz;
function drag(id, event, start) {
if (typeof(start) == 'undefined') {
if (dragging) {
var _sz = parseInt(ge('sz').value) / 512;
var _xofs = sx, _yofs = sy, _vwSz = ssz;
switch (did) {
case 1:
_xofs = sx + (event.x - dX);
_yofs = sy + (event.y - dY);
break;
case 2:
var dt = Math.min(event.x - dX, event.y - dY);
_vwSz = ssz - dt;
_xofs = sx + dt;
_yofs = sy + dt;
break;
case 3:
var dt = Math.min(- (event.x - dX), event.y - dY);
_vwSz = ssz - dt;
_yofs = sy + dt;
break;
case 4:
_vwSz = ssz + Math.min(event.x - dX, event.y - dY);
break;
case 5:
var dt = Math.min(event.x - dX, -(event.y - dY));
_vwSz = ssz - dt;
_xofs = sx + dt;
break;
}
if (_vwSz < 0) {
_vwSz = -_vwSz;
_xofs -= _vwSz;
_yofs -= _vwSz;
did = 2 + (did % 4);
dX = event.x;
dY = event.y;
sx = _xofs;
sy = _yofs;
ssz = _vwSz;
}
ge('xofs').value = parseInt(_xofs * _sz);
ge('yofs').value = parseInt(_yofs * _sz);
ge('vwSz').value = parseInt(_vwSz * _sz);
posHelpers();
}
} else {
dragging = start;
if (!dragging) {
xofs = parseInt(ge('xofs').value);
yofs = parseInt(ge('yofs').value);
vwSz = parseInt(ge('vwSz').value);
redrawMap(2);
} else {
var _sz = parseInt(ge('sz').value) / 512;
sx = parseInt(ge('xofs').value) / _sz;
sy = parseInt(ge('yofs').value) / _sz;
ssz = parseInt(ge('vwSz').value) / _sz;
dX = event.x;
dY = event.y;
did = id;
}
}
}
function clearCache() {
if (land) land.clear();
}