Implementing Zoom on an orbit camera - c++

I wish to zoom in on the target of an orbiting camera.
The camera is manipulated using a function like this:
moveCamera(x,y,z);
Depending on angle the values x,y,z should be different to get a correct zoom feature but I cant figure out an way to do it.
I use functions like getCameraposx, getTargetposy etc.. to get the coordinates for my target and camera.
Zoom kinda works now after PigBens help but I've run in to a problem. Zooming in is no problem but after zooming in too close zooming out stops working. And with too close I'm still quite far away.
Here is my zoom function.
void Camera::orbZoom(bool Zoo)
{
float x;
float y;
float z;
float xc;
float yc;
float zc;
float zoom;
x=getTargetposx();
y=getTargetposy();
z=getTargetposz();
xc=getCameraposx();
yc=getCameraposy();
zc=getCameraposz();
xc=xc-x;
yc=yc-y;
zc=zc-z;
if ( ivan==true){
zoom = 1.02;
if (xc<1){xc=+1.5;}
else if (yc<1){yc=+1.5;}
else if (zc<1){zc=+1.5;}
xc=xc*zoom;
yc=yc*zoom;
zc=zc*zoom;
}
if(ivan==false) {
zoom = 0.98;
xc=xc*zoom;
yc=yc*zoom;
zc=zc*zoom;
}
xc=xc+x;
yc=yc+y;
zc=zc+z;
camerapos.assign(xc,yc,zc);
}
Ok so the last thing didnt work as I wrote in the last comment. I'm thinking there is something else causing this behavior. The limit for when it stops working is just a bit closer to target than cameras starting position or at starting position, not really sure on that. But if I start with zooming out and dont get any closer than the cameras starting position it's working.
I think the bug is in this part of the code but I could be wrong so if anyone want to see some other part just ask. All my other camera behaviors are working correctly. Two modes, orbit and tumble. Pitch, yaw and roll works for both modes and strafing for tumble mode.
Here are for example two of those functions.
void Camera::strafeUp(float distance)
{
camerapos += upvect * distance;
targetpos += upvect * distance;
}
void Camera::tumbleYaw(float angle)
{
Quaternionf rotation((angle*PIdiv180), upvect);
rightvect = rotation.matrix() * rightvect;
forwardvect = rotation.matrix() * forwardvect;
forwardvect.normalize();
rightvect.normalize();
targetpos = camerapos + forwardvect * cameralength;
}

Subtract the target position from the camera position, then scale it, then add the target position in again.
camera_position -= target_position;
camera_position /= zoom_factor;
camera_position += target_position;
Regarding your second problem. My guess is that it's due to lack of precision in floats. When you get down to a certain point, multiplying by 1.02 is not enough to change the float's value to the next higher representable value, so it doesn't change at all. My tests indicate that this doesn't happen until the float is in the 10e-44 range, so you must be using some pretty gigantic units for this to be a problem. A few possible solutions.
Use doubles instead of floats. You will still have the same problem. But it won't come into play until a much much closer zoom.
Use smaller units. I usually just go with 1.0 = 1 meter and I've never run in to this problem.
Enforce a maximum zoom. You would actually do this in combination with the other 2 above.

Related

How to properly move the camera in the direction it's facing

