Created
September 5, 2024 12:33
-
-
Save anotherjesse/9fc8ed64ea89145bcd7c5948c362c216 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script lang="ts"> | |
import { perlinNoise2dFactory } from "squeaker"; | |
let svgCanvas: SVGElement; | |
let numberOfColors = 1; | |
const colorOptions = [3, 4, 5]; | |
const palletes: Record<string, string[]> = { | |
reds: ["#ff0000", "#ff4040", "#ff8080", "#ffc0c0", "#ffe0e0"], | |
greens: ["#00ff00", "#40ff40", "#80ff80", "#c0ffc0", "#e0ffe0"], | |
blues: ["#0000ff", "#4040ff", "#8080ff", "#c0c0ff", "#e0e0ff"], | |
yellows: ["#ffff00", "#ffff40", "#ffff80", "#ffffc0", "#ffffe0"], | |
purples: ["#ff00ff", "#ff40ff", "#ff80ff", "#ffc0ff", "#ffe0ff"], | |
oranges: ["#ff8000", "#ffa040", "#ffc080", "#ffe0c0", "#fff0e0"], | |
pinks: ["#ff00ff", "#ff40ff", "#ff80ff", "#ffc0ff", "#ffe0ff"], | |
grays: ["#808080", "#a0a0a0", "#c0c0c0", "#e0e0e0", "#f0f0f0"], | |
rainbow: ["#ff0000", "#00ff00", "#0000ff", "#ffff00", "#ff00ff"], | |
}; | |
let colorPalette = "rainbow"; | |
interface Turtle { | |
path: string; | |
draw: (group: SVGElement) => void; | |
drawTo: (x: number, y: number) => void; | |
moveTo: (x: number, y: number) => void; | |
relativeDrawTo: (x: number, y: number) => void; | |
} | |
function newTurtle(): Turtle { | |
return { | |
path: ``, | |
draw: function (group: SVGElement) { | |
if (this.path.includes("M")) { | |
const path = document.createElementNS( | |
"http://www.w3.org/2000/svg", | |
"path", | |
); | |
path.setAttribute("d", this.path); | |
group.appendChild(path); | |
} | |
}, | |
moveTo: function (x: number, y: number) { | |
this.path += `M${x.toFixed(4)},${y.toFixed(4)} `; | |
}, | |
drawTo: function (x: number, y: number) { | |
if (this.path.length === 0) { | |
this.path += `M${x.toFixed(4)},${y.toFixed(4)} `; | |
} else { | |
this.path += `L${x.toFixed(4)},${y.toFixed(4)} `; | |
} | |
}, | |
relativeDrawTo: function (x: number, y: number) { | |
this.path += `l${x.toFixed(4)},${y.toFixed(4)} `; | |
}, | |
}; | |
} | |
function drawTurtlePath( | |
selectedNumberOfColors: number, | |
selectedColorPalette: string, | |
) { | |
svgCanvas.innerHTML = ""; | |
const colors = palletes[selectedColorPalette].slice( | |
0, | |
selectedNumberOfColors, | |
); | |
let groups = colors.map((color) => { | |
const group = document.createElementNS( | |
"http://www.w3.org/2000/svg", | |
"g", | |
); | |
group.setAttribute("id", `group-${color.substring(1)}`); | |
group.setAttribute("stroke", color); | |
group.setAttribute("stroke-width", "0.001"); | |
group.setAttribute("fill", "none"); | |
svgCanvas.appendChild(group); | |
return group; | |
}); | |
let seed = 4311214 * 1.125; | |
const noise = perlinNoise2dFactory({ seed }); | |
let scaleX = 20; | |
let scaleY = 20; | |
let stepX = 0.01; | |
let stepY = 0.01; | |
let lineLength = 0.00025; | |
let seen = {}; | |
let pointCount = 0; | |
for (let x = 0; x <= 1; x += stepX) { | |
for (let y = 0; y <= 1; y += stepY) { | |
let turtle = newTurtle(); | |
turtle.moveTo(x, y); | |
let sx = x; | |
let sy = y; | |
for (let i = 0; i < 100; i++) { | |
let noiseValue = noise(sx * scaleX, sy * scaleY); | |
let angle = noiseValue * Math.PI * 2; | |
sx += Math.cos(angle) * lineLength; | |
sy += Math.sin(angle) * lineLength; | |
let r = Math.sqrt( | |
Math.pow(sx - 0.5, 2) + Math.pow(sy - 0.5, 2), | |
); | |
if (r > 0.48) { | |
break; | |
} | |
let key = `${sx.toFixed(4)},${sy.toFixed(4)}`; | |
if (seen[key]) { | |
console.log("break"); | |
break; | |
} | |
seen[key] = true; | |
turtle.drawTo(sx, sy); | |
pointCount++; | |
} | |
turtle.draw(groups[0]); | |
} | |
} | |
console.log("pointCount", pointCount); | |
} | |
function downloadSVG() { | |
const serializer = new XMLSerializer(); | |
const svgCopy = svgCanvas.cloneNode(true) as SVGElement; | |
svgCopy.setAttribute("width", "4in"); | |
svgCopy.setAttribute("height", "4in"); | |
svgCopy.setAttribute("viewBox", "0 0 1 1"); | |
const source = serializer.serializeToString(svgCopy); | |
const blob = new Blob([source], { type: "image/svg+xml" }); | |
const url = URL.createObjectURL(blob); | |
const a = document.createElement("a"); | |
a.href = url; | |
a.download = "lines.svg"; | |
document.body.appendChild(a); | |
a.click(); | |
document.body.removeChild(a); | |
} | |
$: if (svgCanvas) drawTurtlePath(numberOfColors, colorPalette); | |
</script> | |
<div class="controls"> | |
<button | |
id="redrawButton" | |
on:click={() => drawTurtlePath(numberOfColors, colorPalette)} | |
>Redraw SVG</button | |
> | |
<select id="colorCount" bind:value={numberOfColors}> | |
{#each colorOptions as option} | |
<option value={option}>{option}</option> | |
{/each} | |
</select> | |
<select id="colorPalette" bind:value={colorPalette}> | |
{#each Object.keys(palletes) as palette} | |
<option value={palette}>{palette}</option> | |
{/each} | |
</select> | |
<button id="downloadButton" on:click={downloadSVG}>Download SVG</button> | |
</div> | |
<div id="svgContainer"> | |
<svg | |
bind:this={svgCanvas} | |
id="svgCanvas" | |
width="1000" | |
height="1000" | |
viewBox="0 0 1 1" | |
></svg> | |
</div> | |
<style> | |
#svgContainer { | |
border: 1px solid #ccc; | |
margin: 20px 0; | |
} | |
.controls { | |
margin-bottom: 20px; | |
display: flex; | |
gap: 10px; | |
align-items: center; | |
} | |
.controls select { | |
padding: 5px; | |
} | |
</style> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment