Tuesday, January 29, 2013

Dragging Three.js Objects

‹prev | My Chain | next›

Last night I got started on a motion puzzle game for 3D Game Programming for Kids. As with all games in the book, it is built with Three.js. It also uses a little bit of Physijs for gravity and collisions.

The idea behind the game it to position and orient platforms so that the player can reach a goal:


To position the platforms, I thought to introduce something new in the book: mouse interaction. My understanding is that this is a difficult thing in Three.js, though some people have made some helper libraries. Tonight I try it without those helper libraries.

Since Three.js lacks first class support for mouse events, I (or a library) needs to translate DOM mouse events into Three.js action. In this game, the gaming area comprises the entire viewport of the browser, so only a little bit of translation is required:
  document.addEventListener("click", function(event) {
    var position = new THREE.Vector3(
      event.clientX - width/2,
      height/2 - event.clientY,
      500
    );
    var vector = new THREE.Vector3(0, 0, -1);
    var ray = new THREE.Ray(position, vector);
    var intersects = ray.intersectObject(ramp1);
    if (intersects.length > 0) console.log("Clicked ramp1");
    intersects = ray.intersectObject(ramp2);
    if (intersects.length > 0) console.log("Clicked ramp2");    
  });
Once I have the position of the click event translated into Three.js coordinates, I use it to create a ray pointing from that point into the screen (Z=-1). I then use the intersectObject() method of Ray to decide if the ray crosses through either of the ramps, logging to the console if it does.

This works as expected:


So now I am ready to add the ability to drag the selected item. Unfortunately, there does not seem to be proper drag events for Three.js / canvas / WebGL, so I have to settle for marking a ramp active in the mousedown event and storing the current X-Y position of the mouse:
  var active_ramp, mouse_x, mouse_y;
  document.addEventListener("mousedown", function(event) {
    //console.log(event.clientX);
    //console.log(event.clientY);

    mouse_x = event.clientX;
    mouse_y = event.clientY;
    var position = new THREE.Vector3(
      event.clientX - width/2,
      height/2 - event.clientY,
      500
    );
    var vector = new THREE.Vector3(0, 0, -1);
    var ray = new THREE.Ray(position, vector);
    var intersects = ray.intersectObject(ramp1);
    if (intersects.length > 0) active_ramp = ramp1;
    intersects = ray.intersectObject(ramp2);
    if (intersects.length > 0) active_ramp = ramp2;    
  });
I can then use this information in the mousemove handler to determine how much the mouse has moved:
  document.addEventListener("mousemove", function(event) {
    if (!active_ramp) return;
    var x_diff = event.clientX - mouse_x,
        y_diff = event.clientY - mouse_y;
        
    active_ramp.__dirtyPosition = true;
    active_ramp.position.x = active_ramp.position.x + x_diff;
    active_ramp.position.y = active_ramp.position.y - y_diff;
    
    mouse_x = event.clientX;
    mouse_y = event.clientY;
  });
The mousemove event happens constantly, so the guard clause at the top ensures that this particular handler only applies when a ramp has been clicked.

Lastly, I need a mouseup handler to deactivate the current ramp when the player lets go of it:
  document.addEventListener("mouseup", function(event) {
    active_ramp = null;
  });
And that actually works fairly well. I can now move both ramps, making this game much easier to win:



That is actually a rather nice effect and not too much work. I would have preferred proper drag events, but this seems almost simple enough for the book.

(the code so far)


Day #645

1 comment:

  1. Hello, thank you for explaining this topic. I am doing a same stuff, but the problem is that when I change the camera to perspectivecamera, then the edges of the rectangle are not be recognized accurately, and the rectangle is selected when the mouse loosly on it. if you change the camera in your code, you will see what I mean. Do you know how I can correct it? Thank you in advance.

    ReplyDelete