I'm trying to figure out how to make the camera in directx move based on the direction it's facing.
Right now the way I move the camera is by passing the camera's current position and rotation to a class called PositionClass. PositionClass takes keyboard input from another class called InputClass and then updates the position and rotation values for the camera, which is then passed back to the camera class.
I've written some code that seems to work great for me, using the cameras pitch and yaw I'm able to get it to go in the direction I've pointed the camera.
However, when the camera is looking straight up (pitch=90) or straight down (pitch=-90), it still changes the cameras X and Z position (depending on the yaw).
The expected behavior is while looking straight up or down it will only move along the Y axis, not along the X or Z axis.
Here's the code that calculates the new camera position
void PositionClass::MoveForward(bool keydown)
{
float radiansY, radiansX;
// Update the forward speed movement based on the frame time
// and whether the user is holding the key down or not.
if(keydown)
{
m_forwardSpeed += m_frameTime * m_acceleration;
if(m_forwardSpeed > (m_frameTime * m_maxSpeed))
{
m_forwardSpeed = m_frameTime * m_maxSpeed;
}
}
else
{
m_forwardSpeed -= m_frameTime * m_friction;
if(m_forwardSpeed < 0.0f)
{
m_forwardSpeed = 0.0f;
}
}
// ToRadians() just multiplies degrees by 0.0174532925f
radiansY = ToRadians(m_rotationY); //yaw
radiansX = ToRadians(m_rotationX); //pitch
// Update the position.
m_positionX += sinf(radiansY) * m_forwardSpeed;
m_positionY += -sinf(radiansX) * m_forwardSpeed;
m_positionZ += cosf(radiansY) * m_forwardSpeed;
return;
}
The significant portion is where the position is updated at the end.
So far I've only been able to deduce that I have horrible math skills.
So, can anyone help me with this dilemma? I've created a fiddle to help test out the math.
Edit: The fiddle uses the same math I used in my MoveForward function, if you set pitch to 90 you can see that the Z axis is still being modified
Thanks to Chaosed0's answer, I was able to figure out the correct formula to calculate movement in a specific direction.
The fixed code below is basically the same as above but now simplified and expanded to make it easier to understand.
First we determine the amount by which the camera will move, in my case this was m_forwardSpeed, but here I will define it as offset.
float offset = 1.0f;
Next you will need to get the camera's X and Y rotation values (in degrees!)
float pitch = camera_rotationX;
float yaw = camera_rotationY;
Then we convert those values into radians
float pitchRadian = pitch * (PI / 180); // X rotation
float yawRadian = yaw * (PI / 180); // Y rotation
Now here is where we determine the new position:
float newPosX = offset * sinf( yawRadian ) * cosf( pitchRadian );
float newPosY = offset * -sinf( pitchRadian );
float newPosZ = offset * cosf( yawRadian ) * cosf( pitchRadian );
Notice that we only multiply the X and Z positions by the cosine of pitchRadian, this is to negate the direction and offset of your camera's yaw when it's looking straight up (90) or straight down (-90).
And finally, you need to tell your camera the new position, which I won't cover because it largely depends on how you've implemented your camera. Apparently doing it this way is out of the norm, and possibly inefficient. However, as Chaosed0 said, it's what makes the most sense to me!
To be honest, I'm not entirely sure I understand your code, so let me try to provide a different perspective.
The way I like to think about this problem is in spherical coordinates, basically just polar in 3D. Spherical coordinates are defined by three numbers: a radius and two angles. One of the angles is yaw, and the other should be pitch, assuming you have no roll (I believe there's a way to get phi if you have roll, but I can't think of how currently). In conventional mathematics notation, theta is your yaw and phi is your pitch, with radius being your move speed, as shown below.
Note that phi and theta are defined differently, depending on where you look.
Basically, the problem is to obtain a point m_forwardSpeed away from your camera, with the right pitch and yaw. To do this, we set the "origin" to your camera position, obtain a spherical coordinate, convert it to cartesian, and then add it to your camera position:
float radius = m_forwardSpeed;
float theta = m_rotationY;
float phi = m_rotationX
//These equations are from the wikipedia page, linked above
float xMove = radius*sinf(phi)*cosf(theta);
float yMove = radius*sinf(phi)*sinf(theta);
float zMove = radius*cosf(phi);
m_positionX += xMove;
m_positionY += yMove;
m_positionZ += zMove;
Of course, you can condense a lot of this code, but I expanded it for clarity.
You can think about this like drawing a sphere around your camera. Each of the points on the sphere is a potential position in the next timestep, depending on the camera's rotation.
This is probably not the most efficient way to do it, but in my opinion it's certainly the easiest way to think about it. It actually looks like this is nearly exactly what you're trying to do in your code, but the operations on the angles are just a little bit off.

PID controllers for travelling an exact distance with velocity

