I am creating a game using SFML 2.2 and Box2D. The game is similar to Peggle in that you have a cannon attached to the top of the screen. I have the movement of the cannon mostly working but I would like to limit the rotation so it cannot rotate and point straight up (off screen). I have implemented the code below and it works to an extent. The cannon can move fine within the bounds, but if it hits one of the two edges it becomes stuck there. If I press the opposite direction, instead of rotating back around it quickly jumps to the opposite edge, causing the cannon to be either all the way to the right or all the way to the left.
In my game, when the cannon is pointing straight down the angle is 0/360 degrees, all the way left is 90 degrees and all the way right is 270 degrees.
//******************************/
// Aiming Controls
//******************************/
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Left))
{
float sin = sinf(-m_fRotationSpeed*elapsedTime);
float cos = cosf(-m_fRotationSpeed*elapsedTime);
sf::Vector2f newDirection;
newDirection.x = (cos*m_vAimDirection.x) - (sin * m_vAimDirection.y);
newDirection.y = (sin*m_vAimDirection.x) + (cos*m_vAimDirection.y);
//Update sprite rotation
// Find angle of rotation by acos(v1dotv2)
float rotationAngle = acosf(1 * m_vAimDirection.y);
// Check to make sure we get the right direction!
if (m_vAimDirection.x < 0)
rotationAngle = (b2_pi*2.0f) - rotationAngle;
// convert to degrees
rotationAngle *= 180.0f / b2_pi;
// HERE IS WHERE I CHECK THE BOUNDS
if (rotationAngle > 88.0f && rotationAngle < 272.0f)
rotationAngle = 88.0f;
else
m_vAimDirection = newDirection;
//apply new rotation
m_sCannon.setRotation(rotationAngle);
}
else if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Right))
{
float sin = sinf(m_fRotationSpeed*elapsedTime);
float cos = cosf(m_fRotationSpeed*elapsedTime);
sf::Vector2f newDirection;
newDirection.x = (cos*m_vAimDirection.x) - (sin * m_vAimDirection.y);
newDirection.y = (sin*m_vAimDirection.x) + (cos*m_vAimDirection.y);
//Update sprite rotation
// Find angle of rotation by acos(v1dotv2)
float rotationAngle = acosf(1 * m_vAimDirection.y);
// Check to make sure we get the right direction!
if (m_vAimDirection.x < 0)
rotationAngle = (b2_pi*2.0f) - rotationAngle;
// convert to degrees
rotationAngle *= 180.0f / b2_pi;
// HERE IS WHERE I CHECK THE BOUNDS
if (rotationAngle < 272.0f && rotationAngle > 88.0f)
rotationAngle = 272.0f;
else
m_vAimDirection = newDirection;
//apply new rotation
m_sCannon.setRotation(rotationAngle);
}
Related
I'm having problems rotating GameObjects in my engine. I'm trying to rotate in 2 ways.
I'm using MathGeoLib to calculate maths in the engine.
First way: Rotates correctly around axis but if I want to rotate back, if I don't do it following the inverse order then rotation doesn't work properly.
e.g:
Rotate X axis 50 degrees, Rotate Y axis 30 degrees -> Rotate Y axis -50 degrees, Rotate X axis -30 degrees. Works.
Rotate X axis 50 degrees, Rotate Y axis 30 degrees -> Rotate X axis -50 degrees, Rotate Y axis -30 degrees. Doesn't.
Code:
void ComponentTransform::SetRotation(float3 euler_rotation)
{
float3 diff = euler_rotation - editor_rotation;
editor_rotation = euler_rotation;
math::Quat mod = math::Quat::FromEulerXYZ(diff.x * DEGTORAD, diff.y * DEGTORAD, diff.z * DEGTORAD);
quat_rotation = quat_rotation * mod;
UpdateMatrix();
}
Second way: Starts rotating good around axis but after rotating some times, then it stops to rotate correctly around axis, but if I rotate it back regardless of the rotation order it works, not like the first way.
Code:
void ComponentTransform::SetRotation(float3 euler_rotation)
{
editor_rotation = euler_rotation;
quat_rotation = math::Quat::FromEulerXYZ(euler_rotation.x * DEGTORAD, euler_rotation.y * DEGTORAD, euler_rotation.z * DEGTORAD);
UpdateMatrix();
}
Rest of code:
#define DEGTORAD 0.0174532925199432957f
void ComponentTransform::UpdateMatrix()
{
if (!this->GetGameObject()->IsParent())
{
//Get parent transform component
ComponentTransform* parent_transform = (ComponentTransform*)this->GetGameObject()->GetParent()->GetComponent(Component::CompTransform);
//Create matrix from position, rotation(quaternion) and scale
transform_matrix = math::float4x4::FromTRS(position, quat_rotation, scale);
//Multiply the object transform by parent transform
transform_matrix = parent_transform->transform_matrix * transform_matrix;
//If object have childs, call this function in childs objects
for (std::list<GameObject*>::iterator it = this->GetGameObject()->childs.begin(); it != this->GetGameObject()->childs.end(); it++)
{
ComponentTransform* child_transform = (ComponentTransform*)(*it)->GetComponent(Component::CompTransform);
child_transform->UpdateMatrix();
}
}
else
{
//Create matrix from position, rotation(quaternion) and scale
transform_matrix = math::float4x4::FromTRS(position, quat_rotation, scale);
//If object have childs, call this function in childs objects
for (std::list<GameObject*>::iterator it = this->GetGameObject()->childs.begin(); it != this->GetGameObject()->childs.end(); it++)
{
ComponentTransform* child_transform = (ComponentTransform*)(*it)->GetComponent(Component::CompTransform);
child_transform->UpdateMatrix();
}
}
}
MathGeoLib:
Quat MUST_USE_RESULT Quat::FromEulerXYZ(float x, float y, float z) { return (Quat::RotateX(x) * Quat::RotateY(y) * Quat::RotateZ(z)).Normalized(); }
Quat MUST_USE_RESULT Quat::RotateX(float angle)
{
return Quat(float3(1,0,0), angle);
}
Quat MUST_USE_RESULT Quat::RotateY(float angle)
{
return Quat(float3(0,1,0), angle);
}
Quat MUST_USE_RESULT Quat::RotateZ(float angle)
{
return Quat(float3(0,0,1), angle);
}
Quat(const float3 &rotationAxis, float rotationAngleRadians) { SetFromAxisAngle(rotationAxis, rotationAngleRadians); }
void Quat::SetFromAxisAngle(const float3 &axis, float angle)
{
assume1(axis.IsNormalized(), axis);
assume1(MATH_NS::IsFinite(angle), angle);
float sinz, cosz;
SinCos(angle*0.5f, sinz, cosz);
x = axis.x * sinz;
y = axis.y * sinz;
z = axis.z * sinz;
w = cosz;
}
Any help?
Thanks.
Using Euler angles and or Quaternions adds some limitations as it creates singularities which if not handled correctly will make silly things. Sadly almost all new 3D games using it wrongly. You can detect those by the well known things like:
sometimes your view get to very different angle that should not be there
object can not rotate anymore in some direction
object start rotating around different axises than it should
view jumps around singularity pole
view is spinning or flipping until you move/turn again (not the one caused by optic mouse error)
I am using cumulative transform matrices instead:
Understanding 4x4 homogenous transform matrices
Read the whole stuff (especially difference between local and global rotations) then in last 3 links you got C++ examples of how to do this (also read all 3 especially the preserving accuracy ...).
The idea is to have matrix representing your object coordinate system. And when ever you rotate (by mouse, keyboard, NAV,AI,...) you rotate the matrix (incrementally). The same goes for movement. This way they are no limitations or singularities. But also this approach has its problems:
lose of accuracy with time (read the preserving accuracy example to deal with this)
no knowledge about the Euler angles (the angles can be computed from the matrix however)
Both are solvable relatively easily.
Now when you are rotating around local axises you need to take into account that with every rotation around some axis you change the other two. So if you want to get to the original state you need to reverse order of rotations because:
rotate around x by 30deg
rotate around y by 40deg
is not the same as:
rotate around y by 40deg
rotate around x by 30deg
With cumulative matrix if you want to get back you can either iteratively drive your ship until it faces desired directions or remember original matrix and compute the rotations needed to be done one axis at a time. Or convert the matrix difference into quaternion and iterate that single rotation...
I want to make tank's turret be aimed with mouse in a top-down perspective. I have written some code to animate rotation to given angle:
void Tank::rotateTurret(float angle) {
turretRotation += angle;
}
sf::Sprite turret;
void Tank::update(unsigned int time) {
if (turretRotation != 0.0f) {
float rotate;
if (turretRotation > 0.0f) {
rotate = turretRotationSpeed * time;
if (rotate > turretRotation) {
rotate = turretRotation;
turretRotation = 0;
}
else
turretRotation -= rotate;
}
else {
rotate = -turretRotationSpeed * time;
if (rotate < turretRotation) {
rotate = turretRotation;
turretRotation = 0;
}
else
turretRotation -= rotate;
}
turret.rotate(rotate);
}
}
And I can calculate mouse pointer angle relative to top left corner:
void TankPlayerController::update() {
sf::Vector2i mousePosition = sf::Mouse::getPosition(*relativeWindow);
sf::Vector2i mouseMovement = mousePosition - lastMousePosition;
if (mouseMovement.x != 0 || mouseMovement.y != 0) {
float mouseAngle = VectorAngleDeg(mousePosition.x, mousePosition.y);
tank->rotateTurret(???);
lastMousePosition = mousePosition;
}
}
But I have no idea how to combine it together. How should it be done?
You need to calculate the angle to the center of the turret (CoT) from the upper left corner (ULHC) and the angle to the mouse location from the ULHC. Next consider the triangle formed from the lines connecting the ULHC to the CoT, the line connecting the ULHC to the mouse pointer location and the line connecting the CoT to the mouse pointer location. Since you know the distance from the ULHC to the CoT and the distance from the ULHC to the mouse pointer location all you need to do is determine the difference between the angle to the CoT and the mouse pointer position you can use the Law of Cosines to get the angel between the ULHC and the mouse position at the turret and from there the angle to any arbitrary axis you choose.
It would be easier with a picture :|
I've successfully implemented dragging a sprite on the screen, by simply remembering touch position on touchbegan and translating sprite by currentTouchPoint - startingTouchPoint, which gives me nice dragging. But now I want to sprite track my finger instead so it also should rotate. This would give me an advantage of making user able to rotate sprite as well as drag it around with just one finger.
I was getting unexpected results, but I've ran into this code:
http://www.freeactionscript.com/2011/01/mouse-follow-with-easing-smooth-rotation-v2/
There's a demo so you could try it on the browser.
It's pretty much the same I need except sprite should be a lot faster (near the finger all the time) and stop after reaching it's destination instead of orbiting around (while holding touch). I've tried to port it to cocos2d-x, but it doesn't work the same. Well, updatePosition() looks good, sprite is going where it should be pretty much, but updateRotation() is a total mess. It's rotating in strange directions and sometimes even makes a full very fast circle in opposite direction.
onTouchBegan:
//check if any sprite is touched and then assign it to draggedItem
onTouchMove:
_destinationX = touchPoint.x;
_destinationY = touchPoint.y;
onTouchEnd:
//make draggedItem nullptr
void CreatorScene::update(float delta){
if(draggedItem != nullptr){
updateItemPosition();
updateItemRotation();
}
}
void CreatorScene::updateItemRotation()
{
// calculate rotation
_dx = draggedItem->getPositionX() - _destinationX;
_dy = draggedItem->getPositionY() - _destinationY;
// which way to rotate
float rad = atan2(_dy, _dx);
if(_dy < 0) rad += 2 * M_PI;
float rotateTo = CC_RADIANS_TO_DEGREES(rad);
// keep rotation positive, between 0 and 360 degrees
if (rotateTo > draggedItem->getRotation() + 180) rotateTo -= 360;
if (rotateTo < draggedItem->getRotation() - 180) rotateTo += 360;
// ease rotation
_trueRotation = (rotateTo - draggedItem->getRotation()) / ROTATE_SPEED_MAX;
// update rotation
draggedItem->setRotation(draggedItem->getRotation() + _trueRotation);
}
void CreatorScene::updateItemPosition()
{
// check if mouse is down
// if (_isActive)
// {
// update destination
// _destinationX = stage.mouseX;
// _destinationY = stage.mouseY;
// update velocity
_vx += (_destinationX - draggedItem->getPositionX()) / MOVE_SPEED_MAX;
_vy += (_destinationY - draggedItem->getPositionY()) / MOVE_SPEED_MAX;
// }
// else
// {
// // when mouse is not down, update velocity half of normal speed
// _vx += (_destinationX - _player.x) / _moveSpeedMax * .25;
// _vy += (_destinationY - _player.y) / _moveSpeedMax * .25;
// }
// apply decay (drag)
_vx *= DECAY;
_vy *= DECAY;
// if close to target, slow down turn speed
if (sqrtf((_dx*_dx)+(_dy*_dy)) < 50)
{
_trueRotation *= .5;
}
// update position
draggedItem->setPosition(draggedItem->getPosition() + Point(_vx, _vy));
}
What's wrong here? How can I achieve such a smooth movement?
Edit:
Functionality that I want to exactly achieve is here:
https://www.youtube.com/watch?v=RZouMyyNGG8
You can see it on 2:10.
So I nearly implemented a free-flight camera using vectors and something like gluLookAt.
The movement in all 4 directions and rotation around the Y-axis work fine.
For the rotation around the Y-axis I calculate the vector between the eye and center vector and then rotate it with the rotation matrix like this:
Vector temp = vecmath.vector(center.x() - eye.x(),
center.y() - eye.y(), center.z() - eye.z());
float vecX = (temp.x()*(float) Math.cos(-turnSpeed)) + (temp.z()* (float)Math.sin(-turnSpeed));
float vecY = temp.y();
float vecZ = (temp.x()*(float) -Math.sin(-turnSpeed))+ (temp.z()*(float)Math.cos(-turnSpeed));
center = vecmath.vector(vecX, vecY, vecZ);
At the end I just set center to the newly calculated vector.
Now when I try to do the same thing for rotation around the X-axis it DOES rotate the vector but in a very strange way, kind of like it would be moving in a wavy line.
I use the same logic as for the previous rotation, just with the x rotation matrix:
Vector temp = vecmath.vector(center.x() - eye.x(),
center.y() - eye.y(), center.z() - eye.z());
float vecX = temp.x();
float vecY = (temp.y()*(float) Math.cos(turnSpeed)) + (temp.z()* (float)-Math.sin(turnSpeed));
float vecZ = (temp.y()*(float) Math.sin(turnSpeed)) + (temp.z()*(float)Math.cos(turnSpeed));
center = vecmath.vector(vecX, vecY, vecZ);
But why does this not work? Maybe I do something else somewhere wrong?
The problem you're facing is the exact same that I had trouble with the first time I tried to implement the camera movement. The problem occurs because if you first turn so that you are looking straight down the X axis and then try to "tilt" the camera by rotating around the X axis, you will effectively actually spin around the direction you are looking.
I find that the best way to handle camera movement is to accumulate the angles in separate variables and every time rotate completely from origin. If you do this you can first "tilt" by rotating around the X-axis then turn by rotating around the Y-axis. By doing it in this order you make sure that the tilting will always be around the correct axis relative to the camera. Something like this:
public void pan(float turnSpeed)
{
totalPan += turnSpeed;
updateOrientation();
}
public void tilt(float turnSpeed)
{
totalTilt += turnSpeed;
updateOrientation();
}
private void updateOrientation()
{
float afterTiltX = 0.0f; // Not used. Only to make things clearer
float afterTiltY = (float) Math.sin(totalTilt));
float afterTiltZ = (float) Math.cos(totalTilt));
float vecX = (float)Math.sin(totalPan) * afterTiltZ;
float vecY = afterTiltY;
float vecZ = (float)Math.cos(totalPan) * afterTiltZ;
center = eye + vecmath.vector(vecX, vecY, vecZ);
}
I don't know if the syntax is completely correct. Haven't programmed in java in a while.
So I decided to write a ray tracer the other day, but I got stuck because I forgot all my vector math.
I've got a point behind the screen (the eye/camera, 400,300,-1000) and then a point on the screen (a plane, from 0,0,0 to 800,600,0), which I'm getting just by using the x and y values of the current pixel I'm looking for (using SFML for rendering, so it's something like 267,409,0)
Problem is, I have no idea how to cast the ray correctly. I'm using this for testing sphere intersection(C++):
bool SphereCheck(Ray& ray, Sphere& sphere, float& t)
{ //operator * between 2 vec3s is a dot product
Vec3 dist = ray.start - sphere.pos; //both vec3s
float B = -1 * (ray.dir * dist);
float D = B*B - dist * dist + sphere.radius * sphere.radius; //radius is float
if(D < 0.0f)
return false;
float t0 = B - sqrtf(D);
float t1 = B + sqrtf(D);
bool ret = false;
if((t0 > 0.1f) && (t0 < t))
{
t = t0;
ret = true;
}
if((t1 > 0.1f) && (t1 < t))
{
t = t1;
ret = true;
}
return ret;
}
So I get that the start of the ray would be the eye position, but what is the direction?
Or, failing that, is there a better way of doing this? I've heard of some people using the ray start as (x, y, -1000) and the direction as (0,0,1) but I don't know how that would work.
On a side note, how would you do transformations? I'm assuming that to change the camera angle you just adjust the x and y of the camera (or the screen if you need a drastic change)
The parameter "ray" in the function,
bool SphereCheck(Ray& ray, Sphere& sphere, float& t)
{
...
}
should already contain the direction information and with this direction you need to check if the ray intersects the sphere or not. (The incoming "ray" parameter is the vector between the camera point and the pixel the ray is sent.)
Therefore the local "dist" variable seems obsolete.
One thing I can see is that when you create your rays you are not using the center of each pixel in the screen as the point for building the direction vector. You do not want to use just the (x, y) coordinates on the grid for building those vectors.
I've taken a look at your sample code and the calculation is indeed incorrect. This is what you want.
http://www.csee.umbc.edu/~olano/435f02/ray-sphere.html (I took this course in college, this guy knows his stuff)
Essentially it means you have this ray, which has an origin and direction. You have a sphere with a point and a radius. You use the ray equation and plug it into the sphere equation and solve for t. That t is the distance between the ray origin and the intersection point on the spheres surface. I do not think your code does this.
So I get that the start of the ray would be the eye position, but what is the direction?
You have camera defined by vectors front, up, and right (perpendicular to each other and normalized) and "position" (eye position).
You also have width and height of viewport (pixels), vertical field of view (vfov) and horizontal field of view (hfov) in degrees or radians.
There are also 2D x and y coordinates of pixel. X axis (2D) points to the right, Y axis (2D) points down.
For a flat screen ray can be calculated like this:
startVector = eyePos;
endVector = startVector
+ front
+ right * tan(hfov/2) * (((x + 0.5)/width)*2.0 - 1.0)
+ up * tan(vfov/2) * (1.0 - ((y + 0.5f)/height)*2.0);
rayStart = startVector;
rayDir = normalize(endVector - startVector);
That assumes that screen plane is flat. For extreme field of view angles (fov >= 180 degreess) you might want to make screen plane spherical, and use different formulas.
how would you do transformations
Matrices.