Last active
January 5, 2026 09:34
-
-
Save IkhwanAL/97b264f2bda7078f29fdd6b34737392e to your computer and use it in GitHub Desktop.
How I Improve Speed in Canvas
This file contains hidden or 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
| function mapGenerator(option) { | |
| const { permutationTable, width, height } = editorState | |
| let noises = [] | |
| let max = -Infinity | |
| let min = Infinity | |
| const sampleHeight = Math.floor(height / 2) | |
| const sampleWidth = Math.floor(width / 2) | |
| for (let y = 0; y < sampleHeight; y++) { | |
| noises[y] = [] | |
| for (let x = 0; x < sampleWidth; x++) { | |
| const noise = FractalNoise(x, y, permutationTable, option) | |
| if (noise > max) { | |
| max = noise | |
| } | |
| if (noise < min) { | |
| min = noise | |
| } | |
| noises[y][x] = noise | |
| } | |
| } | |
| canvasState.map = normalizeNoise(noises, min, max) | |
| drawMap() | |
| } | |
| /** | |
| * @description Convert the Map from -1 - 1 into 0 - 1 | |
| */ | |
| function normalizeNoise(noises, min, max) { | |
| const range = max - min | |
| for (let y = 0; y < noises.length; y++) { | |
| for (let x = 0; x < noises[y].length; x++) { | |
| noises[y][x] = (noises[y][x] - min) / range | |
| } | |
| } | |
| return noises | |
| } | |
| function drawMap() { | |
| const { map } = canvasState | |
| const width = Math.abs(editorState.x1 - editorState.x0) | |
| const height = Math.abs(editorState.y1 - editorState.y0) | |
| canvasState.dirty = true | |
| const imageData = ctx.createImageData(width, height) | |
| const scaledMap = bilinearInterpolation(map, width, height) | |
| let index = 0 | |
| for (let y = 0; y < scaledMap.length; y++) { | |
| for (let x = 0; x < scaledMap[y].length; x++) { | |
| const grid = scaledMap[y][x] | |
| const n = clamp(grid * 255 | 0, 0, 255) | |
| imageData.data[index++] = n | |
| imageData.data[index++] = n | |
| imageData.data[index++] = n | |
| imageData.data[index++] = 255 | |
| } | |
| } | |
| ctx.putImageData(imageData, 0, 0) | |
| } | |
| function clamp(value, min, max) { | |
| if (value < min) { | |
| return min | |
| } | |
| if (value > max) { | |
| return max | |
| } | |
| return value | |
| } |
This file contains hidden or 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
| export function NewPermutationTable(rand) { | |
| const perm = new Array(256) | |
| for (let index = 0; index < 256; index++) { | |
| perm[index] = index | |
| } | |
| for (let index = 255; index > 0; index--) { | |
| const j = Math.floor(rand() * (index + 1)) | |
| const temp = perm[index] | |
| perm[index] = perm[j] | |
| perm[j] = temp | |
| } | |
| return perm.concat(perm) | |
| } | |
| const INV_SQRT2 = 1 / Math.sqrt(2) | |
| const GRADIENTS = [ | |
| [INV_SQRT2, INV_SQRT2], | |
| [-INV_SQRT2, INV_SQRT2], | |
| [-INV_SQRT2, -INV_SQRT2], | |
| [INV_SQRT2, -INV_SQRT2] | |
| ] | |
| function getGradient(x) { | |
| const v = x & 3 | |
| switch (v) { | |
| case 0: | |
| return GRADIENTS[0] | |
| // return [1.0, 1.0] | |
| case 1: | |
| return GRADIENTS[1] | |
| // return [-1.0, 1.0] | |
| case 2: | |
| return GRADIENTS[2] | |
| // return [-1.0, -1.0] | |
| default: | |
| return GRADIENTS[3] | |
| // return [1.0, -1.0] | |
| } | |
| } | |
| export function perlinNoise(x, y, PERM) { | |
| const floorX = Math.floor(x) | |
| const floorY = Math.floor(y) | |
| const X = floorX & 255 | |
| const Y = floorY & 255 | |
| const xf = x - floorX | |
| const yf = y - floorY | |
| // Change To Array Instead Of Object | |
| const topRightVecToPoint = [xf - 1.0, yf - 1.0] | |
| const topLeftVecToPoint = [xf, yf - 1.0] | |
| const bottomRightVecToPoint = [xf - 1.0, yf] | |
| const bottomLeftVecToPoint = [xf, yf] | |
| let loc | |
| loc = PERM[PERM[X + 1] + Y + 1] | |
| let topRightCornerDirection = getGradient(loc) | |
| loc = PERM[PERM[X] + Y + 1] | |
| let topLeftCornerDirection = getGradient(loc) | |
| loc = PERM[PERM[X] + Y] | |
| const bottomLeftCornerDirection = getGradient(loc) | |
| loc = PERM[PERM[X + 1] + Y] | |
| let bottomRightCornerDirection = getGradient(loc) | |
| const dotTopRight = dot(topRightVecToPoint, topRightCornerDirection) | |
| const dotTopLeft = dot(topLeftVecToPoint, topLeftCornerDirection) | |
| const dotBottomLeft = dot(bottomLeftVecToPoint, bottomLeftCornerDirection) | |
| const dotBottomRight = dot(bottomRightVecToPoint, bottomRightCornerDirection) | |
| const u = fade(xf) | |
| const v = fade(yf) | |
| const lerpTop = lerp(u, dotTopLeft, dotTopRight) | |
| const lerpBottom = lerp(u, dotBottomLeft, dotBottomRight) | |
| return lerp(v, lerpBottom, lerpTop) | |
| } | |
| export const defaultFractalOption = { | |
| octaves: 8, | |
| persistence: 0.1, | |
| lacunarity: 1, | |
| frequency: 1.0, | |
| amplitude: 1.0 | |
| } | |
| export function FractalNoise(x, y, PERM, option = defaultFractalOption) { | |
| let total = 0.0; | |
| let maxValue = 0.0 | |
| const { lacunarity, octaves, persistence, ...options } = option | |
| let { frequency, amplitude } = options | |
| for (let i = 0; i < octaves; i++) { | |
| const n = amplitude * perlinNoise(x * frequency, y * frequency, PERM) | |
| total += n | |
| maxValue += amplitude | |
| amplitude *= persistence | |
| frequency *= lacunarity | |
| } | |
| return total / maxValue | |
| } | |
| function dot(vec1, vec2) { | |
| return (vec1[0] * vec2[0]) + (vec1[1] * vec2[1]) | |
| } | |
| function fade(t) { | |
| return ((6 * t - 15) * t + 10) * t * t * t | |
| } | |
| /** | |
| * @param {number} t - Blend Factor | |
| * @param {number} leftValue - start of point value | |
| * @param {number} rightValue - end of point value | |
| * | |
| */ | |
| function lerp(t, leftValue, rightValue) { | |
| return leftValue + t * (rightValue - leftValue) | |
| } |
This file contains hidden or 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
| export function bilinearInterpolation(map, dx, dy) { | |
| const maxX = map[0].length | |
| const maxY = map.length | |
| const sx = (maxX - 1) / (dx - 1) | |
| const sy = (maxY - 1) / (dy - 1) | |
| let newMap = [] | |
| for (let y = 0; y < dy; y++) { | |
| let tempArr = [] | |
| for (let x = 0; x < dx; x++) { | |
| let originalX = x * sx | |
| let originalY = y * sy | |
| let x1 = Math.floor(originalX) | |
| let y1 = Math.floor(originalY) | |
| let x2 = Math.min(maxX - 1, x1 + 1) | |
| let y2 = Math.min(maxY - 1, y1 + 1) | |
| const horizontalDistance = x2 === x1 ? 0 : (originalX - x1) / (x2 - x1) | |
| const verticalDistance = y2 == y1 ? 0 : (originalY - y1) / (y2 - y1) | |
| const finalValue = calculateDistibution(horizontalDistance, verticalDistance, x1, x2, y1, y2, map) | |
| tempArr.push(finalValue) | |
| } | |
| newMap.push(tempArr) | |
| } | |
| return newMap | |
| } | |
| function calculateDistibution(dx, dy, x1, x2, y1, y2, map) { | |
| const topLeft = map[y1][x1] * ((1 - dx) * (1 - dy)) | |
| const topRight = map[y1][x2] * ((dx) * (1 - dy)) | |
| const bottomLeft = map[y2][x1] * ((1 - dx) * (dy)) | |
| const bottomRight = map[y2][x2] * (dx * dy) | |
| return topLeft + topRight + bottomLeft + bottomRight | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment