├── .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 |
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 |
--------------------------------------------------------------------------------