port over the project list from previous site
This commit is contained in:
parent
d99f1c3870
commit
5bee4a16b2
21 changed files with 704 additions and 17 deletions
|
@ -21,17 +21,17 @@ The world consists of a grid of cells, and you can create new cell types and ass
|
|||
The engine randomly finds places where the one of your rules' <em>input (left)</em> picture matches a place in the world, and replaces it with the <em>output (right)</em> picture.<br>
|
||||
</p>
|
||||
<p>
|
||||
<video src="snad/snake_in_maze.mp4" controls></video><br>
|
||||
<video src="snad/snake_in_maze.mp4" controls></video>
|
||||
</p>
|
||||
<h2 id="groups">groups</h2>
|
||||
<p>
|
||||
A rule can also have cells containing a <em>group</em>. On the input side, that cell will match anything in the group, and on the output side it will become a random cell from the group. A diagonal line through the group icon means that it can also match on positions outside the world, where there are no cells.<br>
|
||||
<img src="snad/group_example.png" alt=""><br>
|
||||
<img src="snad/group_example.png" alt="">
|
||||
</p>
|
||||
<h2 id="copy-cells">copy cells</h2>
|
||||
<p>
|
||||
On the output side, a cell can be set to <em>copy</em> the contents of one cell from the input side, this is useful for applying the same rule to several cell types without randomly swapping between them on the output<br>
|
||||
<img src="snad/copy_example.png" alt=""><br>
|
||||
<img src="snad/copy_example.png" alt="">
|
||||
</p>
|
||||
<h1 id="random-is-easy-right">random is easy, right?</h1>
|
||||
<p>
|
||||
|
@ -43,7 +43,7 @@ A rule can have <em>blank</em> cells. On the input side, this means it will matc
|
|||
<h2 id="worm">worm</h2>
|
||||
<p>
|
||||
Let's say I make a rule for a little worm that moves to the right, this works fine. Its head disappears out of the world because the cell in front of the head in the <em>input</em> pattern is blank, which matches anything, including out of bounds. To make it stop just before the edge, you could set that rule cell to air.<br>
|
||||
<video src="snad/worm_right.mp4" controls></video><br>
|
||||
<video src="snad/worm_right.mp4" controls></video>
|
||||
Now if I want it to do the same thing in but to the left, I can simply draw the rule and worm in the other direction, right?<br>
|
||||
<video src="snad/worm_left.mp4" controls></video><br>
|
||||
Almost. It no longer moves past the edge.<br>
|
||||
|
@ -66,7 +66,7 @@ Once at the start, the engine checks every possible position and every rule, and
|
|||
Now, how should it even use that information? The first thing I tried was to randomly pick one of all the matches. This kind of works but leads to the simulation speed being dependent on the number of matches, so for example sand falls slower if there is a lot of sand that hasn't landed yet.<br>
|
||||
</p>
|
||||
<p>
|
||||
<video src="snad/sand_fall_faster.mp4" controls></video><br>
|
||||
<video src="snad/sand_fall_faster.mp4" controls></video>
|
||||
</p>
|
||||
<p>
|
||||
I got a slightly more even result by multiplying the speed (number of rule executions per frame) by the total number of matches, but this is not a perfect solution as it does not take into account overlapping matches. A cell contained in several rule matches would be more likely to be affected, which in some cases can lead to weird bias (see <a href="#just-one-more-implementation">just one more implementation</a>)<br>
|
||||
|
@ -80,7 +80,7 @@ So now the engine picks a random position, then checks the cache and picks a ran
|
|||
I want to simulate a swarm of bees, so I create a rule for a bee moving by one cell, and enable <code>rotate</code> so it works in all directions. That was easy,<br>
|
||||
</p>
|
||||
<p>
|
||||
<video src="snad/bees_biased.mp4" controls></video><br>
|
||||
<video src="snad/bees_biased.mp4" controls></video>
|
||||
</p>
|
||||
<p>
|
||||
Oh no, the swarm is moving toward the top left instead of spreading equally. But I can see that the bees move in all four directions, so the rule seems to be rotated correctly.<br>
|
||||
|
@ -90,10 +90,14 @@ I was stuck on this problem for quite a while, and it only happened with the new
|
|||
</p>
|
||||
<p>
|
||||
The movement rule has these variants:<br>
|
||||
<img src="snad/bee_rules_rotated.png" alt=""><br>
|
||||
<img src="snad/bee_rules_rotated.png" alt="">
|
||||
</p>
|
||||
<p>
|
||||
Each rule has its origin point in the top-left cell.<br>
|
||||
In the diagram below, the bee would move left when the left (purple) cell is picked by the engine, and up when the top (green) cell is picked.<br>
|
||||
<img src="snad/bee_movement_pattern.png" alt=""><br>
|
||||
<img src="snad/bee_movement_pattern.png" alt="">
|
||||
</p>
|
||||
<p>
|
||||
But to move right or down, it needs the bees own position to be picked by the engine, since the rules always extend right-down. That means when the engine picks the bee's position, there are <em>two</em> possible rules to execute. Therefore, moving right and down both have half the probability overall compared to moving up or left.<br>
|
||||
</p>
|
||||
<h3 id="the-overlap-checker">the overlap checker</h3>
|
||||
|
@ -104,7 +108,7 @@ In this version, the cache is not only searched for matches <em>at</em> the chos
|
|||
Since the root issue was caused by asymmetry, I thought this implementation should be correct. This one was actually my first instinct when I created the cache, but as a <a href="https://gaussian.dev/">friend</a> suggested, an absolute-position based checker seemed (and indeed was) more performant and simpler, so I did that one first.<br>
|
||||
</p>
|
||||
<p>
|
||||
<video src="snad/bees.mp4" controls></video><br>
|
||||
<video src="snad/bees.mp4" controls></video>
|
||||
</p>
|
||||
<p>
|
||||
Now the bees are evenly distributed, just like real bees!<sup>(false)</sup><br>
|
||||
|
@ -114,7 +118,7 @@ Now the bees are evenly distributed, just like real bees!<sup>(false)</sup><br>
|
|||
While I was trying to figure out the reason for the directional bias in the previous implementation, I realised that the overlap checker also has biases. The probability of any given cached match to be executed is dependent on its area, which is not very intuitive. Two objects of different mass fall at different speeds, and adding empty padding to a rule makes it run faster.<br>
|
||||
</p>
|
||||
<p>
|
||||
<video src="snad/rule_size_bias.mp4" controls></video><br>
|
||||
<video src="snad/rule_size_bias.mp4" controls></video>
|
||||
</p>
|
||||
<p>
|
||||
Ideally the red, pink and blue cells should all fall at around the same rate, but the red sand always lands first and the pink last.<br>
|
||||
|
@ -125,7 +129,7 @@ Alright, the Final (I <em>hope</em>) implementation stores an origin offset for
|
|||
<h2 id="fin">fin</h2>
|
||||
<p>
|
||||
If you somehow read through all that, congratulations I think? Now enjoy this fire:<br>
|
||||
<video src="snad/fire.mp4" controls></video><br>
|
||||
<video src="snad/fire.mp4" controls></video>
|
||||
</p>
|
||||
<p>
|
||||
Download the thing and make something fun: <a href="https://git.crispypin.cc/CrispyPin/snad">git.crispypin.cc/CrispyPin/snad</a><br>
|
||||
|
|
|
@ -27,7 +27,7 @@ TODO front image<br>
|
|||
On a whim I decided to see if I could figure out what was wrong and attempt to fix it. How hard could that possibly be?<br>
|
||||
</p>
|
||||
<p>
|
||||
<img src="clock/opened_unmodified.jpg" alt="open pocket watch with many intricate gears visible"><br>
|
||||
<img src="clock/opened_unmodified.jpg" alt="open pocket watch with many intricate gears visible">
|
||||
</p>
|
||||
<h2 id="i-am-a-professional">I am a professional</h2>
|
||||
<p>
|
||||
|
@ -41,8 +41,8 @@ I stripped a short length of cable-tie into a 0.5mm diameter wire, bent it into
|
|||
And to hold the wire in place, I used hot glue because I don't <del>respect the Art of Watchmaking</del> have anything better.<br>
|
||||
</p>
|
||||
<p>
|
||||
<img src="clock/wire_inserted.jpg" alt="a small wire is inserted into the broken gearing"><br>
|
||||
<img src="clock/wire_glued.jpg" alt="the wire is now covered in hot glue"><br>
|
||||
<img src="clock/wire_inserted.jpg" alt="a small wire is inserted into the broken gearing">
|
||||
<img src="clock/wire_glued.jpg" alt="the wire is now covered in hot glue">
|
||||
</p>
|
||||
<p>
|
||||
This seems to have worked pretty well as far as i can tell from winding up the clock and poking around. That gear is not moving sideways anymore and can rotate easily.<br>
|
||||
|
@ -63,8 +63,8 @@ This is where I'm currently stuck, and most likely I'll abandon it forever. I do
|
|||
I tried to create a part that would hold the thicker(0.5mm) part of the gear, just below the bearing, but it's too tight against the rest of the parts. I'm sure that's possible with better tools but i'm out here using a cable tie wire as a drill and a plastic scrap instead of brass. It was fun but ultimately too difficult with no good tools.<br>
|
||||
</p>
|
||||
<p>
|
||||
<img src="clock/stupid_drill.jpg" alt="a small wire in a drill"><br>
|
||||
<img src="clock/plastic_doohickey_closeup.jpg" alt="a small wire in a drill"><br>
|
||||
<img src="clock/stupid_drill.jpg" alt="a small wire in a drill">
|
||||
<img src="clock/plastic_doohickey_closeup.jpg" alt="a small wire in a drill">
|
||||
</p>
|
||||
<p>
|
||||
maybe one day i will ocme back to this project and fix it, maybe not. It was fun anyway<br>
|
||||
|
|
|
@ -31,6 +31,7 @@ this means it is<br>
|
|||
- <a href="/cellthing">snad (cell thing)</a><br>
|
||||
- <a href="/keyboards-are-fun">keyboar</a><br>
|
||||
- <a href="/humans/distracting">distracting</a><br>
|
||||
- <a href="/projects/old-project-list">old projects</a><br>
|
||||
- todo: put more words in the computer<br>
|
||||
</p>
|
||||
<p>
|
||||
|
|
BIN
site/projects/fractal_tree.png
Normal file
BIN
site/projects/fractal_tree.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 123 KiB |
67
site/projects/fractals.css
Normal file
67
site/projects/fractals.css
Normal file
|
@ -0,0 +1,67 @@
|
|||
.demo-render {
|
||||
margin: 20px;
|
||||
width: min-content;
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
.controlbar {
|
||||
background-color: #181818;
|
||||
padding: 2px 5px;
|
||||
height: max-content;
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
align-content: stretch;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.controlbar button,
|
||||
.controlbar input {
|
||||
padding: 4px 6px;
|
||||
height: 25px;
|
||||
border: 1px solid #000;
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
-webkit-box-flex: 1;
|
||||
-ms-flex-positive: 1;
|
||||
flex-grow: 1;
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
.controlbar label {
|
||||
background-color: #181818;
|
||||
color: #fb8;
|
||||
margin: 2px;
|
||||
font-family: 'Consolas', 'Courier New', Courier, monospace;
|
||||
}
|
||||
|
||||
.clickable,
|
||||
nav li,
|
||||
.controlbar button,
|
||||
.controlbar input {
|
||||
border-radius: 2px;
|
||||
background-color: #4a4;
|
||||
}
|
||||
|
||||
.clickable:hover,
|
||||
nav li:hover,
|
||||
.controlbar button:hover,
|
||||
.controlbar input:hover {
|
||||
background-color: #4af;
|
||||
}
|
||||
|
||||
.clickable:active,
|
||||
nav li:active,
|
||||
.controlbar button:active,
|
||||
.controlbar input:active {
|
||||
background-color: #46c;
|
||||
}
|
||||
|
||||
.clickable:disabled,
|
||||
nav li:disabled,
|
||||
.controlbar button:disabled,
|
||||
.controlbar input:disabled {
|
||||
background-color: #8b8;
|
||||
text-decoration: line-through;
|
||||
color: #000;
|
||||
cursor: default;
|
||||
}
|
78
site/projects/fractals.html
Normal file
78
site/projects/fractals.html
Normal file
|
@ -0,0 +1,78 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>something silly - fractals</title>
|
||||
<link rel="stylesheet" href="/style.css">
|
||||
<link rel="icon" type="image/x-icon" href="/cretin.png">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1 id="fractals">Fractals</h1>
|
||||
<p>
|
||||
<code>2021-05-26</code><br>
|
||||
This page is a collection of fractal renderers I made in JS for a school assignment back in 2020.<br>
|
||||
This is the <em>only</em> JS on this website.<br>
|
||||
</p>
|
||||
<h2 id="tree-fractal">Tree fractal</h2>
|
||||
<p>
|
||||
The splitting angle is defined by the mouses x position. The number of iterations is set in the first input field (default is 12). Each branch is slightly smaller than its parent, the factor is the second input field. Click the canvas to pause/resume the image.<br>
|
||||
<div class="demo-render" id="fractal-tree">
|
||||
<canvas></canvas>
|
||||
<div class="controlbar">
|
||||
<input type="number" min=6 max=15 value=12 onchange="fractalTree.setIter(value)">
|
||||
<input type="number" min=0.65 max=1 step=0.025 value=0.75 onchange="fractalTree.setMod(value)">
|
||||
</div>
|
||||
</div>
|
||||
</p>
|
||||
<h2 id="mandelbrot-set">Mandelbrot set</h2>
|
||||
<p>
|
||||
<a href="https://en.wikipedia.org/wiki/Mandelbrot_set">Wikipedia page</a><br>
|
||||
You can click to zoom and set the iteration count in the input field.<br>
|
||||
<div class="demo-render" id="mandelbrot">
|
||||
<canvas></canvas>
|
||||
<div class="controlbar">
|
||||
<button type="button" onclick="mandelbrot.reset()">Reset</button>
|
||||
<input type="number" min=1 max=256 value=48 onchange="mandelbrot.setIter(value)">
|
||||
</div>
|
||||
</div>
|
||||
</p>
|
||||
<h2 id="multibrot-set">Multibrot set</h2>
|
||||
<p>
|
||||
<a href="https://en.wikipedia.org/wiki/Multibrot_set">Wikipedia page</a><br>
|
||||
You can click to zoom and set the iteration count in the input field. The second input controls the exponent.<br>
|
||||
</p>
|
||||
<p>
|
||||
This is quite slow to render.<br>
|
||||
<div class="demo-render" id="multibrot">
|
||||
<canvas></canvas>
|
||||
<div class="controlbar">
|
||||
<button type="button" onclick="multibrot.reset()">Render</button>
|
||||
<input type="number" min=1 max=256 value=48 onchange="multibrot.setIter(value)">
|
||||
<input type="number" min=0 max=32 step=0.1 value=4 onchange="multibrot.setPower(value)">
|
||||
</div>
|
||||
</div>
|
||||
</p>
|
||||
<h2 id="interactive-julia-set">Interactive Julia set</h2>
|
||||
<p>
|
||||
<a href="https://en.wikipedia.org/wiki/Julia_set">Wikipedia page</a><br>
|
||||
You can set the iteration count in the input field.<br>
|
||||
<div class="demo-render" id="julia-set">
|
||||
<canvas></canvas>
|
||||
<div class="controlbar">
|
||||
<button type="button" onclick="juliaSet.reset()">Reset</button>
|
||||
<input type="number" min=8 max=1000 value=80 step=8 onchange="juliaSet.setIter(value)">
|
||||
</div>
|
||||
</div>
|
||||
</p>
|
||||
<p>
|
||||
<script src="/projects/fractals.js"></script>
|
||||
<link rel="stylesheet" href="/projects/fractal.css">
|
||||
|
||||
</main>
|
||||
</body>
|
||||
|
||||
</html>
|
264
site/projects/fractals.js
Normal file
264
site/projects/fractals.js
Normal file
|
@ -0,0 +1,264 @@
|
|||
'use strict'
|
||||
// I made this in school and I had to 'use strict'.
|
||||
// I no longer use JS.
|
||||
|
||||
class FractalTree {
|
||||
constructor(id, iterations, mod) {
|
||||
this.app = document.getElementById(id);
|
||||
console.debug(this.app);
|
||||
this.canvas = this.app.getElementsByTagName("canvas")[0];
|
||||
this.canvas.width = 512;
|
||||
this.canvas.height = 448;
|
||||
this.ctx = this.canvas.getContext("2d");
|
||||
|
||||
this.iterations = iterations;
|
||||
this.mod = mod;
|
||||
this.angle = Math.PI * 0.2;
|
||||
|
||||
this.update = true;
|
||||
|
||||
this.palette = ["#430", "#440", "#450", "#460", "#470", "#480", "#080"];
|
||||
|
||||
this.canvas.addEventListener("mousemove", this.mouseMove.bind(this));
|
||||
this.canvas.addEventListener("mousedown", this.click.bind(this));
|
||||
}
|
||||
|
||||
static vector(l, angle) {
|
||||
return {
|
||||
x: Math.cos(angle) * l,
|
||||
y: Math.sin(angle) * l
|
||||
};
|
||||
}
|
||||
|
||||
tree(x, y, length, i, angle) {
|
||||
if (i==0) { return; }
|
||||
|
||||
let dir = FractalTree.vector(length * this.mod, angle + this.angle);
|
||||
|
||||
this.ctx.beginPath();
|
||||
this.ctx.moveTo(x, y);
|
||||
this.ctx.lineWidth = i;
|
||||
this.ctx.strokeStyle = this.palette[this.iterations-i];
|
||||
this.ctx.lineTo(x + dir.x, y - dir.y);
|
||||
this.ctx.stroke();
|
||||
|
||||
this.tree(x + dir.x, y - dir.y, length*this.mod, i-1, angle - this.angle);
|
||||
this.tree(x + dir.x, y - dir.y, length*this.mod, i-1, angle + this.angle);
|
||||
}
|
||||
|
||||
render() {
|
||||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
this.tree(256, this.canvas.height-64, 128, this.iterations, Math.PI/2 - this.angle);
|
||||
}
|
||||
|
||||
mouseMove(event) {
|
||||
if (this.update) {
|
||||
let x = event.clientX - this.canvas.getBoundingClientRect().left;
|
||||
this.angle = x * Math.PI / this.canvas.width;
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
|
||||
click(event) {
|
||||
this.update = !this.update;
|
||||
if (this.update) {
|
||||
let x = event.clientX - this.canvas.getBoundingClientRect().left;
|
||||
this.angle = x * Math.PI / this.canvas.width;
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
|
||||
setIter(i) {
|
||||
this.iterations = i;
|
||||
this.render();
|
||||
}
|
||||
|
||||
setMod(m) {
|
||||
this.mod = m;
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
|
||||
class Mandelbrot {
|
||||
constructor(id, iter=255, width=512, height=512, minX=-2, minY=-1.5, maxX=1, maxY=1.5) {
|
||||
this.app = document.getElementById(id);
|
||||
this.canvas = this.app.getElementsByTagName("canvas")[0];
|
||||
this.canvas.width = width;
|
||||
this.canvas.height = height;
|
||||
this.ctx = this.canvas.getContext("2d");
|
||||
|
||||
this.renderBtn = this.app.getElementsByClassName("controlbar")[0].getElementsByTagName("button")[0];
|
||||
|
||||
this.init(minX, minY, maxX, maxY);
|
||||
this.defaultZoom = [minX, minY, maxX, maxY];
|
||||
this.iter = iter;
|
||||
|
||||
this.palette = [16, 32, 48, 56, 64, 72, 80, 88, 96, 104, 112, 120, 128, 136, 144, 152, 160, 168, 176, 184, 192, 200, 208, 216, 224, 232, 240, 248];
|
||||
|
||||
this.canvas.addEventListener("mousedown", this.zoom.bind(this));
|
||||
}
|
||||
|
||||
zoom(event) {
|
||||
//zoom function
|
||||
let zoomf = 8;
|
||||
let x = this.reScaleX(event.clientX - this.canvas.getBoundingClientRect().left);
|
||||
let y = this.reScaleY(event.clientY - this.canvas.getBoundingClientRect().top);
|
||||
let dx = (this.maxX - this.minX)/zoomf;
|
||||
let dy = (this.maxY - this.minY)/zoomf;
|
||||
this.init(x - dx, y - dy, x + dx, y + dy);
|
||||
this.render();
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.init(this.defaultZoom[0], this.defaultZoom[1], this.defaultZoom[2], this.defaultZoom[3]);
|
||||
this.render();
|
||||
}
|
||||
|
||||
setIter(x) {
|
||||
this.iter = x;
|
||||
this.render();
|
||||
}
|
||||
|
||||
init(minX, minY, maxX, maxY) {
|
||||
this.minX = minX;
|
||||
this.minY = minY;
|
||||
this.maxX = maxX;
|
||||
this.maxY = maxY;
|
||||
|
||||
this.scaleX = (maxX - minX) / this.canvas.width;
|
||||
this.scaleY = (maxY - minY) / this.canvas.height;
|
||||
}
|
||||
|
||||
reScaleX(x) {
|
||||
return x * this.scaleX + this.minX;
|
||||
}
|
||||
|
||||
reScaleY(y) {
|
||||
return y * this.scaleY + this.minY;
|
||||
}
|
||||
|
||||
point(x0, y0) {
|
||||
let x = 0, y = 0;
|
||||
let i = 0;
|
||||
let x2 = 0, y2 = 0;
|
||||
while (x2 + y2 <= 4 && i < this.iter) {
|
||||
y = 2*x*y + y0;
|
||||
x = x2 - y2 + x0;
|
||||
x2 = x*x;
|
||||
y2 = y*y;
|
||||
i++;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
render() {
|
||||
let start = new Date();
|
||||
let result = this.ctx.getImageData(0,0, this.canvas.width, this.canvas.height);
|
||||
let px = 0;
|
||||
let yp;
|
||||
for (let yPos = 0; yPos < this.canvas.height; yPos++) {
|
||||
yp = this.reScaleY(yPos);
|
||||
for (let xPos = 0; xPos < this.canvas.width; xPos++) {
|
||||
let i = this.point(this.reScaleX(xPos), yp);
|
||||
if (i != this.iter) {
|
||||
let col = this.palette[Math.min(i, this.palette.length-1)]
|
||||
result.data[px] = 0;
|
||||
result.data[px+1] = col;
|
||||
result.data[px+2] = col;
|
||||
} else {
|
||||
result.data[px] = 0;
|
||||
result.data[px+1] = 0;
|
||||
result.data[px+2] = 0;
|
||||
}
|
||||
result.data[px+3] = 255;
|
||||
px += 4;
|
||||
}
|
||||
}
|
||||
this.ctx.putImageData(result, 0, 0);
|
||||
this.renderBtn.textContent = `Reset ${new Date() - start}ms`;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Multibrot extends Mandelbrot {
|
||||
constructor(id, power, iter=255, width=512, height=512, minX=-2, minY=-2, maxX=2, maxY=2) {
|
||||
super(id, iter, width, height, minX, minY, maxX, maxY);
|
||||
this.power = power;
|
||||
}
|
||||
|
||||
point(x0, y0) {
|
||||
let x = 0, y = 0;
|
||||
let i = 0;
|
||||
let xtmp, xxyyn, atn;
|
||||
let n2 = this.power/2;
|
||||
while (x*x + y*y <= 4 && i < this.iter) {
|
||||
xxyyn = (x*x+y*y)**(n2);
|
||||
atn = this.power*Math.atan2(y, x);
|
||||
xtmp = xxyyn * Math.cos(atn) + x0;
|
||||
y = xxyyn * Math.sin(atn) + y0;
|
||||
x = xtmp;
|
||||
i++;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
setPower(x) {
|
||||
this.power = x;
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
|
||||
class Julia extends Mandelbrot {
|
||||
constructor(id, iter=255, width=512, height=512, minX=-2, minY=-2, maxX=2, maxY=2) {
|
||||
super(id, iter, width, height, minX, minY, maxX, maxY);
|
||||
this.cx = this.reScaleX(width/2);
|
||||
this.cy = this.reScaleY(height/3.2);
|
||||
|
||||
this.update = true;
|
||||
|
||||
this.canvas.removeEventListener("mousedown", this.zoom);
|
||||
|
||||
this.canvas.addEventListener("mousedown", this.click.bind(this));
|
||||
this.canvas.addEventListener("mousemove", this.mouseMove.bind(this));
|
||||
}
|
||||
|
||||
click(event) {
|
||||
this.update = !this.update;
|
||||
this.mouseMove(event);
|
||||
}
|
||||
|
||||
zoom() {
|
||||
return;
|
||||
}
|
||||
|
||||
mouseMove(event) {
|
||||
if (this.update) {
|
||||
this.cx = this.reScaleX(event.clientX - this.canvas.getBoundingClientRect().left);
|
||||
this.cy = this.reScaleY(event.clientY - this.canvas.getBoundingClientRect().top);
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
|
||||
point(zx, zy) {
|
||||
let i = 0, xtmp = 0;
|
||||
let zx2 = zx*zx, zy2 = zy*zy;
|
||||
while (zx2 + zy2 < 4 && i < this.iter) {
|
||||
xtmp = zx2 - zy2;
|
||||
zy = 2*zx*zy + this.cy;
|
||||
zx = xtmp + this.cx;
|
||||
zx2 = zx*zx;
|
||||
zy2 = zy*zy;
|
||||
i++;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
let fractalTree = new FractalTree("fractal-tree", 12, 0.75);
|
||||
let mandelbrot = new Mandelbrot("mandelbrot");
|
||||
let multibrot = new Multibrot("multibrot", 4);
|
||||
let juliaSet = new Julia("julia-set", 64, 512, 512);
|
||||
|
||||
fractalTree.render();
|
||||
mandelbrot.render();
|
||||
juliaSet.render();
|
BIN
site/projects/gol.png
Normal file
BIN
site/projects/gol.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.3 KiB |
77
site/projects/old-project-list.html
Normal file
77
site/projects/old-project-list.html
Normal file
|
@ -0,0 +1,77 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>something silly - old-project-list</title>
|
||||
<link rel="stylesheet" href="/style.css">
|
||||
<link rel="icon" type="image/x-icon" href="/cretin.png">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1 id="projects">projects</h1>
|
||||
<p>
|
||||
<code>2024-07-06</code><br>
|
||||
Some older projects that were created before this blog<br>
|
||||
</p>
|
||||
<h3 id="voxel-engine-in-godot-3--rust">Voxel engine in Godot 3 + Rust</h3>
|
||||
<p>
|
||||
start: <code>2022-04-19</code><br>
|
||||
last update: <code>2022-05-05</code><br>
|
||||
source: <a href="https://github.com/CrispyPin/gd-voxel-rs">github.com/CrispyPin/gd-voxel-rs</a><br>
|
||||
</p>
|
||||
<p>
|
||||
I made a voxel engine in rust to learn rust and godot-rust as well as explore voxel systems. It has (practically) infinite terrain generation, lets you place and remove voxels and supports transparent voxel types. The main point was to develop an optimised meshing algorithm.<br>
|
||||
<video src="/projects/voxel_media/demo.mp4" controls></video>
|
||||
This one I actually wrote a post about <a href="/projects/voxels">here</a><br>
|
||||
</p>
|
||||
<h3 id="ovr-utils">OVR Utils</h3>
|
||||
<p>
|
||||
start: <code>2021-05-14</code><br>
|
||||
last update: <code>2022-05-08</code><br>
|
||||
source: <a href="https://github.com/CrispyPin/ovr-utils">github.com/CrispyPin/ovr-utils</a><br>
|
||||
</p>
|
||||
<p>
|
||||
OVR Utils is a VR overlay application that has some useful tools for SteamVR.<br>
|
||||
I wanted an overlay that could tell me the time without having to open the steam dashboard, and to see the battery levels of my controllers easily, but couldn't find one for free that also had Linux support. So I decided to create my own, and also wrote down a long list of other useful tools. So far only a few of these have been implemented, such as the image overlay and the keyboard.<br>
|
||||
</p>
|
||||
<h3 id="raymarched-voxel-rendering">Raymarched voxel rendering</h3>
|
||||
<p>
|
||||
start: <code>2021-04-18</code><br>
|
||||
last update: <code>2021-04-29</code><br>
|
||||
source: <a href="https://github.com/CrispyPin/voxel-raymarcher">github.com/CrispyPin/voxel-raymarcher</a><br>
|
||||
</p>
|
||||
<p>
|
||||
A raymarched voxel renderer made with a Godot shader.<br>
|
||||
<img src="/projects/raymarched-voxels.png" alt="">
|
||||
</p>
|
||||
<h3 id="game-of-life-wallpaper">Game of Life wallpaper</h3>
|
||||
<p>
|
||||
start: <code>2021-03-21</code><br>
|
||||
last update: <code>2022-02-22</code><br>
|
||||
source: <a href="https://github.com/CrispyPin/gol-wallpaper/">github.com/CrispyPin/gol-wallpaper</a><br>
|
||||
</p>
|
||||
<p>
|
||||
This is hosted on github pages at <a href="https://gol.crispypin.cc/?cellsize=5&time=2&margin=0&populate=true">gol.crispypin.cc</a><br>
|
||||
<img src="/projects/gol.png" alt="screenshot of conways game of life">
|
||||
</p>
|
||||
<h3 id="fractals">Fractals</h3>
|
||||
<p>
|
||||
start: <code>2020-03-04</code><br>
|
||||
last update: <code>2020-05-19</code><br>
|
||||
source: <a href="https://github.com/CrispyPin/Webb-1-Projekt/">github.com/CrispyPin/Webb-1-Projekt/</a><br>
|
||||
</p>
|
||||
<p>
|
||||
While I was learning web development and JavaScript I made a page with fractals.<br>
|
||||
It has now been moved <a href="/projects/fractals">here</a><br>
|
||||
<img src="/projects/fractal_tree.png" alt="">
|
||||
</p>
|
||||
<p>
|
||||
back to <a href="/">more recent stuff</a><br>
|
||||
|
||||
</main>
|
||||
</body>
|
||||
|
||||
</html>
|
BIN
site/projects/raymarched-voxels.png
Normal file
BIN
site/projects/raymarched-voxels.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 53 KiB |
BIN
site/projects/voxel_media/demo.mp4
Normal file
BIN
site/projects/voxel_media/demo.mp4
Normal file
Binary file not shown.
BIN
site/projects/voxel_media/demo2.mp4
Normal file
BIN
site/projects/voxel_media/demo2.mp4
Normal file
Binary file not shown.
BIN
site/projects/voxel_media/fig1.png
Normal file
BIN
site/projects/voxel_media/fig1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
BIN
site/projects/voxel_media/fig2.png
Normal file
BIN
site/projects/voxel_media/fig2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 23 KiB |
BIN
site/projects/voxel_media/fig3.png
Normal file
BIN
site/projects/voxel_media/fig3.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 23 KiB |
60
site/projects/voxels.html
Normal file
60
site/projects/voxels.html
Normal file
|
@ -0,0 +1,60 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>something silly - voxels</title>
|
||||
<link rel="stylesheet" href="/style.css">
|
||||
<link rel="icon" type="image/x-icon" href="/cretin.png">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1 id="voxel-engine-in-godot-3-with-rust">Voxel engine in Godot 3 with Rust</h1>
|
||||
<p>
|
||||
<code>2022-05-05</code><br>
|
||||
I made a voxel engine in rust to learn rust and godot-rust as well as explore voxel systems. It has (practically) infinite terrain generation, lets you place and remove voxels and supports transparent voxel types. The main point was to develop an optimised meshing algorithm.<br>
|
||||
</p>
|
||||
<p>
|
||||
<video src="./voxel_media/demo.mp4" controls></video>
|
||||
Demo of fast terrain generation<br>
|
||||
<video src="./voxel_media/demo2.mp4" controls></video>
|
||||
Optimised mesh visualisation<br>
|
||||
</p>
|
||||
<h2 id="optimisedgreedy-mesh-algorithm">Optimised/greedy mesh algorithm</h2>
|
||||
<p>
|
||||
editor's note: This writeup was never finished :D<br>
|
||||
</p>
|
||||
<p>
|
||||
The algorithm I'm using is one I made myself, inspired by a few others. I could not find an easy to understand explanation of how to do it but <a href="https://vercidium.com/blog/voxel-world-optimisations/">this artice</a> and <a href="https://0fps.net/2012/06/30/meshing-in-a-minecraft-game/">this article</a> gave me somewhere to start.<br>
|
||||
</p>
|
||||
<p>
|
||||
In this explanation I will assume basic knowledge of how meshes work and how to do the simplest form of culling for voxel meshes.<br>
|
||||
<img src="./voxel_media/fig1.png" alt=""><br>
|
||||
fig. 1: example set of voxels, with the simplest form of mesh generation applied.<br>
|
||||
</p>
|
||||
<p>
|
||||
To start off, we can break down the problem to 2 dimensions by recognising that each direction along the axis as well as each layer along those directions is independent. We then only have to process a single 2D slice of the voxel domain at once.<br>
|
||||
</p>
|
||||
<p>
|
||||
<img src="./voxel_media/fig2.png" alt=""><br>
|
||||
fig. 2: The algorithm only needs to consider one layer and direction at a time, highlighted in green<br>
|
||||
</p>
|
||||
<p>
|
||||
The first step is to generate "strips", essentially create long quads that cover connected voxels. We can loop through the plane and keep track of at most one active strip. The active strip is the last one started and we grow it as more voxels under it are traversed. In the inner loop we check the voxel at that position as well as the one above. With this we can determine if we need to stop the current strip, start a new one or do nothing. If there is not currently an active strip, a new one should be created when the voxel below is filled and the one above is empty (an exposed surface).<br>
|
||||
</p>
|
||||
<p>
|
||||
<img src="./voxel_media/fig3.png" alt=""><br>
|
||||
fig.3: Long strips of adjacent voxels can be merged into fewer, long quads.<br>
|
||||
</p>
|
||||
<p>
|
||||
.<br>
|
||||
</p>
|
||||
<p>
|
||||
this was incomprehensible, take me <a href="/">home</a><br>
|
||||
|
||||
</main>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -25,7 +25,7 @@ A rule can have *blank* cells. On the input side, this means it will match anyth
|
|||
Let's say I make a rule for a little worm that moves to the right, this works fine. Its head disappears out of the world because the cell in front of the head in the *input* pattern is blank, which matches anything, including out of bounds. To make it stop just before the edge, you could set that rule cell to air.
|
||||
<video src="snad/worm_right.mp4" controls></video>
|
||||
Now if I want it to do the same thing in but to the left, I can simply draw the rule and worm in the other direction, right?
|
||||
<video src="snad/worm_left.mp4" controls></video>
|
||||
<video src="snad/worm_left.mp4" controls></video><br>
|
||||
Almost. It no longer moves past the edge.
|
||||
|
||||
When a rule and position is chosen, the top-left of the rule is aligned with that position. In the example above, the rule would have to start from outside the world to make the worm continue one more step.
|
||||
|
@ -58,9 +58,11 @@ I was stuck on this problem for quite a while, and it only happened with the new
|
|||
|
||||
The movement rule has these variants:
|
||||
<img src="snad/bee_rules_rotated.png" alt="">
|
||||
|
||||
Each rule has its origin point in the top-left cell.
|
||||
In the diagram below, the bee would move left when the left (purple) cell is picked by the engine, and up when the top (green) cell is picked.
|
||||
<img src="snad/bee_movement_pattern.png" alt="">
|
||||
|
||||
But to move right or down, it needs the bees own position to be picked by the engine, since the rules always extend right-down. That means when the engine picks the bee's position, there are *two* possible rules to execute. Therefore, moving right and down both have half the probability overall compared to moving up or left.
|
||||
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ this means it is
|
|||
- [snad (cell thing)](/cellthing)
|
||||
- [keyboar](/keyboards-are-fun)
|
||||
- [distracting](/humans/distracting)
|
||||
- [old projects](/projects/old-project-list)
|
||||
- todo: put more words in the computer
|
||||
|
||||
[other sites](/internets)
|
||||
|
|
53
write/projects/fractals.md
Normal file
53
write/projects/fractals.md
Normal file
|
@ -0,0 +1,53 @@
|
|||
# Fractals
|
||||
`2021-05-26`
|
||||
This page is a collection of fractal renderers I made in JS for a school assignment back in 2020.
|
||||
This is the *only* JS on this website.
|
||||
|
||||
## Tree fractal
|
||||
The splitting angle is defined by the mouses x position. The number of iterations is set in the first input field (default is 12). Each branch is slightly smaller than its parent, the factor is the second input field. Click the canvas to pause/resume the image.
|
||||
<div class="demo-render" id="fractal-tree">
|
||||
<canvas></canvas>
|
||||
<div class="controlbar">
|
||||
<input type="number" min=6 max=15 value=12 onchange="fractalTree.setIter(value)">
|
||||
<input type="number" min=0.65 max=1 step=0.025 value=0.75 onchange="fractalTree.setMod(value)">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
## Mandelbrot set
|
||||
[Wikipedia page](https://en.wikipedia.org/wiki/Mandelbrot_set)
|
||||
You can click to zoom and set the iteration count in the input field.
|
||||
<div class="demo-render" id="mandelbrot">
|
||||
<canvas></canvas>
|
||||
<div class="controlbar">
|
||||
<button type="button" onclick="mandelbrot.reset()">Reset</button>
|
||||
<input type="number" min=1 max=256 value=48 onchange="mandelbrot.setIter(value)">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
## Multibrot set
|
||||
[Wikipedia page](https://en.wikipedia.org/wiki/Multibrot_set)
|
||||
You can click to zoom and set the iteration count in the input field. The second input controls the exponent.
|
||||
|
||||
This is quite slow to render.
|
||||
<div class="demo-render" id="multibrot">
|
||||
<canvas></canvas>
|
||||
<div class="controlbar">
|
||||
<button type="button" onclick="multibrot.reset()">Render</button>
|
||||
<input type="number" min=1 max=256 value=48 onchange="multibrot.setIter(value)">
|
||||
<input type="number" min=0 max=32 step=0.1 value=4 onchange="multibrot.setPower(value)">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
## Interactive Julia set
|
||||
[Wikipedia page](https://en.wikipedia.org/wiki/Julia_set)
|
||||
You can set the iteration count in the input field.
|
||||
<div class="demo-render" id="julia-set">
|
||||
<canvas></canvas>
|
||||
<div class="controlbar">
|
||||
<button type="button" onclick="juliaSet.reset()">Reset</button>
|
||||
<input type="number" min=8 max=1000 value=80 step=8 onchange="juliaSet.setIter(value)">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/projects/fractals.js"></script>
|
||||
<link rel="stylesheet" href="/projects/fractal.css">
|
49
write/projects/old-project-list.md
Normal file
49
write/projects/old-project-list.md
Normal file
|
@ -0,0 +1,49 @@
|
|||
# projects
|
||||
`2024-07-06`
|
||||
Some older projects that were created before this blog
|
||||
|
||||
### Voxel engine in Godot 3 + Rust
|
||||
start: `2022-04-19`
|
||||
last update: `2022-05-05`
|
||||
source: [github.com/CrispyPin/gd-voxel-rs](https://github.com/CrispyPin/gd-voxel-rs)
|
||||
|
||||
I made a voxel engine in rust to learn rust and godot-rust as well as explore voxel systems. It has (practically) infinite terrain generation, lets you place and remove voxels and supports transparent voxel types. The main point was to develop an optimised meshing algorithm.
|
||||
<video src="/projects/voxel_media/demo.mp4" controls></video>
|
||||
This one I actually wrote a post about [here](/projects/voxels)
|
||||
|
||||
### OVR Utils
|
||||
start: `2021-05-14`
|
||||
last update: `2022-05-08`
|
||||
source: [github.com/CrispyPin/ovr-utils](https://github.com/CrispyPin/ovr-utils)
|
||||
|
||||
OVR Utils is a VR overlay application that has some useful tools for SteamVR.
|
||||
I wanted an overlay that could tell me the time without having to open the steam dashboard, and to see the battery levels of my controllers easily, but couldn't find one for free that also had Linux support. So I decided to create my own, and also wrote down a long list of other useful tools. So far only a few of these have been implemented, such as the image overlay and the keyboard.
|
||||
|
||||
### Raymarched voxel rendering
|
||||
start: `2021-04-18`
|
||||
last update: `2021-04-29`
|
||||
source: [github.com/CrispyPin/voxel-raymarcher](https://github.com/CrispyPin/voxel-raymarcher)
|
||||
|
||||
A raymarched voxel renderer made with a Godot shader.
|
||||
<img src="/projects/raymarched-voxels.png" alt="">
|
||||
|
||||
### Game of Life wallpaper
|
||||
start: `2021-03-21`
|
||||
last update: `2022-02-22`
|
||||
source: [github.com/CrispyPin/gol-wallpaper](https://github.com/CrispyPin/gol-wallpaper/)
|
||||
|
||||
This is hosted on github pages at [gol.crispypin.cc](https://gol.crispypin.cc/?cellsize=5&time=2&margin=0&populate=true)
|
||||
<img src="/projects/gol.png" alt="screenshot of conways game of life">
|
||||
|
||||
### Fractals
|
||||
start: `2020-03-04`
|
||||
last update: `2020-05-19`
|
||||
source: [github.com/CrispyPin/Webb-1-Projekt/](https://github.com/CrispyPin/Webb-1-Projekt/)
|
||||
|
||||
While I was learning web development and JavaScript I made a page with fractals.
|
||||
It has now been moved [here](/projects/fractals)
|
||||
<img src="/projects/fractal_tree.png" alt="">
|
||||
|
||||
|
||||
|
||||
back to [more recent stuff](/)
|
31
write/projects/voxels.md
Normal file
31
write/projects/voxels.md
Normal file
|
@ -0,0 +1,31 @@
|
|||
# Voxel engine in Godot 3 with Rust
|
||||
`2022-05-05`
|
||||
I made a voxel engine in rust to learn rust and godot-rust as well as explore voxel systems. It has (practically) infinite terrain generation, lets you place and remove voxels and supports transparent voxel types. The main point was to develop an optimised meshing algorithm.
|
||||
|
||||
<video src="./voxel_media/demo.mp4" controls></video>
|
||||
Demo of fast terrain generation
|
||||
<video src="./voxel_media/demo2.mp4" controls></video>
|
||||
Optimised mesh visualisation
|
||||
|
||||
## Optimised/greedy mesh algorithm
|
||||
editor's note: This writeup was never finished :D
|
||||
|
||||
The algorithm I'm using is one I made myself, inspired by a few others. I could not find an easy to understand explanation of how to do it but [this artice](https://vercidium.com/blog/voxel-world-optimisations/) and [this article](https://0fps.net/2012/06/30/meshing-in-a-minecraft-game/) gave me somewhere to start.
|
||||
|
||||
In this explanation I will assume basic knowledge of how meshes work and how to do the simplest form of culling for voxel meshes.
|
||||
<img src="./voxel_media/fig1.png" alt=""><br>
|
||||
fig. 1: example set of voxels, with the simplest form of mesh generation applied.
|
||||
|
||||
To start off, we can break down the problem to 2 dimensions by recognising that each direction along the axis as well as each layer along those directions is independent. We then only have to process a single 2D slice of the voxel domain at once.
|
||||
|
||||
<img src="./voxel_media/fig2.png" alt=""><br>
|
||||
fig. 2: The algorithm only needs to consider one layer and direction at a time, highlighted in green
|
||||
|
||||
The first step is to generate "strips", essentially create long quads that cover connected voxels. We can loop through the plane and keep track of at most one active strip. The active strip is the last one started and we grow it as more voxels under it are traversed. In the inner loop we check the voxel at that position as well as the one above. With this we can determine if we need to stop the current strip, start a new one or do nothing. If there is not currently an active strip, a new one should be created when the voxel below is filled and the one above is empty (an exposed surface).
|
||||
|
||||
<img src="./voxel_media/fig3.png" alt=""><br>
|
||||
fig.3: Long strips of adjacent voxels can be merged into fewer, long quads.
|
||||
|
||||
.
|
||||
|
||||
this was incomprehensible, take me [home](/)
|
Loading…
Reference in a new issue