Mouse controlled first person movement JS - javascript

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 #2 Here is a jsfiddle.
EDIT I'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.

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 :)

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!

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(",") + ")"
});
});

Related

Issue with JS Mouse Wheel Event Delta

I am working on a project that uses mouse events to zoom and pan a canvas. They work fine independently, however, I would like the canvas to pan towards the cursor while zooming.
My objective: have the cursor location be where you are zooming towards. I have been playing around with this for some time and I was able to get it working.
I did notice something odd. If you reload the page with the cursor over the canvas panning while zooming did not work. What would happen instead, is, the canvas would zoom in but displacement.x and displacement.y would be unchanged.
I believe it is because of event.delta. It is as if event.delta = 0 upon reload only if the cursor location is over the canvas.
Here are my mouse events:
function panCanvasOnZoom(event) {
var sensitivity = 0.05 * event.delta;
var halfSize = { w: canvasSize.w / 2, h: canvasSize.h / 2 }
var centerOrientedMouse = { x: halfSize.w - pmouseX, y: halfSize.h - pmouseY }
var relativeMousePercentage = { x: pmouseX / canvasSize.w, y: pmouseY / canvasSize.h }
var panX = (halfSize.w - pmouseX) * sense * relativeMousePercentage.x;
var panY = (halfSize.h - pmouseY) * sense * relativeMousePercentage.y;
panX += panX * relativeMousePercentage.x;
panY += panY * relativeMousePercentage.y;
displacement.x += panX;
displacement.y += panY;
}
function mouseWheel(event) {
if (pmouseX < 0 || pmouseX > canvasSize.w ||
pmouseY < 0 || pmouseY > canvasSize.h)
return;
panCanvasOnZoom(event);
displacement.z += 0.05 * event.delta;
displacement.z = constrain(displacement.z, 0.05, 9.00);
return false;
}
I am calculating the sensitivity using the event.delta. When I noticed this issue, I began debugging and realized that if I replaced var sensitivity = 0.05 * event.delta with var sensitivity = 0.05 it would essentially replicate the cursor issue.
My question is, is there a way to circumvent event.delta = 0? I do not mean an if statement to check for the condition. I mean something along the lines of setting event.delta to something besides 0 upon initializing.
Also, if you see any improvements regarding the efficiency of panCanvasOnZoom() please post those.

How to do pixel-perfect collision detection of a player and the walls (JavaScript Game)

I'm making a 2D game in JavaScript. For it, I need to be able to "perfectly" check collision between my players(the game has two players, open the picture please) and the walls! I mean, I have a function that actually works, but when I make them jump against the walls they pass through the walls and keep moving until they reach another area or even leave the canvas!
Also, if they are falling down and I make them collide with a wall, they just stop there wich is also pretty bad!
I really need help with that!! It's a university project and I have to finnish it really soon!
My game looks like this
The collision detection function I have is here:
function blockRectangle (objA, objB) {
var distX = (objA.x + objA.width / 2) - (objB.x + objB.width / 2);
var distY = (objA.y + objA.height / 2) - (objB.y + objB.height / 2);
var sumWidth = (objA.width + objB.width) / 2;
var sumHeight = (objA.height + objB.height) / 2;
if (Math.abs(distX) < sumWidth && Math.abs(distY) < sumHeight) {
var overlapX = sumWidth - Math.abs(distX);
var overlapY = sumHeight - Math.abs(distY);
if (overlapX > overlapY) {
objA.y = distY > 0 ? objA.y + overlapY : objA.y - overlapY;
}
else {
objA.x = distX > 0 ? objA.x + overlapX : objA.x - overlapX;
}
}
}
I did the walls with a maze and I'm using a for cycle to check the collisions with all of the walls I have saved in an array!
As you can see here:
for (var i in walls) {
var wall = walls[i];
if ((player.x < (wall.x + wall.width)) && ((player.x + player.width) > wall.x) && (player.y < (wall.y + wall.height)) && ((player.height + player.y) > wall.y)) {
player.falling = false;
}
blockRectangle(player, wall);
}
Please help me!!! Thank you all!
In your case I doubt a pixel perfect collision is required.
You can maintain a boolean matrix to store the position of solid objects. Solid objects like walls or players. Then in every frame you can check if your player is trying to move to a position where there is a solid object, if it is then stop it. You don't have to create grid of width x height in pixels, but rather choose a largest block (single element in the grid) in which each solid object reasonably occupies most of the block.
For example you can choose block size to be player_width / 2 x player_height /2.
See following image with grid
Another simple way could be to just check the background pixel color. Since your game is simple, background and object colors are different. So you just have to check if the player is trying to move somewhere where pixel color is not of background, thus there is a solid object and player should stop. You don't have to test for a lot of pixels, just 1 pixel in the direction the player is trying to move. (1 for horizontal and 1 for vertical). This however can not be used if you don't have a clear background color. Background color here is kind of the boolean grid for us in the previous suggestion.

