I'm trying to write a small 'perspective' javascript app that allows me to fly through a set of x,y,z points that inhabit a 3d space.
I have the concept of a camera which changes its rotation and xyz position, while each point maintains a constant xyz point.
I then have a set of equations that works out how the camera's x,y,z coordinates should be adjusted for flying directly forwards. The x,y,z adjustments obviously depend upon the rotation of the camera.
It almost works, but at certain 'attitudes' the camera position adjustment goes wrong and the flightpath doesn't go straight ahead but goes off at an angle, or even reverses. The equations for working out the projection are as follows:
var directionFactor = 1;
if (direction == 'backward') directionFactor = -1;
sx = Math.sin(cameraView.rotX);
cx = Math.cos(cameraView.rotX);
sy = Math.sin(cameraView.rotY);
cy = Math.cos(cameraView.rotY);
sz = Math.sin(cameraView.rotZ);
cz = Math.cos(cameraView.rotZ);
// Z-Axis
ztrig = Math.sqrt((cx * cx) + (cy * cy)) * (cx * cy);
cameraView.z = cameraView.z + directionFactor *
(Math.abs(airspeed / 15) * ztrig);
// Y-Axis
ytrig = Math.sqrt((sx * sx) + (cz * cz)) * (sx * cz);
cameraView.y = cameraView.y + directionFactor *
(Math.abs(airspeed / 15) *ytrig);
// X-Axis
xtrig = Math.sqrt((cz * cz) + (sy * sy)) * (cz * sy);
cameraView.x = cameraView.x - directionFactor *
(Math.abs(airspeed / 15) * xtrig);
Obviously my equations aren't quite right. Can anyone tell me where I'm going wrong? Much appreciated and thanks.
You have some errors in your equations. (They are valid in the 2d case but not in 3d)
when you calculate
sx = Math.sin(cameraView.rotX);
It does make sense in 2d since :
sx = Math.sin(cameraView.rotX) = x/SQRT(y*y + x*x)
where (x, y) is the position of the camera.
But in 3d it's more complicated :
In 3d :
Thus to obtain the cartesian coordinate :
You may also use 3d matrix to perform rotation.
Related
I've been working on this problem for a bit, and it doesn't seem too hard, but I'm getting tired and it seems more and more complicated the more I try (but it's probably really easy).
My goal is to have a ball bounce off another ball. Seems easy enough.
Ball 2 is controlled by the user's mouse (so far it's sort of like single player pong, but it's a circle instead of a rectangle) so its velocity doesn't matter.
Ball 1 has a few attributes, including dx (the x distance it moves every frame) and dy (dx, but for the y coordinate)
The problem with what I have so far is that you don't know what values will be positive and what will be negative (so the speed can severely increase or decrease instantly), you might be able to fix this using many else if's, but I'm too confused to think right now.
Here is the important part of this function. Also, I've tried to set it up so that dx + dy is always the same, even when the numbers change, so that it looks more natural.
if (collision(ball, paddle)) {
diffX = paddle.x-ball.x;
diffY = paddle.y-ball.y;
totalVel = ball.dx+ball.dy;
dir = {
x : diffX/(diffX+diffY)*-totalVel,
y : diffY/(diffX+diffY)*-totalVel
};
ball.dx = dir.x;
ball.dy = dir.y;
}
Here is a JSFiddle with the full code
https://jsfiddle.net/a2prr0uw/1/
So firstly let's start by defining what a "bounce" is - the speed is the same, but the direction (on both axis) will be inverted. If we treat dx and dy like a vector, then we can first get the incoming speed of the ball like this:
var ballSpeed = Math.sqrt((ball.dx * ball.dx) + (ball.dy * ball.dy));
The above value will always be positive, regardless of what dx and dy are doing.
Next, we'll need the incoming direction of the ball - that bit is the same as what you've currently got:
diffX = paddle.x-ball.x;
diffY = paddle.y-ball.y;
However if we treat this as a vector too, it essentially has a totally unknown length. So, let's normalise it so it's a direction vector with a length of 1:
var distanceBetweenPaddleAndBall = Math.sqrt((diffX * diffX) + (diffY * diffY));
diffX /= distanceBetweenPaddleAndBall;
diffY /= distanceBetweenPaddleAndBall;
diffX and diffY is now a normalised direction vector - the direction the ball is currently going in - and ballSpeed is the speed we'd like it to go.
So now we'll apply our bounce - flip the direction and retain the speed. That becomes this:
dir = {
x : -diffX * ballSpeed,
y : -diffY * ballSpeed
};
Put it all together and we end up with this:
if (collision(ball, paddle)) {
diffX = paddle.x-ball.x;
diffY = paddle.y-ball.y;
// How fast is the ball coming in?
var ballSpeed = Math.sqrt((ball.dx * ball.dx) + (ball.dy * ball.dy));
// How far is the ball from the paddle?
var distanceBetweenPaddleAndBall = Math.sqrt((diffX * diffX) + (diffY * diffY));
// Normalise diffX and diffY so we have a direction vector:
diffX /= distanceBetweenPaddleAndBall;
diffY /= distanceBetweenPaddleAndBall;
// Apply the bounce and the original ball speed:
dir = {
x : -diffX * ballSpeed,
y : -diffY * ballSpeed
};
ball.dx = dir.x;
ball.dy = dir.y;
}
And here it is as a fork of your fiddle too.
not an answer but some considerations on your bouncing logic:
you have to calculate the balls direction (dy/dx)
the collision has also a direction (angle beween both centers = b.x-p.x / b.y-p.y)
the angle after bouncing has to be calculated based on these two angles: using ther 2nd for mirroring
to calculate the new dx & dy after collision you will need the original velocity Math.abs(Math.sqrt(Math.pow(dx)+Math.pow(dy))) of the ball
based on this velocity and the new direction you can calc the new dx & dy
I have found a workaround here (see code below), but I wonder if Fabric has built-in support to set the rotation point of an object to a specific place.
function rotateObject(fabObj, angleRadian, pivotX, pivotY) {
ty = pivotY - fabObj.height / 2.0;
tx = pivotX - fabObj.width / 2.0;
if (angleRadian >= Math.PI * 2) {
angleRadian -= Math.PI * 2;
}
angle2 = Math.atan2(ty, tx);
angle3 = (2 * angle2 + angleRadian - Math.PI) / 2.0;
pdist_sq = tx * tx + ty * ty;
disp = Math.sqrt(2 * pdist_sq * (1 - Math.cos(angleRadian)));
fabObj.set({transformMatrix:[
Math.cos(angleRadian),
Math.sin(angleRadian),
-Math.sin(angleRadian),
Math.cos(angleRadian),
disp * Math.cos(angle3),
disp * Math.sin(angle3)
]});
}
There is no built in method.
By default mouse rotation on fabricjs is around the center point.
If you deactivate centeredRotation on objects, setting it to false:
fabric.Object.prototype.centeredRotation = false
the object will start to rotate around the originX and originY position.
Then you can set for each object a specific originX and originY, that can be numeric and represent any point inside the object ( with values ranging from 0 to 1 ) i m not even sure if you can do it with points outside the object.
At that point the object rotates around that point, just setting the angle property to a desired angle.
Consider that now also position is relative to that origin.
As a side not, do not set transformMatrix of an object. is unsupported and will give you weird controls in case of interactivity with controls.
In your specific example, once found the matrix:
var matrix = [
Math.cos(angleRadian),
Math.sin(angleRadian),
-Math.sin(angleRadian),
Math.cos(angleRadian),
disp * Math.cos(angle3),
disp * Math.sin(angle3)
];
var options = fabric.util.qrDecompose(matrix);
object.set(options);
object.setPositionByOrigin({ x: options.translateX, y: options.translateY }, 'center', 'center');
this should give you same effect but being supported by fabricjs better.
Rotation on fabricjs is around the center point.
This fuction is on developmenting...
but I have implemented to line rotation.
Redrawing line from custom center point to mouse position.
it works well for me.
I'm making a game in javascript, where an object is supposed to bounce from walls. I really tried to get it to work myself, but it never works correctly.
Let's say theres a ball bouncing inside this cage (blue = 30°, brown = 60°);
The ball's coordinates are known. The angle of movement is known. The point of collision (P) coordinates are known. The angle of the wall is known. The ball's position is updating it's coordinates inside a setInterval function using this function:
function findNewPoint(x, y, angle, distance) {
var result = {};
result.x =(Math.cos(angle * Math.PI / 180) * distance + x);
result.y = (Math.sin(angle * Math.PI / 180) * distance + y);
return result;
So, upon colliding, there should be a function that properly changes the ball's angle. It's a very complicated problem it seems, because even if I know that the wall is 30°, its important to know from what side the ball is colliding into it. I tried using the "Reflection across a line in the plane" formula and also some vectors, but it never worked out for me. I'm not expecting a complete answer with code, if someone could suggest in what way this should be programmed, it would help aswell.
Edit:
Thanks for your tips guys, I realized what was causing the most confustion; if I select an angle on the canvas with my mouse, the starting coordinate(0,0) is in the bottom left corner. But since the canvas' starting coordinate is in the top left corner, this has to be considered.
Basically using this formula for calculating the angle:
function angle(cx, cy, ex, ey) {
var dy = ey - cy;
var dx = ex - cx;
var theta = Math.atan2(dy, dx);
theta *= 180 / Math.PI;
return theta;
}
if the ball moved from (50,50) to (100,100), the angle would be -45.
Now, this angle changes in the following way when hitting walls:
If im honest, I got these out of trial and error, am not really understanding why exactly 60 and 120.
It is not wise to use angle for moving ball and calculate Cos/Sin again and again. Instead use unit velocity direction vector with components vx, vy like this:
new_x = old_x + vx * Velocity_Magnitude * Time_Interval
Note that vx = Cos(angle), vy = Sin(angle), but with direction approach you seldom need to use trigonometric functions.
Tilted wall with angle Fi has normal
nx = -Sin(Fi)
ny = Cos(Fi)
To find reflection , you need to calculate dot product of velocity and normal
dot = vx * nx + vy * ny
Velocity after reflection transforms:
vnewx = v.x - 2 * dot * n.x
vnewy = v.y - 2 * dot * n.y
Use these values for further moving
(note that you can use both internal and external normal direction, because direction flip changes both components, and sign of 2 * dot * n.x remains the same)
Examples:
horizontal moving right
vx=1, vy=0
30 degrees wall has normal
nx=-1/2, ny=Sqrt(3)/2
dot = -1/2
vnewx = 1 - 2 * (-1/2) * (-1/2) = 1/2
vnewy = 0 - 2 * (-1/2) * Sqrt(3)/2 = Sqrt(3)/2
(velocity direction angle becomes 60 degrees)
horizontal moving left
vx=-1, vy=0
330 degrees wall (left bottom corner) has normal
nx=1/2, ny=Sqrt(3)/2
dot = -1/2
vnewx = -1 - 2 * (-1/2) * (1/2) = -1/2
vnewy = 0 - 2 * (-1/2) * (Sqrt(3)/2) = Sqrt(3)/2
(velocity direction angle becomes 120 degrees)
Here is a function that returns the angle of reflection given an angle of incidence and a surface angle (in degrees). It also ensures that the returned angle is between 0 and 359 degrees.
function angleReflect(incidenceAngle, surfaceAngle){
var a = surfaceAngle * 2 - incidenceAngle;
return a >= 360 ? a - 360 : a < 0 ? a + 360 : a;
}
Here's a demonstration, where the blue line is the angle of incidence, the purple line is the angle of reflection, and the black line is the surface.
If you're assuming that the ball behaves like light bouncing off a mirror, then the angle of incidence equals the angle of reflection.
So your board is 30° from 0° (straight up). The means the normal (perpendicular to the board at the point the ball hits ) is 300°. Say the ball arrives from 280°, it must leave at 320° as the difference between the angle of incidence and the normal and the angle of reflection and the normal must be equal.
Yes theres a few threads on this, but not many using angles and I'm really trying to figure it out this way,
I'm now stuck on setting the new velocity angles for the circles. I have been looking at:
http://www.hoomanr.com/Demos/Elastic2/
as a reference to it, but I'm stuck now.
Can anybody shed some light?
cx/cy/cx2/cy2 = center x/y for balls 1 and 2.
vx/vy/vx2/vy2 = velocities for x/y for balls 1 and 2
function checkCollision() {
var dx = cx2 - cx; //distance between x
var dy = cy2 - cy; // distance between y
var distance = Math.sqrt(dx * dx + dy * dy);
var ang = Math.atan2(cy - cy2, cx - cx2);
// was displaying these in a div to check
var d1 = Math.atan2(vx, vy); //ball 1 direction
var d2 = Math.atan2(vx2, vy2); //ball 2 direction
// this is where I am stuck, and i've worked out this is completely wrong now
// how do i set up the new velocities for
var newvx = vx * Math.cos(d1 - ang);
var newvy = vy * Math.sin(d1 - ang);
var newvx2 = vx2 * Math.cos(d2 - ang);
var newvy2 = vy2 * Math.sin(d2 - ang);
if (distance <= (radius1 + radius2)) {
//Set new velocity angles here at collision..
}
Heres a codepen link:
http://codepen.io/anon/pen/MwbMxX
A few directions :
• As mentioned in the comments, use only radians (no more *180/PI).
• atan2 takes y as first param, x as second param.
var d1 = Math.atan2(vy, vx); //ball 1 direction in angles
var d2 = Math.atan2(vy2, vx2); //ball 2 direction in angles
• to rotate a vector, compute first its norm, then only project it with the new angle :
var v1 = Math.sqrt(vx*vx+vy*vy);
var v2 = Math.sqrt(vx2*vx2+vy2*vy2);
var newvx = v1 * Math.cos(d1 - ang);
var newvy = v1 * Math.sin(d1 - ang);
var newvx2 = v2 * Math.cos(d2 - ang);
var newvy2 = v2 * Math.sin(d2 - ang);
• You are detecting the collision when it already happened, so both circles overlap, but you do NOT solve the collision, meaning the circles might still overlap on next iteration, leading to a new collision and a new direction taken, ... not solved, etc..
-->> You need to ensure both circles are not colliding any more after you solved the collision.
• Last issue, but not a small one, is how you compute the angle. No more time for you sorry, but it would be helpful both for you and us to build one (several) scheme showing how you compute the angles.
Updated (but not working) codepen here :
http://codepen.io/anon/pen/eNgmaY
Good luck.
Edit :
Your code at codepen.io/anon/pen/oXZvoe simplify to this :
var angle = Math.atan2(dy, dx),
spread = minDistance - distance,
ax = spread * Math.cos(angle),
ay = spread * Math.sin(angle);
vx -= ax;
vy -= ay;
vx2 += ax;
vy2 += ay;
You are substracting the gap between both circles from the speed. Since later you add the speed to the position, that will do the spatial separation (=> no more collision).
I think to understand what vx-=ax means, we have to remember newton : v = a*t, where a is the acceleration, so basically doing vx=-ax means applying a force having the direction between both centers as direction, and the amount by which both circle collided (spread) as intensity. That amount is obviously quite random, hence the numerical instability that you see : sometimes a small effect, sometimes a big one.
look here for a constant punch version :
http://codepen.io/anon/pen/WvpjeK
var angle = Math.atan2(dy, dx),
spread = minDistance - distance,
ax = spread * Math.cos(angle),
ay = spread * Math.sin(angle);
// solve collision (separation)
cx -= ax;
cy -= ay;
// give a punch to the speed
var punch = 2;
vx -= punch*Math.cos(angle);
vy -= punch*Math.sin(angle);
vx2 += punch*Math.cos(angle);
vy2 += punch*Math.sin(angle);
I am struggling with connecting two circles with a line. I am using the famo.us library.
DEMO on Codepen
a.k.a. "Two balls, one line."
The Problem
Angle and length of the line are correct, but the position is wrong.
First attempt
The important part should be lines 114-116:
connection.origin = [.5, .5];
connection.align = [.5, .5];
connection.body.setPosition([
Math.min(sourcePos.x, targetPos.x),
Math.min(sourcePos.y, targetPos.y)
]);
Appearently i am doing something wrong with the math. Playing around with those values gives me all kinds of results, but nothing is close to correct.
Intended solution
(1) The minimal solution would be to connect the centres of the circles with the line.
(2) The better solution would be a line that is only touching the surface of both circles instead of going to the center.
(3) The ideal solution would have arrows on each end of the line to look like a directed graph.
This fixes it :
connection.body.setPosition([
sourcePos.x * Math.cos(angle) + sourcePos.y * Math.sin(angle),
sourcePos.x * Math.sin(-angle)+ sourcePos.y * Math.cos(angle)
]);
Your segment is defined by its extrimity in sourceand the angle and distance to target, thus you have to set its origin to be that of source
The rotation seems to not only rotate the object, but also rotate the coordinates around the origin, so I rotated them by -angle to compensate.
There might be a more famo.usesque way to do it (maybe you can get it to rotate before setting the position, or have the position be 0,0 and add the coordinates as a translation in the transformation).
To get your better solution, still with mostly math, you may keep the same code but
with r the radius of the source ball, remove [r * distX / distance, r * distY / distance] to the coordinates of the segment, to put it in contact with the outer part of the ball
remove both balls' radius from the distance
With that, we get :
var distX = sourcePos.x - targetPos.x;
var distY = sourcePos.y - targetPos.y;
var norm = Math.sqrt(distX * distX + distY * distY);
var distance = norm - (source.size[0]+target.size[0])/2;
var angle = -Math.atan2(-distY, distX);
connection.angle = angle;
connection.size = [distance, 2, 0];
connection.align = [.5, .5];
connection.origin = [.5, .5];
var posX = sourcePos.x - source.size[0]/2 * (distX / norm);
var posY = sourcePos.y - source.size[0]/2 * (distY / norm);
connection.body.setPosition([
posX * Math.cos(angle) + posY * Math.sin(angle),
posX * Math.sin(-angle)+ posY * Math.cos(angle)
]);
result on this fork : http://codepen.io/anon/pen/qEjPLg
I think the fact that the line length is off when the balls go fast is a timing issue. Most probably you compute the segment's length and position at a moment when the ball's centres are not yet updated for that frame.