OpenGL Camera almost done

BlitzMax Forums/BlitzMax OpenGL Programming/OpenGL Camera almost done

Finally got a decent camera ported over. The only real problem I'm having is the orientation. I set the initial camera y position to 5 and it puts it at -5 in the world.

Can you guys check this out and see why the Y axis is inverted. Btw, the vector.bmx is below in it's own codebox.

The problem may lie here:
cam.ViewDir.Set(0.0, 0.0, 1.0)
cam.UpVector.Set(0.0, 1.0, 0.0)
cam.RightVector.Set(1.0, 0.0, 0.0)

The z component of ViewDir was originally set to -1 and that made positioning the camera on the z axis be in reverse. Soon as I get the bugs worked out I'll put this in the archives.

Controls: Arrow Keys to move and strafe - Mouse to look around.

Camera.bmx
'Advanced Camera
'converted by Chroma from a site I can't remember atm cuz my brain is fried now...

Global width = 1024
Global height = 768

GLGraphics(width,height,32,85,GRAPHICS_BACKBUFFER|GRAPHICS_DEPTHBUFFER)
'glClear(GL_COLOR_BUFFER_BIT)
'glLoadIdentity();

Include "vector.bmx"

Const PIdiv180:Double = (Pi/180.0)

ReShape(width,height)


CenterX = GraphicsWidth()/2
CenterY = GraphicsHeight()/2


'Init Camera
Global cam:Camera = Camera.Create()
	cam.Position.Set(0.0, 0.0, 0.0)
	cam.ViewDir.Set(0.0, 0.0, -1.0)
	cam.UpVector.Set(0.0, 1.0, 0.0)
	cam.RightVector.Set(1.0, 0.0, 0.0)
	cam.RotatedX = 0.0
	cam.RotatedY = 0.0
	cam.RotatedZ = 0.0

MoveMouse CenterX,CenterY

cam.position.set(0,5,20)

'Main Loop
While Not KeyHit(KEY_ESCAPE)
glClear(GL_COLOR_BUFFER_BIT)
glLoadIdentity();

'Keyboard Controls
If KeyDown(KEY_UP) Then cam.moveforward(-0.1)
If KeyDown(KEY_DOWN) Then cam.moveforward(0.1)
If KeyDown(KEY_LEFT) Then cam.straferight(-0.1)
If KeyDown(KEY_RIGHT) Then cam.straferight(0.1)

'Mouselook Controls
cam.rotatex( -(MouseY()-CenterY) * 3.0 )
cam.rotatey( -(MouseX()-CenterX) * 3.0 )
cam.rotatez(cam.RotatedZ)



'Render Camera
cam.render()

'Render the DisplayList
Display()

'Reset Mouse
MoveMouse CenterX,CenterY

Flip
Wend

'Draw Some Cool Stuff To Look At
Function Display()
	glTranslatef(0.0,-0.5,-6.0)

	glScalef(3.0,1.0,3.0)
	
	 size! = 2.0
	 LinesX = 30
	 LinesZ = 30

	halfsize! = size / 2.0
	glColor3f(1.0,1.0,1.0)
	glPushMatrix()
		glTranslatef(0.0,-halfsize ,0.0)
		DrawNet(size,LinesX,LinesZ)
		glTranslatef(0.0,size,0.0)
		DrawNet(size,LinesX,LinesZ)
	glPopMatrix()
	glColor3f(0.0,0.0,1.0)
	glPushMatrix()
		glTranslatef(-halfsize,0.0,0.0);
		glRotatef(90.0,0.0,0.0,halfsize)
		DrawNet(size,LinesX,LinesZ)
		glTranslatef(0.0,-size,0.0)
		DrawNet(size,LinesX,LinesZ)
	glPopMatrix()
	glColor3f(1.0,0.0,0.0)
	glPushMatrix()
		glTranslatef(0.0,0.0,-halfsize)	
		glRotatef(90.0,halfsize,0.0,0.0)
		DrawNet(size,LinesX,LinesZ)
		glTranslatef(0.0,size,0.0)
		DrawNet(size,LinesX,LinesZ)
	glPopMatrix()
	glFlush()
