1
0

added Grown visualizer to the Generator documentation

This commit is contained in:
NiLSPACE 2016-06-23 21:43:40 +02:00
parent e03851a78f
commit a2795beb56
3 changed files with 253 additions and 1 deletions

View File

@ -1,6 +1,8 @@
<html>
<head>
<title>Generating terrain in Cuberite</title>
<title>Generating terrain in Cuberite</title>
<script src="js/ValueMap.js"></script>
<script src="js/grown.js"></script>
</head>
<body>
<h1>Generating terrain in Cuberite</h1>
@ -429,9 +431,41 @@ using the same approach as in MultiStepMap - by using a thresholded 2D Perlin no
</tr>
</table>
<p>Of further note is the existence of two sets of the IntGen classes, representing the individual operations. There are the cProtIntGen class descendants, which are used for prototyping the connections between the operations - it's easy to just chain several operations after each other and they automatically use the correct array dimensions. However, it is possible to further optimize the calculations by moving the array dimensions into template parameters (so that they are, in fact, constant from the code's point of view, and so highly optimizable). This is what the cIntGen class descendants do. Unfortunately, this optimization makes it difficult to change the operation chain - when a new operation is added or removed in the chain, the array sizes for the rest of the chain change and they all have to be updated manually. So the optimal strategy was to use the cProtIntGen classes to find out the best-looking combination of operations, and once the combination was found, to rewrite it using cIntGen classes for performance.
</p>
<br />
Here is a visualizer where you can play with the algorithm that the Grown biome generator uses. Note that rendering the map takes way longer than the zoom/smooth operation.
<table>
<tr>
<td rowspan="5">
<canvas id="stage" width="600" height="600"></canvas>
<noscript>JavaScript is needed to use the visualizer</noscript>
</td>
</tr>
<tr>
<td>
<button id="grownZoomButton" onclick="btnZoom(event.target)">Zoom</button>
</td>
</tr>
<tr>
<td>
<button onclick="btnSmooth()">Smooth</button>
</td>
</tr>
<tr>
<td>
<button onclick="btnReset()">Reset</button>
</td>
</tr>
<tr>
<td>
<button onclick="btnAutomatic(event.target)">Auto</button>
</td>
</tr>
</table>
<hr />

139
docs/js/ValueMap.js Normal file
View File

@ -0,0 +1,139 @@

