Mouse controlled first person movement JS

I'm trying to implement a first person movement using the mouse. I do have it working with keyboard yet I'm having difficulties implementing it using mouse since movement to a specific side isn't that clear (i.e moving left can include moving up or down). I want to use the matrix3d in order to receive changed values of the position.

EDIT #2Here is a jsfiddle .

EDITI've pasted the new code I've managed to resolve:

$(document).on('mousemove', function (e) {
        var MOVE = 10; // how much to move
        var XTURN = 1; // how much to rotate
        var YTURN = 1; // how much to rotate
        var transformer, origMat, translationMatrix, result;
        transformer = document.getElementById("transformer");

        if ($.browser.webkit)
            origMat = new WebKitCSSMatrix(window.getComputedStyle(transformer).webkitTransform);

        //turn left
        if (e.pageX < xPrev) {
            if (XTURN < 0) {
                XTURN *= -1;
            }
            xPrev = e.pageX;
        //turn right
        } else {
            if (XTURN > 0) {
                XTURN *= -1;
            }
            xPrev = e.pageX;

        }
        //look up
        if (e.pageY < yPrev) {
            if (YTURN < 0) {
                YTURN *= -1;
            } 
            yPrev = e.pageY;
        //look down
        } else {
            if (YTURN > 0) {
                YTURN *= -1;
            }
            yPrev = e.pageY;
        }

        translationMatrix = new WebKitCSSMatrix("matrix3d(" + cos(XTURN).toFixed(10) + ",0," + sin(XTURN).toFixed(10) + ",0,0,"+ cos(-YTURN).toFixed(10) +","+ sin(YTURN).toFixed(10) +",0, " + sin(-XTURN).toFixed(10) + ","+ sin(-YTURN).toFixed(10) +"," + cos(XTURN).toFixed(10) + ",0,0,0,0,1)");

        transformer.style.webkitTransform = translationMatrix.multiply(origMat).toString();
    });

As you can see (Sorry for the one line matrix) I'm stating the changes of the X and Y rotations on the same matrix change and then committing it, the issue now is with the cos(XTURN).toFixed(10) which can be related to the X and Y rotations, so you can see it works but not perfect. Would appreciate any tips/ideas.

P.S I don't want to use the Pointer Lock API , even though it's great, since I want it to support maximal number of browsers.

Problem courtesy of: Shahar Galukman

Solution

Pure JavaScript is mostly better than libraries (unless it's a " Code less do more " thing),

since you can understand what your code really does.

This is my entire JavaScript code:

var velocity = 0.5;

document.onmousemove = function(e) {
    var angleX = e.pageY * velocity * Math.PI / 180;
    var angleY = e.pageX * velocity * Math.PI / 180;
    document.getElementById('transformer').style.webkitTransform = 'matrix3d(' + Math.cos(-angleY) + ',0,' + Math.sin(-angleY) + ',0,' + (Math.sin(angleX)*Math.sin(-angleY)) + ',' + Math.cos(angleX) + ',' + (-Math.sin(angleX)*Math.cos(-angleY)) + ',0,' + (-Math.cos(angleX)*Math.sin(-angleY)) + ',' + Math.sin(angleX) + ',' + (Math.cos(angleX)*Math.cos(-angleY)) + ',0,0,0,0,1)';
};

And this is the fiddle .

It works!

(I even made an example of this using the Pointer Lock API: fiddle (click the square to begin)

Explanation:

First, a velocity variable to easily set the rotation speed.

Then, a mousemove event which has the two rotation variabls set.

The last line is to convert from rotateX and rotateY transformations, to matrix3d as requested. This Stackoverflow question helped me get to the following solution.

rotateX(angleX) is equal to the following matrix:

1               0               0               0


0               cos(angleX)     -sin(angleX)    0


0               sin(angleX)     cos(angleX)     0


0               0               0               1

rotateY(angleY) is equal to the following matrix:

cos(angleY)     0               sin(angleY)     0


0               1               0               0


-sin(angleY)    0               cos(angleY)     0


0               0               0               1

And to use them both together, you need to multiply the two matrices. So I wrote a small JavaScript tool to give me the calculation I need to do to get the result of this multiplication.

The result:

cos(angleY)               sin(angleX)*sin(angleY)   cos(angleX)*sin(angleY)   0



0                         cos(angleX)               -sin(angleX)              0



-sin(angleY)              sin(angleX)*cos(angleY)   cos(angleX)*cos(angleY)   0



0                         0                         0                         1

And that's the way to convert rotateX and rotateY to matrix3d .

Hope it helps :)

Solution courtesy of: ThatWeirdo

Discussion

It's not quite clear to me what your high level goal is. It sounds like you're trying to implement a Counterstrike-like game in JS and CSS. Which is awesome! For the rest of this answer, I'm going to assume that you are trying to do something like that.

Realistically, you must use the Pointer Lock API. Otherwise, you won't be able to turn around by only moving the mouse left. You'll hit the edge of the browser window and stop turning. The browser support isn't great, but it's by far a better experience for the gamer!

In order to render your world with CSS transforms, you need to do a complicated series of transforms to generate the matrix for every side of every object visible in the game world. This is because the browser's perspective is always looking directly along the Z axis. So in order to animate things "around" the viewer's eye, you have to translate and rotate them around. After a bit of poking around, I came to the conclusion that doing all the transforms in CSS is prohibitively slow (and complicated!). But never fear, there's another way! WebGL or Canvas to the rescue!

Take a look at Isaac Sukin's game Nemesis . It's an excellent example, and he's written a tutorial to come up with something similar! The library it's based on, Three.js, is very widely used and has a very understandable API. It takes almost all of the hard part out, and lets you just make a 3D world!

Good luck with the game!

Discussion courtesy of: tangphillip

Using quaternions is really easier. I found an implementation in Google closure library so I made an example (also, check the jsFiddle ):

goog.require('goog.vec.Quaternion');

var velocity = 0.5;

var lastX = null;
var lastY = null;
var angleX = 0;
var angleY = 0;

$(document).on('mousemove', function (e) {
    if (lastX == null) lastX = e.pageX;
    if (lastY == null) lastY = e.pageY;

    angleX += (e.pageX - lastX) * velocity * Math.PI / 180;
    angleY += (e.pageY - lastY) * velocity * Math.PI / 180;

    lastX = e.pageX;
    lastY = e.pageY;

    var quat = goog.vec.Quaternion.concat(
        goog.vec.Quaternion.fromAngleAxis(angleX, [0, 1, 0], []),
        goog.vec.Quaternion.fromAngleAxis(-angleY, [1, 0, 0], []), []);

    var matrix = goog.vec.Quaternion.toRotationMatrix4(quat, []);

    $("#transformer").css({
        webkitTransform: "matrix3d(" + matrix.join(",") + ")"
    });
});

Discussion courtesy of: IcanDivideBy0

This recipe can be found in it's original form on Stack Over Flow .