Hey math people.
I'm noob at math but have expertise in gluing stuff found on internet together to create something.
So, recently I started playing with visualization of math stuff I find interesting at a given moment..... for the sake of visualizing and animating.
If interested in code that created visualization (it's a JavaScript):
// ui controls
let circleRadius = ui.number('Radius', 120, 50, 300);
let animateVertices = ui.toggle('Animate Angles', false);
let animationSpeed = ui.number('Anim Speed', 1, 0, 5);
let angleVertexA = ui.number('Angle A', 20, 0, 360);
let angleVertexB = ui.number('Angle B', 70, 0, 360);
let angleVertexC = ui.number('Angle C', 130, 0, 360);
let angleVertexD = ui.number('Angle D', 185, 0, 360);
let angleVertexE = ui.number('Angle E', 245, 0, 360);
let angleVertexF = ui.number('Angle F', 315, 0, 360);
// animate angles randomly if toggled
if (animateVertices) {
let animationStartFrame = 5.0 * timeline.fps; // Start after Pascal line finishes (5 seconds)
if (frame > animationStartFrame) {
let elapsedAnimFrame = (frame - animationStartFrame) * animationSpeed;
let framesPerPhase = 100; // Frames per animation phase
let numVertexPairs = 3; // 3 pairs of angles (A+D, B+E, C+F)
let currentCycle = math.floor(elapsedAnimFrame / framesPerPhase);
let frameWithinCycle = elapsedAnimFrame % framesPerPhase;
// Accumulates active time for each pair so they pause exactly where they left off
function getActiveTimeForPair(pairIndex) {
let completedFullSets = math.floor(currentCycle / numVertexPairs);
let activePairInCurrentSet = currentCycle % numVertexPairs;
let completedCyclesForPair = completedFullSets;
if (activePairInCurrentSet > pairIndex) completedCyclesForPair++;
let accumulatedFrames = completedCyclesForPair * framesPerPhase;
if (activePairInCurrentSet === pairIndex) {
// Smooth ease in/out for the movement phase
let normalizedProgress = frameWithinCycle / framesPerPhase;
let easedProgress = anim.cubicBezier(normalizedProgress, 0.4, 0.0, 0.2, 1.0);
accumulatedFrames += easedProgress * framesPerPhase;
}
return accumulatedFrames;
}
// frequency multiplier so the noise traverses a smooth, single-direction slope
// rather than oscillating quickly (jiggling) during the 100 frame window
let noiseFrequency = 0.003;
let noiseTimeAD = getActiveTimeForPair(0) * noiseFrequency;
let noiseTimeBE = getActiveTimeForPair(1) * noiseFrequency;
let noiseTimeCF = getActiveTimeForPair(2) * noiseFrequency;
// Pair 0: A & D
angleVertexA += anim.noise(noiseTimeAD, 0, 1) * 180;
angleVertexD += anim.noise(noiseTimeAD, 0, 2) * 180;
// Pair 1: B & E
angleVertexB += anim.noise(noiseTimeBE, 0, 3) * 180;
angleVertexE += anim.noise(noiseTimeBE, 0, 4) * 180;
// Pair 2: C & F
angleVertexC += anim.noise(noiseTimeCF, 0, 5) * 180;
angleVertexF += anim.noise(noiseTimeCF, 0, 6) * 180;
}
}
// colors
let colorPairAD = '#ef4444'; // red
let colorPairBE = '#22c55e'; // green
let colorPairCF = '#3b82f6'; // blue
let colorPascalLine = '#eab308'; // yellow
// timings
let currentTime = time;
function easeProgress(phaseStart, phaseEnd, currentT) {
let normalizedT = math.clamp((currentT - phaseStart) / (phaseEnd - phaseStart), 0, 1);
return anim.cubicBezier(normalizedT, 0.25, 0.1, 0.25, 1.0);
}
// animation phases (points appear after hexagon)
let progressCircle = easeProgress(0.0, 0.5, currentTime);
let progressHexagon = easeProgress(0.5, 1.5, currentTime);
let progressVertices = easeProgress(1.5, 2.0, currentTime);
let progressExtensions = easeProgress(2.0, 3.2, currentTime);
let progressIntersections = easeProgress(3.2, 3.8, currentTime);
let progressPascalLine = easeProgress(3.8, 5.0, currentTime);
// helpers
function getPointOnCircle(angleDeg) {
let angleRad = math.rad(angleDeg);
return { x: math.cos(angleRad) * circleRadius, y: math.sin(angleRad) * circleRadius };
}
function computeLineIntersection(lineAStart, lineAEnd, lineBStart, lineBEnd) {
let denominator = (lineAStart.x - lineAEnd.x) * (lineBStart.y - lineBEnd.y) - (lineAStart.y - lineAEnd.y) * (lineBStart.x - lineBEnd.x);
if (math.abs(denominator) < 0.001) return { x: 9999, y: 9999 };
let paramT = ((lineAStart.x - lineBStart.x) * (lineBStart.y - lineBEnd.y) - (lineAStart.y - lineBStart.y) * (lineBStart.x - lineBEnd.x)) / denominator;
return {
x: lineAStart.x + paramT * (lineAEnd.x - lineAStart.x),
y: lineAStart.y + paramT * (lineAEnd.y - lineAStart.y)
};
}
function createStyledLine(startPt, endPt, strokeColor) {
return create.path({ d: `M ${startPt.x} ${startPt.y} L ${endPt.x} ${endPt.y}` }).stroke({ color: strokeColor, width: 2 }).fill('none');
}
// calc points
let vertexA = getPointOnCircle(angleVertexA), vertexB = getPointOnCircle(angleVertexB), vertexC = getPointOnCircle(angleVertexC);
let vertexD = getPointOnCircle(angleVertexD), vertexE = getPointOnCircle(angleVertexE), vertexF = getPointOnCircle(angleVertexF);
// calc intersections
let intersectionP = computeLineIntersection(vertexA, vertexB, vertexD, vertexE);
let intersectionQ = computeLineIntersection(vertexB, vertexC, vertexE, vertexF);
let intersectionR = computeLineIntersection(vertexC, vertexD, vertexF, vertexA);
// draw circle
if (progressCircle > 0) {
let boundingCircle = create.ellipse({ radiusX: circleRadius, radiusY: circleRadius })
.stroke({ color: '#555', width: 2 })
.fill('none');
output.add(node('trimPath', { end: progressCircle }, [boundingCircle]));
}
// draw hexagon
let hexagonSides = [
createStyledLine(vertexA, vertexB, colorPairAD), createStyledLine(vertexD, vertexE, colorPairAD),
createStyledLine(vertexB, vertexC, colorPairBE), createStyledLine(vertexE, vertexF, colorPairBE),
createStyledLine(vertexC, vertexD, colorPairCF), createStyledLine(vertexF, vertexA, colorPairCF)
];
if (progressHexagon > 0) { output.add(node('trimPath', { end: progressHexagon }, hexagonSides)); }
// draw extensions
function createExtensionLine(sideStart, sideEnd, targetIntersection, strokeColor) {
let ptSideStart = { x: sideStart.x, y: sideStart.y };
let ptSideEnd = { x: sideEnd.x, y: sideEnd.y };
let ptTarget = { x: targetIntersection.x, y: targetIntersection.y };
let distFromStart = math.distance(ptSideStart, ptTarget);
let distFromEnd = math.distance(ptSideEnd, ptTarget);
let nearerEndpoint = distFromStart > distFromEnd ? sideEnd : sideStart;
return createStyledLine(nearerEndpoint, targetIntersection, strokeColor);
}
let extensionLines = [
createExtensionLine(vertexA, vertexB, intersectionP, colorPairAD), createExtensionLine(vertexD, vertexE, intersectionP, colorPairAD),
createExtensionLine(vertexB, vertexC, intersectionQ, colorPairBE), createExtensionLine(vertexE, vertexF, intersectionQ, colorPairBE),
createExtensionLine(vertexC, vertexD, intersectionR, colorPairCF), createExtensionLine(vertexF, vertexA, intersectionR, colorPairCF)
];
if (progressExtensions > 0) { output.add(node('trimPath', { end: progressExtensions }, extensionLines)); }
// draw pascal line
let ptIntersectionP = { x: intersectionP.x, y: intersectionP.y };
let ptIntersectionQ = { x: intersectionQ.x, y: intersectionQ.y };
let ptIntersectionR = { x: intersectionR.x, y: intersectionR.y };
let distPQ = math.distance(ptIntersectionP, ptIntersectionQ);
let distQR = math.distance(ptIntersectionQ, ptIntersectionR);
let distRP = math.distance(ptIntersectionR, ptIntersectionP);
let maxSpanDist = math.max(distPQ, distQR, distRP);
let pascalLineStart = intersectionP, pascalLineEnd = intersectionQ;
if (maxSpanDist === distQR) { pascalLineStart = intersectionQ; pascalLineEnd = intersectionR; }
else if (maxSpanDist === distRP) { pascalLineStart = intersectionR; pascalLineEnd = intersectionP; }
let spanDirection = { x: pascalLineEnd.x - pascalLineStart.x, y: pascalLineEnd.y - pascalLineStart.y };
let spanLength = math.distance({ x: 0, y: 0 }, { x: spanDirection.x, y: spanDirection.y });
let overshootAmount = 25;
let extendedStart = { x: pascalLineStart.x - (spanDirection.x / spanLength) * overshootAmount, y: pascalLineStart.y - (spanDirection.y / spanLength) * overshootAmount };
let extendedEnd = { x: pascalLineEnd.x + (spanDirection.x / spanLength) * overshootAmount, y: pascalLineEnd.y + (spanDirection.y / spanLength) * overshootAmount };
let pascalLinePath = createStyledLine(extendedStart, extendedEnd, colorPascalLine).stroke({ width: 3 });
if (progressPascalLine > 0) { output.add(node('trimPath', { end: progressPascalLine }, [pascalLinePath])); }
// draw points & labels
function drawLabeledPoint(position, label, dotColor, animProgress, labelColor = '#000000') {
if (animProgress <= 0) return;
let distFromOrigin = math.distance({ x: 0, y: 0 }, { x: position.x, y: position.y });
let labelOffset = distFromOrigin < 0.001
? { x: 15, y: 15 }
: { x: (position.x / distFromOrigin) * 25, y: (position.y / distFromOrigin) * 25 };
let pointDot = create.ellipse({ radiusX: 4, radiusY: 4 })
.translate(position.x, position.y)
.fill(dotColor)
.scale(animProgress, animProgress);
let pointLabel = create.text({ content: label, fontSize: 18, color: labelColor, fontFamily: 'sans-serif' })
.translate(position.x + labelOffset.x - 5, position.y + labelOffset.y + 5)
.opacity(animProgress);
output.add(pointDot, pointLabel);
}
// primary vertices
drawLabeledPoint(vertexA, 'A', '#000000', progressVertices, '#000000');
drawLabeledPoint(vertexB, 'B', '#000000', progressVertices, '#000000');
drawLabeledPoint(vertexC, 'C', '#000000', progressVertices, '#000000');
drawLabeledPoint(vertexD, 'D', '#000000', progressVertices, '#000000');
drawLabeledPoint(vertexE, 'E', '#000000', progressVertices, '#000000');
drawLabeledPoint(vertexF, 'F', '#000000', progressVertices, '#000000');
// intersections
drawLabeledPoint(intersectionP, 'P', colorPairAD, progressIntersections, colorPairAD);
drawLabeledPoint(intersectionQ, 'Q', colorPairBE, progressIntersections, colorPairBE);
drawLabeledPoint(intersectionR, 'R', colorPairCF, progressIntersections, colorPairCF);