I don't understand this code, I spent like 8 hours trying to write it, gave up, went to sleep for 3 hours and wrote this when I woke up
A Pen by not important on CodePen.
<canvas | |
id="js-canvas" | |
height="512" | |
width="512" | |
></canvas> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/seedrandom/2.4.2/lib/alea.js"></script> | |
<script src="https://cdn.rawgit.com/jwagner/simplex-noise.js/87440528bcf8ec89840e974d8f76cfe3da548c37/simplex-noise.min.js"></script> |
setTimeout(() => { | |
const segmentIterations = 2; // number of segments with their own colors | |
const textureIterations = 2; // number of segments per [thing] that use the colors | |
const simplex = new SimplexNoise(new alea(+(new Date))); // 5625463739 | |
const canvasEl = document.getElementById('js-canvas'); | |
const $ = canvasEl.getContext('2d'); | |
const { | |
width, | |
height | |
} = canvasEl; | |
const centerX = width / 2; | |
const centerY = height / 2; | |
const pointsUp = GeoGenTextures.buildPoints(3, Math.PI * 1.5); | |
const pointsDown = GeoGenTextures.buildPoints(3, Math.PI * 0.5); | |
textureNest($, centerX, centerY, width, height, segmentIterations, textureIterations, simplex, [pointsUp, pointsDown]); | |
}, 0); | |
function textureNest ($, centerX, centerY, width, height, segmentIterations, textureIterations, simplex, [centerPoints, outerPoints], flip = false) { | |
const radius = Math.min(width, height) / 2; | |
[[0, 0], ...centerPoints].map(([x, y]) => [x * (radius / 2) + centerX, y * (radius / 2) + centerY]).forEach(([x, y], i) => { | |
const upsideDown = flip ? !!i : !i; | |
if (segmentIterations) { | |
textureNest($, x, y, radius, radius, segmentIterations - 1, textureIterations, simplex, i ? [centerPoints, outerPoints] : [outerPoints, centerPoints], upsideDown); | |
} else { | |
const pX = x + (-radius / 2); | |
const pY = y + (-radius / 2); | |
let offset = ((Math.PI * 2) / 3) * 2; | |
const points = ((flip ? !i : !!i) ? GeoGenTextures.buildPoints(3, Math.PI * 1.5 + offset) : GeoGenTextures.buildPoints(3, Math.PI * 0.5 + offset)); | |
const resultPoints = points.map(([qX, qY]) => { | |
return [ | |
x + (qX * (radius / 2)), | |
y + (qY * (radius / 2)) | |
]; | |
}); | |
const colors = resultPoints.map(([asdfX, asdfY], jdfyas) => { | |
asdfX = Math.round(asdfX); | |
asdfY = Math.round(asdfY); | |
return (Math.floor(((simplex.noise2D(Math.round(asdfX) - width / 2, Math.round(asdfY) - height / 2) + 1) / 2) * 0x1000000).toString(16)); | |
}); | |
const textureEl = GeoGenTextures.createTile(width, height, colors.slice(0), textureIterations, upsideDown); | |
$.drawImage(textureEl, 0, 0, width, height, pX, pY, radius, radius); | |
} | |
}); | |
} | |
const GeoGenTextures = { | |
lookup: {}, | |
generateHash: (width, height, colors, iterations, flip) => { | |
return `#{width}-${height}-${colors.join('-')}-${iterations}-${flip}`; | |
}, | |
createTile: (width, height, colors, iterations, flip) => { | |
const hash = GeoGenTextures.generateHash(width, height, colors, iterations, flip); | |
if (!GeoGenTextures.lookup[hash]) { | |
const canvasEl = document.createElement('canvas'); | |
canvasEl.width = width; | |
canvasEl.height = height; | |
const $ = canvasEl.getContext('2d'); | |
const centerX = width / 2; | |
let centerY = (height / 2); | |
let radius = Math.min(width, height) / 2; | |
const pointsUp = GeoGenTextures.buildPoints(3, Math.PI * 1.5); | |
const pointsDown = GeoGenTextures.buildPoints(3, Math.PI * 0.5); | |
const colorPoints = (flip ? pointsDown : pointsUp).map(([x, y], icu81mi) => { | |
return { | |
color: colors[icu81mi], | |
x: x * radius + centerX, | |
y: y * radius + centerY | |
}; | |
}); | |
const colorPicker = GeoGenTextures.calculateColor(colorPoints); | |
let points = [pointsDown, pointsUp]; | |
if (flip) { | |
points = [pointsUp, pointsDown]; | |
} | |
GeoGenTextures.triangleNest($, centerX, centerY, radius, points, colorPicker, iterations); | |
GeoGenTextures.lookup[hash] = canvasEl; | |
} | |
return GeoGenTextures.lookup[hash]; | |
}, | |
calculateColor: (colorPoints) => { | |
const colorFactors = colorPoints.map(({color, x, y}) => { | |
const colorInt = parseInt(color, 16); | |
return { | |
x, | |
y, | |
r: colorInt >> 16 & 0xff, | |
g: colorInt >> 8 & 0xff, | |
b: colorInt >> 0 & 0xff | |
}; | |
}); | |
const pointA = [colorFactors[0].x, colorFactors[0].y]; | |
const pointB = [colorFactors[1].x, colorFactors[1].y]; | |
const pointC = [colorFactors[2].x, colorFactors[2].y]; | |
const totalArea = GeoGenTextures.measureArea(pointA, pointB, pointC); | |
return (pointX, pointY) => { | |
const pointD = [pointX, pointY]; // barycentric interpolation | |
let factors = [ | |
GeoGenTextures.measureArea(pointA, pointB, pointD) / totalArea, | |
GeoGenTextures.measureArea(pointB, pointC, pointD) / totalArea | |
]; | |
factors.push(1 - (factors[0] + factors[1])); | |
const highestFactorIndex = factors.indexOf(Math.max.apply({}, factors)); | |
// factors = factors.map((f, i) => i === highestFactorIndex ? 1 : 0); // 5625463739 | |
const r = (colorFactors[0].r * factors[0]) + (colorFactors[1].r * factors[1]) + (colorFactors[2].r * factors[2]); | |
const g = (colorFactors[0].g * factors[0]) + (colorFactors[1].g * factors[1]) + (colorFactors[2].g * factors[2]); | |
const b = (colorFactors[0].b * factors[0]) + (colorFactors[1].b * factors[1]) + (colorFactors[2].b * factors[2]); | |
return '#' + ((b | g << 8 | r << 16) | 0x1000000).toString(16).substring(1); | |
}; | |
}, | |
triangleNest: ($, x, y, radius, [centerPoints, outerPoints], colorPicker, iteration) => { | |
const newRadius = radius / 2; | |
if (!iteration) { | |
GeoGenTextures.drawPolygon($, x, y, newRadius, centerPoints, colorPicker(x, y)); | |
} else { | |
GeoGenTextures.triangleNest($, x, y, newRadius, [outerPoints, centerPoints], colorPicker, iteration - 1); | |
} | |
outerPoints.forEach(([pointX, pointY], edgeIndex) => { | |
const newX = pointX * newRadius + x; | |
const newY = pointY * newRadius + y; | |
if (!iteration) { | |
GeoGenTextures.drawPolygon($, newX, newY, newRadius, outerPoints, colorPicker(newX, newY)); | |
} else { | |
GeoGenTextures.triangleNest($, newX, newY, newRadius, [centerPoints, outerPoints], colorPicker, iteration - 1); | |
} | |
}); | |
}, | |
measureArea: (pointA, pointB, pointC) => { // code from http://www.w3resource.com/javascript-exercises/javascript-basic-exercise-4.php | |
const side1 = GeoGenTextures.measureDistance(pointA, pointB); | |
const side2 = GeoGenTextures.measureDistance(pointB, pointC); | |
const side3 = GeoGenTextures.measureDistance(pointC, pointA); | |
const perimeter = (side1 + side2 + side3) / 2; | |
return Math.sqrt(perimeter * ((perimeter - side1) * (perimeter - side2) * (perimeter - side3))); | |
}, | |
measureDistance: ([x1, y1], [x2, y2]) => { | |
return Math.sqrt(((x2 - x1) * (x2 - x1)) + ((y2 - y1) * (y2 - y1))); | |
}, | |
drawPolygon: ($, x, y, radius, [firstPoint, ...otherPoints], color) => { | |
$.lineWidth = 1; | |
$.strokeStyle = color; | |
$.fillStyle = color; | |
$.beginPath(); | |
$.moveTo(x + firstPoint[0] * radius, y + firstPoint[1] * radius); | |
[...otherPoints, firstPoint].forEach(nextPoint => { | |
$.lineTo(x + nextPoint[0] * radius, y + nextPoint[1] * radius); | |
}); | |
$.fill(); | |
$.stroke(); | |
$.closePath(); | |
}, | |
buildPoints: (edgeCount, rotationOffset = 0) => { | |
const stepSize = (Math.PI * 2) / edgeCount; | |
return Array(...(new Array(edgeCount))).map((_, edgeIndex) => [ | |
Math.cos(edgeIndex * stepSize + rotationOffset), | |
Math.sin(edgeIndex * stepSize + rotationOffset) | |
]); | |
} | |
}; |
canvas { | |
display: block; | |
margin: 0 auto; | |
} |
I don't understand this code, I spent like 8 hours trying to write it, gave up, went to sleep for 3 hours and wrote this when I woke up
A Pen by not important on CodePen.