Computer graphics - common problems
About
I seem to come up against the same computing graphics problems again and again, so I've written some of these down using C++ code to solve them.
Before beginning notice that:
- I've written most of these from memory, and haven't checked them just yet (some variables might be around the wrong way)! Doh!
- Most of these functions are short enough they should be made inline!
- I've calculated all angles in radians, but to convert to degrees is easy enough if you specify the right constants and multiply radians by "RADS_TO_DEGS":
const float PI = 3.141592654; const float RADS_TO_DEGS = 180.0/PI; const float DEGS_TO_RADS = PI/180.0;
Rotating an Object to Face a Point
One very common problem in computer graphics is rotating an object to face towards a point. To do this we must first calculate the angle(s) we need to rotate the object.
Solving this in 2D is easy:
//----------
//-- Calculate the angle in radians from one point (cX,cY)
//-- to another (targetX,targetY) in 2D
float radiansToPointIn2D(float cX, float cY, float targetX, float targetY)
{
float dX = targetX-cX; // Distance along X.
float dY = targetY-cY; // Distance along Y.
float radsToPt = atan2(dY, dX); // tan = opp / adj
return radsToPt;
}
In this case the centre of the object we want to rotate is (cX,cY), and the point we want it to face is (targetX, targetY). We can now rotate each point (pX,pY) in the object around its center (cX,cY) by:
//----------
//-- Rotate a point (pX,py) by the given number of radians (rads)
//-- about another point (cX,cY) in 2D
float rotateAboutCenter2D(float &pX, float &pY, float cX, float cY, float rads)
{
float dX = pX-cX; // Distance along X.
float dY = pY-cY; // Distance along Y.
float radsToPt = atan2(dY, dX); // tan = opposite / adjacent
float hypotenuse = sqrt(dX*dX + dY*dY); // c^2 = a^2 + b^2
float newAngle = radsToPt + rads; // The angle we want from the center to our point.
pX = hypotenuse*cos(newAngle)+centerX; // cos = adj / hyp
pY = hypotenuse*sin(newAngle)+centerY; // sin = opp / hyp
}
Solving this in 3D gets a bit tricker:
For reasons which will become apparent I have adopted the "right hand rule": x is left, y is vertically up, and z is away from you. Imagine your head is the object you want to rotate. The most important thing to understand is that we only need two angles to face something - pitch (looking up/down by rotating on x axis) then yaw (looking left/right by rotating in the y axis). To calculate these angles:
//----------
//-- Calculate the pitch (pitchX) and yaw (yawY) in radians from one point (c)
//-- to another (targetP) in 3D
void calcPitchAndYaw(Point c, Point targetP, float &pitchX, float &yawY)
{
float dX = targetP.x-c.x; // Horizontal plane.
float dY = targetP.y-c.y; // Vertical plane.
float dZ = targetP.z-c.z; // Horizontal plane.
float hypotenuse = sqrt(dX*dX + dZ*dZ); // Distance along horizontal plane.
pitchX = atan2(dY, hypotenuse); // Angle to look up/down.
yawY = atan2(dX, dZ); // Angle to look left/right.
}
And now the tricky part, we want to rotate each point (p) in the object around its centre (c) in 3D:
//----------
//-- Rotate a point (p) by the given pitch (pitchX) and yaw (yawY) in radians
//-- about another point (c) in 3D
void point_rotatePointAroundPoint(&Point p, Point c, float pitchX, float yawY)
{
rotateAboutCenter2D(p.x, p.z, c.x, c.z, pitchX); // Rotate around X axis.
rotateAboutCenter2D(p.x, p.y, c.x, c.y, yawY ); // Rotate around Y axis.
//rotateAboutCenter2D(p.y, p.x,p.y, p.x, rollZ ); // Rotate around Z axis (DON'T NEED).
}
Thanks to the "rotateAboutCenter2D()", the above function has been made much easier. Once again, because we are only rotating points (with no direction) we need to rotate around the Z axis at all. Rotating around three axes is needed when doing more complex matrix multiplications.
Calculating a Random Direction
Calculating a random direction in 2D is obviously very easy:
float randomAngle = randomFloat(0,360);
Calculating a non-bias random direction in 3D is harder, because if we chose a random yaw and pitch (between 0 and 360 degrees), or generate a random angle for all axis, we will bias angles at the top and bottom poles. The method I've come up with to calculate a random direction is based on the fact a sphere occupies 52% of the minimum cube you can box around it. In this method, I first generate a random vector (represented by a point) and to my random "3D angle" I call once again on "calcPitchAndYaw()":
//------------------------
//-- Randomly generates a 3D direction in 3D spance and return a vector
//-- of length (length) along that direction
Point randomVector(float length)
{
Point vect(0,0,0); // Random vector.
//## GENERATE A RANDOM DIRECTION:
while (true)
{
vect.x = randomFloat(-1.0,1);
vect.y = randomFloat(-1.0,1);
vect.z = randomFloat(-1.0,1);
float dist = sqrt((vect.x*vect.x) + (vect.y*vect.y) + (vect.z*vect.z));
if (dist <= 1 && dist != 0)
break;
}
float fractToExtend = length / dist;
vect.x *= fractToExtend;
vect.y *= fractToExtend;
vect.z *= fractToExtend;
return vect;
}
//------------------------
//-- Creates a true, random direction in 3D
void generateRandomYawAndPitch(float &pitchX, float &yawY)
{
Ipoint randomVect = randomVector(1.0f);
calcPitchAndYaw(Point(0,0,0), randomVect, pitchX, yawY)
}