I am trying to make a robot in a simulation move an exact distance by sending messages with a linear velocity. Right now my implementation doesn't make the robot move an exact distance. This is some sample code:
void Robot::travel(double x, double y)
{
// px and py are the current positions (constantly gets updated as the robot moves in the simulation)
// x and y is the target position that I want to go to
double startx = px;
double starty = py;
double distanceTravelled = 0;
align(x, y);
// This gets the distance from the robot's current position to the target position
double distance = calculateDistance(px, py, x, y);
// Message with velocity
geometry_msgs::Twist msg;
msg.linear.x = 1;
msg.angular.z = 0;
ros::Rate loop_rate(10);
while (distanceTravelled < distance)
{
distanceTravelled = calculateDistance(startx, starty, px, py);
// Publishes message telling the robot to move with the linear.x velocity
RobotVelocity_pub.publish(msg);
ros::spinOnce();
loop_rate.sleep();
}
}
I asked around and someone suggested that using a PID controller for a feedback loop would solve this problem, but after reading the Wikipedia page I don't really get how I'd use it in this context. The Wikipedia page has pseudo code for the PID algorithm, but I have no idea what corresponds to what.
previous_error = setpoint - process_feedback
integral = 0
start:
wait(dt)
error = setpoint - process_feedback
integral = integral + (error*dt)
derivative = (error - previous_error)/dt
output = (Kp*error) + (Ki*integral) + (Kd*derivative)
previous_error = error
goto start
How would I implement this in the context of velocities and distances? Is distance error? Or velocity? Can anyone help please? What is the integral? Derivative? Kp? Ki? Kd? e.t.c.
Thanks.
For your problem, the setpoint will be (x,y). process_feedback will be (px,py). and output will be the velocity at which you need to travel. Kp, Ki, and Kd are parameters that you can tune to get the kind of behavior you want. For example, if Kd is too low you could shoot past the target by not slowing down enough as you approach it.
A PID controller takes three things into consideration:
error: where you want to be vs. where you are
This is certainly a big factor. If you are at point A and your target is at point B, then the vector from A to B tells you a lot about how you need to steer, but it isn't the only factor.
derivative: how fast you are approaching
If you are approaching the target quickly and you are close to it, you actually need to slow down. The derivative helps take that into consideration.
integral: alignment error
Your robot may not actually do exactly what you tell it to do. The integral helps determine how much you need to compensate for that.

Determining Resting contact between sphere and plane when using external forces

