Last active
April 20, 2021 12:42
-
-
Save pfirpfel/56b4ef27922d02d89273c8740fb88bea to your computer and use it in GitHub Desktop.
Wavy knot
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
<html> | |
<head> | |
<title>Wavy Clip Mask</title> | |
</head> | |
<body> | |
<input id="karma" type="range" min="0" max="100" value="50" style="width: 500px;"/> | |
<br> | |
<canvas id="canvas"></canvas> | |
<script src="knot.js" type="text/javascript"></script> | |
<script src="wave.js" type="text/javascript"></script> | |
<script src="main.js" type="text/javascript"></script> | |
<script> | |
init(); | |
</script> | |
</body> | |
</html> |
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
var generateKnot = function(thickness, padding){ | |
var a = thickness; // thickness of knot strings | |
var b = padding; // padding between crossings | |
var transformPath = function(path, transX, transY){ | |
return path.map(function(coord){ | |
return [coord[0] + transX, coord[1] + transY]; | |
}); | |
}; | |
var rotatePath = function(path, centerX, centerY, angle){ | |
return path.map(function(coord){ | |
var s = Math.sin(angle); | |
var c = Math.cos(angle); | |
var x = coord[0] - centerX; | |
var y = coord[1] - centerY; | |
var newX = x * c - y * s; | |
var newY = x * s + y * c; | |
return [newX + centerX, newY + centerY]; | |
}); | |
}; | |
var c = 3*a + 2*b; | |
var d = 4*a + 3*b; | |
var e = a + b; | |
var baseP = [[0,0],[c,0],[c,c],[a+b,c],[a+b,c-a],[c-a,c-a],[c-a,a],[a,a],[a,d],[0,d]]; | |
var baseI = [[0,0],[a,0],[a,c],[0,c]]; | |
var baseJ = [[0,0],[c,0],[c,e+a],[c-a,e+a],[c-a,a],[a,a],[a,d],[0,d]]; | |
var knot = []; | |
// Ps | |
knot.push(transformPath(rotatePath(baseP, 0, 0, Math.PI*1.5), 0, c)); | |
knot.push(transformPath(rotatePath(baseP, 0, 0, Math.PI*0.5), d+d+2*b+a, d+2*b+a+e)); | |
// Js | |
knot.push(transformPath(baseJ, d+b, 0)); | |
knot.push(transformPath(rotatePath(baseJ, 0, 0, Math.PI*0.5), d+d+2*b+a, c-a)); | |
knot.push(transformPath(rotatePath(baseJ, 0, 0, Math.PI), c+e+a+b, d+d+2*b+a)); | |
knot.push(transformPath(rotatePath(baseJ, 0, 0, Math.PI*1.5), 0, c+d+b)); | |
//Is | |
knot.push(transformPath(rotatePath(baseI, 0, 0, Math.PI*0.5), c+c+b, d+b)); | |
knot.push(transformPath(baseI, c-a, c+b)); | |
knot.push(transformPath(baseI, 6*a+6*b, c+b)); | |
var height = 2*d+2*b+a; | |
var hypo = height*Math.pow(2,1/2); | |
// rotate knot around center | |
knot = knot.map(function(path){ | |
return rotatePath(path, height/2, height/2, Math.PI*0.25); | |
}); | |
// move knot to visible canvas | |
knot = knot.map(function(path){ | |
return transformPath(path, (hypo-height)/2-((2*a+2*b)*Math.pow(2,1/2)/2), (hypo-height)/2); | |
}); | |
knot = knot.map(function(path){ | |
return path.map(function(coord){ | |
return coord.map(Math.round); | |
}); | |
}); | |
return knot; | |
} | |
if(typeof exports != 'undefined') exports.generateKnot = generateKnot; |
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
var init = function(){ | |
// knot properties | |
var thinkness = 20; | |
var padding = 7; | |
// generate knot | |
var shape = generateKnot(thinkness, padding); | |
// calculate knot dimensions | |
var height = (9 * thinkness + 8 * padding) * Math.pow(2, 1/2); | |
var cutoffEdges = (2 * thinkness + 2 * padding) * Math.pow(2, 1/2); | |
// generate wave level | |
var wave = new waveBox({ | |
x: 0, | |
y: 0, | |
width: height - cutoffEdges, | |
height: height, | |
initialLevel: 0.5, | |
springs: 25 | |
}); | |
var canvas = document.getElementById('canvas'); | |
if (canvas.getContext) { | |
var ctx = canvas.getContext('2d'); | |
canvas.width = Math.ceil(height - cutoffEdges); | |
canvas.height = Math.ceil(height); | |
var drawPath = function(path){ | |
var i; | |
ctx.moveTo(path[0][0], path[0][1]); | |
for(i = 0; i < path.length; i++){ | |
ctx.lineTo(path[i][0], path[i][1]); | |
} | |
ctx.closePath(); | |
}; | |
ctx.strokeStyle = "#303030"; | |
ctx.lineWidth = 2; | |
var boundaries = wave.getBoundaries(); | |
var render = function(timestamp){ | |
var karma = document.getElementById('karma').value; | |
var karmaPercentage = karma / 100; | |
wave.setLevel(karmaPercentage); | |
ctx.clearRect(boundaries.x1, boundaries.y1, boundaries.x2, boundaries.y2); | |
ctx.save(); | |
ctx.beginPath(); | |
shape.forEach(drawPath); | |
ctx.clip(); | |
ctx.fillStyle = "#e0e0e0"; | |
ctx.fill(); | |
ctx.fillStyle = "#000000"; | |
wave.draw(ctx, timestamp, false); | |
ctx.stroke(); | |
ctx.restore(); | |
window.requestAnimationFrame(render); | |
} | |
} | |
window.requestAnimationFrame(render); | |
} |
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
var waveBox = function(config){ | |
config = config || {}; | |
/**---------------------- | |
Configurable values | |
--------------------------**/ | |
// top left origin of avg 100% level | |
// (animation has overflow above this point) | |
var originX = (config.x !== undefined) ? config.x : 50; | |
var originY = (config.y !== undefined) ? config.y : 50; | |
// width of the whole box | |
var width = (config.width !== undefined) ? config.width : 250; | |
// height of maxium level | |
// (distance between origin and "bottom") | |
// note: the water has an overflow | |
var height = (config.height !== undefined) ? config.height : 200; | |
// number of springs | |
// the higher the prettier (and slower) | |
var numOfsprings = (config.springs !== undefined) ? config.springs : 25; | |
// current wate level, change via setLevel(level) | |
// value between 0.0 and 1.0 | |
var relativeLevel = (config.initialLevel !== undefined) ? config.initialLevel : 0.5; | |
// characteristics of the waves | |
var tension = 0.025; | |
var dampening = 0.08; | |
var spread = 0.1; | |
// characteristics of the splash | |
var splashInterval = 1600; // in ms | |
var splashVelocityBase = 0.1; | |
var splashVelocityDynamic = 0.05; | |
//--------------------------------------------- | |
// area which gets affected by the animation | |
var boundaries = { | |
x1: Math.max(0, originX - 1), | |
y1: Math.max(0, originY - width * 0.3), // includes top overflow | |
x2: originX + width + 1, | |
y2: originY + height | |
}; | |
// expose boundaries | |
this.getBoundaries = function(){ | |
return { | |
x1: boundaries.x1, | |
y1: boundaries.y1, | |
x2: boundaries.x2, | |
y2: boundaries.y2 | |
}; | |
}; | |
// init springs | |
var springs = Array.apply(null, Array(numOfsprings)).map(function () { | |
return { | |
level: relativeLevel, | |
velocity: 0 | |
}; | |
}); | |
// update level and velocity of spring | |
var updateSpring = function(spring){ | |
var levelDelta = relativeLevel - spring.level; | |
spring.velocity += (tension * levelDelta - spring.velocity * dampening); | |
spring.level += spring.velocity; | |
return spring; | |
} | |
// calculate values of springs in new frame | |
var update = function(){ | |
var currentPass, passes = 4, curr = 0; | |
springs = springs.map(updateSpring); | |
var leftDeltas = [], rightDeltas = []; | |
for(currentPass = 0; currentPass < passes; currentPass++){ | |
for(curr = 0; curr < springs.length; curr++){ | |
if(curr > 0){ | |
leftDeltas[curr] = spread * (springs[curr].level - springs[curr - 1].level); | |
springs[curr - 1].velocity += leftDeltas[curr]; | |
} | |
if(curr < springs.length - 1){ | |
rightDeltas[curr] = spread * (springs[curr].level - springs[curr + 1].level); | |
springs[curr + 1].velocity += rightDeltas[curr]; | |
} | |
} | |
for(curr = 0; curr < springs.length; curr++){ | |
if(curr > 0){ | |
springs[curr - 1].level += leftDeltas[curr]; | |
} | |
if(curr < springs.length - 1){ | |
springs[curr + 1].level += rightDeltas[curr]; | |
} | |
} | |
} | |
}; | |
// drop something into the water at a specific position | |
var splash = function(index, velocity){ | |
if(index > 0 && index < springs.length){ | |
springs[index].velocity = velocity; | |
} | |
}; | |
var springWidth = width / numOfsprings; | |
var drawWaveBox = function(ctx, clear){ | |
clear = (typeof clear !== 'undefined') ? clear : true; | |
ctx.save(); | |
if(clear) ctx.clearRect(boundaries.x1, boundaries.y1, boundaries.x2, boundaries.y2); | |
ctx.beginPath(); | |
var lastY = originY + height * (1 - springs[0].level); | |
ctx.moveTo(originX, lastY); | |
for(var i = 1; i < springs.length; i++){ | |
var y = originY + height * (1 - springs[i].level); | |
var x = originX + i * springWidth; | |
ctx.quadraticCurveTo(x - springWidth * 0.5, lastY, x, y); | |
lastY = y; | |
} | |
x = originX + springs.length * springWidth; | |
ctx.quadraticCurveTo(x - springWidth * 0.5, lastY, x ,lastY); | |
ctx.lineTo(x, originY + height); | |
ctx.lineTo(originX, originY + height); | |
ctx.closePath(); | |
ctx.fill(); | |
ctx.restore(); | |
} | |
var lastSplash = 0; | |
var drawWaveFrame = function(ctx, timestamp, clear){ | |
// determine whether we have to "splash" | |
if(lastSplash == 0 || (timestamp - lastSplash) > splashInterval){ | |
var splashIndex = Math.floor(Math.random() * springs.length); | |
var splashVelocity = splashVelocityBase + Math.random() * splashVelocityDynamic; | |
splash(splashIndex, splashVelocity); | |
lastSplash = timestamp || 0; | |
} | |
// calculate new waves | |
update(); | |
// draw them | |
drawWaveBox(ctx, clear); | |
}; | |
this.setLevel = function(level){ | |
if(level >= 0 && level <= 1){ | |
relativeLevel = level; | |
} | |
}; | |
this.draw = drawWaveFrame; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
https://bl.ocks.org/pfirpfel/56b4ef27922d02d89273c8740fb88bea