Monday, May 20, 2024
 Popular · Latest · Hot · Upcoming
168
rated 0 times [  170] [ 2]  / answers: 1 / hits: 21691  / 11 Years ago, tue, november 5, 2013, 12:00:00

I’m looking for a way to create a wave in a shape designed in canvas. After much research I found something that is pretty close to what I want:





var c = document.getElementById('c'),
ctx = c.getContext('2d'),
cw = c.width = window.innerWidth,
ch = c.height = window.innerHeight,
points = [],
tick = 0,
opt = {
count: 5,
range: {
x: 20,
y: 80
},
duration: {
min: 20,
max: 40
},
thickness: 10,
strokeColor: '#444',
level: .35,
curved: true
},
rand = function(min, max) {
return Math.floor((Math.random() * (max - min + 1)) + min);
},
ease = function(t, b, c, d) {
if ((t /= d / 2) < 1) return c / 2 * t * t + b;
return -c / 2 * ((--t) * (t - 2) - 1) + b;
};

ctx.lineJoin = 'round';
ctx.lineWidth = opt.thickness;
ctx.strokeStyle = opt.strokeColor;

var Point = function(config) {
this.anchorX = config.x;
this.anchorY = config.y;
this.x = config.x;
this.y = config.y;
this.setTarget();
};

Point.prototype.setTarget = function() {
this.initialX = this.x;
this.initialY = this.y;
this.targetX = this.anchorX + rand(0, opt.range.x * 2) - opt.range.x;
this.targetY = this.anchorY + rand(0, opt.range.y * 2) - opt.range.y;
this.tick = 0;
this.duration = rand(opt.duration.min, opt.duration.max);
}

Point.prototype.update = function() {
var dx = this.targetX - this.x;
var dy = this.targetY - this.y;
var dist = Math.sqrt(dx * dx + dy * dy);

if (Math.abs(dist) <= 0) {
this.setTarget();
} else {
var t = this.tick;
var b = this.initialY;
var c = this.targetY - this.initialY;
var d = this.duration;
this.y = ease(t, b, c, d);

b = this.initialX;
c = this.targetX - this.initialX;
d = this.duration;
this.x = ease(t, b, c, d);

this.tick++;
}
};

Point.prototype.render = function() {
ctx.beginPath();
ctx.arc(this.x, this.y, 3, 0, Math.PI * 2, false);
ctx.fillStyle = '#000';
ctx.fill();
};

var updatePoints = function() {
var i = points.length;
while (i--) {
points[i].update();
}
};

var renderPoints = function() {
var i = points.length;
while (i--) {
points[i].render();
}
};

var renderShape = function() {
ctx.beginPath();
var pointCount = points.length;
ctx.moveTo(points[0].x, points[0].y);
var i;
for (i = 0; i < pointCount - 1; i++) {
var c = (points[i].x + points[i + 1].x) / 2;
var d = (points[i].y + points[i + 1].y) / 2;
ctx.quadraticCurveTo(points[i].x, points[i].y, c, d);
}
ctx.lineTo(-opt.range.x - opt.thickness, ch + opt.thickness);
ctx.lineTo(cw + opt.range.x + opt.thickness, ch + opt.thickness);
ctx.closePath();
ctx.fillStyle = 'hsl(' + (tick / 2) + ', 80%, 60%)';
ctx.fill();
ctx.stroke();
};

var clear = function() {
ctx.clearRect(0, 0, cw, ch);
};

var loop = function() {
window.requestAnimFrame(loop, c);
tick++;
clear();
updatePoints();
renderShape();
//renderPoints();
};

var i = opt.count + 2;
var spacing = (cw + (opt.range.x * 2)) / (opt.count - 1);
while (i--) {
points.push(new Point({
x: (spacing * (i - 1)) - opt.range.x,
y: ch - (ch * opt.level)
}));
}

window.requestAnimFrame = function() {
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(a) {
window.setTimeout(a, 1E3 / 60)
}
}();

loop();

canvas {
display: block;
}

<canvas id=c></canvas>





http://codepen.io/jackrugile/pen/BvLHg



The problem is that the movement of the wave appears a bit unreal. I'd like to keep this notion of random motion and not have a shape that repeats itself by moving from left to right but it will be great if I found a way to create a ‘realistic’ water movement (good fluid dynamics, collisions of this wave with the edges of its container (custom shape)).



I think I'm asking a lot but ... A small line of research could help :)


More From » html

 Answers
258

Interference



You can make a more realistic wave using interference.




  • Have one big wave (swell) running slowly with a big motion

  • Have another one or two smaller sine waves running (oscillators)

  • All with random amplitudes

  • Mix the waves horizontally using average and calculate the various points

  • Draw the result using a cardinal spline (or if the resolution is high you can just draw simple lines between the points instead).



Use various parameters so you can adjust it live to find a good combination.



You can also add oscillators to represent the z axis to make it more realistic in case you want to layer the waves to make a pseudo-3D wave.



Example



I cannot give you wave collision, fluid dynamics - that would be a bit too broad for SO but I can give you a fluid-ish wave example (as you have the point of each segment you can use that for collision detection).



And example would be to create an oscillator object which you could set the various settings on such as amplitude, rotation speed (phase) etc.



Then have a mixer function which mixes the result of these oscillators that you use.



Live demo here (full-screen version here)



Demo



The oscillator object in this demo look like this:



function osc() {

/// various settings
this.variation = 0.4; /// how much variation between random and max
this.max = 100; /// max amplitude (radius)
this.speed = 0.02; /// rotation speed (for radians)

var me = this, /// keep reference to 'this' (getMax)
a = 0, /// current angle
max = getMax(); /// create a temp. current max

/// this will be called by mixer
this.getAmp = function() {

a += this.speed; /// add to rotation angle

if (a >= 2.0) { /// at break, reset (see note)
a = 0;
max = getMax();
}

/// calculate y position
return max * Math.sin(a * Math.PI) + this.horizon;
}

function getMax() {
return Math.random() * me.max * me.variation +
me.max * (1 - me.variation);
}

return this;
}


This do all the setup and calculations for us and all we need to do is to call the getAmp() to get a new value for each frame.



Instead of doing it manually we can use a mixer. This mixer allows us to add as many oscillators we want to the mix:



function mixer() {

var d = arguments.length, /// number of arguments
i = d, /// initialize counter
sum = 0; /// sum of y-points

if (d < 1) return horizon; /// if none, return

while(i--) sum += arguments[i].getAmp(); /// call getAmp and sum

return sum / d + horizon; /// get average and add horizon
}


Putting this in a loop with a point recorder which shifts the point in one direction will create a fluid looking wave.



The demo above uses three oscillators. (A tip in that regard is to keep the rotation speed lower than the big swell or else you will get small bumps on it.)



NOTE: The way I create a new random max is not the best way as I use a break point (but simple for demo purpose). You can instead replace this with something better.


[#74500] Monday, November 4, 2013, 11 Years  [reply] [flag answer]
Only authorized users can answer the question. Please sign in first, or register a free account.
emilee

Total Points: 365
Total Questions: 113
Total Answers: 109

Location: Monaco
Member since Fri, Sep 24, 2021
3 Years ago
;