This question has one major question, and one minor question. I believe I am right in either question from my research, but not both.
For my physics loop, the first thing I do is apply a gravitational force to my TotalForce for a rigid body object. I then check for collisions using my TotalForce and my Velocity. My TotalForce is reset to (0, 0, 0) after every physics loop, although I will keep my velocity.
I am familiar with doing a collision check between a moving sphere and a static plane when using only velocity. However, what if I have other forces besides velocity, such as gravity? I put the other forces into TotalForces (right now I only have gravity). To compensate for that, when I determine that the sphere is not currently overlapping the plane, I do
Vector3 forces = (sphereTotalForces + sphereVelocity);
Vector3 forcesDT = forces * fElapsedTime;
float denom = Vec3Dot(&plane->GetNormal(), &forces);
However, this can be problematic for how I thought was suppose to be resting contact. I thought resting contact was computed by
denom * dist == 0.0f
Where dist is
float dist = Vec3Dot(&plane->GetNormal(), &spherePosition) - plane->d;
(For reference, the obvious denom * dist > 0.0f meaning the sphere is moving away from the plane)
However, this can never be true. Even when there appears to be "resting contact". This is due to my forces calculation above always having at least a .y of -9.8 (my gravity). When when moving towards a plane with a normal of (0, 1, 0) will produce a y of denom of -9.8.
My question is
1) Am I calculating resting contact correctly with how I mentioned with my first two code snippets?
If so,
2) How should my "other forces" such as gravity be used? Is my use of TotalForces incorrect?
For reference, my timestep is
mAcceleration = mTotalForces / mMass;
mVelocity += mAcceleration * fElapsedTime;
Vector3 translation = (mVelocity * fElapsedTime);
EDIT
Since it appears that some suggested changes will change my collision code, here is how i detect my collision states
if(fabs(dist) <= sphereRadius)
{ // There already is a collision }
else
{
Vector3 forces = (sphereTotalForces + sphereVelocity);
float denom = Vec3Dot(&plane->GetNormal(), &forces);
// Resting contact
if(dist == 0) { }
// Sphere is moving away from plane
else if(denom * dist > 0.0f) { }
// There will eventually be a collision
else
{
float fIntersectionTime = (sphereRadius - dist) / denom;
float r;
if(dist > 0.0f)
r = sphereRadius;
else
r = -sphereRadius;
Vector3 collisionPosition = spherePosition + fIntersectionTime * sphereVelocity - r * planeNormal;
}
}
You should use if(fabs(dist) < 0.0001f) { /* collided */ } This is to acocunt for floating point accuracies. You most certainly would not get an exact 0.0f at most angles or contact.
the value of dist if negative, is in fact the actual amount you need to shift the body back onto the surface of the plane in case it goes through the plane surface. sphere.position = sphere.position - plane.Normal * fabs(dist);
Once you have moved it back to the surface, you can optionally make it bounce in the opposite direction about the plane normal; or just stay on the plane.
parallel_vec = Vec3.dot(plane.normal, -sphere.velocity);
perpendicular_vec = sphere.velocity - parallel_vec;
bounce_velocity = parallel - perpendicular_vec;
you cannot blindly do totalforce = external_force + velocity unless everything has unit mass.
EDIT:
To fully define a plane in 3D space, you plane structure should store a plane normal vector and a point on the plane. http://en.wikipedia.org/wiki/Plane_(geometry) .
Vector3 planeToSphere = sphere.point - plane.point;
float dist = Vector3.dot(plane.normal, planeToSphere) - plane.radius;
if(dist < 0)
{
// collided.
}
I suggest you study more Maths first if this is the part you do not know.
NB: Sorry, the formatting is messed up... I cannot mark it as code block.
EDIT 2:
Based on my understanding on your code, either you are naming your variables badly or as I mentioned earlier, you need to revise your maths and physics theory. This line does not do anything useful.
float denom = Vec3Dot(&plane->GetNormal(), &forces);
A at any instance of time, a force on the sphere can be in any direction at all unrelated to the direction of travel. so denom essentially calculates the amount of force in the direction of the plane surface, but tells you nothing about whether the ball will hit the plane. e.g. gravity is downwards, but a ball can have upward velocity and hit a plane above. With that, you need to Vec3Dot(plane.normal, velocity) instead.
Alternatively, Mark Phariss and Gerhard Powell had already give you the physics equation for linear kinematics, you can use those to directly calculate future positions, velocity and time of impact.
e.g. s = 0.5 * (u + v) * t; gives the displacement after future time t. compare that displacement with distance from plane and you get whether the sphere will hit the plane. So again, I suggest you read up on http://en.wikipedia.org/wiki/Linear_motion and the easy stuff first then http://en.wikipedia.org/wiki/Kinematics .
Yet another method, if you expect or assume no other forces to act on the sphere, then you do a ray / plane collision test to find the time t at which it will hit the plane, in that case, read http://en.wikipedia.org/wiki/Line-plane_intersection .
There will always be -9.8y of gravity acting on the sphere. In the case of a suspended sphere this will result in downwards acceleration (net force is non-zero). In the case of the sphere resting on the plane this will result in the plane exerting a normal force on the sphere. If the plane was perfectly horizontal with the sphere at rest this normal force would be exactly +9.8y which would perfectly cancel the force of gravity. For a sphere at rest on a non-horizontal plane the normal force is 9.8y * cos(angle) (angle is between -90 and +90 degrees).
Things get more complicated when a moving sphere hits a plane as the normal force will depend on the velocity and the plane/sphere material properties. Depending what your application requirements are you could either ignore this or try some things with the normal forces and see how it works.
For your specific questions:
I believe contact is more specifically just when dist == 0.0f, that is the sphere and plane are making contact. I assume your collision takes into account that the sphere may move past the plane in any physics time step.
Right now you don't appear to have any normal forces being put on the sphere from the plane when they are making contact. I would do this by checking for contact (dist == 0.0f) and if true adding the normal force to the sphere. In the simple case of a falling sphere onto a near horizontal plane (angle between -90 and +90 degrees) it would just be sphereTotalForces += Vector3D(0, 9.8 * cos(angle), 0).
Edit:
From here your equation for dist to compute the distance from the edge of sphere to the plane may not be correct depending on the details of your problem and code (which isn't given). Assuming your plane goes through the origin the correct equation is:
dist = Vec3Dot(&spherePosition, &plane->GetNormal()) - sphereRadius;
This is the same as your equation if plane->d == sphereRadius. Note that if the plane is not at the origin then use:
D3DXVECTOR3 vecTemp(spherePosition - pointOnPlane);
dist = Vec3Dot(&vecTemp, &plane->GetNormal()) - sphereRadius;
The exact solution to this problem involves some pretty serious math. If you want an approximate solution I strongly recommend developing it in stages.
1) Make sure your sim works without gravity. The ball must travel through space and have inelastic (or partially elastic) collisions with angled frictionless surfaces.
2) Introduce gravity. This will change ballistic trajectories from straight lines to parabolae, and introduce sliding, but it won't have much effect on collisions.
3) Introduce static and kinetic friction (independently). These will change the dynamics of sliding. Don't worry about friction in collisions for now.
4) Give the ball angular velocity and a moment of inertia. This is a big step. Make sure you can apply torques to it and get realistic angular accelerations. Note that realistic behavior of a spinning mass can be counter-intuitive.
5) Try sliding the ball along a level surface, under gravity. If you've done everything right, its angular velocity will gradually increase and its linear velocity gradually decrease, until it breaks into a roll. Experiment with giving the ball some initial spin ("draw", "follow" or "english").
6) Try the same, but on a sloped surface. This is a relatively small step.
If you get this far you'll have a pretty realistic sim. Don't try to skip any of the steps, you'll only give yourself headaches.
Answers to your physics problems:
f = mg + other_f; // m = mass, g = gravity (9.8)
a = f / m; // a = acceleration
v = u + at; // v = new speed, u = old speed, t = delta time
s = 0.5 * (u + v) *t;
When you have a collision, you change the both speeds to 0 (or v and u = -(u * 0.7) if you want it to bounce).
Because speed = 0, the ball is standing still.
If it is 2D or 3D, then you just change the speed in the direction of the normal of the surface to 0, and keep the parallel speed the same. That will result in the ball rolling on the surface.
You must move the ball to the surface if it cuts the surface. You can make collision distance to a small amount (for example 0.001) to make sure it stay still.
http://www.physicsforidiots.com/dynamics.html#vuat
Edit:
NeHe is an amazing source of game engine design:
Here is a page on collision detection with very good descriptions:
http://nehe.gamedev.net/tutorial/collision_detection/17005/
Edit 2: (From NeHe)
double DotProduct=direction.dot(plane._Normal); // Dot Product Between Plane Normal And Ray Direction
Dsc=(plane._Normal.dot(plane._Position-position))/DotProduct; // Find Distance To Collision Point
Tc= Dsc*T / Dst
Collision point= Start + Velocity*Tc
I suggest after that to take a look at erin cato articles (the author of Box2D) and Glenn fiedler articles as well.
Gravity is a strong acceleration and results in strong forces. It is easy to have faulty simulations because of floating imprecisions, variable timesteps and euler integration, very quickly.
The repositionning of the sphere at the plane surface in case it starts to burry itself passed the plane is mandatory, I noticed myself that it is better to do it only if velocity of the sphere is in opposition to the plane normal (this can be compared to face culling in 3D rendering: do not take into account backfaced planes).
also, most physics engine stops simulation on idle bodies, and most games never take gravity into account while moving, only when falling. They use "navigation meshes", and custom systems as long as they are sure the simulated objet is sticking to its "ground".
I don't know of a flawless physics simulator out there, there will always be an integration explosion, a missed collision (look for "sweeped collision").... it takes a lot of empirical fine-tweaking.
Also I suggest you look for "impulses" which is a method to avoid to tweak manually the velocity when encountering a collision.
Also take a look to "what every computer scientist should know about floating points"
good luck, you entered a mine field, randomly un-understandable, finger biting area of numerical computer science :)
For higher fidelity (wouldn't solve your main problem), I'd change your timestep to
mAcceleration = mTotalForces / mMass;
Vector3 translation = (mVelocity * fElapsedTime) + 0.5 * mAcceleration * pow(fElapsedTime, 2);
mVelocity += mAcceleration * fElapsedTime;
You mentioned that the sphere was a rigid body; are you also modeling the plane as rigid? If so, you'd have an infinite point force at the moment of contact & perfectly elastic collision without some explicit dissipation of momentum.
Force & velocity cannot be summed (incompatible units); if you're just trying to model the kinematics, you can disregard mass and work with acceleration & velocity only.
Assuming the sphere is simply dropped onto a horizontal plane with a perfectly inelastic collision (no bounce), you could do [N.B., I don't really know C syntax, so this'll be Pythonic]
mAcceleration = if isContacting then (0, 0, 0) else (0, -9.8, 0)
If you add some elasticity (say half momentum conserved) to the collision, it'd be more like
mAcceleration = (0, -9.8, 0) + if isContacting then (0, 4.9, 0)

2d rotation around point

I'm trying to add some features into my editor that allow to the user things like dragging a selected item to a given direction
although i've ran into a damn issue.
This is my code :
//Origin
double objectx = selection->getX();
double objecty = selection->getY();
//Point
double pointerx = input->getMouseX();
double pointery = input->getMouseY();
//Displacement
double displacementx = fabs(pointerx - objectx);
double displacementy = fabs(pointery - objecty);
//Angle
double angle = atan2(displacementy,displacementx);
//Point
double pointx = displacementx * cosf(angle) + displacementy * sinf(angle);
double pointy = displacementy * cosf(angle) - displacementx * sinf(angle);
//Final position
double fx = objectx + pointx;
double fy = objecty + pointy;
//Save alpha
const bool alpha = graphics->getAlpha();
//Draw selection
graphics->setAlpha(false);
graphics->color(selection->getColor());
graphics->renderQd(selection->getBitmap(),
CRect(objectx,
objecty,
selection->getWidth(),
selection->getHeight()));
//Draw pointer around selection
graphics->setAlpha(true);
graphics->color(editor::ssImg[0]->getColor());
graphics->renderQd(editor::ssImg[0]->getBitmap(),
CRect(objectx + pointx,
objecty + pointy,
editor::ssImg[0]->getWidth(),
editor::ssImg[0]->getHeight()));
//Restore alpha
graphics->setAlpha(alpha);
The exact issue is that the selection pointer doesn't follow mouse's rotation only but its actually at mouse's position(!).
The wanted behaviour is a pointer locked at selection's offset but pointing to the mouse's angle.
Anyone good at math sees anything wrong here ?
As I understand it, your wanted behavior presumes the existence of three points: an origin around which you're rotating, a "mouse" to provide the direction relative to the origin, and a "selection" to provide the distance from the origin. (Somewhat confusingly, the result of your code is the "selection pointer". I take it that "selection" means the original position of the selected object, and "selection pointer" means the position it's been dragged to so far?)
Your code, however, only actually refers to two of those points: (objectx, objecty), which I assume is the origin, and (pointerx, pointery), which I assume is the "mouse". Your code never refers to the "selection"; so, naturally, the "selection" has no effect on the result of your code.
There are a few other problems — Oli Charlesworth points out, in a comment above, that you're wrongly dividing your angle by π/180, which means that you apply a very small rotation (which is why it looks like you end up with selection pointer = mouse; in fact, they can be up to a few degrees apart relative to the origin of rotation, but that's not instantly noticeable) — but rather than fix those problems, I'd recommend you change your approach. Instead of generating "selection pointer" by rotating "selection" to match the angle of "mouse", I recommend that you generate it by scaling "mouse" to match the magnitude of "selection". The math for this is more straightforward, IMHO.
If you do want to stick with the approach of generating "selection pointer" by rotating "selection" to match the angle of "mouse", then you have two main things to fix. Your current code rotates "mouse" by the current angle of "mouse". Part of the fix is to rotate "selection" instead; the other part of the fix is to rotate by the difference between the current angles of "mouse" and "selection".