Using circle math for solid collision

function distance(x1, y1, x2, y2) {
var x = x1 - x2;
var y = y1 - y2;
return(Math.sqrt((x*x) + (y*y)))
};
function collisionCirc(circ1, circ2) {
var d = distance(circ1.x, circ1.y, circ2.x, circ2.y);
var r = circ1.radius + circ2.radius;
return(r > d);
};
function collisionCircPoint(circ1, circ2) {
var cx = ((circ1.x * circ2.radius) + (circ2.x * circ1.radius)) / (circ1.radius + circ2.radius);
var cy = ((circ1.y * circ2.radius) + (circ2.y * circ1.radius)) / (circ1.radius + circ2.radius);
var p = [cx, cy];
return p;
};
function angleDegrees(x1, y1, x2, y2) {
return (Math.atan2(y2 - y1, x2 - x1) * 180 / Math.PI) + 180;
};
function updateCollisions() {
var a;
var p;
Player.hitArea = new PIXI.Circle(Player.sprite.x, Player.sprite.y, 20);
MapObjects.chest.hitArea = new PIXI.Circle(MapObjects.chest.x, MapObjects.chest.y, 20);
if (collisionCirc(Player.hitArea, MapObjects.chest.hitArea)) {
a = angleDegrees(Player.sprite.x, Player.sprite.y, MapObjects.chest.x, MapObjects.chest.y);
p = collisionCircPoint(Player.hitArea, MapObjects.chest.hitArea);
Player.sprite.x = p[0];
Player.sprite.y = p[1];
};
};
I have 2 sprites on the map and each has a circle hitArea defined. I am trying to make a smooth circular collision that the player cannot pass through. I thought I could just set the Player.sprite's coordinates to the point of collision but it just warps him to the MapObjects.chest's coordinates, even though the point of collision is correct and is 20 pixels from the MapObject.chest's center. What am I doing wrong or what more information is needed to create a collision much like the JavaScript physics libraries where I can circle around a circle object?
The collision point is between the player and the obstacle. If you move the player towards the collision point, you are actually moving the player closer. For example, if there's exactly 40 px (r1+r2) between the player and the obstacle, the collision point is between them, at only 20 px from the obstacle!
When you have multiple objects, getting it right when the collision has already happened is difficult. If there is only one obstacle nearby, you can simply move the player directly away from the obstacle. However, this way the player might actually end up inside another obstacle.
Another solution is to go back to the start and try smaller movements, until there is no collision. This way you would eventually get it right, but this might also be slow.
The mathematically correct solution is to calculate the maximum distance to move before the collision happens. This is done by solving the following vector equation:
# p = player position before moving
# o = obstacle position
# u = player direction (unit vector)
# d = distance to move
distance(o, p + d * u) = o.radius + p.radius
That's mathematics, you may solve it by yourself or using a tool like Wolfram Alpha.
Solving this equation will give you zero, one or two possible values for the distance. Negative values you can dismiss, as they mean that the player is already past the obstacle. If you get only one value, it means that the player would merely brush the obstacle, which you can also dismiss. Two values mean that the collision happens between these distances; the smaller value is where the collision starts, and the larger value is where the player would already be through the obstacle. Also, if one value is positive and the other is negative, it means that the player is already inside the obstacle, which should never happen.
You should run this check for all nearby obstacles and then move the player according to the smallest non-negative result (that is, zero or positive), or less, if the player can't move that fast.
Finally, to circle around a round object, you can move the player a little bit in a perpendicular direction (either left or right, depending on which side of the obstacle the player will be passing) after a collision, if this doesn't cause any new collisions.
There are many other possible implementations.
Player.hitArea = new PIXI.Circle(Player.sprite.x, Player.sprite.y, 20);
MapObjects.chest.hitArea = new PIXI.Circle(MapObjects.chest.x, MapObjects.chest.y, 20);
if (collisionCirc(Player.hitArea, MapObjects.chest.hitArea)) {
p = collisionCircPoint(Player.hitArea, MapObjects.chest.hitArea);
a = angleDegrees(Player.sprite.x, Player.sprite.y, MapObjects.chest.x, MapObjects.chest.y);
if (Player.sprite.x - MapObjects.chest.x > 0) {
Player.sprite.x += 1;
} else if (Player.sprite.x + MapObjects.chest.x > 0) {
Player.sprite.x -= 1;
};
if (Player.sprite.y - MapObjects.chest.y > 0) {
Player.sprite.y += 1;
} else if (Player.sprite.y + MapObjects.chest.y > 0) {
Player.sprite.y -= 1;
};
};
};
I added that and it actually works well enough minus the player speed being slightly too fast when running into the MapObjects.chest's hitArea at certain angles. Work on that later.

