├── .gitignore ├── snowpack.config.js ├── src ├── common │ ├── math.js │ ├── reset.css │ ├── runtime.js │ ├── canvas.js │ ├── vector.js │ └── noise.js ├── logistic-map │ ├── index.css │ └── index.js ├── infections │ └── index.js └── beatty │ └── index.js ├── public ├── beatty.html ├── infections.html ├── index.html └── logistic-map.html └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /snowpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | mount: { 3 | src: '/dist', 4 | public: '/', 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /src/common/math.js: -------------------------------------------------------------------------------- 1 | export function lerp(v0, v1, t) { 2 | return v0 * (1 - t) + v1 * t 3 | } 4 | 5 | export function ilerp(v0, v1, t) { 6 | return (t - v0) / (v1 - v0) 7 | } 8 | -------------------------------------------------------------------------------- /src/common/reset.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | 5 | box-sizing: border-box; 6 | } 7 | 8 | html, 9 | body { 10 | width: 100%; 11 | height: 100%; 12 | } 13 | -------------------------------------------------------------------------------- /src/common/runtime.js: -------------------------------------------------------------------------------- 1 | export function run(fn) { 2 | window.addEventListener( 3 | 'load', 4 | () => { 5 | fn().catch((error) => { 6 | console.error(error) 7 | }) 8 | }, 9 | { once: true } 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /src/common/canvas.js: -------------------------------------------------------------------------------- 1 | export function responsiveCanvasHook(canvas, fn) { 2 | function adjustSize() { 3 | canvas.width = window.innerWidth 4 | canvas.height = window.innerHeight 5 | fn() 6 | } 7 | 8 | window.addEventListener('resize', adjustSize) 9 | 10 | adjustSize() 11 | } 12 | -------------------------------------------------------------------------------- /public/beatty.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Beatty sequence 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /public/infections.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Infections 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/logistic-map/index.css: -------------------------------------------------------------------------------- 1 | .controls { 2 | position: fixed; 3 | top: 32px; 4 | left: 32px; 5 | 6 | display: flex; 7 | } 8 | 9 | .control-group { 10 | display: flex; 11 | 12 | flex-direction: column; 13 | 14 | justify-content: center; 15 | 16 | margin: 0 16px; 17 | } 18 | 19 | .control { 20 | display: flex; 21 | } 22 | 23 | .control label { 24 | margin-right: 16px; 25 | } 26 | 27 | .control label[for='xconst'] { 28 | margin-right: 9px; 29 | } 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "math", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "snowpack dev", 8 | "build": "snowpack build" 9 | }, 10 | "keywords": [], 11 | "author": "are", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "snowpack": "^3.1.2" 15 | }, 16 | "prettier": { 17 | "semi": false, 18 | "singleQuote": true 19 | }, 20 | "dependencies": { 21 | "animejs": "^3.2.1" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Infections 5 | 6 | 7 | 8 | 9 | 10 |

Math and simulations

11 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /public/logistic-map.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Logistic map 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 | 16 | 25 |
26 |
27 | 30 | 31 |
32 |
33 |
34 | 𝑥𝑛+1 = 𝑟𝑥𝑛 (1 - 𝑥𝑛) 35 |
36 |
37 | 38 |
39 |
40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/common/vector.js: -------------------------------------------------------------------------------- 1 | class Vector { 2 | x 3 | y 4 | 5 | constructor(x, y) { 6 | this.x = x 7 | this.y = y 8 | } 9 | 10 | times(scalar) { 11 | this.x *= scalar 12 | this.y *= scalar 13 | return this 14 | } 15 | 16 | add(vector) { 17 | this.x += vector.x 18 | this.y += vector.y 19 | return this 20 | } 21 | 22 | rotate(rad) { 23 | const px = this.x 24 | const py = this.y 25 | 26 | this.x = Math.cos(rad) * px - Math.sin(rad) * py 27 | this.y = Math.sin(rad) * px + Math.cos(rad) * py 28 | return this 29 | } 30 | 31 | clone() { 32 | return new Vector(this.x, this.y) 33 | } 34 | 35 | dot(other) { 36 | return this.x * other.x + this.y * other.y 37 | } 38 | 39 | angle(other) { 40 | return Math.acos(this.norm().dot(other.norm())) 41 | } 42 | 43 | perpendicular(clock = false) { 44 | if (clock) { 45 | return new Vector(this.y, -this.x) 46 | } else { 47 | return new Vector(-this.y, this.x) 48 | } 49 | } 50 | 51 | norm() { 52 | if (this.x === 0 && this.y === 0) { 53 | return new Vector(this.x, this.y) 54 | } 55 | 56 | return new Vector(this.x / this.magnitude(), this.y / this.magnitude()) 57 | } 58 | 59 | magnitude() { 60 | return Math.sqrt(Math.pow(this.x, 2) + Math.pow(this.y, 2)) 61 | } 62 | } 63 | 64 | export const ec = (x, y) => new Vector(x, y) 65 | export const zero = () => new Vector(0, 0) 66 | 67 | export const add = (...vectors) => 68 | vectors.reduce((a, v) => new Vector(a.x + v.x, a.y + v.y), zero()) 69 | 70 | export const times = (v, s) => new Vector(v.x * s, v.y * s) 71 | -------------------------------------------------------------------------------- /src/logistic-map/index.js: -------------------------------------------------------------------------------- 1 | import '../common/reset.css' 2 | import './index.css' 3 | 4 | import { run } from '../common/runtime' 5 | import { responsiveCanvasHook } from '../common/canvas' 6 | import { lerp, ilerp } from '../common/math' 7 | 8 | function bindLabel(labelFor, onChange = () => {}) { 9 | const label = document.querySelector(`label[for="${labelFor}"]`) 10 | const format = label.dataset['format'] 11 | const slider = document.querySelector(`input#${label.htmlFor}`) 12 | 13 | const update = (newX) => { 14 | const newValue = Number(newX ?? slider.value) 15 | slider.value = newValue 16 | label.innerHTML = format.replace('{}', newValue.toFixed(2)) 17 | onChange(newValue) 18 | } 19 | 20 | slider.addEventListener('input', () => update()) 21 | update() 22 | 23 | return update 24 | } 25 | 26 | function logisticalMap(x, r) { 27 | return r * x * (1 - x) 28 | } 29 | 30 | function delay(ms) { 31 | return new Promise((res) => setTimeout(res, ms)) 32 | } 33 | 34 | run(async () => { 35 | const canvas = document.querySelector('#app') 36 | const ctx = canvas.getContext('2d') 37 | 38 | let x1 = -1, 39 | r = -1, 40 | n = 0, 41 | x = -1 42 | 43 | const updateR = bindLabel('rconst', (value) => { 44 | n = 0 45 | r = value 46 | x = x1 47 | }) 48 | 49 | bindLabel('xconst', (value) => { 50 | n = 0 51 | x1 = value 52 | x = x1 53 | }) 54 | 55 | document.querySelector('#plot4').addEventListener('click', async () => { 56 | ctx.clearRect(0, 0, window.innerWidth, window.innerHeight) 57 | for (let sr = 3; sr < 4; sr += 0.0005) { 58 | updateR(sr) 59 | for (let i = 0; i < 1000; i++) plot() 60 | await delay(1) 61 | } 62 | }) 63 | 64 | function plot() { 65 | const width = window.innerWidth 66 | const height = window.innerHeight 67 | 68 | const centerX = width / 2 69 | const centerY = height / 2 70 | 71 | const startX = 0 72 | const startY = 0 73 | 74 | const px = lerp(0, width, ilerp(3, 4, r)) 75 | const py = lerp(0, height, ilerp(1, 0, x)) 76 | 77 | ctx.fillStyle = 'rgba(0, 0, 0, 0.2)' 78 | ctx.fillRect(startX + px, startY + py, 1, 1) 79 | 80 | x = logisticalMap(x, r) 81 | n += 1 82 | } 83 | 84 | responsiveCanvasHook(canvas, () => {}) 85 | }) 86 | -------------------------------------------------------------------------------- /src/infections/index.js: -------------------------------------------------------------------------------- 1 | import '../common/reset.css' 2 | import { run } from '../common/runtime.js' 3 | import { responsiveCanvasHook } from '../common/canvas.js' 4 | import * as v from '../common/vector.js' 5 | import * as noise from '../common/noise.js' 6 | 7 | class Ball { 8 | position 9 | velocity 10 | color 11 | mass = 1 12 | 13 | forces = [] 14 | friction = 0 15 | 16 | constructor({ position, velocity, color }) { 17 | this.position = position ?? v.ec(0, 0) 18 | this.velocity = velocity ?? v.ec(0, 0) 19 | this.color = 'red' 20 | } 21 | 22 | addForce(v) { 23 | this.forces.push(v) 24 | } 25 | 26 | draw(ctx) { 27 | ctx.fillStyle = this.color 28 | ctx.beginPath() 29 | ctx.arc(this.position.x, this.position.y, 5, 0, Math.PI * 2) 30 | ctx.fill() 31 | } 32 | 33 | update(dt, canvas) { 34 | const value = noise.perlin2(this.position.x / 10, this.position.y / 10) 35 | 36 | let f = this.velocity 37 | .clone() 38 | .perpendicular(value >= 0) 39 | .norm() 40 | .times(Math.abs(value) * 10 * this.velocity.magnitude()) 41 | 42 | this.addForce(f) 43 | 44 | if (this.position.x < 0) { 45 | this.position.x = 0 46 | this.addForce(v.ec((-this.velocity.x / dt) * 2, 0)) 47 | } 48 | 49 | if (this.position.x > canvas.width) { 50 | this.position.x = canvas.width 51 | this.addForce(v.ec((-this.velocity.x / dt) * 2, 0)) 52 | } 53 | 54 | if (this.position.y < 0) { 55 | this.position.y = 0 56 | this.addForce(v.ec(0, (-this.velocity.y / dt) * 2)) 57 | } 58 | 59 | if (this.position.y > canvas.height) { 60 | this.position.y = canvas.height 61 | this.addForce(v.ec(0, (-this.velocity.y / dt) * 2)) 62 | } 63 | 64 | if (this.velocity.magnitude() > 85) { 65 | this.friction = 0.01 66 | } else { 67 | this.friction = 0 68 | } 69 | 70 | const ff = v.add(...this.forces).times(1 / this.mass) 71 | 72 | this.velocity.add(v.times(ff, dt)) 73 | this.velocity.times(1 - this.friction) 74 | this.position.add(v.times(this.velocity, dt)) 75 | 76 | this.forces = [] 77 | } 78 | } 79 | 80 | run(async function main() { 81 | noise.seed(Math.random()) 82 | const canvas = document.getElementById('app') 83 | canvas.width = window.innerWidth 84 | canvas.height = window.innerHeight 85 | const ctx = canvas.getContext('2d') 86 | const noiseCanvas = document.createElement('canvas') 87 | const noiseCtx = noiseCanvas.getContext('2d') 88 | 89 | noiseCanvas.width = window.innerWidth 90 | noiseCanvas.height = window.innerHeight 91 | 92 | const noiseData = noiseCtx.createImageData( 93 | noiseCanvas.width, 94 | noiseCanvas.height 95 | ) 96 | 97 | for (let x = 0; x < window.innerWidth; x++) { 98 | for (let y = 0; y < window.innerHeight; y++) { 99 | const value = ((noise.perlin2(x / 10, y / 10) + 1) / 2) * 255 100 | const index = y * noiseData.width * 4 + x * 4 101 | 102 | noiseData.data[index] = value / 3 103 | noiseData.data[index + 1] = value / 3 104 | noiseData.data[index + 2] = value / 3 105 | noiseData.data[index + 3] = 60 106 | } 107 | } 108 | 109 | noiseCtx.putImageData(noiseData, 0, 0) 110 | 111 | const paused = false 112 | 113 | const balls = Array.from({ length: 1000 }, () => { 114 | const ball = new Ball({ 115 | position: v.ec( 116 | Math.random() * window.innerWidth, 117 | Math.random() * window.innerHeight 118 | ), 119 | }) 120 | 121 | ball.addForce(v.ec(5000, 0).rotate(Math.random() * Math.PI * 2)) 122 | 123 | return ball 124 | }) 125 | 126 | balls[1].color = 'lime' 127 | 128 | function update(dt) { 129 | for (const ball of balls) { 130 | ball.update(dt, canvas) 131 | } 132 | } 133 | 134 | function draw() { 135 | // ctx.clearRect(0, 0, canvas.width, canvas.height) 136 | ctx.drawImage(noiseCanvas, 0, 0) 137 | for (const ball of balls) { 138 | ball.draw(ctx) 139 | } 140 | } 141 | 142 | let ct = -16.6 143 | function loop(nt) { 144 | const dt = (nt - ct) / 1000 145 | requestAnimationFrame(loop) 146 | if (!paused) { 147 | update(dt) 148 | draw() 149 | } 150 | ct = nt 151 | } 152 | 153 | loop(0) 154 | 155 | responsiveCanvasHook(canvas, () => {}) 156 | }) 157 | -------------------------------------------------------------------------------- /src/beatty/index.js: -------------------------------------------------------------------------------- 1 | import '../common/reset.css' 2 | 3 | import { run } from '../common/runtime.js' 4 | import { responsiveCanvasHook } from '../common/canvas.js' 5 | 6 | import anime from 'animejs' 7 | 8 | function makePoint(o) { 9 | return { opacity: 0, textOpacity: 0, ...o } 10 | } 11 | 12 | function generateArray(length, fn) { 13 | return Array.from({ length }, (_, i) => fn(i)) 14 | } 15 | 16 | function drawBackground(ctx) { 17 | ctx.fillStyle = 'black' 18 | ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height) 19 | } 20 | 21 | function drawNumberLine(ctx, { scale, width }) { 22 | const centerX = ctx.canvas.width / 2 23 | const centerY = ctx.canvas.height / 2 24 | 25 | const stretch = scale * (ctx.canvas.width / 2) 26 | 27 | ctx.fillStyle = `rgba(255, 255, 255, ${0.5 + scale / 2})` 28 | ctx.fillRect(centerX - stretch, centerY - width / 2, stretch * 2, width) 29 | } 30 | 31 | function drawPoints(ctx, points) { 32 | const centerY = ctx.canvas.height / 2 33 | const spaceBetween = ctx.canvas.width / points.length 34 | const pointWidth = 4 35 | const pointHeight = 20 36 | 37 | for (const [i, { opacity, textOpacity }] of points.entries()) { 38 | const positionX = spaceBetween * i + spaceBetween / 2 39 | 40 | ctx.fillStyle = `rgba(255, 255, 255, ${opacity})` 41 | ctx.fillRect( 42 | positionX - pointWidth / 2, 43 | centerY - pointHeight / 2, 44 | pointWidth, 45 | pointHeight 46 | ) 47 | 48 | ctx.font = 'bold 22px Georgia' 49 | ctx.textAlign = 'center' 50 | ctx.fillStyle = `rgba(255, 255, 255, ${textOpacity})` 51 | ctx.fillText(`${i}`, positionX, centerY + 40) 52 | } 53 | } 54 | 55 | function lerpf(v0, v1, t) { 56 | return v0 * (1 - t) + v1 * t 57 | } 58 | 59 | function drawSegment(ctx, { xPoints, len, x, xLabel, segment }) { 60 | const spaceBetween = ctx.canvas.width / len 61 | const centerY = ctx.canvas.height / 2 62 | const segmentWidth = 4 63 | const pointWidth = 4 64 | const pointHeight = 40 65 | 66 | for (let [ 67 | i, 68 | { opacity, textOpacity, value, lerp, segment }, 69 | ] of xPoints.entries()) { 70 | const xStart = 71 | (value - x) * spaceBetween + (spaceBetween / 2 - segmentWidth / 2) 72 | 73 | const xPosition = 74 | value * spaceBetween + (spaceBetween / 2 - segmentWidth / 2) 75 | const flooredXPosition = 76 | lerpf(value, Math.floor(value), lerp) * spaceBetween + 77 | (spaceBetween / 2 - segmentWidth / 2) 78 | 79 | ctx.fillStyle = `rgba(255, 223, 0, ${segment})` 80 | ctx.font = 'bold 22px Georgia' 81 | 82 | ctx.fillText( 83 | xLabel, 84 | xStart + (spaceBetween * x * segment + segmentWidth) / 2, 85 | centerY - segmentWidth / 2 - 40 86 | ) 87 | 88 | ctx.fillRect( 89 | xStart, 90 | centerY - segmentWidth / 2 - 20, 91 | spaceBetween * x * segment + segmentWidth / 2, 92 | segmentWidth 93 | ) 94 | 95 | ctx.fillStyle = `rgba(255, 223, 0, ${opacity})` 96 | 97 | ctx.fillRect( 98 | flooredXPosition, 99 | centerY - pointHeight / 2, 100 | pointWidth, 101 | pointHeight 102 | ) 103 | } 104 | } 105 | 106 | function drawTitle(ctx, { opacity, text }) { 107 | ctx.fillStyle = `rgba(255, 223, 0, ${opacity})` 108 | 109 | ctx.font = '42px Georgia' 110 | ctx.fillText(text, ctx.canvas.width / 2, 200) 111 | } 112 | 113 | function draw(ctx, state) { 114 | drawBackground(ctx) 115 | drawNumberLine(ctx, state.numberLine) 116 | drawPoints(ctx, state.points) 117 | drawSegment(ctx, state) 118 | drawTitle(ctx, state.title) 119 | } 120 | 121 | run(async function main() { 122 | const canvas = document.getElementById('app') 123 | const ctx = canvas.getContext('2d') 124 | 125 | const x = (1 + Math.sqrt(5)) / 2 126 | const len = 25 127 | 128 | const state = { 129 | len: len, 130 | numberLine: { scale: 0, width: 4 }, 131 | points: generateArray(len, (i) => makePoint({ value: i })), 132 | x: x, 133 | xLabel: 'φ', 134 | xPoints: generateArray(len / 1.5, (i) => 135 | makePoint({ value: (i + 1) * x, lerp: 0, segment: 0 }) 136 | ), 137 | title: { opacity: 0, text: 'Beatty sequence (generated by φ)' }, 138 | } 139 | 140 | const timeline = anime.timeline({ 141 | update: () => draw(ctx, state), 142 | }) 143 | 144 | timeline.add({ 145 | targets: state.title, 146 | 147 | opacity: 1, 148 | 149 | easing: 'linear', 150 | duration: 3000, 151 | }) 152 | 153 | timeline.add({ 154 | targets: state.numberLine, 155 | 156 | scale: 1, 157 | 158 | easing: 'easeInQuint', 159 | duration: 1000, 160 | }) 161 | 162 | timeline.add({ 163 | targets: state.points, 164 | 165 | opacity: 1, 166 | 167 | easing: 'linear', 168 | duration: 1000, 169 | delay: anime.stagger(100, { from: 'center' }), 170 | }) 171 | 172 | timeline.add({ 173 | targets: state.points, 174 | 175 | textOpacity: 1, 176 | 177 | easing: 'linear', 178 | duration: 1000, 179 | delay: anime.stagger(50, { from: 'first' }), 180 | }) 181 | 182 | for (let [i, point] of state.xPoints.entries()) { 183 | timeline.add({ 184 | targets: state.xPoints[i], 185 | 186 | segment: 1, 187 | 188 | easing: 'easeInOutQuad', 189 | duration: 250, 190 | }) 191 | 192 | timeline.add({ 193 | targets: state.xPoints[i], 194 | 195 | opacity: 1, 196 | 197 | easing: 'easeInOutQuad', 198 | duration: 250, 199 | }) 200 | } 201 | 202 | timeline.add({ 203 | targets: state.xPoints, 204 | 205 | segment: 0, 206 | 207 | easing: 'easeInOutQuad', 208 | duration: 500, 209 | }) 210 | 211 | for (let [i, point] of state.xPoints.entries()) { 212 | timeline.add({ 213 | targets: state.xPoints[i], 214 | 215 | lerp: 1, 216 | 217 | easing: 'easeInOutQuad', 218 | duration: 500, 219 | }) 220 | } 221 | 222 | // timeline.add({ 223 | // targets: state.segment, 224 | 225 | // opacity: 1, 226 | 227 | // easing: 'easeInOutQuad', 228 | // duration: 500, 229 | // }) 230 | 231 | responsiveCanvasHook(canvas, () => draw(ctx, state)) 232 | }) 233 | -------------------------------------------------------------------------------- /src/common/noise.js: -------------------------------------------------------------------------------- 1 | function Grad(x, y, z) { 2 | this.x = x 3 | this.y = y 4 | this.z = z 5 | } 6 | 7 | Grad.prototype.dot2 = function (x, y) { 8 | return this.x * x + this.y * y 9 | } 10 | 11 | Grad.prototype.dot3 = function (x, y, z) { 12 | return this.x * x + this.y * y + this.z * z 13 | } 14 | 15 | var grad3 = [ 16 | new Grad(1, 1, 0), 17 | new Grad(-1, 1, 0), 18 | new Grad(1, -1, 0), 19 | new Grad(-1, -1, 0), 20 | new Grad(1, 0, 1), 21 | new Grad(-1, 0, 1), 22 | new Grad(1, 0, -1), 23 | new Grad(-1, 0, -1), 24 | new Grad(0, 1, 1), 25 | new Grad(0, -1, 1), 26 | new Grad(0, 1, -1), 27 | new Grad(0, -1, -1), 28 | ] 29 | 30 | var p = [ 31 | 151, 32 | 160, 33 | 137, 34 | 91, 35 | 90, 36 | 15, 37 | 131, 38 | 13, 39 | 201, 40 | 95, 41 | 96, 42 | 53, 43 | 194, 44 | 233, 45 | 7, 46 | 225, 47 | 140, 48 | 36, 49 | 103, 50 | 30, 51 | 69, 52 | 142, 53 | 8, 54 | 99, 55 | 37, 56 | 240, 57 | 21, 58 | 10, 59 | 23, 60 | 190, 61 | 6, 62 | 148, 63 | 247, 64 | 120, 65 | 234, 66 | 75, 67 | 0, 68 | 26, 69 | 197, 70 | 62, 71 | 94, 72 | 252, 73 | 219, 74 | 203, 75 | 117, 76 | 35, 77 | 11, 78 | 32, 79 | 57, 80 | 177, 81 | 33, 82 | 88, 83 | 237, 84 | 149, 85 | 56, 86 | 87, 87 | 174, 88 | 20, 89 | 125, 90 | 136, 91 | 171, 92 | 168, 93 | 68, 94 | 175, 95 | 74, 96 | 165, 97 | 71, 98 | 134, 99 | 139, 100 | 48, 101 | 27, 102 | 166, 103 | 77, 104 | 146, 105 | 158, 106 | 231, 107 | 83, 108 | 111, 109 | 229, 110 | 122, 111 | 60, 112 | 211, 113 | 133, 114 | 230, 115 | 220, 116 | 105, 117 | 92, 118 | 41, 119 | 55, 120 | 46, 121 | 245, 122 | 40, 123 | 244, 124 | 102, 125 | 143, 126 | 54, 127 | 65, 128 | 25, 129 | 63, 130 | 161, 131 | 1, 132 | 216, 133 | 80, 134 | 73, 135 | 209, 136 | 76, 137 | 132, 138 | 187, 139 | 208, 140 | 89, 141 | 18, 142 | 169, 143 | 200, 144 | 196, 145 | 135, 146 | 130, 147 | 116, 148 | 188, 149 | 159, 150 | 86, 151 | 164, 152 | 100, 153 | 109, 154 | 198, 155 | 173, 156 | 186, 157 | 3, 158 | 64, 159 | 52, 160 | 217, 161 | 226, 162 | 250, 163 | 124, 164 | 123, 165 | 5, 166 | 202, 167 | 38, 168 | 147, 169 | 118, 170 | 126, 171 | 255, 172 | 82, 173 | 85, 174 | 212, 175 | 207, 176 | 206, 177 | 59, 178 | 227, 179 | 47, 180 | 16, 181 | 58, 182 | 17, 183 | 182, 184 | 189, 185 | 28, 186 | 42, 187 | 223, 188 | 183, 189 | 170, 190 | 213, 191 | 119, 192 | 248, 193 | 152, 194 | 2, 195 | 44, 196 | 154, 197 | 163, 198 | 70, 199 | 221, 200 | 153, 201 | 101, 202 | 155, 203 | 167, 204 | 43, 205 | 172, 206 | 9, 207 | 129, 208 | 22, 209 | 39, 210 | 253, 211 | 19, 212 | 98, 213 | 108, 214 | 110, 215 | 79, 216 | 113, 217 | 224, 218 | 232, 219 | 178, 220 | 185, 221 | 112, 222 | 104, 223 | 218, 224 | 246, 225 | 97, 226 | 228, 227 | 251, 228 | 34, 229 | 242, 230 | 193, 231 | 238, 232 | 210, 233 | 144, 234 | 12, 235 | 191, 236 | 179, 237 | 162, 238 | 241, 239 | 81, 240 | 51, 241 | 145, 242 | 235, 243 | 249, 244 | 14, 245 | 239, 246 | 107, 247 | 49, 248 | 192, 249 | 214, 250 | 31, 251 | 181, 252 | 199, 253 | 106, 254 | 157, 255 | 184, 256 | 84, 257 | 204, 258 | 176, 259 | 115, 260 | 121, 261 | 50, 262 | 45, 263 | 127, 264 | 4, 265 | 150, 266 | 254, 267 | 138, 268 | 236, 269 | 205, 270 | 93, 271 | 222, 272 | 114, 273 | 67, 274 | 29, 275 | 24, 276 | 72, 277 | 243, 278 | 141, 279 | 128, 280 | 195, 281 | 78, 282 | 66, 283 | 215, 284 | 61, 285 | 156, 286 | 180, 287 | ] 288 | // To remove the need for index wrapping, double the permutation table length 289 | var perm = new Array(512) 290 | var gradP = new Array(512) 291 | 292 | // This isn't a very good seeding function, but it works ok. It supports 2^16 293 | // different seed values. Write something better if you need more seeds. 294 | export const seed = function (seed) { 295 | if (seed > 0 && seed < 1) { 296 | // Scale the seed out 297 | seed *= 65536 298 | } 299 | 300 | seed = Math.floor(seed) 301 | if (seed < 256) { 302 | seed |= seed << 8 303 | } 304 | 305 | for (var i = 0; i < 256; i++) { 306 | var v 307 | if (i & 1) { 308 | v = p[i] ^ (seed & 255) 309 | } else { 310 | v = p[i] ^ ((seed >> 8) & 255) 311 | } 312 | 313 | perm[i] = perm[i + 256] = v 314 | gradP[i] = gradP[i + 256] = grad3[v % 12] 315 | } 316 | } 317 | 318 | seed(0) 319 | 320 | /* 321 | for(var i=0; i<256; i++) { 322 | perm[i] = perm[i + 256] = p[i]; 323 | gradP[i] = gradP[i + 256] = grad3[perm[i] % 12]; 324 | }*/ 325 | 326 | // Skewing and unskewing factors for 2, 3, and 4 dimensions 327 | var F2 = 0.5 * (Math.sqrt(3) - 1) 328 | var G2 = (3 - Math.sqrt(3)) / 6 329 | 330 | var F3 = 1 / 3 331 | var G3 = 1 / 6 332 | 333 | // 2D simplex noise 334 | export const simplex2 = function (xin, yin) { 335 | var n0, n1, n2 // Noise contributions from the three corners 336 | // Skew the input space to determine which simplex cell we're in 337 | var s = (xin + yin) * F2 // Hairy factor for 2D 338 | var i = Math.floor(xin + s) 339 | var j = Math.floor(yin + s) 340 | var t = (i + j) * G2 341 | var x0 = xin - i + t // The x,y distances from the cell origin, unskewed. 342 | var y0 = yin - j + t 343 | // For the 2D case, the simplex shape is an equilateral triangle. 344 | // Determine which simplex we are in. 345 | var i1, j1 // Offsets for second (middle) corner of simplex in (i,j) coords 346 | if (x0 > y0) { 347 | // lower triangle, XY order: (0,0)->(1,0)->(1,1) 348 | i1 = 1 349 | j1 = 0 350 | } else { 351 | // upper triangle, YX order: (0,0)->(0,1)->(1,1) 352 | i1 = 0 353 | j1 = 1 354 | } 355 | // A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and 356 | // a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where 357 | // c = (3-sqrt(3))/6 358 | var x1 = x0 - i1 + G2 // Offsets for middle corner in (x,y) unskewed coords 359 | var y1 = y0 - j1 + G2 360 | var x2 = x0 - 1 + 2 * G2 // Offsets for last corner in (x,y) unskewed coords 361 | var y2 = y0 - 1 + 2 * G2 362 | // Work out the hashed gradient indices of the three simplex corners 363 | i &= 255 364 | j &= 255 365 | var gi0 = gradP[i + perm[j]] 366 | var gi1 = gradP[i + i1 + perm[j + j1]] 367 | var gi2 = gradP[i + 1 + perm[j + 1]] 368 | // Calculate the contribution from the three corners 369 | var t0 = 0.5 - x0 * x0 - y0 * y0 370 | if (t0 < 0) { 371 | n0 = 0 372 | } else { 373 | t0 *= t0 374 | n0 = t0 * t0 * gi0.dot2(x0, y0) // (x,y) of grad3 used for 2D gradient 375 | } 376 | var t1 = 0.5 - x1 * x1 - y1 * y1 377 | if (t1 < 0) { 378 | n1 = 0 379 | } else { 380 | t1 *= t1 381 | n1 = t1 * t1 * gi1.dot2(x1, y1) 382 | } 383 | var t2 = 0.5 - x2 * x2 - y2 * y2 384 | if (t2 < 0) { 385 | n2 = 0 386 | } else { 387 | t2 *= t2 388 | n2 = t2 * t2 * gi2.dot2(x2, y2) 389 | } 390 | // Add contributions from each corner to get the final noise value. 391 | // The result is scaled to return values in the interval [-1,1]. 392 | return 70 * (n0 + n1 + n2) 393 | } 394 | 395 | // 3D simplex noise 396 | export const simplex3 = function (xin, yin, zin) { 397 | var n0, n1, n2, n3 // Noise contributions from the four corners 398 | 399 | // Skew the input space to determine which simplex cell we're in 400 | var s = (xin + yin + zin) * F3 // Hairy factor for 2D 401 | var i = Math.floor(xin + s) 402 | var j = Math.floor(yin + s) 403 | var k = Math.floor(zin + s) 404 | 405 | var t = (i + j + k) * G3 406 | var x0 = xin - i + t // The x,y distances from the cell origin, unskewed. 407 | var y0 = yin - j + t 408 | var z0 = zin - k + t 409 | 410 | // For the 3D case, the simplex shape is a slightly irregular tetrahedron. 411 | // Determine which simplex we are in. 412 | var i1, j1, k1 // Offsets for second corner of simplex in (i,j,k) coords 413 | var i2, j2, k2 // Offsets for third corner of simplex in (i,j,k) coords 414 | if (x0 >= y0) { 415 | if (y0 >= z0) { 416 | i1 = 1 417 | j1 = 0 418 | k1 = 0 419 | i2 = 1 420 | j2 = 1 421 | k2 = 0 422 | } else if (x0 >= z0) { 423 | i1 = 1 424 | j1 = 0 425 | k1 = 0 426 | i2 = 1 427 | j2 = 0 428 | k2 = 1 429 | } else { 430 | i1 = 0 431 | j1 = 0 432 | k1 = 1 433 | i2 = 1 434 | j2 = 0 435 | k2 = 1 436 | } 437 | } else { 438 | if (y0 < z0) { 439 | i1 = 0 440 | j1 = 0 441 | k1 = 1 442 | i2 = 0 443 | j2 = 1 444 | k2 = 1 445 | } else if (x0 < z0) { 446 | i1 = 0 447 | j1 = 1 448 | k1 = 0 449 | i2 = 0 450 | j2 = 1 451 | k2 = 1 452 | } else { 453 | i1 = 0 454 | j1 = 1 455 | k1 = 0 456 | i2 = 1 457 | j2 = 1 458 | k2 = 0 459 | } 460 | } 461 | // A step of (1,0,0) in (i,j,k) means a step of (1-c,-c,-c) in (x,y,z), 462 | // a step of (0,1,0) in (i,j,k) means a step of (-c,1-c,-c) in (x,y,z), and 463 | // a step of (0,0,1) in (i,j,k) means a step of (-c,-c,1-c) in (x,y,z), where 464 | // c = 1/6. 465 | var x1 = x0 - i1 + G3 // Offsets for second corner 466 | var y1 = y0 - j1 + G3 467 | var z1 = z0 - k1 + G3 468 | 469 | var x2 = x0 - i2 + 2 * G3 // Offsets for third corner 470 | var y2 = y0 - j2 + 2 * G3 471 | var z2 = z0 - k2 + 2 * G3 472 | 473 | var x3 = x0 - 1 + 3 * G3 // Offsets for fourth corner 474 | var y3 = y0 - 1 + 3 * G3 475 | var z3 = z0 - 1 + 3 * G3 476 | 477 | // Work out the hashed gradient indices of the four simplex corners 478 | i &= 255 479 | j &= 255 480 | k &= 255 481 | var gi0 = gradP[i + perm[j + perm[k]]] 482 | var gi1 = gradP[i + i1 + perm[j + j1 + perm[k + k1]]] 483 | var gi2 = gradP[i + i2 + perm[j + j2 + perm[k + k2]]] 484 | var gi3 = gradP[i + 1 + perm[j + 1 + perm[k + 1]]] 485 | 486 | // Calculate the contribution from the four corners 487 | var t0 = 0.6 - x0 * x0 - y0 * y0 - z0 * z0 488 | if (t0 < 0) { 489 | n0 = 0 490 | } else { 491 | t0 *= t0 492 | n0 = t0 * t0 * gi0.dot3(x0, y0, z0) // (x,y) of grad3 used for 2D gradient 493 | } 494 | var t1 = 0.6 - x1 * x1 - y1 * y1 - z1 * z1 495 | if (t1 < 0) { 496 | n1 = 0 497 | } else { 498 | t1 *= t1 499 | n1 = t1 * t1 * gi1.dot3(x1, y1, z1) 500 | } 501 | var t2 = 0.6 - x2 * x2 - y2 * y2 - z2 * z2 502 | if (t2 < 0) { 503 | n2 = 0 504 | } else { 505 | t2 *= t2 506 | n2 = t2 * t2 * gi2.dot3(x2, y2, z2) 507 | } 508 | var t3 = 0.6 - x3 * x3 - y3 * y3 - z3 * z3 509 | if (t3 < 0) { 510 | n3 = 0 511 | } else { 512 | t3 *= t3 513 | n3 = t3 * t3 * gi3.dot3(x3, y3, z3) 514 | } 515 | // Add contributions from each corner to get the final noise value. 516 | // The result is scaled to return values in the interval [-1,1]. 517 | return 32 * (n0 + n1 + n2 + n3) 518 | } 519 | 520 | // ##### Perlin noise stuff 521 | 522 | function fade(t) { 523 | return t * t * t * (t * (t * 6 - 15) + 10) 524 | } 525 | 526 | function lerp(a, b, t) { 527 | return (1 - t) * a + t * b 528 | } 529 | 530 | // 2D Perlin Noise 531 | export const perlin2 = function (x, y) { 532 | // Find unit grid cell containing point 533 | var X = Math.floor(x), 534 | Y = Math.floor(y) 535 | // Get relative xy coordinates of point within that cell 536 | x = x - X 537 | y = y - Y 538 | // Wrap the integer cells at 255 (smaller integer period can be introduced here) 539 | X = X & 255 540 | Y = Y & 255 541 | 542 | // Calculate noise contributions from each of the four corners 543 | var n00 = gradP[X + perm[Y]].dot2(x, y) 544 | var n01 = gradP[X + perm[Y + 1]].dot2(x, y - 1) 545 | var n10 = gradP[X + 1 + perm[Y]].dot2(x - 1, y) 546 | var n11 = gradP[X + 1 + perm[Y + 1]].dot2(x - 1, y - 1) 547 | 548 | // Compute the fade curve value for x 549 | var u = fade(x) 550 | 551 | // Interpolate the four results 552 | return lerp(lerp(n00, n10, u), lerp(n01, n11, u), fade(y)) 553 | } 554 | 555 | // 3D Perlin Noise 556 | export const perlin3 = function (x, y, z) { 557 | // Find unit grid cell containing point 558 | var X = Math.floor(x), 559 | Y = Math.floor(y), 560 | Z = Math.floor(z) 561 | // Get relative xyz coordinates of point within that cell 562 | x = x - X 563 | y = y - Y 564 | z = z - Z 565 | // Wrap the integer cells at 255 (smaller integer period can be introduced here) 566 | X = X & 255 567 | Y = Y & 255 568 | Z = Z & 255 569 | 570 | // Calculate noise contributions from each of the eight corners 571 | var n000 = gradP[X + perm[Y + perm[Z]]].dot3(x, y, z) 572 | var n001 = gradP[X + perm[Y + perm[Z + 1]]].dot3(x, y, z - 1) 573 | var n010 = gradP[X + perm[Y + 1 + perm[Z]]].dot3(x, y - 1, z) 574 | var n011 = gradP[X + perm[Y + 1 + perm[Z + 1]]].dot3(x, y - 1, z - 1) 575 | var n100 = gradP[X + 1 + perm[Y + perm[Z]]].dot3(x - 1, y, z) 576 | var n101 = gradP[X + 1 + perm[Y + perm[Z + 1]]].dot3(x - 1, y, z - 1) 577 | var n110 = gradP[X + 1 + perm[Y + 1 + perm[Z]]].dot3(x - 1, y - 1, z) 578 | var n111 = gradP[X + 1 + perm[Y + 1 + perm[Z + 1]]].dot3(x - 1, y - 1, z - 1) 579 | 580 | // Compute the fade curve value for x, y, z 581 | var u = fade(x) 582 | var v = fade(y) 583 | var w = fade(z) 584 | 585 | // Interpolate 586 | return lerp( 587 | lerp(lerp(n000, n100, u), lerp(n001, n101, u), w), 588 | lerp(lerp(n010, n110, u), lerp(n011, n111, u), w), 589 | v 590 | ) 591 | } 592 | --------------------------------------------------------------------------------