Preserving rotations in OpenGL

I'm drawing an object (say, a cube) in OpenGL that a user can rotate by clicking / dragging the mouse across the window. The cube is drawn like so:
void CubeDrawingArea::redraw()
{
Glib::RefPtr gl_drawable = get_gl_drawable();
gl_drawable->gl_begin(get_gl_context());
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glPushMatrix();
{
glRotated(m_angle, m_rotAxis.x, m_rotAxis.y, m_rotAxis.z);
glCallList(m_cubeID);
}
glPopMatrix();
gl_drawable->swap_buffers();
gl_drawable->gl_end();
}
and rotated with this function:
bool CubeDrawingArea::on_motion_notify_event(GdkEventMotion* motion)
{
if (!m_leftButtonDown)
return true;
_3V cur_pos;
get_trackball_point((int) motion->x, (int) motion->y, cur_pos);
const double dx = cur_pos.x - m_lastTrackPoint.x;
const double dy = cur_pos.y - m_lastTrackPoint.y;
const double dz = cur_pos.z - m_lastTrackPoint.z;
if (dx || dy || dz)
{
// Update angle, axis of rotation, and redraw
m_angle = 90.0 * sqrt((dx * dx) + (dy * dy) + (dz * dz));
// Axis of rotation comes from cross product of last / cur vectors
m_rotAxis.x = (m_lastTrackPoint.y * cur_pos.z) - (m_lastTrackPoint.z * cur_pos.y);
m_rotAxis.y = (m_lastTrackPoint.z * cur_pos.x) - (m_lastTrackPoint.x * cur_pos.z);
m_rotAxis.z = (m_lastTrackPoint.x * cur_pos.y) - (m_lastTrackPoint.y * cur_pos.x);
redraw();
}
return true;
}
There is some GTK+ stuff in there, but it should be pretty obvious what it's for. The get_trackball_point() function projects the window coordinates X Y onto a hemisphere (the virtual "trackball") that is used as a reference point for rotating the object. Anyway, this more or less works, but after I'm done rotating, and I go to rotate again, the cube snaps back to the original position, obviously, since m_angle will be reset back to near 0 the next time I rotate. Is there anyway to avoid this and preserve the rotation?
Yeah, I ran into this problem too.
What you need to do is keep a rotation matrix around that "accumulates" the current state of rotation, and use it in addition to the rotation matrix that comes from the current dragging operation.
Say you have two matrices, lastRotMx and currRotMx. Make them members of CubeDrawingArea if you like.
You haven't shown us this, but I assume that m_lastTrackPoint is initialized whenever the mouse button goes down for dragging. When that happens, copy currRotMx into lastRotMx.
Then in on_motion_notify_event(), after you calculate m_rotAxis and m_angle, create a new rotation matrix draggingRotMx based on m_rotAxis and m_angle; then multiply lastRotMx by draggingRotMx and put the result in currRotMx.
Finally, in redraw(), instead of
glRotated(m_angle, m_rotAxis.x, m_rotAxis.y, m_rotAxis.z);
rotate by currRotMx.
Update: Or instead of all that... I haven't tested this, but I think it would work:
Make cur_pos a class member so it stays around, but it's initialized to zero, as is m_lastTrackPoint.
Then, whenever a new drag motion is started, before you initialize m_lastTrackPoint, let _3V dpos = cur_pos - m_lastTrackPoint (pseudocode).
Finally, when you do initialize m_lastTrackPoint based on the mouse event coords, subtract dpos from it.
That way, your cur_pos will already be offset from m_lastTrackPoint by an amount based on the accumulation of offsets from past arcball drags.
Probably error would accumulate as well, but it should be gradual enough so as not to be noticeable. But I'd want to test it to be sure... composed rotations are tricky enough that I don't trust them without seeing them.
P.S. your username is demotivating. Suggest picking another one.
P.P.S. For those who come later searching for answers to this question, the keywords to search on are "arcball rotation". An definitive article is Ken Shoemake's section in Graphical Gems IV. See also this arcball tutorial for JOGL.