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.