summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNiLSPACE <niels.breuker@hotmail.nl>2016-06-23 21:43:40 +0200
committerNiLSPACE <niels.breuker@hotmail.nl>2016-06-24 13:36:25 +0200
commita2795beb56079d3ff46ce98ce30f4e14ded29402 (patch)
tree38b34bea06e795aa4f1bea6ebc8c347a29c15dd2
parentAdded ltdTM to BACKERS (diff)
downloadcuberite-a2795beb56079d3ff46ce98ce30f4e14ded29402.tar
cuberite-a2795beb56079d3ff46ce98ce30f4e14ded29402.tar.gz
cuberite-a2795beb56079d3ff46ce98ce30f4e14ded29402.tar.bz2
cuberite-a2795beb56079d3ff46ce98ce30f4e14ded29402.tar.lz
cuberite-a2795beb56079d3ff46ce98ce30f4e14ded29402.tar.xz
cuberite-a2795beb56079d3ff46ce98ce30f4e14ded29402.tar.zst
cuberite-a2795beb56079d3ff46ce98ce30f4e14ded29402.zip
-rw-r--r--docs/Generator.html36
-rw-r--r--docs/js/ValueMap.js139
-rw-r--r--docs/js/grown.js79
3 files changed, 253 insertions, 1 deletions
diff --git a/docs/Generator.html b/docs/Generator.html
index 89dff3502..4d17826c1 100644
--- a/docs/Generator.html
+++ b/docs/Generator.html
@@ -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 />
diff --git a/docs/js/ValueMap.js b/docs/js/ValueMap.js
new file mode 100644
index 000000000..31ddba27e
--- /dev/null
+++ b/docs/js/ValueMap.js
@@ -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();
+ }
+}
diff --git a/docs/js/grown.js b/docs/js/grown.js
new file mode 100644
index 000000000..1658ce21c
--- /dev/null
+++ b/docs/js/grown.js
@@ -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;