Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Rotating camera in 3D?

edited January 2015 in Questions Posts: 136

I'm trying to rotate the cameras view, so I was wondering if there is a way to rotate the camera and the cameras view on a Y axis, without having to manipulate the cameras look X and look Z? It just seems like it would be a lot simpler if there was.

Tagged:

Comments

  • edited January 2015 Posts: 1,976

    I made a class to help with this a long time ago.

    Cam3D = class()
    
    function Cam3D:init()
        self.pos = vec3(0, 5, 0)
        self.rot = vec3(90, 0, 0)
        self.fov = 45
        self.clip = 8192
        self.dist = 0
    end
    
    function Cam3D:pose()
        local dy = math.cos(math.rad(self.rot.x))
        local xzMult = 1 - math.abs(math.cos(math.rad(self.rot.x)))
        local dx, dz = math.sin(math.rad(self.rot.y)) * xzMult, math.cos(math.rad(self.rot.y)) * xzMult
        local upx = math.sin(math.rad(self.rot.z + self.rot.y))
        local upy = math.cos(math.rad(self.rot.z))
        local upz = math.cos(math.rad(self.rot.z + self.rot.y))
        camera(self.pos.x - dx * self.dist, self.pos.y - dy * self.dist, self.pos.z - dz * self.dist, self.pos.x + dx, self.pos.y + dy, self.pos.z + dz, upx, upy, upz)
        perspective(self.fov, WIDTH / HEIGHT, 0.05, self.clip)
    end
    
    function Cam3D:getRot()
        local dy = math.cos(math.rad(self.rot.x))
        local xzMult = 1 - math.abs(math.cos(math.rad(self.rot.x)))
        local dx, dz = math.sin(math.rad(self.rot.y)) * xzMult, math.cos(math.rad(self.rot.y)) * xzMult
        local upx = math.sin(math.rad(self.rot.z + self.rot.y))
        local upy = math.cos(math.rad(self.rot.z))
        local upz = math.cos(math.rad(self.rot.z + self.rot.y))
        return 0 - dx * self.dist, 0 - dy * self.dist, 0 - dz * self.dist, dx, dy, dz, upx, upy, upz
    end
    

    Just create a new Cam3D object, and you can set its pos and rot vec3s to translate and rotate it. Call its pose() function to position the camera and set the perspective (before you draw your 3D stuff)

  • Posts: 136

    @SkyTheCoder thanks, i'll try that out and see if it helps me to make sense of this.

  • IgnatzIgnatz Mod
    edited January 2015 Posts: 5,396

    @jrohanian - you may find this a bit simpler to work with. Slide the angle parameter to look left and right. All you really need from the code below is the AdjustCamLook function. Just run it each time you change the viewing angle, to adjust the camera look values.

    NB if the function stuff at the end of the parameter line is confusing, don't worry about it, it simply runs AdjustCamLook for you whenever you change Angle, and the parameter is only there just to show you it works.

    function setup()
        parameter.number("Angle",-90,90,0,function() AdjustCamLook(Angle) end)
    end
    
    function AdjustCamLook(r)
        r=math.rad(r)
        camLook=vec3(math.sin(r),0,math.cos(r))*100
    end
    
    function draw()
        background(50)
        perspective()   
        camera(0,0,300,camLook.x,camLook.y,camLook.z)
        sprite("Planet Cute:Character Princess Girl",0,0)
    end
    
    
  • Posts: 1,976

    @Ignatz But with that, can you look up and down, and tilt the camera? ;)

  • IgnatzIgnatz Mod
    Posts: 5,396

    no, but he just wanted to rotate on y!

  • Posts: 136

    @ignatz Looks great, what is the importance of using math.rad?

  • Posts: 136

    @SkyTheCoder your code looks like something I might have to work up to a bit, though.

  • IgnatzIgnatz Mod
    Posts: 5,396

    Codea's trig functions use radians not degrees, so you need to convert them

  • IgnatzIgnatz Mod
    Posts: 5,396

    @SkyTheCoder - wrt your code, why do you recalculate all the settings at each draw? Why not calculate them just once when the rotation changes (how does your class allow changes, btw), and store the camera vector for use in draw?

  • edited January 2015 Posts: 1,976

    @Ignatz It didn't occur to me to calculate them only once - easy enough to fix

    Cam3D = class()
    
    function Cam3D:init()
        self.pos = vec3(0, 5, 0)
        self.rot = vec3(90, 0, 0)
        self.fov = 45
        self.clip = 8192
        self.dist = 0
        self.lastPos = vec3(self.pos.x, self.pos.y - 1, self.pos.z)
        self.lastRot = vec3(self.rot.x, self.rot.y - 1, self.rot.z)
        self.dy = 0
        self.xzMult = 1
        self.dx, self.dz = 0, 1
        self.upx = 0
        self.upy = 1
        self.upz = 0
    end
    
    function Cam3D:pose()
        if self.pos.x ~= self.lastPos.x or self.pos.y ~= self.lastPos.y or self.pos.z ~= self.lastRot.z or self.rot.x ~= self.lastRot.x or self.rot.y ~= self.lastRot.y or self.rot.z ~= self.lastRot.z then
            self.dy = math.cos(math.rad(self.rot.x))
            self.xzMult = 1 - math.abs(math.cos(math.rad(self.rot.x)))
            self.dx, self.dz = math.sin(math.rad(self.rot.y)) * self.xzMult, math.cos(math.rad(self.rot.y)) * self.xzMult
            self.upx = math.sin(math.rad(self.rot.z + self.rot.y))
            self.upy = math.cos(math.rad(self.rot.z))
            self.upz = math.cos(math.rad(self.rot.z + self.rot.y))
        end
        camera(self.pos.x - self.dx * self.dist, self.pos.y - self.dy * self.dist, self.pos.z - self.dz * self.dist, self.pos.x + self.dx, self.pos.y + self.dy, self.pos.z + self.dz, self.upx, self.upy, self.upz)
        perspective(self.fov, WIDTH / HEIGHT, 0.05, self.clip)
        self.lastPos = vec3(self.pos.x, self.pos.y, self.pos.z)
        self.lastRot = vec3(self.rot.x, self.rot.y, self.rot.z)
    end
    
    function Cam3D:getRot()
        return 0 - self.dx * self.dist, 0 - self.dy * self.dist, 0 - self.dz * self.dist, self.dx, self.dy, self.dz, self.upx, self.upy, self.upz
    end
    

    (untested, not on iPad)

  • edited January 2015 Posts: 136

    @ignatz , I used your code, then modified it a bit to see if it work for what I am trying to do, and it does, but I notice once I rotate to approximately 180 degrees, The cameras rotation will skip forward. It's really kind of weird. The way I am using it is calling the function every frame with a variable, angle, then adding to and subtracting from the variable to rotate. Is this because of sine and cosine?

  • IgnatzIgnatz Mod
    Posts: 5,396

    @jrohanian - it's hard to say without seeing exactly how you rotated, but once you start rotating in 3 dimensions, you can't just add new rotations to previous ones, and there can be discontinuities and problems at 90 and 180 degrees depending on circumstances.

    It's taken me about a year to partly understand it, and I am writing an ebook about it, which I hope to finish.

    This post of mine describes some of the problems. It's not totally accurate, but will give you some idea.

    https://coolcodea.wordpress.com/2013/12/28/142-3d-rotations-flying-a-plane/

  • You are doing rotations in a classic camera on a tripod form, which is what you want for most things. I did some code yonks ago for totally free rotation in space where the camera can roll upside down/sideways etc. If anyones interested I can dig that out.

  • Jmv38Jmv38 Mod
    Posts: 3,280

    @Spacemonkey for sure!

  • IgnatzIgnatz Mod
    Posts: 5,396

    I have a quaternion rotation class which is the right way to deal with incremental rotations in any direction, but it takes a little explanation

  • Yeah, mines Quartenion based. Can't explain it, I think I understood it when I did it, but not any more...

    Grab Andrew Stacey's quartenion library here and stick it in a tab http://www.math.ntnu.no/~stacey/documents/Codea/Library/Quaternion.lua

    Then I have a camDir and camUp I define in setup

    camDir = vec3(0,0,1)
        camUp = vec3(0,1,0)
    

    Draw with

    camera(position.x, position.y,position.z, position.x+camDir.x, position.y+camDir.y, position.z+ camDir.z, camUp.x, camUp.y, camUp.z)
    

    Where position is where in 3d space the camera is.

    Finally in touch

    --get the horizon for vertical rotation
                q = Quaternion.Rotation(math.rad(90),camUp)
                horizontal = camDir^q
                --rotate camPos around vertical
                q = Quaternion.Rotation(math.rad(touch.deltaX/3),camUp)
                camDir = camDir^q
                --rotate camPos and camUp around horizontal
                q = Quaternion.Rotation(math.rad(touch.deltaY/3), horizontal)
                camDir = camDir^q
                camUp = camUp^q
    

    The /3 is just a factor for how fast I wanted it to rotate...

  • Posts: 136

    @ignatz yeah I read the section of your eBook on quaternions, but most of it went right over my head. I think i'll go through it again.

  • IgnatzIgnatz Mod
    Posts: 5,396

    It still goes over my head... @-) There are several ways to derive them - 4D geometry, matrix math, and imaginary numbers - and one day I hope to understand just one of them.

  • edited January 2015 Posts: 136

    @ignatz as long as I know how to use it i'll try not to worry too much about how it works.

  • edited January 2015 Posts: 300

    This is a pretty good explanation of quaternions

  • edited January 2015 Posts: 136

    @ignatz @Crumble but according to this video quaternions are never needed for single axis rotations so why does this bother me when I am only rotating on the Y-axis? Could it be because I am misusing my eulers?

  • IgnatzIgnatz Mod
    Posts: 5,396

    @jrohanian - I would have thought you would be ok rotating on just y. Maybe you should post some code that shows this behaviour.

  • Posts: 136

    its just me experimenting with some of the stuff in ignatz ebook

    -- hello 3d
    
    -- Use this function to perform your initial setup
    function setup()
       camX,camY,camZ=5,100,-100
       lookX,lookY,lookZ=1,15,1
       X,Y,Z=50,0,-1000
       moveZ=5
       angle = 0
       arg = 1
       vy = 0
       camLook = vec3(0,0,0)
       argc = 1
       RX,RY,RZ=0,0,0
       RX2,RY2,RZ2=0,0,0
       targetAngle = 0
       rX,rY,rZ=0.1,0.5,1.0
       mapLevel = 0
       SetupCube(25)
    
    end
    function SetupCube(s)
    --sprite("Platformer Art:Block Brick")    
       b = Tile(("Platformer Art:Block Brick"), 1)
       b:AddSurface(vec3(-400,0,0),vec3(400,0,-1000),1)
           a = Tile(("Platformer Art:Block Brick"), 3)
       a:AddSurface(vec3(-400,0,0),vec3(-400,200,-1000),0.25)
       a:AddSurface(vec3(-400,0,-1000),vec3(400,200,-1000),0.5)
       a:AddSurface(vec3(400,0,0),vec3(400,200,-1000),0.25)
       a:AddSurface(vec3(-400,0,0),vec3(400,200,0),0.25)
       h = Tile(("Platformer Art:Block Brick"),1)
       h:AddSurface(vec3(-400,200,0),vec3(400,200,-1000),1)
    
    end
    -- This function gets called once every frame
    function draw()
               print("look x "..lookX.." and look z "..lookZ.." angle "..angle)
       -- This sets a dark background color 
       background(220)
       perspective() -- tells codea we is in the third dimension
       camera(camX,camY,camZ,camLook.x,camLook.y,camLook.z)
    
     a.mesh:draw()
     b.mesh:draw() 
     h.mesh:draw()
    
    
       if CurrentTouch.tapCount == 3 then vy = vy + 1 end
       AdjustCamLook(vy)
    
    end   
    
    function AdjustCamLook(r)
       r=math.rad(r)
       camLook=vec3(math.sin(r),1,math.cos(r))*100
    end
    
    
    wallFloorLibrary = class()
    
    --This class tiles an image across a rectangle of any size and provides a mesh
    --You can add as many rectangles as you like to the same mesh
    --Each rectangle must be vertical or horizontal, not at an angle, ie floor, roof or walls
    Tile=class()
    
    --img = image name or the image itself
    --s = scale of image (0.5 reduces by half, 2 doubles its size,
    function Tile:init(img,s)
       --if type(img)=="text" then img=readImage(img) end
       img = readImage(img)
       --img=readImage("Platformer Art:Block Brick")
       self.img=img
       self.iw,self.ih=img.width,img.height
       self.mesh=mesh()
       self.mesh.texture=self.img
       self.mesh.shader=shader(Tile.Shader.vertexShader,Tile.Shader.fragmentShader)
       self.scale=s
       self.v,self.t={},{}
    end
    
    --The parameters are as follows
    --p1 = vec3(x,y,z) = a corner position 
    --p2 = vec3(x,y,z) = the diagonally opposite corner position 
    --s = scale of image (0.5 reduces by half, 2 doubles its size, default is what was provided in the init function)
    function Tile:AddSurface(p1,p2,s)
       s=s or self.scale
       local w,h=self.img.width*s,self.img.height*s
       local d=p2-p1
       local v,t
       if d.x==0 then 
           v={vec3(p1.x,p1.y,p1.z),vec3(p1.x,p1.y,p2.z),vec3(p1.x,p2.y,p2.z),vec3(p1.x,p2.y,p1.z)}
           t={vec2(0,0),vec2(d.z/w,0),vec2(d.z/w,d.y/h),vec2(0,d.y/h)}
       elseif d.y==0 then 
           v={vec3(p1.x,p1.y,p1.z),vec3(p2.x,p1.y,p1.z),vec3(p2.x,p1.y,p2.z),vec3(p1.x,p1.y,p2.z)}
           t={vec2(0,0),vec2(d.x/w,0),vec2(d.x/w,d.z/h),vec2(0,d.z/h)}
       elseif d.z==0 then 
           v={vec3(p1.x,p1.y,p1.z),vec3(p2.x,p1.y,p1.z),vec3(p2.x,p2.y,p1.z),vec3(p1.x,p2.y,p1.z)}
           t={vec2(0,0),vec2(d.x/w,0),vec2(d.x/w,d.y/h),vec2(0,d.y/h)}       
       else return nil
       end
       local seq={1,2,3,3,4,1}
       for i=1,6 do
           table.insert(self.v,v[seq[i]])
           table.insert(self.t,t[seq[i]])
       end  
       self.mesh.vertices=self.v
       self.mesh.texCoords=self.t  
    end
    
    Tile.Shader = {
    vertexShader = [[
    
    uniform mat4 modelViewProjection;
    attribute vec4 position;
    attribute vec4 color;
    attribute vec2 texCoord;
    varying lowp vec4 vColor;
    varying highp vec2 vTexCoord;
    
    void main()
    {
       vColor = color;
       vTexCoord = texCoord;
       gl_Position = modelViewProjection * position;
    }
    
    ]],
    fragmentShader = [[
    
    precision highp float;
    uniform lowp sampler2D texture;
    varying lowp vec4 vColor;
    varying highp vec2 vTexCoord;
    
    void main()
    {
       lowp vec4 col = texture2D( texture, vec2(mod(vTexCoord.x,1.0), mod(vTexCoord.y,1.0)));
       gl_FragColor = col;
    }
    
    ]]}
    
  • IgnatzIgnatz Mod
    Posts: 5,396

    So where is the problem?

  • Posts: 136

    @Ignatz same thing, it skips from 180 degrees of rotation to about 270.

  • IgnatzIgnatz Mod
    edited January 2015 Posts: 5,396

    Try this for camLook instead of what you have

    camLook=vec3(math.sin(r)*1000,1,math.cos(r)*1000)
    
  • Posts: 136

    @Crumble do you have an example of applying quaternions in code? I think I sort of understand them.

  • edited January 2015 Posts: 300

    @jrohanian No sorry, I had to learn about them in one of the math classes (linear algebra I believe) for my major. Have never applied them in code, and hoped I would never see them again. :D

  • Posts: 422

    I wrote a quaternion library for Codea. Search the forum for quaternions to find it.

  • edited January 2015 Posts: 502

    I liked that video, took some code from the forum to visualize the quaternion rotation in Codea:

    function setup()
        parameter.number("W",-2,2,1)
        parameter.number("X",-2,2,0)
        parameter.number("Y",-2,2,0)
        parameter.number("Z",-2,2,0)
        m = mesh()
        m:addRect(0,0,1,1)
        m.texture = "Space Art:Red Ship"
        m.shader = qshader()
        m.shader.centre = vec3(0,0,0)
    
        time = {value=0}
        tween(.5, time, {value=1}, {loop=tween.loop.pingpong})
    end
    
    function draw()
        background(57, 57, 57, 255)
        perspective()
        camera(0,0,5,0,0,0,0,1,0)
    
        local t = time.value
        pushMatrix()
        translate(-.8,1.5)
        scale(.3)
        m.shader.q = vec4(1,0,0,0):normalize()
        m:draw()
        translate(1.5,0)
        m.shader.q = vec4(1-t,t,0,0):normalize()
        m:draw()
        translate(1.5,0)
        m.shader.q = vec4(1-t,0,t,0):normalize()
        m:draw()
        translate(1.5,0)
        m.shader.q = vec4(1-t,0,0,t):normalize()
        m:draw()
        popMatrix()
    
        m.shader.q = vec4(W,X,Y,Z):normalize()
        m:draw()
    end
    
    function qshader()
        return shader([[
        //
        // A rotation vertex shader
        //
    
        //This is the current model * view * projection matrix
        // Codea sets it automatically
        uniform mat4 modelViewProjection;
    
        // Parameters set by user
        uniform vec3 centre;
        uniform vec4 q;
    
        //This is the current mesh vertex position, color and tex coord
        // Set automatically
        attribute vec4 position;
        attribute vec4 color;
        attribute vec2 texCoord;
    
        varying lowp vec4 vColor;
        varying highp vec2 vTexCoord;
    
        // Basic quaternion functions
        mediump vec4 qmult (mediump vec4 p, mediump vec4 q)
        {
            mediump float a = p.x * q.x - p.y * q.y - p.z * q.z - p.w * q.w;
            mediump float b = p.x * q.y + p.y * q.x + p.z * q.w - p.w * q.z;
            mediump float c = p.x * q.z - p.y * q.w + p.z * q.x + p.w * q.y;
            mediump float d = p.x * q.w + p.y * q.z - p.z * q.y + p.w * q.x;
            return vec4(a,b,c,d);
        }
    
        mediump vec4 qconj (mediump vec4 q)
        {
            return vec4(q.x,-q.y,-q.z,-q.w);
        }
    
        mediump vec3 qvmult(mediump vec4 q, mediump vec3 v)
        {
            mediump vec4 p = vec4(0,v);
            mediump vec4 pq = qmult(q,qmult(p,qconj(q)));
            return vec3(pq.yzw);
        }
    
    
        void main()
        {
            //Pass the mesh color to the fragment shader
            vColor = color;
            vTexCoord = vec2(texCoord.x, 1.0 - texCoord.y);
            mediump vec3 pos = position.xyz/position.w - centre;
            pos = qvmult(q,pos);
            pos += centre;
            //Multiply the vertex position by our combined transform
            gl_Position = modelViewProjection * vec4(pos,1);
        }
    
        ]], [[
        //
        // A basic fragment shader
        //
    
        //Default precision qualifier
        precision highp float;
    
        //This represents the current texture on the mesh
        uniform lowp sampler2D texture;
    
        //The interpolated vertex color for this fragment
        varying lowp vec4 vColor;
    
        //The interpolated texture coordinate for this fragment
        varying highp vec2 vTexCoord;
    
        void main()
        {
            //Sample the texture at the interpolated coordinate
            lowp vec4 col = texture2D( texture, vTexCoord );
            // colour by given colour
            col*=vColor;
            //Set the output color to the texture color
            gl_FragColor = col;
        }
    
        ]])
    end
    
  • Posts: 136

    thanks

Sign In or Register to comment.