EndFunction


Function DrawNet(size:Float, LinesX:Int, LinesZ:Int)
	glBegin(GL_LINES)
	For xc = 0 To LinesX-1 
	
		glVertex3f(	-size / 2.0 + xc / Float(LinesX-1)*size,0.0,size / 2.0)
		glVertex3f(	-size / 2.0 + xc / Float(LinesX-1)*size,0.0,size / -2.0)
	Next
	For zc = 0 To LinesX-1
	
		glVertex3f(	size / 2.0,0.0,-size / 2.0 + zc / Float(LinesZ-1)*size)
		glVertex3f(	size / -2.0,0.0,-size / 2.0 + zc / Float(LinesZ-1)*size);
	Next
		glEnd()
End Function


Function ReShape(x:Int, y:Int)
	If y = 0 And x = 0 Return
	glMatrixMode(GL_PROJECTION)  
	glLoadIdentity()
	gluPerspective(45.0,x/y,0.5,2000.0)
	glMatrixMode(GL_MODELVIEW)
	glViewport(0,0,x,y)
End Function


Type Camera
	Field Position:Vector
	Field ViewDir:Vector
	Field UpVector:Vector
	Field RightVector:Vector
	Field RotatedX:Float
	Field RotatedY:Float
	Field RotatedZ:Float
	
	Function Create:Camera( xpos:Float=0, ypos:Float=0, zpos:Float=0 )
		Local c:Camera = New Camera
		c.Position:Vector = Vector.Create(xpos,ypos,zpos)
		c.ViewDir:Vector = Vector.Create()
		c.UpVector:Vector = Vector.Create()
		c.RightVector:Vector = Vector.Create()
		Return c
	End Function

	Method Render()
		Local ViewPoint:Vector = New Vector
		ViewPoint.Add( Position, ViewDir ) 
		gluLookAt( Position.x,Position.y,Position.z,ViewPoint.x,ViewPoint.y,ViewPoint.z,UpVector.x,UpVector.y,UpVector.z)
	End Method
	
	
'	Method Rotate(p:Float,y:Float,r:Float)
'	End Method
	
'	Method Turn(pr:Float,yr:Float,rr:Float)
'	End Method
	
'	Method Move(xd:Float,yd:Float,zd:Float)
'	End Method

'	Method Point(entity:Int)
'	End Method
	
'	Method AlignTo(x:Float,y:Float,z:Float)
'	End Method

'	Method Zoom(zm:Float)
'	End Method

	Method RotateX( Angle:Float )
		self.RotatedX :+ Angle
		Local temp1:Vector = Vector.Create( ViewDir.x, ViewDir.y, ViewDir.z )
		Local temp2:Vector = Vector.Create( UpVector.x, UpVector.y, UpVector.z )
		Local temp3:Vector = Vector.Create()
		temp1.MulScalar( Cos(Angle * PIdiv180) )
		temp2.MulScalar( Sin(Angle * PIdiv180) )
		temp3.Add(temp1,temp2)
		temp3.Normalize()
		self.ViewDir.Set( temp3.x, temp3.y, temp3.z )
		UpVector.CrossProduct(ViewDir,RightVector)
		UpVector.MulScalar(-1)
	End Method
	
	Method RotateY( Angle:Float )
		self.RotatedY :+ Angle
		Local temp4:Vector = Vector.Create( ViewDir.x, ViewDir.y, ViewDir.z )
		Local temp5:Vector = Vector.Create( RightVector.x, RightVector.y, RightVector.z )
		Local temp6:Vector = Vector.Create()
		temp4.MulScalar( Cos(Angle * PIdiv180) )
		temp5.MulScalar( Sin(Angle * PIdiv180) )
		temp6.Sub(temp4,temp5)
		temp6.Normalize()
		self.ViewDir.Set( temp6.x, temp6.y, temp6.z )
		RightVector.CrossProduct(ViewDir,UpVector)
	End Method

	Method RotateZ( Angle:Float )
		self.RotatedZ :+ Angle
		Local temp7:Vector = Vector.Create( RightVector.x, RightVector.y, RightVector.z )
		Local temp8:Vector = Vector.Create( UpVector.x, UpVector.y, UpVector.z )
		Local temp9:Vector = Vector.Create()
		temp7.MulScalar( Cos(Angle * PIdiv180) )
		temp8.MulScalar( Sin(Angle * PIdiv180) )
		temp9.Add(temp7,temp8)
		temp9.Normalize()
		RightVector.set( temp9.x, temp9.y, temp9.z )
		UpVector.CrossProduct(ViewDir, RightVector)
		UpVector.MulScalar(-1)
	End Method

	Method MoveForward( Distance:Float )
		Local temp:Vector = Vector.Create(ViewDir.x,ViewDir.y,ViewDir.z)
		temp.MulScalar(-Distance)
		Position.Add( Position, temp )
	End Method

	Method StrafeRight( Distance:Float )
		Local temp:Vector = Vector.Create(RightVector.x,RightVector.y,RightVector.z)
		temp.MulScalar(Distance)
		Position.Add( Position, temp )
	End Method

	Method MoveUpward( Distance:Float )
		Position.Add( Position, UpVector.MulScalar(Distance) )
	End Method

