In raycaster I am developing I am trying to implement hemisphere random sampling, with option to rotate hemisphere to direction and then take random point.
First version worked fine because sampling was uniform, and change of direction was just swapping to other hemisphere, which was simple.
Vec3f UniformSampleSphere() {
const Vec2f& u = GetVec2f(); // get two random numbers
float z = 1 - 2 * u.x;
float r = std::sqrt(std::max((float)0, (float)1 - z * z));
float phi = 2 * PI_F * u.y;
return Vec3f(r * std::cos(phi), r * std::sin(phi), z);
}
Vec3f GetRandomOnHemiSphere(Vec3f direction) {
auto toReturn = GetRandomOnSphere();
if (Dot(toReturn - direction, toReturn) < 0)
toReturn = -toReturn;
return toReturn;
}
But with cosine weighted hemisphere sampling I am in trouble to rotate properly and find random direction in correctly rotated hemisphere.
On picture's left we can see what is working now, and on right is after applying magic rotation that is that big deal I want.
So final function will be something like this:
Vec3f GetRandomOnHemiSphere(Vec3f direction) {
auto toReturn = CosineSampleHemisphere();
/*
Some magic here that rotates to correct direction of hemisphere
*/
return toReturn;
}
I used code from Socine weighted hemisphere sampling.
I am trying to create a uvn quaternion based camera in opengl, having used a variety of tutorials listed below, and having read up on quaternions and axis angle rotation. I am left with a peculiar bug which I cannot seem to fix.
Basically the camera seems to work fine up until the camera is rotated approx 45 degrees from +z at this point tilting the camera up or down seems to tilt the camera around its target axis, turning the up vector.
By the time the camera faces along -z tilting up or down gives the illusion of the opposite, up tilts down and down tilts up.
I have seen other implementations suggesting the use of a non uvn system where quaternions are accumulated into one which describes the current orientation as a delta from some arbitrary start angle. This sounds great however I can't seem to work out exactly how I would implement this, specifically the conversion from this to a view matrix.
Elsewhere on SO I read about splitting the rotation into two quaternions that represent the yaw and pitch separately but I'm not convinced that this is the cause of the problem since in this context, correct me if I am wrong but my understanding is that the order in which you apply the two rotations does not matter.
Relevant Source Code Snippets:
Quarternion Operations
Quaternion<TValue> conjugate() const{
return Quaternion({ { -m_values[X], -m_values[Y], -m_values[Z], m_values[W] } });
};
Quaternion<TValue>& operator*=(const Quaternion<TValue>& rhs) {
TValue x, y, z, w;
w = rhs[W] * m_values[W] - rhs[X] * m_values[X] - rhs[Y] * m_values[Y] - rhs[Z] * m_values[Z];
x = rhs[W] * m_values[X] + rhs[X] * m_values[W] - rhs[Y] * m_values[Z] + rhs[Z] * m_values[Y];
y = rhs[W] * m_values[Y] + rhs[X] * m_values[Z] + rhs[Y] * m_values[W] - rhs[Z] * m_values[X];
z = rhs[W] * m_values[Z] - rhs[X] * m_values[Y] + rhs[Y] * m_values[X] + rhs[Z] * m_values[W];
m_values[X] = x;
m_values[Y] = y;
m_values[Z] = z;
m_values[W] = w;
return *this;
};
static Quaternion<TValue> rotation(Vector<3, TValue> axis, TValue angle){
float x, y, z, w;
TValue halfTheta = angle / 2.0f;
TValue sinHalfTheta = sin(halfTheta);
return Quaternion<TValue>({ { axis[X] * sinHalfTheta, axis[Y] * sinHalfTheta, axis[Z] * sinHalfTheta, cos(halfTheta) } });
};
Vector Rotation Operation
Vector<dimensions, TValue> rotate(const Vector<3, TValue> axis, float angle){
Quaternion<TValue> R = Quaternion<TValue>::rotation(axis, angle);
Quaternion<TValue> V = (*this);
Vector<dimensions, TValue> result = R * V * R.conjugate();
return result;
}
Camera Methods
Camera::Camera(Vector<2, int> windowSize, float fov, float near, float far):
m_uvn(Matrix<4, float>::identity()),
m_translation(Matrix<4, float>::identity()),
m_ar(windowSize[Dimensions::X] / (float)windowSize[Dimensions::Y]),
m_fov(fov),
m_near(near),
m_far(far),
m_position(),
m_forward({ { 0, 0, 1 } }),
m_up({ { 0, 1, 0 } })
{
setViewMatrix(Matrix<4, float>::identity());
setProjectionMatrix(Matrix<4, float>::perspective(m_ar, m_near, m_far, m_fov));
};
Matrix<4, float> Camera::getVPMatrix() const{
return m_vp;
};
const Vector<3, float> Camera::globalY = Vector<3, float>({ { 0, 1, 0 } });
void Camera::setProjectionMatrix(const Matrix<4, float> p){
m_projection = p;
m_vp = m_projection * m_view;
};
void Camera::setViewMatrix(const Matrix<4, float> v){
m_view = v;
m_vp = m_projection * m_view;
};
void Camera::setTranslationMatrix(const Matrix<4, float> t){
m_translation = t;
setViewMatrix(m_uvn * m_translation);
}
void Camera::setPosition(Vector<3, float> position){
if (position != m_position){
m_position = position;
setTranslationMatrix(Matrix<4, float>::translation(-position));
}
};
void Camera::moveForward(float ammount){
setPosition(m_position + (m_forward * ammount));
}
void Camera::moveRight(float ammount){
setPosition(m_position + (getRight() * ammount));
}
void Camera::moveUp(float ammount){
setPosition(m_position + (m_up * ammount));
}
void Camera::setLookAt(Vector<3, float> target, Vector<3, float> up){
Vector<3, float> newUp = up.normalize();
Vector<3, float> newForward = target.normalize();
if (newUp != m_up || newForward != m_forward){
m_up = newUp;
m_forward = newForward;
Vector<3, float> newLeft = getLeft();
m_up = newLeft * m_forward;
m_uvn = generateUVN();
setViewMatrix(m_uvn * m_translation);
}
};
void Camera::rotateX(float angle){
Vector<3, float> hAxis = (globalY * m_forward).normalize();
m_forward = m_forward.rotate(hAxis, angle).normalize();
m_up = (m_forward * hAxis).normalize();
m_uvn = generateUVN();
setViewMatrix(m_translation * m_uvn);
}
void Camera::rotateY(float angle){
Vector<3, float> hAxis = (globalY * m_forward).normalize();
m_forward = m_forward.rotate(globalY, angle).normalize();
m_up = (m_forward * hAxis).normalize();
m_uvn = generateUVN();
setViewMatrix(m_translation * m_uvn);
}
Vector<3, float> Camera::getRight(){
return (m_forward * m_up).normalize();
}
Vector <3, float> Camera::getLeft(){
return (m_up * m_forward).normalize();
}
};
I am guessing that the problem is in either my implementation of a quaternion or the way I am using it, but due to the complex nature of the system I cannot seem to pin down the problem any further than that. Due to the weird bugs being experienced I am unsure if there is just something wrong with the way I am trying to implement the camera?
Tutorials
https://www.youtube.com/watch?v=1Aw1PDu33PI
http://www.gamedev.net/page/resources/_/technical/math-and-physics/a-simple-quaternion-based-camera-r1997
Quarternion/Vector Math
http://mathworld.wolfram.com/Quaternion.html
https://en.wikipedia.org/wiki/Cross_product
http://ogldev.atspace.co.uk/www/tutorial13/tutorial13.html
http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/index.htm
Old question but still a relevant topic so I'll give some pointers. The thing to remember is a quaternion is a 3D map. Unit, and to a lesser degree Pure, quaternions provide a consistent way to say this rotation is this value. The benefits of this over the Euler angles are they attempt to reconstruct orientation from rotation about some axis where as the quaternion directly correlates to the orientation and can avoid gimbal lock
Specifically for a quaternion camera let Q{1,0,0,0} where w=1, the corresponding matrix to this quaternion is the identity matrix. There for any valid unit quaternion when decomposed into a 3x3 matrix gives you the (usually) world space rotation of the camera. However you don't even need that because you can define your camera space so that say X{1,0,0} Y{0,1,0} Z{0,0,-1} then multiply these unit axes by your cameras orientation quaternion and the resulting vector is the transformed unit vector. These then create your right up and front vectors which can be used to build the 3x3 rotation of the view transform.
Moving the camera should be relatively straight forward at that point. The linear movement vectors can be easily reconstructed and applied to the camera position and the angular movement can be achieved by multiplying the the camera quaternion with the direction which is the normal of the corresponding plane in which the rotation happens. For example, turning left and right is a rotation that happens in there XZ plane there for the Y vector{as a unit quaternion not a pure quaternion} would be multiplied with the camera quaternion producing the desired rotational effect
So I'm trying to make the player shoot a bullet that goes towards the mouse in a wavey pattern. I can get the bullet to move in a wavey pattern (albeit not really how I predicted), but not towards the mouse.
Vector2 BulletFun::sine(Vector2 vec) {
float w = (2 * PI) / 1000; // Where 1000 is the period
float waveNum = (2 * PI) / 5; // Where 5 is the wavelength
Vector2 k(0.0F, waveNum);
float t = k.dot(vec) - (w * _time);
float x = 5 * cos(t); // Where 5 is the amplitude
float y = 5 * sin(t);
Vector2 result(x, y);
return result;
}
Right now the speed isn't much of a concern, that shouldn't be too much of a problem once I have this figured out. I do get some angle change, but it seems to be reversed and only 1/8th a circle.
I'm probably miscalculating something somewhere. I just kind of learned about wave vectors.
I've tried a few other things, such as 1 dimensional travelling waves and another thing involving adjusting a normal sine wave by vec. Which had more or less the same result.
Thanks!
EDIT:
vec is the displacement from the player's location to the mouse click location. The return is a new vector that is adjusted to follow a wave pattern, BulletFun::sine is called each time the bullet receives and update.
The setup is something like this:
void Bullet::update() {
_velocity = BulletFun::sine(_displacement);
_location.add(_velocity); // add is a property of Tuple
// which Vector2 and Point2 inherit
}
In pseudocode, what you need to do is the following:
waveVector = Vector2(travelDistance,amplitude*cos(2*PI*frequency*travelDistance/unitDistance);
cosTheta = directionVector.norm().dot(waveVector.norm());
theta = acos(cosTheta);
waveVector.rotate(theta);
waveVector.translate(originPosition);
That should compute the wave vector in a traditional coordinate frame, and then rotate it to the local coordinate frame of the direction vector (where the direction vector is the local x-axis), and then translate the wave vector relative to your desired origin position of the wave beam or whatever...
This will result in a function very similar to
Vector2
BulletFun::sine(Bullet _bullet, float _amplitude, float _frequency, float _unitDistance)
{
float displacement = _bullet.getDisplacement();
float omega = 2.0f * PI * _frequency * _displacement / _unitDistance;
// Compute the wave coordinate on the traditional, untransformed
// Cartesian coordinate frame.
Vector2 wave(_displacement, _amplitude * cos(omega));
// The dot product of two unit vectors is the cosine of the
// angle between them.
float cosTheta = _bullet.getDirection().normalize().dot(wave.normalize());
float theta = acos(cosTheta);
// Translate and rotate the wave coordinate onto
// the direction vector.
wave.translate(_bullet.origin());
wave.rotate(theta);
}
I am currently trying to work on getting my virtual trackball to work from any angle. When I am looking at it from the z axis, it seems to work fine. I hold my mouse down, and move the mouse up... the rotation will move accordingly.
Now, if I change my viewing angle / position of my camera and try to move my mouse. The rotation will occur as if I were looking from the z axis. I cannot come up with a good way to get this to work.
Here is the code:
void Renderer::mouseMoveEvent(QMouseEvent *e)
{
// Get coordinates
int x = e->x();
int y = e->y();
if (isLeftButtonPressed)
{
// project current screen coordinates onto hemi sphere
Point sphere = projScreenCoord(x,y);
// find axis by taking cross product of current and previous hemi points
axis = Point::cross(previousPoint, sphere);
// angle can be found from magnitude of cross product
double length = sqrt( axis.x * axis.x + axis.y * axis.y + axis.z * axis.z );
// Normalize
axis = axis / length;
double lengthPrev = sqrt( previousPoint.x * previousPoint.x + previousPoint.y * previousPoint.y + previousPoint.z * previousPoint.z );
double lengthCur = sqrt( sphere.x * sphere.x + sphere.y * sphere.y + sphere.z * sphere.z );
angle = asin(length / (lengthPrev * lengthCur));
// Convert into Degrees
angle = angle * 180 / M_PI;
// 'add' this rotation matrix to our 'total' rotation matrix
glPushMatrix(); // save the old matrix so we don't mess anything up
glLoadIdentity();
glRotatef(angle, axis[0], axis[1], axis[2]); // our newly calculated rotation
glMultMatrixf(rotmatrix); // our previous rotation matrix
glGetFloatv(GL_MODELVIEW_MATRIX, (GLfloat*) rotmatrix); // we've let OpenGL do our matrix mult for us, now get this result & store it
glPopMatrix(); // return modelview to its old value;
}
// Project screen coordinates onto a unit hemisphere
Point Renderer::projScreenCoord(int x, int y)
{
// find projected x & y coordinates
double xSphere = ((double)x/width)*2.0 - 1.0;
double ySphere = ( 1 - ((double)y/height)) * 2.0 - 1.0;
double temp = 1.0 - xSphere*xSphere - ySphere*ySphere;
// Do a check so you dont do a sqrt of a negative number
double zSphere;
if (temp < 0){ zSphere = 0.0;}
else
{zSphere = sqrt(temp);}
Point sphere(xSphere, ySphere, zSphere);
// return the point on the sphere
return sphere;
}
I am still fairly new at this. Sorry for the trouble and thanks for all the help =)
The usual way involves quaternions. E.g., in sample code originally from SGI.
I'm representing a shape as a set of coordinates in 3D, I'm trying to rotate the whole object around an axis (In this case the Z axis, but I'd like to rotate around all three once I get it working).
I've written some code to do this using a rotation matrix:
//Coord is a 3D vector of floats
//pos is a coordinate
//angles is a 3d vector, each component is the angle of rotation around the component axis
//in radians
Coord<float> Polymers::rotateByMatrix(Coord<float> pos, const Coord<float> &angles)
{
float xrot = angles[0];
float yrot = angles[1];
float zrot = angles[2];
//z axis rotation
pos[0] = (cosf(zrot) * pos[0] - (sinf(zrot) * pos[1]));
pos[1] = (sinf(zrot) * pos[0] + cosf(zrot) * pos[1]);
return pos;
}
The image below shows the object I'm trying to rotate (looking down the Z axis) before the rotation is attempted, each small sphere indicates one of the coordinates I'm trying to rotate
alt text http://www.cs.nott.ac.uk/~jqs/notsquashed.png
The rotation is performed for the object by the following code:
//loop over each coordinate in the object
for (int k=start; k<finish; ++k)
{
Coord<float> pos = mp[k-start];
//move object away from origin to test rotation around origin
pos += Coord<float>(5.0,5.0,5.0);
pos = rotateByMatrix(pos, rots);
//wrap particle position
//these bits of code just wrap the coordinates around if the are
//outside of the volume, and write the results to the positions
//array and so shouldn't affect the rotation.
for (int l=0; l<3; ++l)
{
//wrap to ensure torroidal space
if (pos[l] < origin[l]) pos[l] += dims[l];
if (pos[l] >= (origin[l] + dims[l])) pos[l] -= dims[l];
parts->m_hPos[k * 4 + l] = pos[l];
}
}
The problem is that when I perform the rotation in this way, with the angles parameter set to (0.0,0.0,1.0) it works (sort of), but the object gets deformed, like so:
alt text http://www.cs.nott.ac.uk/~jqs/squashed.png
which is not what I want. Can anyone tell me what I'm doing wrong and how I can rotate the entire object around the axis without deforming it?
Thanks
nodlams
Where you do your rotation in rotateByMatrix, you compute the new pos[0], but then feed that into the next line for computing the new pos[1]. So the pos[0] you're using to compute the new pos[1] is not the input, but the output. Store the result in a temp var and return that.
Coord<float> tmp;
tmp[0] = (cosf(zrot) * pos[0] - (sinf(zrot) * pos[1]));
tmp[1] = (sinf(zrot) * pos[0] + cosf(zrot) * pos[1]);
return tmp;
Also, pass the pos into the function as a const reference.
const Coord<float> &pos
Plus you should compute the sin and cos values once, store them in temporaries and reuse them.