// 033 - Gerstner Ocean Waves (botany)
// 1:1 Original algorithm engine source
function createGerstnerOceanWaves() {
const WAVE_LINES = 32;
const POINTS_PER_LINE = 160;
return {
setup() {
},
render(context, timeState, params) {
const { ctx, width, height } = context;
const speed = Number(params.waveSpeed || 1.2);
const steepness = Number(params.steepness || 0.65);
const t = timeState.time * speed;
ctx.fillStyle = "#050a12";
ctx.fillRect(0, 0, width, height);
const waves = [
{ dx: 1, dy: 0.1, length: 140, amp: 26, s: 1 },
{ dx: 0.8, dy: 0.6, length: 85, amp: 14, s: 1.4 },
{ dx: 0.5, dy: -0.8, length: 50, amp: 8, s: 1.8 },
{ dx: -0.7, dy: 0.7, length: 30, amp: 4, s: 2.2 }
];
for (let l = 0; l < WAVE_LINES; l++) {
const normL = l / WAVE_LINES;
const originY = height * 0.25 + normL * (height * 0.65);
ctx.beginPath();
for (let i = 0; i <= POINTS_PER_LINE; i++) {
const normX = i / POINTS_PER_LINE;
const x0 = normX * width;
const y0 = originY;
let displacedX = x0;
let displacedY = y0;
for (let w = 0; w < waves.length; w++) {
const wv = waves[w];
const k = 2 * Math.PI / wv.length;
const wSpeed = Math.sqrt(9.8 * k) * wv.s;
const phase = k * (wv.dx * x0 + wv.dy * y0) - wSpeed * t;
const q = steepness / (k * wv.amp * waves.length);
displacedX -= wv.dx / k * (q * Math.sin(phase));
displacedY -= wv.amp * Math.cos(phase);
}
if (i === 0) ctx.moveTo(displacedX, displacedY);
else ctx.lineTo(displacedX, displacedY);
}
const hue = (195 + normL * 35) % 360;
const lightness = 40 + (1 - normL) * 35;
ctx.strokeStyle = hsla(hue, 90, lightness, 0.75);
ctx.lineWidth = 1.6 - normL * 0.6;
ctx.stroke();
}
}
};
}
// Default parameters from content metadata
const defaultParams = [
{
"key": "waveSpeed",
"label": "Wave Swell Velocity",
"type": "range",
"min": 0.4,
"max": 3,
"step": 0.1,
"defaultValue": 1.2,
"description": "Ocean current propagation rate"
},
{
"key": "steepness",
"label": "Trochoidal Steepness (Q)",
"type": "range",
"min": 0.2,
"max": 1,
"step": 0.05,
"defaultValue": 0.65,
"description": "Crest sharpness & horizontal pinching"
}
];
if (!window.__art_instances) window.__art_instances = {};
if (!window.__art_instances['gerstner-ocean-waves']) {
const inst = typeof createGerstnerOceanWaves === 'function' ? createGerstnerOceanWaves() : null;
if (inst && inst.setup) {
inst.setup({ ctx, width, height, dpr: 1, aspectRatio: width / height }, defaultParams);
}
window.__art_instances['gerstner-ocean-waves'] = inst;
}
const instance = window.__art_instances['gerstner-ocean-waves'];
if (instance && instance.render) {
instance.render(
{ ctx, width, height, dpr: 1, aspectRatio: width / height },
{ time, deltaTime: dt, frameCount: Math.floor(time * 60), fps: 60 },
defaultParams
);
}