So I have successfully managed to rotate, move, etc with keyboard keys my camera through my environment. I do this by multiplying my view matrix by rotational matrices:
ViewMatrix = ViewMatrix X Z_Rotational_Matrix(yaw_increment) X X_Rotational_Matrix(pitch_increment) X Y_Rotational_Matrix(roll_increment)
I can do so with inputs of yaw, pitch, and roll. So I am next trying to do these rotations with the mouse. I believe I have all of the heavy lifting done and should just need to supply the yaw, and pitch to my rotational matrix. Correct me if this is the wrong thought process.
I can capture the mouse world coordinate when I click and when I move so supposedly it should just be math based on initial_mouse_click_coordinates and current_mouse_click_coordinates. My thought is to project two vectors, one from the initial_mouse_click_coordinate and the other from current_mouse_click_coordinates. Both vectors are parallel to the vector created from my camera location and my camera lookat point. Then I can calculate the XY planar angle and the XZ planar angle. Once these are determined I pass these as the yaw and pitch to my rotational matrices.
Determining two angles between my two lines:
The problem I have is that these values appear to be really small so on the screen nothing really happens. Am I going about this the wrong way completely?
If this is the right method to try, am I messing up on my math somewhere?
'we have initial_mouse_world_coordinates and mouse_world_coordinates. These are the points on the x screen in world coordinates.
'Two lines must be constructed going through these points, parallel with our lookat vector.
'The two angles between these two vectors are the angles to use on our lookat vector
'X = X0 + Rx*T
'Y = Y0 + Ry*T
'Z = Z0 + Rz*T
'therefore T = (Z-Z0)/Rz
'at Z = 0: T = -Z0/Rz
Dim t As Decimal = -initial_mouse_world_coordinates.Z / cam.lookat.Z
Dim y As Decimal = initial_mouse_world_coordinates.Y + (cam.lookat.Y * t)
Dim x As Decimal = initial_mouse_world_coordinates.X + (cam.lookat.X * t)
Dim z As Decimal = 0
'new point = x,y,z => translate to new vector
Dim startline As New Vector3(initial_mouse_world_coordinates.X - x, initial_mouse_world_coordinates.Y - y, initial_mouse_world_coordinates.Z - z)
t = -mouse_world_coordinates.Z / cam.lookat.Z
y = mouse_world_coordinates.Y + (cam.lookat.Y * t)
x = mouse_world_coordinates.X + (cam.lookat.X * t)
z = 0
Dim endline As New Vector3(mouse_world_coordinates.X - x, mouse_world_coordinates.Y - y, mouse_world_coordinates.Z - z)
'now simply find the two angles between these two lines
'cos(theida) = ((Ai,Ak) ⋅ (Bi,Bk)) / (||Ai,Ak|| * ||Bi,Bk||)
Try
theida = Acos(((startline.X * endline.X) + (startline.Z * endline.Z)) / (Sqrt(startline.X ^ 2 + startline.Z ^ 2) * Sqrt(endline.X ^ 2 + endline.Z ^ 2)))
Catch
theida = 0
End Try
Try
phi = Acos(((startline.X * endline.X) + (startline.Y * endline.Y)) / (Sqrt(startline.X ^ 2 + startline.Y ^ 2) * Sqrt(endline.X ^ 2 + endline.Y ^ 2)))
Catch
phi = 0
End Try
theida = theida * (180 / PI)
phi = phi * (180 / PI)
Any help or guidance is appreciated. Again I may be going at this with the wrong idea in the first place.
Related
Background
I think I have the basics down for creating my OpenGL environment. I can render simple objects, can handle lighting, etc. I am trying to create a test environment that I can move around in similar to the FPS game style. I want to be able to "walk" forward and backwards in the direction I am looking at.
How my other methods are done
I have developed a walk forward and backwards function but am having a bit of difficulty with rotating my camera. I have a few variables that seem to work. I have cam.lookat which is the point to which my camera looks at, cam.position which is the point my camera is at, and distance which is a calculation between the two (which in my case I keep constant).
So I may be going at this completely wrong but here is what I have done so far:
My setupviewport sub that is called on initial load:
Private Sub SetupViewport()
Dim w As Integer = GLcontrol1.Width
Dim h As Integer = GLcontrol1.Height
Dim perspective1 As Matrix4 = cam.GetViewMatrix() * Matrix4.CreatePerspectiveFieldOfView(1.3F, GLcontrol1.Width / CSng(GLcontrol1.Height), 0.1F, 2000.0F)
GL.MatrixMode(MatrixMode.Projection)
GL.LoadIdentity()
GL.Ortho(0, w, h, 0, -1, 1)
GL.LoadMatrix(perspective1)
GL.MatrixMode(MatrixMode.Modelview)
GL.LoadIdentity()
GL.Viewport(0, 0, w, h)
GL.Enable(EnableCap.DepthTest)
GL.DepthFunc(DepthFunction.Less)
End Sub
Here is my Paint event that is called per frame change:
Private Sub GlControl1_Paint(ByVal sender As System.Object, ByVal e As System.Windows.Forms.PaintEventArgs)
GL.Clear(ClearBufferMask.ColorBufferBit)
GL.Clear(ClearBufferMask.DepthBufferBit)
GL.DepthMask(True)
GL.Enable(EnableCap.DepthTest)
GL.ClearDepth(1.0F)
GL.MatrixMode(MatrixMode.Modelview)
GL.LoadIdentity()
Dim lightColor0 As Single() = {light_intensity, light_intensity, light_intensity, 1.0F}
Dim lightPos0 As Single() = {cam.Position.X, cam.Position.Y, cam.Position.Z, 1.0F}
GL.Light(LightName.Light0, LightParameter.Diffuse, lightColor0)
GL.Light(LightName.Light0, LightParameter.Position, lightPos0)
GL.Enable(EnableCap.Light0)
Dim mat_specular As Single() = {1.0F, 1.0F, 1.0F, 1.0F}
Dim mat_shininess As Single() = {50.0F}
GL.Material(MaterialFace.Front, MaterialParameter.Specular, mat_specular)
GL.Material(MaterialFace.Front, MaterialParameter.Shininess, mat_shininess)
draw_extras()
GL.Flush()
GLcontrol1.SwapBuffers()
End Sub
The way I have constructed my "move forward and backwards" sub is as follows:
Private Sub move_forward_and_back(ByVal forward As Boolean, ByVal delta As Single)
'change the distance between camera and lookat
'distance = old distance
Dim curdistance As Single = Sqrt((cam.Position.X - cam.lookat.X) ^ 2 + (cam.Position.Y - cam.lookat.Y) ^ 2 + (cam.Position.Z - cam.lookat.Z) ^ 2)
'curdistance = distance
Dim deltadistance As Single = 0.1
Dim newdistance As Single = deltadistance
'The formula to use for the new point is Xnew = +/-((X2-X1)/Dold)*Dnew+X2
'This formula results with two possible points because of the +/-
'Both points are calculated and then evaluated
Dim newcamx As Single = 0
Dim newcamy As Single = 0
Dim newcamz As Single = 0
Dim newlookatx As Single = 0
Dim newlookaty As Single = 0
Dim newlookatz As Single = 0
Dim newx1 As Single = (((cam.Position.X - cam.lookat.X) / curdistance) * newdistance) + cam.Position.X
Dim newy1 As Single = (((cam.Position.Y - cam.lookat.Y) / curdistance) * newdistance) + cam.Position.Y
Dim newz1 As Single = (((cam.Position.Z - cam.lookat.Z) / curdistance) * newdistance) + cam.Position.Z
Dim newdistance1 As Single = Math.Sqrt((newx1 - cam.lookat.X) ^ 2 + (newy1 - cam.lookat.Y) ^ 2 + (newz1 - cam.lookat.Z) ^ 2)
Dim newx2 As Single = (-((cam.Position.X - cam.lookat.X) / curdistance) * newdistance) + cam.Position.X
Dim newy2 As Single = (-((cam.Position.Y - cam.lookat.Y) / curdistance) * newdistance) + cam.Position.Y
Dim newz2 As Single = (-((cam.Position.Z - cam.lookat.Z) / curdistance) * newdistance) + cam.Position.Z
Dim newdistance2 As Single = Math.Sqrt((newx2 - cam.lookat.X) ^ 2 + (newy2 - cam.lookat.Y) ^ 2 + (newz2 - cam.lookat.Z) ^ 2)
'The one with the greater distance is considered the one to use for "moving away"
'The one with the less distance is considered the one to use for "moving towards"
If forward = True Then 'use one with less distance
If newdistance1 > newdistance2 Then
newcamx = newx2
newcamy = newy2
newcamz = newz2
Else
newcamx = newx1
newcamy = newy1
newcamz = newz1
End If
Else 'use one with greater distance
If newdistance1 < newdistance2 Then
newcamx = newx2
newcamy = newy2
newcamz = newz2
Else
newcamx = newx1
newcamy = newy1
newcamz = newz1
End If
End If
'newcamx, newcamy, and newcamz are calculated for where the camera needs to move to
'need to move lookat the same distance in the same direction
If forward = True Then
newdistance = curdistance + delta
Else
newdistance = curdistance - delta
End If
newx1 = (((cam.Position.X - cam.lookat.X) / curdistance) * newdistance) + cam.Position.X
newy1 = (((cam.Position.Y - cam.lookat.Y) / curdistance) * newdistance) + cam.Position.Y
newz1 = (((cam.Position.Z - cam.lookat.Z) / curdistance) * newdistance) + cam.Position.Z
newdistance1 = Math.Sqrt((newx1 - cam.lookat.X) ^ 2 + (newy1 - cam.lookat.Y) ^ 2 + (newz1 - cam.lookat.Z) ^ 2)
newx2 = (-((cam.Position.X - cam.lookat.X) / curdistance) * newdistance) + cam.Position.X
newy2 = (-((cam.Position.Y - cam.lookat.Y) / curdistance) * newdistance) + cam.Position.Y
newz2 = (-((cam.Position.Z - cam.lookat.Z) / curdistance) * newdistance) + cam.Position.Z
newdistance2 = Math.Sqrt((newx2 - cam.lookat.X) ^ 2 + (newy2 - cam.lookat.Y) ^ 2 + (newz2 - cam.lookat.Z) ^ 2)
If forward = True Then 'we want the one that is smaller
If newdistance1 < newdistance2 Then
newlookatx = newx1
newlookaty = newy1
newlookatz = newz1
Else
newlookatx = newx2
newlookaty = newy2
newlookatz = newz2
End If
Else
If newdistance1 < newdistance2 Then
newlookatx = newx1
newlookaty = newy1
newlookatz = newz1
Else
newlookatx = newx2
newlookaty = newy2
newlookatz = newz2
End If
End If
'now simply assign values
cam.Position.X = newcamx
cam.Position.Y = newcamy
cam.Position.Z = newcamz
cam.lookat.X = newlookatx
cam.lookat.Y = newlookaty
cam.lookat.Z = newlookatz
newdistance = Sqrt((cam.Position.X - cam.lookat.X) ^ 2 + (cam.Position.Y - cam.lookat.Y) ^ 2 + (cam.Position.Z - cam.lookat.Z) ^ 2)
End Sub
I am just calculating the camera position points based on a constant distance and knowing the camera lookat. This is done as a calculation instead of matrices which hopefully is good practice.
Now all of this works fine so far but am providing it as background for how I am going about my environment
Problem
I am trying to create a rotate camera function based on two angles. It should keep the distance and cam.position constant while only changing the cam.lookat point. What I have so far:
Private Sub rotate_camera(ByVal deltaangle1 As Single, ByVal deltaangle2 As Single)
Dim curdistance As Single = Sqrt((cam.Position.X - cam.lookat.X) ^ 2 + (cam.Position.Y - cam.lookat.Y) ^ 2 + (cam.Position.Z - cam.lookat.Z) ^ 2)
angle1 += deltaangle1
angle2 += deltaangle2
If angle1 >= 360 Then
angle1 = angle1 - 360
End If
If angle2 >= 360 Then
angle2 = angle2 - 360
End If
If angle1 < 0 Then
angle1 = angle1 + 360
End If
If angle2 < 0 Then
angle2 = angle2 + 360
End If
deltax = distance * Sin(deltaangle1 * (PI / 180)) * Cos(deltaangle2 * (PI / 180))
deltay = distance * Sin(deltaangle1 * (PI / 180)) * Sin(deltaangle2 * (PI / 180))
deltaz = distance * Cos(deltaangle1 * (PI / 180))
deltaz = Sqrt((distance ^ 2) - (deltax ^ 2) - (deltay) ^ 2) * zsign
'now simply assign values
cam.lookat.X = cam.lookat.X + deltax
cam.lookat.Y = cam.lookat.Y + deltay
cam.lookat.Z = cam.lookat.Z + deltaz
'distance = Sqrt((cam.Position.X - cam.lookat.X) ^ 2 + (cam.Position.Y - cam.lookat.Y) ^ 2 + (cam.Position.Z - cam.lookat.Z) ^ 2)
End Sub
So I have three problems:
My deltay is always positive. My formulas do not have direction How do I include in my formulas whether or not the delta should be positive or negative?
On similar lines, there appears to be a reflection point at 180 degrees where the direction of rotation reverses. How should I take into account this reflection?
On a slightly different note, my camera appears to have its positive X axis in line with the negative world X axis. How do I fix this? How can I have maintain my cam.position and cam.lookat, while rotating the camera?
I know there is a lot to read here but I do hope the questions are basic. I provided enough detail as I am open to adjusting other methods if I am totally off in left field.
Update per questions and answer below
So thank you for all of the help so far! I am mostly there I think. I still have this line which uses the final matrix:
Dim perspective1 As Matrix4 = cam.GetViewMatrix() * Matrix4.CreatePerspectiveFieldOfView(1.3F, GLcontrol1.Width / CSng(GLcontrol1.Height), 0.1F, 2000.0F)
but I have changed the cam.getviewmatrix function using advice to this:
Public Function GetViewMatrix() As Matrix4
Dim myforwardvector As Vector3
Dim rotational_matrix_y As Matrix3
Dim rotational_matrix_x As Matrix3
Dim rotational_matrix_z As Matrix3
Dim upvector As Vector3
If invert_z = False Then
upvector = Vector3.UnitZ
Else
upvector = -Vector3.UnitZ
End If
rotational_matrix_x.M11 = 1
rotational_matrix_x.M12 = 0
rotational_matrix_x.M13 = 0
rotational_matrix_x.M21 = 0
rotational_matrix_x.M22 = Cos(theida * (PI / 180))
rotational_matrix_x.M23 = -Sin(theida * (PI / 180))
rotational_matrix_x.M31 = 0
rotational_matrix_x.M32 = Sin(theida * (PI / 180))
rotational_matrix_x.M33 = Cos(theida * (PI / 180))
rotational_matrix_y.M11 = Cos(theida * (PI / 180))
rotational_matrix_y.M12 = 0
rotational_matrix_y.M13 = Sin(theida * (PI / 180))
rotational_matrix_y.M21 = 0
rotational_matrix_y.M22 = 1
rotational_matrix_y.M23 = 0
rotational_matrix_y.M31 = -Sin(theida * (PI / 180))
rotational_matrix_y.M32 = 0
rotational_matrix_y.M33 = Cos(theida * (PI / 180))
rotational_matrix_z.M11 = Cos(theida * (PI / 180))
rotational_matrix_z.M12 = -Sin(theida * (PI / 180))
rotational_matrix_z.M13 = 0
rotational_matrix_z.M21 = Sin(theida * (PI / 180))
rotational_matrix_z.M22 = Cos(theida * (PI / 180))
rotational_matrix_z.M23 = 0
rotational_matrix_z.M31 = 0
rotational_matrix_z.M32 = 0
rotational_matrix_z.M33 = 1
Dim rotational_matrix As Matrix3
rotational_matrix = Matrix3.Mult(Matrix3.Mult(rotational_matrix_x, rotational_matrix_y), rotational_matrix_z)
myforwardvector = multiply_matrix3_by_vector3(rotational_matrix, myforwardvector)
lookat = multiply_vector3_by_scalar(myforwardvector, distance)
Return Matrix4.LookAt(Position, lookat, upvector)
End Function
The loading of the environment works but nothing changes in my environment when I change theida. I do call my SetupViewPort to refresh the matrix and repaint it like normal. Am I missing something in my matrix creation?
This is what happens currently when holding down the button that increases my yaw only (just changing the X matrix). Keep in mind that one sphere is located at (0,0,0):
Improvement based on second answer
My new ViewMatrix function is as follows:
Dim view_matrix As Matrix4 = Matrix4.LookAt(Position, lookat, up_vector)
'transform to x axis first
view_matrix = view_matrix * Get_Transform_Matrix(-Position.X, 0, 0)
'rotate around x axis
view_matrix = view_matrix * Get_Rotational_Matrix("x", yaw)
'trnsform back
view_matrix = view_matrix * Get_Transform_Matrix(Position.X, 0, 0)
'transform to y axis first
view_matrix = view_matrix * Get_Transform_Matrix(0, -Position.Y, 0)
'rotate around y axis
view_matrix = view_matrix * Get_Rotational_Matrix("y", pitch)
'trnsform back
view_matrix = view_matrix * Get_Transform_Matrix(Position.Y, 0, 0)
'transform to z axis first
view_matrix = view_matrix * Get_Transform_Matrix(0, -Position.Z, 0)
'rotate around z axis
view_matrix = view_matrix * Get_Rotational_Matrix("z", roll)
'trnsform back
view_matrix = view_matrix * Get_Transform_Matrix(Position.Z, 0, 0)
Return view_matrix
My Get_Rotational_Matrix function that retrieves the correct matrix to use for the given rotation:
Public Function Get_Rotational_Matrix(ByVal matrix_name As String, ByVal angle As Single) As OpenTK.Matrix4
'yaw = x, pitch = y, z = roll
Dim rotational_matrix_ As Matrix4
Select Case matrix_name
Case "x"
rotational_matrix_.M11 = 1
rotational_matrix_.M12 = 0
rotational_matrix_.M13 = 0
rotational_matrix_.M14 = 0
rotational_matrix_.M21 = 0
rotational_matrix_.M22 = Cos(angle * (PI / 180))
rotational_matrix_.M23 = -Sin(angle * (PI / 180))
rotational_matrix_.M24 = 0
rotational_matrix_.M31 = 0
rotational_matrix_.M32 = Sin(angle * (PI / 180))
rotational_matrix_.M33 = Cos(angle * (PI / 180))
rotational_matrix_.M34 = 0
rotational_matrix_.M41 = 0
rotational_matrix_.M42 = 0
rotational_matrix_.M43 = 0
rotational_matrix_.M44 = 1
Case "y"
rotational_matrix_.M11 = Cos(angle * (PI / 180))
rotational_matrix_.M12 = 0
rotational_matrix_.M13 = Sin(angle * (PI / 180))
rotational_matrix_.M14 = 0
rotational_matrix_.M21 = 0
rotational_matrix_.M22 = 1
rotational_matrix_.M23 = 0
rotational_matrix_.M24 = 0
rotational_matrix_.M31 = -Sin(angle * (PI / 180))
rotational_matrix_.M32 = 0
rotational_matrix_.M33 = Cos(angle * (PI / 180))
rotational_matrix_.M34 = 0
rotational_matrix_.M41 = 0
rotational_matrix_.M42 = 0
rotational_matrix_.M43 = 0
rotational_matrix_.M44 = 1
Case "z"
rotational_matrix_.M11 = Cos(angle * (PI / 180))
rotational_matrix_.M12 = -Sin(angle * (PI / 180))
rotational_matrix_.M13 = 0
rotational_matrix_.M14 = 0
rotational_matrix_.M21 = Sin(angle * (PI / 180))
rotational_matrix_.M22 = Cos(angle * (PI / 180))
rotational_matrix_.M23 = 0
rotational_matrix_.M24 = 0
rotational_matrix_.M31 = 0
rotational_matrix_.M32 = 0
rotational_matrix_.M33 = 1
rotational_matrix_.M34 = 0
rotational_matrix_.M41 = 0
rotational_matrix_.M42 = 0
rotational_matrix_.M43 = 0
rotational_matrix_.M44 = 1
End Select
'
Return rotational_matrix_
End Function
This seems to work great! Last question, how do I apply the rotations to the up_vector so I can keep track of it? Notice how my new method never changes that.
I think you should have a private member where you store your forward vector. The direction pointing in front of your camera. At start you should look in the z direction so for example it could be myForwardvector = vec3(0,0,1).
You also should store the distance to the point you are aiming.
So the lookAt position should become something like:
vec3 position = myForwardVector * radius;
Now when when you want to rotate your forward vector depending on your input, your mouse etc ...
you can use spherical coordinates
where theta is the angle when you rotate left/right (yaw) and Phi top/bottom (pitch). Roll is not used in a fps.
If you want to use matrice you can start by a litlle example and just implementing the yaw movement.
this is what thoose matrices look like:
Use the one from the middle and replace theta by your yaw angle.
Now each time you made a movement you change your forwardVector:
myForwardVector = Rotationmatrice * myForwardVector;
myLookAtMatrice = glm::lookat(myCameraPos, myFowardVector * radius, upVector)
Hope it helps,
note that you will update your upVector using the same method.
#DraykoonD answer is good. But It seems you're a bit confused with geometry. So, here are some explanations:
If your objects don't move, you just have to steps to go: Camera and Projection.
Camera
Transforming the camera means translating and/or rotating it. Both movements are usually expressed by an only matrix, usually called "lookAt" matrix.
If you work with matrices, then C= R*T is the operation (lookAt C = Rotation * Translation, in this order, not T*R)
Translation
While T is pretty simple, perhaps you want to replace matrices operation with some code on your own. Something like:
camera.x = camera.x + deltaX
camera.y = camera.x + deltaY
camera.z = camera.x + deltaZ
Note that the deltas are not the necessary the same, as the user may whish to move in 1/2/3 axis at once.
If, instead of the above, you want to move the camera by stepSizein the current direction (that it aims to) then get a unit-vector in that direction and calculate deltas and use them as before:
vx = target.x - camera.x
vy = target.y - camera.y
vz = target.x - camera.z
modulus = Sqrt(vx*vx + vy*vy + vz*vz)
' components of the unit vector
dx = vx / modulus
dy = vy / modulus
dz = vz / modulus
' stepSize is a signed value, forward or backwards
deltaX = dx * stepSize
deltaY = dy * stepSize
deltaZ = dz * stepSize
Rotation
Fist of all: Things rotate by an angle around an axis, not around a point.
Read the last sentence again. It means that you can not simply give two angles and get the rotation, you also need the order in which those two rotations occurs, because the result is not the same if you change the order.
Now read it once more. It means you need an axis for each rotation.
What confuses people is that rotation needs an origin. Think of the nail of a compass you use to draw an arc.
So: an axis, an angle, and an origin.
OK. Let's explain rotations in a FPS-game-like. We will use for the origin of the rotation the camera position. Again, don't call this "rotating around a point".
Let's say you have your axis X+ to right, Y+ deep into the screen, Z+ up. This is a right-handled system: if you calculate the cross product of two unit vectors aligned with the axis you get the third unit-vector. Cross product, as matrices multiplication, is not commutative; so pay attention to the order, or you'll get a vector in the opposite direction.
This axis system is easy to understand if you use it as the world coordinates system.
A left-handled system is the same, but with some sign changed, for example Z= Y x X, instead of Z= X x Y
If your first rotation is over Z axis then, applying the Rz matrix shown in the Wikipedia Rotation to the current X-vector and Y-vector we get the new X',Y' vectors, Z' is the same as Z. If in the first place the camera target was (0, d, 0) then post-multiply it by Rz to get the new target (tx, ty, 0)
"Pre" or "post" matrix multiplication depends on if the matrix is column or row mayor order. Typically in OpenGL and many graphics APIs column mayor order is used, which is the same as that Wikipedia link.
Now suppose the user wants a down-up rotation. The axis needed is the X' we obtained before, after Z-axis rotation. This is a "local" axis. If we apply the Rx matrix we get a local rotation. Right. But what we need is a "global" direction, so as the objects are located well.
Here's when matrix stuff shines: just apply R2= Rx*Rz instead of R2= Rx. If you apply R2 to (0, d, 0) you get the new2 target (t2x, t2y, t2z)
It's so simple as "stacking" the transformations: Cn= Cn-1 * ... *C5*C4*C3*C2*C1 where Ck is the column-mayor-order matrix for the 'k' transformation (translation or rotation or whatever). Just be aware of the order; row-mayor-order reverses it.
Once you have your first "camera matrix" the following matrices are just a matter of stacking.
Note: translations can not be represented by a 3x3 matrix. But a 4x4 does. Using 4x4 matrices allows stacking any transformation.
Now the user wants a Z-axis (right-left) rotation again. Mmmm, let me see... When we do a rotation the whole axis-system changes. That means that now, after those two previous rotations, the Z-local-axis, where the "up-camera" vector is, is not the same as the Z-global. But the user does want a Z-global rotation, not a local one (user wants X-local but Z-global)
We've seen before that each Ck matrix represents a local transformation. What we need now is the Z-global axis expressed in local system. That is: Zv= R2 * (0,0,1)
OK, but I only know of Rx,Ry,Rz matrices and now Z-global is not the same as none of my local axis. True. We need the matrix rotation around any axis. This link shows it.
All right. But... how is the "first camera matrix" built? This lookAt link shows it. The parameters needed (camera position, target and up-camera) depends on what you initially want to "see".
If you are curious about the deduction of the matrix, I tell you that a transform matrix row is the new axis vector expressed in old axis vectors. And that you can get vectors using the cross product and normalizing them.
Good. Instead of playing with stacked matrices (I know they degenerate after many transformations due to numerical issues) I want to get "camera.position", "camera.target" and "camera.up" (important: all of them in global coordinates) and use that lookAtmatrix on my own, for every camera movement. Then you must calculate those three vectors every time.
The translation is showed at the beggining of this answer. You must translate both camera.position and camera.target. camera.up is a vector, not a point; thus, translating a vector has no sense.
For rotations recall we use the position of the camera as the origin of any rotation. That means that before rotating the target transform it's global coordinates to the origin, then rotate, then undo the translation, Something like this:
target.x = target.x - camera.position.x
target.y = target.y - camera.position.y
target.z = target.z - camera.position.z
target = RotateGeneric(axis, angle, target)
target.x = target.x + camera.position.x
target.y = target.y + camera.position.y
target.z = target.z + camera.position.z
I think it's good you have a RotateGeneric function that uses the matrix in the link I wrote some lines ago.
To rotate the up-camera vector you can use the same function (without translations, it's a vector, right?)
You are doing all calculations on global system. Z-axis rotation is simple. But X-local-axis, at any moment... how do we get it? Well, there are two options:
Keep track of its current (x,y,z) direction. In other words, rotate
it also as you do for up-camera.
Use matrix magic again. Every
rotation can be undone. The matrix needed is the inverse of the
current matrix, IC= C-1. So if you have the X-local xv=(1,0,0)
and want its components in global coordinates then xg= IC * xv. Of course, you need to update and store the current C matrix.
If you also rotate around Y-axis, the process is the same as for X-axis.
Projection
Using matrix stack, or lookAt matrix at every movement, what you get is an axis-system such as X and Y axis are aligned with OpenGL device axis-system.
Now two jobs left: projecting and dealing with Z-axis orientation.
As you correctly suspect, matrix is again here.
I will not write here about orthographic nor perspective matrix representations, that's not the subject of the question.
Just point out that those matrices have a -1 in a proper site that reverses the sign of Z, so OpenGL left-handed device system is happy. Well, that mostly always used value is not strictly needed; but if +1 is set, then you should change the depth compare function to tell OpenGL to use a right-handed system instead.
Stack the projection matrix "after" the camera matrix M= P*C and that's all.
I found a great script to arrange objects (shapes) into a circle here:
Aligning Shapes in a Circle using VBA, Microsoft Community
Sub Test()
Call AlignShapesInCircle(720 / 2, 540 / 2, 100, ActiveWindow.Selection.ShapeRange)
End Sub
Function AlignShapesInCircle(x As Single, y As Single, r As Single, shprng As ShapeRange)
'x,y = center point of the circle
'r = radius of the circle
'shprng = the shape selection that needs to be arranged
Dim angle As Single
Dim currentangle As Single
Dim x1 As Single
Dim y1 As Single
Dim i As Integer
currentangle = 0
angle = 360 / shprng.count
For currentangle = 0 To 359 Step angle
i = i + 1
x1 = r * Cos(D2R(currentangle))
y1 = r * Sin(D2R(currentangle))
shprng(i).Left = x + x1
shprng(i).Top = y + y1
Next
End Function
Function D2R(Degrees) As Double
D2R = Degrees / 57.2957795130823
End Function
Function R2D(Radians) As Double
R2D = 57.2957795130823 * Radians
End Function
Now I want the shapes to rotate so that if I use arrows the tip will always show towards the center.
I have to introduce a line here:
shprng(i).Left = x + x1
shprng(i).Top = y + y1
shprng(i).Rotation = ???
Any ideas where I could find the proper formula?
Silly - figured it out - it was easier than I thought. Don't need any SIN and COS which frightened me - just:
shprng(i).Rotation = (360 / (shprng.Count)) * (i - 1)
I've drawn a line, using an X1, X2, Y1, Y2 variable. I'm looking to find any number of points along a this line, and to that extent I've written this:
Private Function genPointsArray(ByRef xPointArray() As Integer, ByRef yPointArray() As Integer, startX As Integer, finX As Integer, startY As Integer, finY As Integer, numPoints As Integer) As Integer
ReDim xPointArray(numPoints)
ReDim yPointArray(numPoints)
xPointArray(0) = startX
xPointArray(numPoints - 1) = finX
yPointArray(0) = startY
yPointArray(numPoints - 1) = finY
For i = 1 To numPoints - 2
xPointArray(i) = xPointArray(i - 1) + (finX - startX) \ numPoints
yPointArray(i) = yPointArray(i - 1) + (finY - startY) \ numPoints
Next
Return 0
End Function
As you can see, it accepts the X1, X2, Y1, Y2 variables (startX etc), two arrays to store the resultant points, and a number of points to find. The issue it has is that (worryingly often) a point is a pixel off (due to the actual result being a decimal). This then gets progressively worse as every following point is 2,3,4,5 etc pixels off, making the effect quite noticeable. Does anyone know of a way to make sure every point is along the line- either through a better algorithm or validation?
Thanks
The reason why your generated points drift is because you're allowing the error of the integer rounding to accumulate. It's like walking with your eyes closed.
Instead of basing each point from the previous, keep going back to your original endpoints. Each point that results will be off at worst one due to rounding.
Replace
xPointArray(0) = startX
xPointArray(numPoints - 1) = finX
yPointArray(0) = startY
yPointArray(numPoints - 1) = finY
For i = 1 To numPoints - 2
xPointArray(i) = xPointArray(i - 1) + (finX - startX) \ numPoints
yPointArray(i) = yPointArray(i - 1) + (finY - startY) \ numPoints
Next
With just
For i = 0 To numPoints - 1
xPointArray(i) = startX + (finX - startX) * i / numPoints
yPointArray(i) = startX + (finX - startX) * i / numPoints
Next
Please forgive any syntax or loop bound errors; I don't write VBA
In terms of geometry a line is unidimensional, i.e. it doesn't have width and hence it's hard to know if a point is on a line without defining a criterion.
A common way to test if a point resides on a line is to compute the distance from that point to the line, and if such a distance is small enough (less than an epsilon value), then the point is considered to be on the line. I don't know VB but here is snippet that illustrates this idea:
boolean inLine(Point2D a, Point2D b, Point2D p)
{
double a1 = b.x() - a.x();
double b1 = b.y() - a.y();
double a2 = p.x() - a.x();
double b2 = p.y() - a.y();
double alpha = Math.atan2(b1, a1);
double beta = Math.atan2(b2, a2);
double theta = Math.abs(alpha - beta);
double dist = Math.abs(a.distanceTo(p) * Math.sin(theta));
double eps = Math.abs(fx(3) - fx(0));
return dist < eps;
}
This algorithm is known as Distance from a Point to a Line.
The epsilon value depends on your particular problem, how much precision is needed, for most applications 1e-9 will be OK.
The method distanceTo() in the code simply computes the Euclidean distance between two points.
A foolproof formula is ((N - I) * Start + I * Fin) / N. For I=0, gives you exactly Start, and for I=N, gives you exactly Fin. Intermediate values will be regularly aligned.
This works both in integer and floating-point coordinates, but mind overflows.
Given a rectangle of width w and height h. and a coordinate x,y in that rectangle I would like to identify which triangle I am within.
i.e. the function should take parameters(x,y) and return a,b,c,d or a zero based number representing that triangle index i.e. (0=A,1=B,2=C,3=D) if they are in that order.
I think this would be something like >= the formula of the red line and >= the formula of the green line?
I'd like to implement this in VB.NET
aboveRed = x*h > y*w;
aboveGreen = (w-x)*h > y*w;
if (aboveRed)
{
if (aboveGreen) return "C"; else return "B";
}
else
{
if (aboveGreen) return "D"; else return "A";
}
Equation of green line: h * x + w * y = h * w
Equation of red line: x * h - y * w = 0
Public Function GetTriangleNumber(ByVal x As Integer, ByVal y As Integer)
As Integer
Dim overGreenLine As Boolean = ((((h * x) + (w * y)) - (h * w)) < 0)
Dim overRedLine As Boolean = (((h * x) - (w * y)) > 0)
If overGreenLine Then
Return IIf(overRedLine, 2, 3)
End If
Return IIf(overRedLine, 1, 0)
End Function
I would consider the angle of the line to the point from the top left and top right corners. If it is less than 45 degrees (adjusting for the base direction of the edge) in both cases then the point is in C. Other combinations will cover the other three triangles.
You don't actually need to calculate inverse trig functions to do this, as the ratio of the lengths of the lines gives you enough information (and sin(45)... or rather sin(pi/4) is a fixed value).
Given the image below
1. I have updated the image based on feedback from Steven_W so there are 4 triangles in the right hand diagram instead of 3 which makes more sense!
2. update image again to label sub triangles as A, B, C, D in grey
What is the pseudo algorithm for mapping a coordinate (x,y) in the left hand square such that a coordinate (u,v) is produced within the rectangle bounding the triangle on the right so that points are interpolated between the mapping points as illustrated on the diagram?
1 to 4 are equidistant on the triangle from left to right even though my illustration is a bit rough around the edges :)
This is to generate a rough and ready panel for the lid of a skybox from the top half a 360 degree panoramic photo.
update 3 based on feedback
The first step appears to be working out which triangle we are in for the left hand diagram based on the (x,y) coordinates.
The second steep is to work out the distance along the vertices of that triangle. Then use those distances to get the coordinates on the related triangle in the diagram on the right
update 4 - code to identify triangle in left hand diagram
Public Function TriangleIndex(ByVal x As Integer, ByVal y As Integer, ByVal w as integer, ByVal h as integer) as integer
Dim AboveForwardSlashDiagonal As Boolean = ((((h * x) + (w * y)) - (h * w)) < 0)
Dim AboveBackSlashDiagonal As Boolean = (((h * x) - (w * y)) > 0)
If AboveForwardSlashDiagonal Then
If AboveBackSlashDiagonal
return 2 ' C
else
return 3 ' D
end if
else
If AboveBackSlashDiagonal
return 1 ' B
else
return 0 ' A
end if
End If
End Function
update 5 - template for code solution
w1 and h1 are dimensions of left diagram
w2 and h2 are dimensions of right diagram
Private Function TranslateToTriangle(ByVal x1 As Integer, ByVal y1 As Integer, ByVal w1 As Integer, ByVal h1 As Integer, ByVal w2 As Integer, ByVal h2 As Integer) As System.Drawing.Point
Dim ReturnPoint As New System.Drawing.Point
select case TriangleIndex(x1,y1,w1,h1)
case 0
case 1
case 2
case 3
end select
Return ReturnPoint
End Function
update 6 formula for area of triangle given it's lengths - which might be helpful in calculating barycentric weights?
Private Function AreaOfTriangle(ByVal LengthA As Single, ByVal LengthB As Single, ByVal LengthC As Single) As Single
Dim Perimeter As Single = LengthA + LengthB + LengthC
Return 1 / 4 * Math.Sqrt(Perimeter * (Perimeter - 2 * LengthA) * (Perimeter - 2 * LengthB) * (Perimeter - 2 * LengthC))
End Function
Well, your comment to #Steven_W's answer makes the answer to your question clearer. You actually want to map points in the 4 triangles (125, 235, 435, 415) in your square box to the corresponding 4 triangles in your other square box (125, 235, 435, 415). Oh, you don't have triangle 415 in your second box, perhaps you should or maybe not.
So now you have to map points from triangle to triangle which should be easy. As coordinates for each point in your 'start' triangle use its position relative to all 3 vertices, then use the same position relative to the 3 vertices of the 'destination' triangle. You could probably get away with using just 2 of the triangle vertices for coordinates.
HTH
The Wikipedia entry trilinear coordinates explains the maths well enough I think.
Let's consider first the case of the triangle a.
Assuming that your origin is at point 5, the coordinates of points 1 and 2 are (-x0, y0) and (x0, y0), we should have the following.
The mapping from the old coordinates (x, y) into new (xnew, ynew) must be linear. This means, we've got the following formulae with still undefined coefficients:
xnew = A*x + B*y + C
ynew = D*x + E*y + F
How can we determine the coefficients? We've got three pairs of values: (-x0, y0) -> (-x0, y0), (x0, y0) -> (-x0/2, y0) and (0, 0) -> (0, -y0). This gives us the following:
-x0 = -A*x0 + B*y0 + C (1) -x0/2 = A*x0 + B*y0 + C (3)
y0 = -D*x0 + E*y0 + F (2) y0 = D*x0 + E*y0 + F (4)
0 = A*0 + B*0 + C (5)
-y0 = D*0 + E*0 + F (6)
Good so far. (5) gives us C = 0, (6) gives F = -y0. Adding (2) and (4) we get 2*y0 = 2*E*y0 + 2*(-y0), hence E = 2. Subtracting (2) and (4) we get 0 = 2*D*x0, hence D = 0. Adding (1) and (2) and taking into account that C = 0, we get -(3/4)*x0 = 2*B*y0, hence B = -3/4*x0/y0. Lastly, subtracting (1) and (2) we get x0/2 = 2*A*x0, hence A = 1/4.
Now, we can write down the needed mapping:
xnew = 0.25*x - 0.75*(x0/y0)*y
ynew = 2*y - y0
The same way, for triangle c we obtain:
xnew = -0.25*x - 0.25*(x0/y0)*y
ynew = -2*y - y0
It is not really clear why certain points map to their counterpart on the triangle.
For example, where should a point map to that is equidistant between '1' and '4' ?
Or, a point just "above" number 5 ?