End Type


Vector.bmx
'// Vector Math Library v2.0
'// BlitzMAX Edition
'// by Chroma


Type Vector
	Field x:Float
	Field y:Float
	Field z:Float
	
	Const Tol:Float = 0.001
	
	Function Create:Vector( x:Float = 0, y:Float = 0, z:Float = 0 )
		Local v:Vector = New Vector
		v.x = x
		v.y = y
		v.z = z
		Return v
	End Function
	
	Method Set( x:Float = 0, y:Float = 0, z:Float = 0 )
		self.x = x
		self.y = y
		self.z = z
	End Method
	
	Method Add( v1:Vector, v2:Vector )
		self.x = v1.x + v2.x
		self.y = v1.y + v2.y
		self.z = v1.z + v2.z
	End Method
	
	Method Sub( v1:Vector, v2:Vector )
		self.x = v1.x - v2.x
		self.y = v1.y - v2.y
		self.z = v1.z - v2.z
	End Method
	
	Method Mul( v1:Vector, v2:Vector )
		self.x = v1.x * v2.x
		self.y = v1.y * v2.y
		self.z = v1.z * v2.z
	End Method
	
	Method Div( v1:Vector, v2:Vector )
		self.x = v1.x / v2.x
		self.y = v1.y / v2.y
		self.z = v1.z / v2.z
	End Method
	
	Method AddScalar( s:Float )
		self.x :+ s
		self.y :+ s
		self.z :+ s
	End Method
	
	Method SubScalar( s:Float )
		self.x :- s
		self.y :- s
		self.z :- s
	End Method
	
	Method MulScalar( s:Float )
		self.x :* s
		self.y :* s
		self.z :* s
	End Method
	
	Method DivScalar( s:Float )
		self.x :/ s
		self.y :/ s
		self.z :/ s
	End Method
		
	Method Magnitude:Float()
		Return Sqr(self.x * self.x + self.y * self.y + self.z * self.z)
	End Method

	Method Normalize()
		Local mag:Float = self.Magnitude()
		If mag = 0.0 Then self.Set(0.0,0.0,0.0);Return
		self.x :/ mag
		self.y :/ mag
		self.z :/ mag
		'If ( Abs( self.x ) < Tol ) self.x = 0.0
		'If ( Abs( self.y ) < Tol ) self.y = 0.0
		'If ( Abs( self.z ) < Tol ) self.z = 0.0
	End Method
	
	Method CrossProduct( v1:Vector, v2:Vector)
		self.x =  v1.y * v2.z  -  v1.z * v2.y 
		self.y = -v1.x * v2.z  +  v1.z * v2.x 
		self.z =  v1.x * v2.y  -  v1.y * v2.x
	EndMethod
	
	Method DotProduct:Float(v:Vector)
		Return self.x * v.x + self.y * v.y + self.z * v.z
	EndMethod

	Method AddTimeStep( v:Vector, time_step:Float )
		self.x :+ v.x * time_step
		self.y :+ v.y * time_step
		self.z :+ v.z * time_step
	End Method
	
	Method Show( xpos:Int, ypos:Int, label:String )
		DrawText label+"_X: "+self.x, xpos, ypos
		DrawText label+"_Y: "+self.y, xpos, ypos + 20
		DrawText label+"_Z: "+self.z, xpos, ypos + 40
	End Method
	
