Sine wave animation not occurring at expected origin

- 1 answer

Ad

I'm playing around with three.js and trying to create a sine wave animation that emanates from the point on where a plane is clicked with the mouse. The thing is that the origin of the ripple isn't exactly where you click it - It appears to always come from the middle of the plane although clicking towards the furthest extents of the plane does seem to change the wave somehow.

Math has never been my strength so I'm sure it's some sort of logic issue!

Code:

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html" charset="utf-8" />
    </head>
    <body>
    </body>
    <script src="three.min.js"></script>
    <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
    <script>
        var scene = new THREE.Scene();
        var camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 10000);
        var renderer = new THREE.WebGLRenderer();
        renderer.setClearColor(0x17293a);
        renderer.setSize(window.innerWidth, window.innerHeight);
        document.body.appendChild(renderer.domElement);
        var mouse = new THREE.Vector3();

        var planeGeometry = new THREE.PlaneGeometry(200, 100, 50, 50);
        var planeMaterial = new THREE.MeshLambertMaterial({color: 0xEF3523, wireframe: true});
        var plane = new THREE.Mesh(planeGeometry, planeMaterial);
        plane.geometry.dynamic = true;
        plane.position.set(0, 0, 0);
        plane.rotation.x = -0.5 * Math.PI;
        scene.add(plane);
        camera.position.set(0, 90, 100);
        camera.lookAt(scene.position);

        var raycaster = new THREE.Raycaster();

        var checkMousePos = function() {
            mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
            mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
            raycaster.setFromCamera( mouse, camera );   
            var intersects = raycaster.intersectObjects( scene.children );
                console.log(intersects.length);
                if (intersects.length > 0) {
                    var wave = {};
                    wave.x = intersects[0].point.x;
                    wave.y = intersects[0].point.y;
                    wave.z = intersects[0].point.z;
                    drawWave(wave);
                }
        };

        drawWave = function(wave){
            var center = new THREE.Vector3(wave.x, wave.y, wave.z);
            var anim = function(ts) {
                requestAnimationFrame(anim);
                var vLength = plane.geometry.vertices.length;
                for (var i = 0; i < vLength; i++) {
                    var v = plane.geometry.vertices[i];
                    var dist = new THREE.Vector3(v.x, v.y, v.z).sub(center);
                    var size = 5.0;
                    var magnitude = 4;
                    v.z = Math.sin(dist.length()/-size + (ts/500)) * magnitude;
                }
                plane.geometry.verticesNeedUpdate = true;
                renderer.render(scene, camera);             
            }
            anim();
        };

        $(document).on('click', function(e) {
            checkMousePos();
        });


        var render = function() {
            requestAnimationFrame(render);
            renderer.render(scene, camera);
        };
        render();
    </script>
</html>

JSfiddle: https://jsfiddle.net/dwo3kvLp/

Ad

Answer

Ad

If you remove the line where you rotate the plane (plane.rotation.x = -0.5 * Math.PI;) you'll notice that it works just fine.

So the x, y, and z coordinates after the rotation don't necessarily mean what you would intuitively think those directions would be just from looking at it (and they definitely don't refer to the same directions as the x and y that you capture form the user click). What's happening is that your y and z coordinates are implemented to refer to the old orientation.

If you add a console.log statement to print out your wave.x, wave.y, and wave.z values just after they're assigned and right before calling drawWave(wave), then open the console and click around a bit, you should see the problem. (You can do this right on the JSFiddle page)

It turns out that by the time you're assigning the points to the wave at the bottom of checkMousePos, the direction that looks like it would be "y" ("forward-backward") is governed by "z" and stays basically constant plus-or-minus the height of the waves. That's why the wave origin is always in the center (forward-backward-wise) but moves left-right just fine.

So, all you need to do to fix it is change these lines:

wave.y = intersects[0].point.y;
wave.z = intersects[0].point.z;

to

wave.y = intersects[0].point.z * -1;
wave.z = intersects[0].point.y;

and it should work.

Full Updated Code:

var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(75,
    window.innerWidth/window.innerHeight, 0.1, 10000);
var renderer = new THREE.WebGLRenderer();
renderer.setClearColor(0x17293a);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var mouse = new THREE.Vector3();

var planeGeometry = new THREE.PlaneGeometry(200, 100, 50, 50);
var planeMaterial = new THREE.MeshLambertMaterial({color: 0xEF3523, wireframe: true});
var plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.geometry.dynamic = true;
plane.position.set(0, 0, 0);
plane.rotation.x = -0.5 * Math.PI;
scene.add(plane);
camera.position.set(0, 90, 100);
camera.lookAt(scene.position);

var raycaster = new THREE.Raycaster();

var checkMousePos = function() {
  mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
  mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
  raycaster.setFromCamera( mouse, camera ); 
  var intersects = raycaster.intersectObjects( scene.children );
  console.log(intersects.length);
  if (intersects.length > 0) {
    var wave = {};
    wave.x = intersects[0].point.x;
    wave.y = intersects[0].point.z * -1;
    wave.z = intersects[0].point.y;
    drawWave(wave);
  }
};

drawWave = function(wave){
  var center = new THREE.Vector3(wave.x, wave.y, wave.z);
  var anim = function(ts) {
    requestAnimationFrame(anim);
    var vLength = plane.geometry.vertices.length;
    for (var i = 0; i < vLength; i++) {
      var v = plane.geometry.vertices[i];
      var dist = new THREE.Vector3(v.x, v.y, v.z).sub(center);
      var size = 5.0;
      var magnitude = 4;
      v.z = Math.sin(dist.length()/-size + (ts/500)) * magnitude;
    }
    plane.geometry.verticesNeedUpdate = true;
    renderer.render(scene, camera);             
  }
  anim();
};

$(document).on('click', function(e) {
  checkMousePos();
});


var render = function() {
  requestAnimationFrame(render);
  renderer.render(scene, camera);
};
render();
Ad
source: stackoverflow.com
Ad