Making waves with thress.js

So I wanted to start making some posts on three.js examples, and one of the first ideas that came to mind was to make a waves example. In this post I will be wrirting about helper method that I made that can be used to create an instance of buffered geometry that is set of points that move in a wave like pattern.

1 - What to know

This is a post on a three.js example where I made some waves. In this example I am just using the Points material, as in this example I only have points set out for the buffered geometry that I am using. As such it would be a good idea to get up to speed with the Points material, and buffered geometry if you have not done so before hand. This is also a more advanced post on three.js, if you are new to three.js you might want to look at my getting started post on three.js first.

1.1 - version numbers matter

When working out this example I was using revision 98 of htree.js

2 - The wave Example

The wave example I made involves a helper method that can be used to create, or update geometry, buffered geometry, or just about anything by making the helper a higher-order function. This method accepts another method as one of the arguments that is passed the x,y,and z values for each point that will compose the vertices of the wave. I then use this method in conjunction with others to help make an update the geometry of the wave.

2.1 - The waveGrid helper

Here is the wave grid helper method that accepts a method that I can use to define what to do for each point in the grid of points. I use this to create an instance of buffer geometry and again later to update it in a loop.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// Wave grid helper
var waveGrid = function (opt) {
opt = opt || {};
opt.width = opt.width || 30;
opt.depth = opt.depth || 30;
opt.height = opt.height || 1;
opt.forPoint = opt.forPoint || function () {};
opt.context = opt.context || opt;
opt.xStep = opt.xStep || 0.075;
opt.yStep = opt.yStep || 0.1;
opt.zStep = opt.zStep || 0.075;
opt.waveOffset = opt.waveOffset === undefined ? 0 : opt.waveOffset;
var points = [],
radPer,
x = 0,
i = 0,
y,
z;
// points
while (x < opt.width) {
z = 0;
while (z < opt.depth) {
// radian percent
radPer = (z / opt.depth + (1 / opt.width * x) + opt.waveOffset) % 1;
// y value of point
y = Math.cos(Math.PI * 4 * radPer) * opt.height;
// call forPoint
opt.forPoint.call(opt.context, x * opt.xStep, y * opt.yStep, z * opt.zStep, i);
// step z, and point index
z += 1;
i += 3;
}
x += 1;
};
};

2.2 -Make Points helper

Here I have a method that makes use of my waveGrid method by making the initial state of the buffered geometry, as well as updating it as well.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// make a points mesh
var makePoints = function () {
var geometry = new THREE.BufferGeometry();
var points = [],
opt = {};
opt.forPoint = function (x, y, z, i) {
points.push(x, y, z);
};
waveGrid(opt);
var vertices = new Float32Array(points);
// itemSize = 3 because there are 3 values (components) per vertex
geometry.addAttribute('position', new THREE.BufferAttribute(vertices, 3));
return new THREE.Points(
// geometry as first argument
geometry,
// then Material
new THREE.PointsMaterial({
size: .05
}));
};

2.3 - Update Points

I again use my waveGrid method to update points.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// update points
var updatePoints = function (points, per) {
var position = points.geometry.getAttribute('position');
// update points
waveGrid({
waveOffset: per,
xStep: 0.125,
zStep: 0.125,
forPoint: function (x, y, z, i) {
position.array[i] = x - 2;
position.array[i + 1] = y - 2;
position.array[i + 2] = z - 2;
}
});
position.needsUpdate = true;
}

2.4 - Get it going

So now it is time to get this all working with the usual scene, camera, and renderer. I just use my makePoints helper to make the instance of a Points mesh that makes use of my geometry, and the Points material.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// RENDER
var renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(320, 240);
document.getElementById('demo').appendChild(renderer.domElement);
// SCENE
var scene = new THREE.Scene();
// POINTS
var points = makePoints();
scene.add(points);
// CAMERA
var camera = new THREE.PerspectiveCamera(40, 320 / 240, .001, 1000);
camera.position.set(3.4, 8, 3.4);
// CONTROLS
var controls = new THREE.OrbitControls(camera, renderer.domElement);
renderer.render(scene, camera);
// LOOP
var frame = 0,
maxFrame = 100,
loop = function () {
requestAnimationFrame(loop);
updatePoints(points, frame / maxFrame);
renderer.render(scene, camera);
frame += 1;
frame %= maxFrame;
};
loop();