I'm developing a WebGL game where the user would use the orientation of the device to control the camera ie look around. From the three rotations of deviceorientation event, I converted them to Quaternions.
THe problem starts here: I need to have the user to hold the device horizontally. In this position, the beta angle will be used to turn around the vertical axis y from the user's perspective.
However, the beta angle only goes from -180 to 180, any number outside will cause the other axes to change, to get a valid rotation. Due to how my WebGL camera is handled (I'm using Unity Engine), I need to separate the axes to remap them to the Camera coordinate accordingly, and to offset the angles from the horizontal pose (adding 90 degrees to the Y axis for example, because the device is rolled back). I have to convert the quaternion back to 3 euler angles and control each camera axis through those.
Is there a way to solve this?
Here's what I use to get the device orientation from the web:
var degtorad = Math.PI / 180; // Degree-to-Radian conversion
function getQuaternion(alpha, beta, gamma) {
var _x = beta ? beta * degtorad : 0; // beta value
var _y = gamma ? gamma * degtorad : 0; // gamma value
var _z = alpha ? alpha * degtorad : 0; // alpha value
var cX = Math.cos(_x / 2);
var cY = Math.cos(_y / 2);
var cZ = Math.cos(_z / 2);
var sX = Math.sin(_x / 2);
var sY = Math.sin(_y / 2);
var sZ = Math.sin(_z / 2);
//
// ZXY quaternion construction.
//
var w = cX * cY * cZ - sX * sY * sZ;
var x = sX * cY * cZ - cX * sY * sZ;
var y = cX * sY * cZ + sX * cY * sZ;
var z = cX * cY * sZ + sX * sY * cZ;
return [w, x, y, z];
}
function GyroEvent(e) {
var quat = getQuaternion(e.alpha, e.beta, e.gamma + 90);
// w
unityInstance.SendMessage("Main Camera", "GetGyroW", quat[0]);
// x
unityInstance.SendMessage("Main Camera", "GetGyroX", quat[1]);
// y
unityInstance.SendMessage("Main Camera", "GetGyroY", quat[2]);
// z
unityInstance.SendMessage("Main Camera", "GetGyroZ", quat[3]);
}
window.addEventListener("deviceorientation", GyroEvent);
I'm working on an HTML Canvas demo to learn more about circle to circle collision detection and response. I believe that the detection code is correct but the response math is not quite there.
The demo has been implemented using TypeScript, which is a typed superset of JavaScript that is transpiled to plain JavaScript.
I believe that the problem exists within the checkCollision method of the Circle class, specifically the math for calculating the new velocity.
The blue circle position is controlled by the mouse (using an event listener). If the red circle collides from the right side of the blue circle, the collision response seems to work correctly, but if it approaches from the left it does not respond correctly.
I am looking for some guidance on how I can revise the checkCollision math to correctly handle the collision from any angle.
Here is a CodePen for a live demo and dev environment:
CodePen
class DemoCanvas {
canvasWidth: number = 500;
canvasHeight: number = 500;
canvas: HTMLCanvasElement = document.createElement('canvas');
constructor() {
this.canvas.width = this.canvasWidth;
this.canvas.height = this.canvasHeight;
this.canvas.style.border = '1px solid black';
this.canvas.style.position = 'absolute';
this.canvas.style.left = '50%';
this.canvas.style.top = '50%';
this.canvas.style.transform = 'translate(-50%, -50%)';
document.body.appendChild(this.canvas);
}
clear() {
this.canvas.getContext('2d').clearRect(0, 0, this.canvas.width, this.canvas.height);
}
getContext(): CanvasRenderingContext2D {
return this.canvas.getContext('2d');
}
getWidth(): number {
return this.canvasWidth;
}
getHeight(): number {
return this.canvasHeight;
}
getTop(): number {
return this.canvas.getBoundingClientRect().top;
}
getRight(): number {
return this.canvas.getBoundingClientRect().right;
}
getBottom(): number {
return this.canvas.getBoundingClientRect().bottom;
}
getLeft(): number {
return this.canvas.getBoundingClientRect().left;
}
}
class Circle {
x: number;
y: number;
xVelocity: number;
yVelocity: number;
radius: number;
color: string;
canvas: DemoCanvas;
context: CanvasRenderingContext2D;
constructor(x: number, y: number, xVelocity: number, yVelocity: number, color: string, gameCanvas: DemoCanvas) {
this.radius = 20;
this.x = x;
this.y = y;
this.xVelocity = xVelocity;
this.yVelocity = yVelocity;
this.color = color;
this.canvas = gameCanvas;
this.context = this.canvas.getContext();
}
public draw(): void {
this.context.fillStyle = this.color;
this.context.beginPath();
this.context.arc(this.x, this.y, this.radius, 0, 2 * Math.PI);
this.context.fill();
}
public move(): void {
this.x += this.xVelocity;
this.y += this.yVelocity;
}
checkWallCollision(gameCanvas: DemoCanvas): void {
let top = 0;
let right = 500;
let bottom = 500;
let left = 0;
if(this.y < top + this.radius) {
this.y = top + this.radius;
this.yVelocity *= -1;
}
if(this.x > right - this.radius) {
this.x = right - this.radius;
this.xVelocity *= -1;
}
if(this.y > bottom - this.radius) {
this.y = bottom - this.radius;
this.yVelocity *= -1;
}
if(this.x < left + this.radius) {
this.x = left + this.radius;
this.xVelocity *= -1;
}
}
checkCollision(x1: number, y1: number, r1: number, x2: number, y2: number, r2: number) {
let distance: number = Math.abs((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
// Detect collision
if(distance < (r1 + r2) * (r1 + r2)) {
// Respond to collision
let newVelocityX1 = (circle1.xVelocity + circle2.xVelocity) / 2;
let newVelocityY1 = (circle1.yVelocity + circle1.yVelocity) / 2;
circle1.x = circle1.x + newVelocityX1;
circle1.y = circle1.y + newVelocityY1;
circle1.xVelocity = newVelocityX1;
circle1.yVelocity = newVelocityY1;
}
}
}
let demoCanvas = new DemoCanvas();
let circle1: Circle = new Circle(250, 250, 5, 5, "#F77", demoCanvas);
let circle2: Circle = new Circle(250, 540, 5, 5, "#7FF", demoCanvas);
addEventListener('mousemove', function(e) {
let mouseX = e.clientX - demoCanvas.getLeft();
let mouseY = e.clientY - demoCanvas.getTop();
circle2.x = mouseX;
circle2.y = mouseY;
});
function loop() {
demoCanvas.clear();
circle1.draw();
circle2.draw();
circle1.move();
circle1.checkWallCollision(demoCanvas);
circle2.checkWallCollision(demoCanvas);
circle1.checkCollision(circle1.x, circle1.y, circle1.radius, circle2.x, circle2.y, circle2.radius);
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
Elasic 2D collision
The problem is likely because the balls do not move away from each other and then in the next frame they are still overlapping and it gets worse. My guess from just looking at the code.
A simple solution.
Before you can have the two balls change direction you must ensure that they are positioned correctly. They must be just touching, (no overlay) or they can get caught up in each other.
Detect collision, and fix position.
// note I am using javascript.
// b1,b2 are the two balls or circles
// b1.dx,b1.dy are velocity (deltas) to save space same for b2
// get dist between them
// first vect from one to the next
const dx = b2.x - b1.x;
const dy = b2.y - b1.y;
// then distance
const dist = Math.sqrt(dx*dx + dy*dy);
// then check overlap
if(b1.radius + b2.radius >= dist){ // the balls overlap
// normalise the vector between them
const nx = dx / dist;
const ny = dy / dist;
// now move each ball away from each other
// along the same line as the line between them
// Use the ratio of the radius to work out where they touch
const touchDistFromB1 = (dist * (b1.radius / (b1.radius + b2.radius)))
const contactX = b1.x + nx * touchDistFromB1;
const contactY = b1.y + ny * touchDistFromB1;
// now move each ball so that they just touch
// move b1 back
b1.x = contactX - nx * b1.radius;
b1.y = contactY - ny * b1.radius;
// and b2 in the other direction
b2.x = contactX + nx * b2.radius;
b2.y = contactY + ny * b2.radius;
If one is static
If one of the balls is static then you can keep its position and move the other ball.
// from contact test for b1 is immovable
if(b1.radius + b2.radius >= dist){ // the balls overlap
// normalise the vector between them
const nx = dx / dist;
const ny = dy / dist;
// move b2 away from b1 along the contact line the distance of the radius summed
b2.x = b1.x + nx * (b1.radius + b2.radius);
b2.y = b1.y + ny * (b1.radius + b2.radius);
Now you have the balls correctly separated a you can calculate the new trajectories
Changing the trajectories.
There are a wide variety of ways to do this, but the one I like best is the elastic collision. I created a function from the Elastic collision in Two dimensional space wiki source and have been using it in games for some time.
The function and information is in the snippet at the bottom.
Next I will show how to call the function continuing on from the code above
// get the direction and velocity of each ball
const v1 = Math.sqrt(b1.dx * b1.dx + b1.dy * b1.dy);
const v2 = Math.sqrt(b2.dx * b2.dx + b2.dy * b2.dy);
// get the direction of travel of each ball
const dir1 = Math.atan2(b1.dy, b1.dx);
const dir2 = Math.atan2(b2.dy, b2.dx);
// get the direction from ball1 center to ball2 cenet
const directOfContact = Math.atan2(ny, nx);
// You will also need a mass. You could use the area of a circle, or the
// volume of a sphere to get the mass of each ball with its radius
// this will make them react more realistically
// An approximation is good as it is the ratio not the mass that is important
// Thus ball are spheres. Volume is the cubed radius
const mass1 = Math.pow(b1.radius,3);
const mass1 = Math.pow(b2.radius,3);
And finally you can call the function
ellastic2DCollistionD(b1, b2, v1, v2, d1, d2, directOfContact, mass1, mass2);
And it will correctly set the deltas of both balls.
Moving the ball position along their deltas is done after the collision function
b1.x += b1.dx;
b1.y += b1.dy;
b2.x += b1.dx;
b2.y += b1.dy;
If one of the balls is static you just ignore the deltas.
Elasic 2D collision function
Derived from information at Elastic collision in Two dimensional space wiki
// obj1, obj2 are the object that will have their deltas change
// velocity1, velocity2 is the velocity of each
// dir1, dir2 is the direction of travel
// contactDir is the direction from the center of the first object to the center of the second.
// mass1, mass2 is the mass of the first and second objects.
//
// function ellastic2DCollistionD(obj1, obj2, velocity1, velocity2, dir1, dir2, contactDir, mass1, mass2){
// The function applies the formula below twice, once fro each object, allowing for a little optimisation.
// The formula of each object's new velocity is
//
// For 2D moving objects
// v1,v2 is velocity
// m1, m2 is the mass
// d1 , d2 us the direction of moment
// p is the angle of contact;
//
// v1* cos(d1-p) * (m1 - m2) + 2 * m2 * v2 * cos(d2- p)
// vx = ----------------------------------------------------- * cos(p) + v1 * sin(d1-p) * cos(p + PI/2)
// m1 + m2
// v1* cos(d1-p) * (m1 - m2) + 2 * m2 * v2 * cos(d2- p)
// vy = ----------------------------------------------------- * sin(p) + v1 * sin(d1-p) * sin(p + PI/2)
// m1 + m2
// More info can be found at https://en.wikipedia.org/wiki/Elastic_collision#Two-dimensional
// to keep the code readable I use abbreviated names
function ellastic2DCollistionD(obj1, obj2, v1, v2, d1, d2, cDir, m1, m2){
const mm = m1 - m2;
const mmt = m1 + m2;
const v1s = v1 * Math.sin(d1 - cDir);
const cp = Math.cos(cDir);
const sp = Math.sin(cDir);
var cdp1 = v1 * Math.cos(d1 - cDir);
var cdp2 = v2 * Math.cos(d2 - cDir);
const cpp = Math.cos(cDir + Math.PI / 2)
const spp = Math.sin(cDir + Math.PI / 2)
var t = (cdp1 * mm + 2 * m2 * cdp2) / mmt;
obj1.dx = t * cp + v1s * cpp;
obj1.dy = t * sp + v1s * spp;
cDir += Math.PI;
const v2s = v2 * Math.sin(d2 - cDir);
cdp1 = v1 * Math.cos(d1 - cDir);
cdp2 = v2 * Math.cos(d2 - cDir);
t = (cdp2 * -mm + 2 * m1 * cdp1) / mmt;
obj2.dx = t * -cp + v2s * -cpp;
obj2.dy = t * -sp + v2s * -spp;
}
Note just realized that you are using a typeScript and the function above is specifically type agnostic. Does not care about obj1, obj2 type, and will add the deltas to any object that you pass.
You will have to change the function for typeScript.
The velocity vector should change by a multiple of the normal vector at the collision point, which is also the normalized vector between the circle mid points.
There are several posts here and elsewhere on elastic circle collisions and the computation of the impulse exchange (for instance Collision of circular objects, with jsfiddle for planet billiard https://stackoverflow.com/a/23671054/3088138).
If circle2 is bound to the mouse, then the event listener should also update the velocity using the difference to the previous point and the difference of time stamps, or better some kind of moving average thereof. The mass of this circle in the collision formulas is to be considered infinite.
As you are using requestAnimationFrame, the spacing of the times it is called is to be considered random. It would be better to use actual time stamps and some effort at implementing the Euler method (or whatever the resulting order 1 integration method amounts to) using the actual time increments. The collision procedure should not contain a position update, as that is the domain of the integration step, which in turn makes it necessary to add a test that the disks are actually moving together.
I am developing a game where there are enemies and cars.So currently my cars move towards the enemies in a diagonal shape they hit them and earn points.Now I want to make the hero to make the move in a straight manner(like snake game no diagonal movement) and then hit the enemy and gain points.
So my current way of hitting the enemy is like
function angleBetweenTwoPoints(p1, p2) {
return Math.atan2(p2.y - p1.y, p2.x - p1.x) * 180 / Math.PI;
}
function degreeToRadian(degrees) {
return degrees * (Math.PI / 180);
}
var angle = angleBetweenTwoPoints(this.target.position, this.position);
var cos = Math.cos(degreeToRadian(angle)) * -1;
var sin = Math.sin(degreeToRadian(angle));
this.angle = angle;
this.position.x += cos * this.speed;
this.position.y -= sin * this.speed;
if (distance(this.position, this.target.position) < 10) {
this.target.position.x = Math.random() * mainCanvas.width;
this.target.position.y = Math.random() * mainCanvas.height;
this.hitCount++;
console.log("Hit!");
ctx.fillText("points : " + hitCount, 32, 32);
}
So now I want to make the car move by not diagonally and in a straight manner.
This is my working game.Any help is appreciated.
Use Pixi : http://www.pixijs.com/
And GSAP : https://greensock.com/
You will find easily everything to do it better ;)
My Rocket is hitting this Inertia object, as defined in handleCollision. I'm passing in a rocket which has a .r value for its theta and .power for its magnitude.
I'm wanting to update my .rotation & .magnitude according to an inelastic collision as defined by Wikipedia
When colliding from the left, my Inertia moves to the right.
But when colliding from the right it errors and moves exactly 180 degrees off. So if the rocket is up and right at a 45 degree angle from the inertia object, the object will move up and right at a 45 degree angle.
What am I missing here? I thought it might be an issue with the atan function so I converted by the y component & x component of the vector to radians first, same issue.
handleCollision(rocket) {
var angle = rocket.r * Math.PI / 180.0;
var rr = this.rotation * Math.PI / 180;
var rocketVector = {'x' : r.power * Math.cos(angle), 'y' : r.power * Math.sin(angle)};
var inertiaVector = {'x' : this.magnitude * Math.cos(rr), 'y' : this.magnitude * Math.sin(rr)};
var rMass = 10;
var shipMass = 10;
var x = (rMass * rocketVector.x) + (shipMass * inertiaVector.x);
var y = (rMass * rocketVector.y) + (shipMass * inertiaVector.y);
var xDividedByMass = x / (rMass + shipMass);
var yDividedByMass = y / (rMass + shipMass);
var yRadians = (yDividedByMass * Math.PI / 180);
var xRadians = (xDividedByMass * Math.PI / 180);
var theta = Math.atan( yRadians / xRadians);
theta = theta * 180 / Math.PI;
console.log(theta);
var hypotenuse = Math.sqrt((xDividedByMass * xDividedByMass) + (yDividedByMass * yDividedByMass));
this.magnitude = hypotenuse;
this.rotation = theta;
if (this.rotation < 0) {
this.rotation += 360;
} else if (this.rotation > 360) {
this.rotation -= 360;
}
}
If xDividedbyMass>0, you are great because you are quadrant I or IV where arctangent kicks out its values. If you do not like the negative angle, okay add 360 like you did.
But if x<0 and y>0, you will get a negative angle and want to add 180 to get to Q II (tangent has a period of 180). And if x<0, and y<0, you are in QIII and again arctan gives you something in Q1 to which you must add 180.
The logic will look something like this.
if ((x > 0) && (y<0)) {
this.rotation += 360;
} else if (x<0) {
this.rotation += 180;
}
I am new to two.js. I am trying some basic experiments with rubber ball example to reposition ball on every second as per random input instead of mouse movement.
So, I have written below code, but it is removing rubber ball effect after some iteration. I don't know what is going wrong.
Second problem, after some iteration, rubber ball is changing its shape from circle to oval kind of shape.
JSFiddle: http://jsfiddle.net/2v93n/ Tried many times, but not working live with jsFiddle.
<body>
<script>
var two = new Two({
fullscreen: true,
autostart: true
}).appendTo(document.body);
Two.Resoultion = 32;
var delta = new Two.Vector();
var mouse = new Two.Vector();
var drag = 0.1;
var radius = 25;
var shadow = two.makeCircle(two.width / 2, two.height / 2, radius);
var ball = two.makeCircle(two.width / 2, two.height / 2, radius);
ball.noStroke().fill = 'green';shadow.noStroke().fill = 'rgba(0, 0, 0, 0.2)'; shadow.scale = 0.85;
function moveRubberBall() {
shadow.offset = new Two.Vector(- radius / 2, radius * 2);
_.each(ball.vertices, function(v) {
v.origin = new Two.Vector().copy(v);
});
mouse.x = Math.floor((Math.random() * 1000) + 1);
mouse.y = Math.floor((Math.random() * 600) + 1);
shadow.offset.x = 5 * radius * (mouse.x - two.width / 2) / two.width;
shadow.offset.y = 5 * radius * (mouse.y - two.height / 2) / two.height;
two.bind('update', function() {
delta.copy(mouse).subSelf(ball.translation);
_.each(ball.vertices, function(v, i) {
var dist = v.origin.distanceTo(delta);
var pct = dist / radius;
var x = delta.x * pct;
var y = delta.y * pct;
var destx = v.origin.x - x;
var desty = v.origin.y - y;
v.x += (destx - v.x) * drag;
v.y += (desty - v.y) * drag;
shadow.vertices[i].copy(v);
});
ball.translation.addSelf(delta);
shadow.translation.copy(ball.translation);
shadow.translation.addSelf(shadow.offset);
});
}
var auto_refresh = setInterval("moveRubberBall()", 1000);
</script>
</body>
Please help somebody.
The main issue is that two.bind('update'...) is within moveRubberBall(). When you bind to update it means this function will be called on requestAnimationFrame, effectively 60FPS. When the event is bound every time moveRubberBall is called — once a second — it adds another callback. So after 5 seconds there will be 5 updates called 60FPS. I updated your fiddle to import two.js correctly and fix this error outlined:
http://jsfiddle.net/2v93n/1/