'	Method QuatToVec(q:Quat)
'		self.x = q.x
'		self.y = q.y
'		self.z = q.z
'	End Method

End Type

'// Degrees To Radians Conversion
Function DegreesToRadians:Float(deg:Float)
	Return deg * Pi / 180.0
End Function

'// Radians To Degrees Conversion
Function RadiansToDegrees:Float(rad:Float)
	Return rad * 180.0 / Pi
End Function


Just found out that the z axis is backwards in opengl so what I had originally was right all along:

cam.Position.Set(0.0, 0.0, 0.0)
cam.ViewDir.Set(0.0, 0.0, -1.0)
cam.UpVector.Set(0.0, 1.0, 0.0)
cam.RightVector.Set(1.0, 0.0, 0.0)


Nice of you to say 'thank you.' -_-

Thanks for letting me know the z axis is backwards in opengl.

I've noticed that when you move the mouse in a circle you appear to be rotating on the z axis, but rotatedz is zero

Can anyone explain whats going on?

Sure, the cam.rotatez() command is basically like saying a=a+1. It's not actually setting the z rotation to 0 but just adding 0 to the existing value.

I'm trying to come up with a way to imitate the EntityRotate command but with no success so far. =( I don't really care to implement quats at this point so there still has to be a way to do it in it's current incarnation.

think you are misunderstanding me.
move the mouse in a circle a few times you get a z rotation rotateedz is still 0

Yep...I just saw that and was on my way back here to post it.

Something coming from x and y somehow...

The thing is...I know someone on here knows what's going on...it just takes longer these days for someone to see posts and reply because it seems the boards are less traveled these days. =\

:) I'm fairly sure on of the view vectors is getting messed up, I'm not too sure you can combine those vector rotations X then Y then Z like you're doing, I think you need to do them together in one XYZ calculation.

Hey hey, I believe I sent you some source code.

Yep you did and thanks much but I haven't taken a peek at it yet. I'll do that now and see if it helps.

I'm not too sure you can combine those vector rotations X then Y then Z like you're doing, I think you need to do them together in one XYZ calculation


If you could post a modification that'd be great. =)

I had a quick play, but the origional C code suffers the same "feature", I'll take a look at the later after some sleep...

found some code that uses quats (no nasty glulook at vectors :D ) to do local camera rotations

but that too has the same "feature" rotate the mouse
and you end up with a local z axis rotation too...

so I really dont understand whats going on if anyone could explain I'd be most greatful

heres the code for what its worth:
SuperStrict 
Type point
	Field x#,y#,z#,w#

	Method Sub:point( v1:point, v2:point )
		Local t:point=New point
		t.x = v1.x - v2.x
		t.y = v1.y - v2.y
		t.z = v1.z - v2.z
		Return t
	End Method
	
	Method add:Point(v1:point,v2:point)
		Local t:point=New point
		t.x=v1.x+v2.x
		t.y=v1.y+v2.y
		t.z=v1.z+v2.z
		Return t
	EndMethod
	
	Method getlength:Float()
		Return Float(Sqr( x*x + y*y + z*z ))
	End Method
	
	Method cross:point(p1:point,p2:point)
		Local t:point=New point
		t.x = ((p1.y * p2.z) - (p1.z * p2.y))
   		t.y = ((p1.z * p2.x) - (p1.x * p2.z))
   		t.z = ((p1.x * p2.y) - (p1.y * p2.x))
		Return t
	EndMethod
	
	Method Normal:point()
		Local length:Float = getlength()

		If length = 0.0 Then length = 1.0
		
		x = x/length
		y = y/length
		z = z/length
		w = w/length
	EndMethod
	
	Method mult:point(f:Float)
		x:*f ; y:*f ; z:*f
	EndMethod
	
	Method QuatFromAxis:point(a:point, phi:Float)
			Local t:point=New point
            Local sine:Float
            
            phi = phi * 0.5
            sine = Float(Sin(phi))
            
            a.Normal()

            t.x = a.x
			t.y = a.y
			t.z = a.z

            t.x:*sine
            t.y:*sine
            t.z:*sine
            t.w = Float(Cos(phi))
			Return t
	EndMethod
	
	Method quatadd:point(q:point)
		Local result:point=New point
		Local v1:point=New point,v2:point=New point,crossp:point=New point,v3:point=New point
		v1.x=x ; v1.y=y ; v1.z=z
		v2.x=q.x ; v2.y=q.y ; v2.z=q.z
		
		crossp=Cross(v2, v1)

		v1.mult(q.w)
		v2.mult(w)
            
        v3 = v3.add(v1,v2)
        v3 = v3.add(crossp,v3)

		v3.w = w * q.w - (x * q.x + y * q.y + z * q.z)
            
		result.x = v3.x; result.y = v3.y; result.z = v3.z; result.w = v3.w

        Return result;
	EndMethod
	
	Method CreateMatrix(pMatrix:Float[] Var)