const g_DistanceBetweenSquares = 0;//.01;
const g_Colors = [
"#0000FF",
"#00FF00",
"#FF0000",
"#FF00FF",
"#00FFFF",
"#FFFF00",
"#000000",
"#9BADFF"
]
class ValueMap {
constructor() {
this.values = new Uint8Array(4 * 4);
this.sizeX = 4;
this.sizeZ = 4;
this.reset();
}
reset() {
this.sizeX = 4;
this.sizeZ = 4;
this.values = new Uint8Array(this.sizeX * this.sizeZ);
for (let x = 0; x < this.sizeX; x++)
{
for (let z = 0; z < this.sizeZ; z++)
{
this.values[x + this.sizeZ * z] = Math.floor(Math.random() * 8);
}
}
}
chooseRandomNumber() {
let numArguments = arguments.length;
return arguments[Math.floor(Math.random() * arguments.length)];
}
smooth() {
let sizeZ = this.sizeZ - 2;
let sizeX = this.sizeX - 2;
let cache = new Uint8Array((this.sizeX - 2) * (this.sizeZ - 2));
for (let z = 0; z < sizeZ; z++)
{
for (let x = 0; x < sizeX; x++)
{
let val = this.values[x + 1 + (z + 1) * this.sizeX];
let above = this.values[x + 1 + z * this.sizeX];
let below = this.values[x + 1 + (z + 2) * this.sizeX];
let left = this.values[x + (z + 1) * this.sizeX];
let right = this.values[x + 2 + (z + 1) * this.sizeX];
if ((left == right) && (above == below))
{
if (Math.random() < 0.5)
{
val = left;
}
else
{
val = below;
}
}
else
{
if (left == right)
{
val = left;
}
if (above == below)
{
val = above;
}
}
cache[x + z * sizeX] = val;
}
}
this.values = cache;
this.sizeX -= 2;
this.sizeZ -= 2;
}
zoom() {
let lowStepX = (this.sizeX - 1) * 2;
let lowStepZ = (this.sizeZ - 1) * 2;
let cache = new Uint8Array(lowStepX * lowStepZ);
for (let z = 0; z < this.sizeZ - 1; z++)
{
let idx = (z * 2) * lowStepX;
let PrevZ0 = this.values[z * this.sizeX];
let PrevZ1 = this.values[(z + 1) * this.sizeX];
for (let x = 0; x < this.sizeX - 1; x++)
{
let ValX1Z0 = this.values[x + 1 + z * this.sizeX];
let ValX1Z1 = this.values[x + 1 + (z + 1) * this.sizeX];
cache[idx] = PrevZ0;
cache[idx + lowStepX] = this.chooseRandomNumber(PrevZ0, PrevZ1);
cache[idx + 1] = this.chooseRandomNumber(PrevZ0, ValX1Z0);
cache[idx + 1 + lowStepX] = this.chooseRandomNumber(PrevZ0, ValX1Z0, PrevZ1, ValX1Z1);
idx += 2;
PrevZ0 = ValX1Z0;
PrevZ1 = ValX1Z1;
}
}
this.values = cache;
this.sizeX = lowStepX;
this.sizeZ = lowStepZ;
}
visualize(context, canvas) {
context.clearRect(0, 0, canvas.width, canvas.height);
const squareSizeX = canvas.width / (this.sizeX - 1) - g_DistanceBetweenSquares;
const squareSizeY = canvas.height / (this.sizeZ - 1) - g_DistanceBetweenSquares;
for (let x = 0; x < this.sizeX - 1; x++)
{
for (let y = 0; y < this.sizeZ - 1; y++)
{
let renderX = canvas.width / (this.sizeX - 1) * x + g_DistanceBetweenSquares;
let renderY = canvas.height / (this.sizeZ - 1) * y + g_DistanceBetweenSquares;
context.fillStyle = g_Colors[this.values[x + y * this.sizeZ]];
context.fillRect(renderX, renderY, squareSizeX, squareSizeY);
}
}
context.save();
context.globalCompositeOperation = 'difference';
context.fillStyle = 'white';
context.fillText("Size: " + (this.sizeX - 1) + "x" + (this.sizeZ - 1), 5, 10);
context.restore();
}
}

79
docs/js/grown.js Normal file
View File

@ -0,0 +1,79 @@
let g_Canvas = null;
let g_Context = null;
let g_ValueMap = null;
function init() {
g_Canvas = document.getElementById("stage");
g_Context = g_Canvas.getContext("2d");
g_ValueMap = new ValueMap();
g_ValueMap.visualize(g_Context, g_Canvas);
}
function btnZoom(btn) {
g_ValueMap.zoom();
g_ValueMap.visualize(g_Context, g_Canvas);
if (
(g_ValueMap.sizeX * 2 - 1 > 600) ||
(g_ValueMap.sizeZ * 2 - 1 > 600)
) {
btn.disabled = true;
}
}
function btnSmooth() {
g_ValueMap.smooth();
g_ValueMap.visualize(g_Context, g_Canvas);
}
function btnReset() {
g_ValueMap.reset();
g_ValueMap.visualize(g_Context, g_Canvas);
document.getElementById("grownZoomButton").disabled = false;
}
function btnAutomatic(target) {
target.disabled = true;
document.getElementById("grownZoomButton").disabled = true;
// Reset the valuemap. We don't want to continue on a 500x500 map.
g_ValueMap.reset();
g_ValueMap.visualize(g_Context, g_Canvas);
const animationTimeBetween = 350;
let zoom = () => { g_ValueMap.zoom() };
let smooth = () => { g_ValueMap.smooth() };
let actions = [];
for (let i = 0; i < 6; i++) actions.push(zoom); // First zoom 6 times
for (let i = 0; i < 3; i++) actions.push(smooth); // Then smooth 3 times
for (let i = 0; i < 2; i++) actions.push(zoom); // Zoom 2 times
for (let i = 0; i < 2; i++) actions.push(smooth); // And finally smooth 2 more times.
let update = () => {
if (actions[0] == null) {
target.disabled = false;
return;
}
actions[0].call();
g_ValueMap.visualize(g_Context, g_Canvas);
actions.splice(0, 1);
setTimeout(update, animationTimeBetween);
};
setTimeout(update, animationTimeBetween + 500);
}
window.onload = init;