// This is an example Chiasm plugin based on HTML5 Canvas. // This displays a wave simulation. // Clicking on the canvas interacts with the simulation. // Note that D3.js is not used at all here. function Wave (){ var my = ChiasmComponent({ waterColor: "red", skyColor: Model.None, gridSize: 50, pullStrength: 0.01, dampeningFactor: 0.99, initialHeight: 0.5 }); // These functions will be populated later when the canvas is available. var drawCells = function (){}; var clearCanvas = function (){}; var executeMouseInteraction = function (){}; var iterateSimulation = function (){}; var canvas = document.createElement("canvas"); var c = canvas.getContext("2d"); var cells = []; // Poor man's RAF polyfill var nextFrame = requestAnimationFrame || setTimeout; // Expose the canvas as the element that Chiasm will append to its container. my.el = canvas; // This function executes once per animation frame function executeFrame(){ nextFrame(executeFrame); clearCanvas(); drawCells(); iterateSimulation(); executeMouseInteraction(); } // Initialize the cells. my.when(["gridSize", "initialHeight"], function (gridSize, initialHeight) { cells = []; // Initialize the water height for(var i = 0; i < gridSize; i++){ cells.push({ // for a still initial surface //height: 0.5, // for an initial wave: height: i === Math.floor(gridSize*1/4) ? 2 : initialHeight, velocity: 0 }); } }); my.when(["box", "gridSize", "waterColor", "skyColor"], function (box, gridSize, waterColor, skyColor){ clearCanvas = function (){ canvas.width = box.width; canvas.height = box.height; if(skyColor !== Model.None){ c.fillStyle = skyColor; c.fillRect(0, 0, box.width, box.height); } } drawCells = function (){ c.beginPath(); c.moveTo(box.width, box.height); c.lineTo(0, box.height); for(var i = 0; i < gridSize; i++){ var cell = cells[i]; var x = i / (gridSize-1) * box.width; var y = box.height - cell.height * box.height; c.lineTo(x,y); } c.closePath(); c.fillStyle = waterColor; c.fill(); } }); my.when(["gridSize", "pullStrength", "dampeningFactor", "initialHeight"], function (gridSize, pullStrength, dampeningFactor, initialHeight){ var conservationOfMassCorrection = 0; // Increment the wave simulation: // Neighboring cells pull on one another. iterateSimulation = function(){ var avgHeight = 0; for(var i = 0; i < gridSize; i++){ // center cell var c = cells[i]; // left neighbor var l = cells[((i - 1) + gridSize) % gridSize]; // right neighbor var r = cells[(i + 1) % gridSize]; // pull toward neighbors c.velocity += pullStrength * (l.height - c.height); c.velocity += pullStrength * (r.height - c.height); // increment velocity c.height += c.velocity; // ensure conservation of mass c.height += conservationOfMassCorrection; // apply dampening c.velocity *= dampeningFactor; avgHeight += c.height; } avgHeight /= (gridSize - 1); conservationOfMassCorrection = initialHeight - avgHeight; } }); my.when(["mouseDown", "mouseX", "mouseY", "box"], function (mouseDown, mouseX, mouseY, box){ var cellWidth = 1 / (cells.length - 1) * box.width; // Pull the wave cell closest to the mouse executeMouseInteraction = function(){ if(mouseDown){ for(var i = 0; i < cells.length; i++){ var x = i / (cells.length - 1) * box.width; if(Math.abs(x - mouseX) < cellWidth){ var cell = cells[i]; cell.height = 1 - mouseY / box.height; cell.velocity = 0; } } } } }); // Record when the mouse is pressed canvas.addEventListener("mousedown", function (e){ my.mouseDown = true; // This line prevents unintended drag behavior, like selecting page elements. e.preventDefault(); }); // Record when the mouse is moved canvas.addEventListener("mousemove", function (e){ var rect = canvas.getBoundingClientRect(); my.mouseX = e.clientX - rect.left; my.mouseY = e.clientY - rect.top; }); // Record when the mouse is released canvas.addEventListener("mouseup", function (e){ my.mouseDown = false; }); // Draw the first frame executeFrame(); return my; }