'	         // Calculate the first row.
	         pMatrix[0]  = 1.0 - 2.0 * (y * y + z * z); 
	         pMatrix[1]  = 2.0 * (x * y - z * w);
	         pMatrix[2]  = 2.0 * (z * x + y * w);
	         pMatrix[3]  = 0.0  

'	         // Calculate the second row.
	         pMatrix[4]  = 2.0 * (x * y + z * w)  
	         pMatrix[5]  = 1.0 - 2.0 * (z * z + x * x) 
	         pMatrix[6]  = 2.0 * (y * z - x * w)  
	         pMatrix[7]  = 0.0  

'	         // Calculate the third row.
	         pMatrix[8]  = 2.0 * (z * x - y * w)
	         pMatrix[9]  = 2.0 * (y * z + x * w)
	         pMatrix[10] = 1.0 - 2.0 * (y * y + x * x)
	         pMatrix[11] = 0.0;  

'	         // Calculate the fourth row.
	         pMatrix[12] = 0  
	         pMatrix[13] = 0  
	         pMatrix[14] = 0  
	         pMatrix[15] = 1.0
	EndMethod
EndType

GLGraphics(640,480,0,0,GRAPHICS_BACKBUFFER|GRAPHICS_DEPTHBUFFER)

Global CenterX:Int = GraphicsWidth()/2
Global CenterY:Int = GraphicsHeight()/2

glMatrixMode(GL_PROJECTION)  
glLoadIdentity()
gluPerspective(45.0,GraphicsWidth()/GraphicsHeight(),0.5,1000.0)
glMatrixMode(GL_MODELVIEW)
glViewport(0,0,GraphicsWidth(),GraphicsHeight())
Global oldquat:Point=New point,newquat:point=New point
oldquat.w=1;newquat.w=1	
Global mx:Int,my:Int

	
While Not KeyHit(KEY_ESCAPE)
	glClear(GL_COLOR_BUFFER_BIT)
	
	mx=MouseX();my=MouseY()
	MoveMouse centerx,centery
	oldquat=calcrot((2.0 * centerx - centerx*2) / centerx,(2.0 * centery - centery*2) / centery,(2.0 * mx - centerx*2) / centerx,(2.0 * my - centery*2) / centery)
	newquat=oldquat.quatadd(newquat)
	
	glloadidentity()
	Local m:Float[16]
	newquat.CreateMatrix(m)
	glMultMatrixf(m)

	display()

	Flip

Wend

Const TRACKBALL_SIZE:Float=0.5

Function calcrot:point(x1:Float,y1:Float,x2:Float,y2:Float)
	Local axis:point=New point
	Local p1:point=New point, p2:point=New point, difference:point=New point
	Local phi:Float
	Local length:Float
	Local q:point=New point
	
	p1.x = x1
	p1.y = y1
	p1.z = ProjectPointToSphere(TRACKBALL_SIZE, x1, y1)
	p2.x = x2
	p2.y = y2
	p2.z = ProjectPointToSphere(TRACKBALL_SIZE, x2, y2)
	difference = difference.sub(p1,p2)
	length = difference.getlength() / (2.0 * TRACKBALL_SIZE)
	If length > 1.0 Then length = 1.0
	If length < -1.0 Then length = -1.0
	phi = 2.0 * Float(ASin(length))
	axis=axis.Cross(p2, p1)
	q=q.QuatFromAxis(axis, phi)
	Return q
	
