Skip to content

Instantly share code, notes, and snippets.

@clindsey
Last active February 9, 2017 06:32
Show Gist options
  • Save clindsey/e6bf5f0ef67910303dadca272dd098dd to your computer and use it in GitHub Desktop.
Save clindsey/e6bf5f0ef67910303dadca272dd098dd to your computer and use it in GitHub Desktop.
triangle-nest-7.0.0
<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;
}

triangle-nest-7.0.0

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.

License.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment