From cf3b913530ee25ef47454b96e19c3c64928ee5ee Mon Sep 17 00:00:00 2001 From: EJ Fox Date: Thu, 15 Aug 2024 19:00:05 -0400 Subject: [PATCH] feat: add basin viz --- package.json | 5 ++ pages/playground/001.vue | 180 ++++++++++++++++++++++++++++++++++++++- yarn.lock | 22 +++++ 3 files changed, 203 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 5761c12ba..c06cbfd44 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "dependencies": { "@anthropic-ai/sdk": "^0.25.2", "@ejfox/gpt-browser": "^2.0.0", + "@iconify-json/ei": "^1.1.9", "@nuxt/content": "^2.13.2", "@nuxt/ui": "^2.18.4", "@nuxtjs/google-fonts": "^3.2.0", @@ -37,10 +38,13 @@ "d3-voronoi": "^1.1.4", "date-fns": "^3.6.0", "eslint-config-prettier": "^9.1.0", + "fast-simplex-noise": "^4.0.0", "front-matter": "^4.0.2", "inquirer": "^10.1.8", "js-md5": "^0.8.3", "nuxt-gtag": "^2.1.0", + "p-limit": "^6.1.0", + "p5": "^1.10.0", "rehype-external-links": "^3.0.0", "remark-emoji": "^5.0.1", "remark-external-links": "^9.0.1", @@ -50,6 +54,7 @@ "remark-unwrap-images": "^4.0.0", "remark-wiki-link": "^2.0.1", "rss": "^1.2.2", + "simplex-noise": "^4.0.3", "unicode-string": "^2.1.0", "unicodechar-string": "^1.0.0", "uuid": "^10.0.0", diff --git a/pages/playground/001.vue b/pages/playground/001.vue index 92a998f0e..1cf0bdefc 100644 --- a/pages/playground/001.vue +++ b/pages/playground/001.vue @@ -1,11 +1,183 @@ +const { width, height } = useWindowSize() +const p5Container = ref(null); +let p5Instance = null; - \ No newline at end of file +console.log(width.value, height.value); + +// Interactive controls +const gridSize = ref(72); +const noiseScale = ref(0.05); +const terrainHeight = ref(140); +const rotationSpeed = ref(0.005); +const maxGridSize = ref(Math.floor(Math.min(width.value, height.value) / 20)); + +const terrainZoom = ref(0.9) + +// Other variables +const canvasWidth = width.value; +const canvasHeight = height.value; +let terrain = []; +let peakPoint = { x: 0, y: 0, z: 0 }; +let valleyPoint = { x: 0, y: 0, z: 0 }; +let midPoint = { x: 0, y: 0, z: 0 }; +let angle = 0; +let font; + +onMounted(async () => { + if (process.client) { + const p5 = await import('p5'); + + const sketch = (p) => { + p.preload = () => { + font = p.loadFont('https://cdnjs.cloudflare.com/ajax/libs/ink/3.1.10/fonts/Roboto/roboto-medium-webfont.ttf'); + }; + + p.setup = () => { + p.createCanvas(canvasWidth, canvasHeight, p.WEBGL); + p.textFont(font); + p.textSize(16); + p.textAlign(p.CENTER, p.CENTER); + generateTerrain(); + }; + + p.draw = () => { + p.background(0); + p.stroke(255, 50); + p.noFill(); + + p.rotateX(p.PI / 3); + p.rotateZ(angle); + angle += rotationSpeed.value; + + drawTerrain(); + drawPeakValleyConnection(); + drawLabel(peakPoint, "SPLIFF", [255, 0, 0]); + drawLabel(valleyPoint, "JAVASCRIPT", [0, 0, 255]); + drawLabel(midPoint, "OPTIMUM CREATIVITY", [0, 255, 0]); + }; + + function generateTerrain() { + terrain = []; + peakPoint = { x: 0, y: 0, z: -Infinity }; + valleyPoint = { x: 0, y: 0, z: Infinity }; + + for (let y = 0; y < gridSize.value; y++) { + terrain[y] = []; + for (let x = 0; x < gridSize.value; x++) { + let noiseVal = p.noise(x * noiseScale.value, y * noiseScale.value); + let z = p.map(noiseVal, 0, 1, -terrainHeight.value / 2, terrainHeight.value / 2); + terrain[y][x] = z; + + if (z > peakPoint.z) { + peakPoint = { x, y, z }; + } + if (z < valleyPoint.z) { + valleyPoint = { x, y, z }; + } + } + } + + midPoint = { + x: (peakPoint.x + valleyPoint.x) / 2, + y: (peakPoint.y + valleyPoint.y) / 2, + z: (peakPoint.z + valleyPoint.z) / 2 + }; + } + + function drawTerrain() { + let s = canvasWidth / gridSize.value; + p.beginShape(p.LINES); + for (let y = 0; y < gridSize.value - 1; y++) { + for (let x = 0; x < gridSize.value; x++) { + p.vertex(x * s * terrainZoom.value - canvasWidth / 2, y * s * terrainZoom.value - canvasHeight / 2, terrain[y][x]); + p.vertex(x * s * terrainZoom.value - canvasWidth / 2, (y + 1) * s * terrainZoom.value - canvasHeight / 2, terrain[y + 1][x]); + if (x < gridSize.value - 1) { + p.vertex(x * s * terrainZoom.value - canvasWidth / 2, y * s * terrainZoom.value - canvasHeight / 2, terrain[y][x]); + p.vertex((x + 1) * s * terrainZoom.value - canvasWidth / 2, y * s * terrainZoom.value - canvasHeight / 2, terrain[y][x + 1]); + } + } + } + p.endShape(); + } + + function drawPeakValleyConnection() { + let s = canvasWidth / gridSize.value; + p.stroke(255, 255, 0); + p.strokeWeight(2); + p.line( + peakPoint.x * s * terrainZoom.value - canvasWidth / 2, peakPoint.y * s * terrainZoom.value - canvasHeight / 2, peakPoint.z, + valleyPoint.x * s * terrainZoom.value - canvasWidth / 2, valleyPoint.y * s * terrainZoom.value - canvasHeight / 2, valleyPoint.z + ); + p.strokeWeight(1); + } + + function drawLabel(point, text, color) { + let s = canvasWidth / gridSize.value; + p.push(); + p.translate(point.x * s * terrainZoom.value - canvasWidth / 2, point.y * s * terrainZoom.value - canvasHeight / 2, point.z); + p.fill(color); + p.noStroke(); + p.sphere(5); + p.translate(0, -20, 0); + + p.rotateZ(-angle); + p.rotateX(-p.PI / 3); + + p.fill(255); + p.text(text, 0, 0); + p.pop(); + } + }; + + p5Instance = new p5.default(sketch, p5Container.value); + } +}); + +watch([gridSize, noiseScale, terrainHeight, terrainZoom], generateTerrain); + +function generateTerrain() { + if (p5Instance) { + p5Instance.generateTerrain(); + } +} + +onUnmounted(() => { + if (process.client && p5Instance) { + p5Instance.remove(); + } +}); + \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 6ed72902d..33e4058fe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -816,6 +816,13 @@ dependencies: "@tanstack/vue-virtual" "^3.0.0-beta.60" +"@iconify-json/ei@^1.1.9": + version "1.1.9" + resolved "https://registry.yarnpkg.com/@iconify-json/ei/-/ei-1.1.9.tgz#c70199aa0edb7bbaf28088dbf5ada7bbf904ccc8" + integrity sha512-I1Ea4rQZ0QPa44+iYpQoAdtUiHvdw07VvZgJmPZ7WfsicTyF85yA/Gj9me2tHaQwaQ/zS6HCH+lkEWH6A1XHJA== + dependencies: + "@iconify/types" "*" + "@iconify-json/heroicons@^1.1.23": version "1.1.24" resolved "https://registry.yarnpkg.com/@iconify-json/heroicons/-/heroicons-1.1.24.tgz#91b8e1a1f6fffdc5104c5fa4e7195164d5234c2e" @@ -6146,6 +6153,11 @@ fast-npm-meta@^0.1.1: resolved "https://registry.yarnpkg.com/fast-npm-meta/-/fast-npm-meta-0.1.1.tgz#2fb1e111595aec787ec523c06901ccb1d44a9422" integrity sha512-uS9DjGncI/9XZ6HJFrci0WzSi++N8Jskbb2uB7+9SQlrgA3VaLhXhV9Gl5HwIGESHkayYYZFGnVNhJwRDKCWIA== +fast-simplex-noise@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/fast-simplex-noise/-/fast-simplex-noise-4.0.0.tgz#aeec71d2f4deebab310c088dc465bbc875c2a1b7" + integrity sha512-2zRCJkPdMnPyXNEeI5IYgH555Nbd/j9nC8YVoOm5dmFDjqxuLD888nEAGB8cSUIyxLlNHCZVlZ7AO27Fm3boJA== + fastq@^1.6.0: version "1.17.1" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" @@ -9193,6 +9205,11 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== +p5@^1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/p5/-/p5-1.10.0.tgz#91dd95e7266d943718e565119106cbb419d49e66" + integrity sha512-6cWYBFhnZz7jNC6p1VWvlt3QReMqrRSmO90bgECQIKB9oko2w/sKrOAVMyei5tjIzSYcSY0JHy+BRtSAWq24jQ== + pac-proxy-agent@^7.0.0: version "7.0.2" resolved "https://registry.yarnpkg.com/pac-proxy-agent/-/pac-proxy-agent-7.0.2.tgz#0fb02496bd9fb8ae7eb11cfd98386daaac442f58" @@ -10516,6 +10533,11 @@ simple-swizzle@^0.2.2: dependencies: is-arrayish "^0.3.1" +simplex-noise@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/simplex-noise/-/simplex-noise-4.0.3.tgz#e60b840ecfb9ac972870c8c7979bd867a5b4032f" + integrity sha512-qSE2I4AngLQG7BXqoZj51jokT4WUXe8mOBrvfOXpci8+6Yu44+/dD5zqDpOx3Ux792eamTd2lLcI8jqFntk/lg== + sirv@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/sirv/-/sirv-2.0.4.tgz#5dd9a725c578e34e449f332703eb2a74e46a29b0"