Canvas circle collision, how to work out where circles should move to once collided?

I am having a go at building a game in html canvas. It's a Air Hockey game and I have got pretty far though it. There are three circles in the game, the disc which is hit and the two controllers(used to hit the disc/circle).
I've got the disc rebounding off the walls and have a function to detect when the disc has collided with a controller. The bit I am struggling with is when the two circle's collide, the controller should stay still and the disc should move away in the correct direction. I've read a bunch of article's but still can't get it right.
Here's a Codepen link my progress so far. You can see that the puck rebounds off the controller but not in the correct direction. You'll also see if the puck comes from behind the controller it goes through it.
http://codepen.io/allanpope/pen/a01ddb29cbdecef58197c2e829993284?editors=001
I think what I am after is elastic collision but not sure on how to work it out. I've found this article but have been unable to get it working.
http://gamedevelopment.tutsplus.com/tutorials/when-worlds-collide-simulating-circle-circle-collisions--gamedev-769
Heres is my collision detection function. Self refer's to the disc and the controller[i] is the controller the disc hits.
this.discCollision = function() {
for (var i = 0; i < controllers.length; i++) {
// Minus the x pos of one disc from the x pos of the other disc
var distanceX = self.x - controllers[i].x,
// Minus the y pos of one disc from the y pos of the other disc
distanceY = self.y - controllers[i].y,
// Multiply each of the distances by itself
// Squareroot that number, which gives you the distance between the two disc's
distance = Math.sqrt(distanceX * distanceX + distanceY * distanceY),
// Added the two disc radius together
addedRadius = self.radius + controllers[i].radius;
// Check to see if the distance between the two circles is smaller than the added radius
// If it is then we know the circles are overlapping
if (distance <= addedRadius) {
var newVelocityX = (self.velocityX * (self.mass - controllers[i].mass) + (2 * controllers[i].mass * controllers[i].velocityX)) / (self.mass + controllers[i].mass);
var newVelocityY = (self.velocityY * (self.mass - controllers[i].mass) + (2 * controllers[i].mass * controllers[i].velocityX)) / (self.mass + controllers[i].mass);
self.velocityX = newVelocityX;
self.velocityY = newVelocityY;
self.x = self.x + newVelocityX;
self.y = self.y + newVelocityY;
}
}
}
Updated
Deconstructed a circle collision demo & tried to implement their collision formula. This is it below, works for hitting the puck/disc forward & down but wont hit the back backwards or up for some reason.
this.discCollision = function() {
for (var i = 0; i < controllers.length; i++) {
// Minus the x pos of one disc from the x pos of the other disc
var distanceX = self.x - controllers[i].x,
// Minus the y pos of one disc from the y pos of the other disc
distanceY = self.y - controllers[i].y,
// Multiply each of the distances by itself
// Squareroot that number, which gives you the distance between the two disc's
distance = Math.sqrt(distanceX * distanceX + distanceY * distanceY),
// Added the two disc radius together
addedRadius = self.radius + controllers[i].radius;
// Check to see if the distance between the two circles is smaller than the added radius
// If it is then we know the circles are overlapping
if (distance < addedRadius) {
var normalX = distanceX / distance,
normalY = distanceY / distance,
midpointX = (controllers[i].x + self.x) / 2,
midpointY = (controllers[i].y + self.y) / 2,
delta = ((controllers[i].velocityX - self.velocityX) * normalX) + ((controllers[i].velocityY - self.velocityY) * normalY),
deltaX = delta*normalX,
deltaY = delta*normalY;
// Rebound puck
self.x = midpointX + normalX * self.radius;
self.y = midpointY + normalY * self.radius;
self.velocityX += deltaX;
self.velocityY += deltaY;
// Accelerate once hit
self.accelerationX = 3;
self.accelerationY = 3;
}
}
}
I'm not great at these types of math problems, but it looks like you need to rotate your vectors around sine and cosine angles. I will point you at a working example and the source code that drives it. I did not derive this example.
I solved just the circle collision detection part of this problem recently, but one solution I came across includes code for establishing new vector directions. Ira Greenburg hosts his original source at processing.org. Ira further cites Keith Peter's Solution in Foundation Actionscript Animation: Making Things Move!
I copied Ira's code into Processing's Javascript mode then pushed it to Github Pages so you can see it before you try it.
The main issue with my code was the user controller was attached to the mouse. When a collision would happen, the function would run constantly because the circles where still touching due to the mouse position. I changed my code so the controller is controlled by the users keyboard.
I also asked for help on reddit & got some help with my collision code. Some good resources where linked.
(http://www.reddit.com/r/javascript/comments/3cjivi/having_a_go_at_building_an_air_hockey_game_stuck/)

Mirroring right half of webcam image

I saw that you have helped David with his mirroring canvas problem before. Canvas - flip half the image
I have a similar problem and hope that maybe you could help me.
I want to apply the same mirror effect on my webcam-canvas, but instead of the left side, I want to take the RIGHT half of the image, flip it and apply it to the LEFT.
This is the code you've posted for David. It also works for my webcam cancas. Now I tried to change it, so that it works for the other side, but unfortunately I'm not able to get it.
for(var y = 0; y < height; y++) {
for(var x = 0; x < width / 2; x++) { // divide by 2 to only loop through the left half of the image.
var offset = ((width* y) + x) * 4; // Pixel origin
// Get pixel
var r = data[offset];
var g = data[offset + 1];
var b = data[offset + 2];
var a = data[offset + 3];
// Calculate how far to the right the mirrored pixel is
var mirrorOffset = (width - (x * 2)) * 4;
// Get set mirrored pixel's colours
data[offset + mirrorOffset] = r;
data[offset + 1 + mirrorOffset] = g;
data[offset + 2 + mirrorOffset] = b;
data[offset + 3 + mirrorOffset] = a;
}
}
Even if the accepted answer you're relying on uses imageData, there's absolutely no use for that.
Canvas allows, with drawImage and its transform (scale, rotate, translate), to perform many operations, one of them being to safely copy the canvas on itself.
Advantages is that it will be way easier AND way way faster than handling the image by its rgb components.
I'll let you read the code below, hopefully it's commented and clear enough.
The fiddle is here :
http://jsbin.com/betufeha/2/edit?js,output
One output example - i took also a mountain, a Canadian one :-) - :
Original :
Output :
html
<canvas id='cv'></canvas>
javascript
var mountain = new Image() ;
mountain.onload = drawMe;
mountain.src = 'http://www.hdwallpapers.in/walls/brooks_mountain_range_alaska-normal.jpg';
function drawMe() {
var cv=document.getElementById('cv');
// set the width/height same as image.
cv.width=mountain.width;
cv.height = mountain.height;
var ctx=cv.getContext('2d');
// first copy the whole image.
ctx.drawImage(mountain, 0, 0);
// save to avoid messing up context.
ctx.save();
// translate to the middle of the left part of the canvas = 1/4th of the image.
ctx.translate(cv.width/4, 0);
// flip the x coordinates to have a mirror effect
ctx.scale(-1,1);
// copy the right part on the left part.
ctx.drawImage(cv,
/*source */ cv.width/2,0,cv.width/2, cv.height,
/*destination*/ -cv.width/4, 0, cv.width/2, cv.height);
// restore context
ctx.restore();
}

Categories

Resources