EndFunction

Function ProjectPointToSphere:Float(radius:Float,x:Float,y:Float)
	Local z#,length#
	length = Float(Sqr(x * x + y * y))
	If length < radius * 0.70710678118654752440 Then 
		z = Float(Sqr(radius * radius - length * length))
	Else
		radius = radius / 1.41421356237309504880
        z = radius * radius / length
	EndIf
	Return z
EndFunction

'Draw Some Cool Stuff To Look At
Function Display()
'	glTranslatef(0.0,-0.5,-6.0)

	glScalef(3.0,1.0,3.0)
	
	Local size# = 2.0
	Local LinesX# = 30
	Local LinesZ# = 30

	Local halfsize# = size / 2.0
	glColor3f(1.0,1.0,1.0)
	glPushMatrix()
		glTranslatef(0.0,-halfsize ,0.0)
		DrawNet(size,LinesX,LinesZ)
		glTranslatef(0.0,size,0.0)
		DrawNet(size,LinesX,LinesZ)
	glPopMatrix()
	glColor3f(0.0,0.0,1.0)
	glPushMatrix()
		glTranslatef(-halfsize,0.0,0.0);
		glRotatef(90.0,0.0,0.0,halfsize)
		DrawNet(size,LinesX,LinesZ)
		glTranslatef(0.0,-size,0.0)
		DrawNet(size,LinesX,LinesZ)
	glPopMatrix()
	glColor3f(1.0,0.0,0.0)
	glPushMatrix()
		glTranslatef(0.0,0.0,-halfsize)	
		glRotatef(90.0,halfsize,0.0,0.0)
		DrawNet(size,LinesX,LinesZ)
		glTranslatef(0.0,size,0.0)
		DrawNet(size,LinesX,LinesZ)
	glPopMatrix()
	glFlush()
EndFunction


Function DrawNet(size:Float, LinesX:Int, LinesZ:Int)
	glBegin(GL_LINES)
	For Local xc:Int = 0 To LinesX-1 
	
		glVertex3f(	-size / 2.0 + xc / Float(LinesX-1)*size,0.0,size / 2.0)
		glVertex3f(	-size / 2.0 + xc / Float(LinesX-1)*size,0.0,size / -2.0)
	Next
	For Local zc:Int = 0 To LinesX-1
	
		glVertex3f(	size / 2.0,0.0,-size / 2.0 + zc / Float(LinesZ-1)*size)
		glVertex3f(	size / -2.0,0.0,-size / 2.0 + zc / Float(LinesZ-1)*size);
	Next
	glEnd()
End Function


Got your email Chris. That's a bad feature imo. We need a way to be able to set the z rot to 0. Kinda hard to do though when you're getting z rotation from x and y but it's not showing up on the z parameters. =(

nah lol, its my bad! see the "small demo" topic, its a genuine effect of a rotation on two axes...

Yeah but we still have to find a way to reset it to 0 on the z-axis...

its not rotated on the z axis, it just looks like it is
LOCALLY there is no z axis rotation.

I *think* thats whats going on, blitz3d behaves in the same way anyhow

if you have blitz3d try this

Graphics3D 640,480
SetBuffer BackBuffer()

cam1=CreateCamera()

plane=CreatePlane()
grass_tex=LoadTexture( "terrain.png" )

EntityTexture plane,grass_tex
PositionEntity plane,0,-5,0
MoveMouse 320,240
While Not KeyDown( 1 )

TurnEntity cam1,0,mx#,0,False
TurnEntity cam1,my#,0,0,False

RenderWorld

mx#=-(MouseX()-320)
my#=-(MouseY()-240)
MoveMouse 320,240
Flip

Wend

End


Can you guys tell me if this solves anything?