3D Camera Class/Controler

A 3d camera class i’m working on for future projects


--# Main

function setup()
    parameter.action("LookAtCenter",function()cam:lookAt(vec3(0,0,0))end)
    parameter.boolean("FixLookAt",false)
    parameter.number("CamDist",0,2000,400)
    touches = {}
    cam = Camera()
    cam.crosshair = true
    ls,rs = Stick(10),Stick(3,WIDTH-120)
end

function draw()
    background(40, 40, 50)
    perspective()
    
    cam:draw()
    
    cam.fix = FixLookAt
    cam.dist = CamDist
    
    cam.angH = cam.angH + rs.x
    cam.angV = cam.angV + rs.y
    
    cam:movement(ls.y,ls.x)
    
    rect(0,0,100,100)
    pushMatrix()
    strokeWidth(5)
    translate(0,-200,0)rotate(90,1,0,0)
    for i = -5,5 do
        line(-500,i*100,500,i*100)
        line(i*100,-500,i*100,500)
    end
    popMatrix()
    
    ortho()
    viewMatrix(matrix())
    
    ls:draw()
    rs:draw()
end

function touched(touch)
    if touch.state == ENDED then
        touches[touch.id] = nil
    else
        touches[touch.id] = touch
    end
end

--# Camera

Camera = class()

function Camera:init(eX,eY,eZ,lX,lY,lZ,uX,uY,uZ)
    self.eye = vec3(eX or 0, eY or 0, eZ or 400)
    self.lat = vec3(lX or 0, lY or 0, lZ or 0)
    self.upV = vec3(uX or 0, uY or 1, uZ or 0)
    self.dist = self.lat:dist(self.eye)
    self.angH = 90
    self.angV = -90
    self.fix = false
    self.crosshair = false
end

function Camera:draw()
    if self.fix then
        self.eye = -self:rotatePoint() + self.lat
    else
        self.lat =  self:rotatePoint() + self.eye
    end
    camera(
    self.eye.x, self.eye.y, self.eye.z,
    self.lat.x, self.lat.y, self.lat.z,
    self.upV.x, self.upV.y, self.upV.z)
    if self.crosshair then self:drawCrosshair(50) end
end

function Camera:movement(z,x)
    if z and z ~= 0 then
        local zVel = (self.lat-self.eye):normalize() * z
        self.eye = self.eye + zVel
        self.lat = self.lat + zVel
    end
    if x and x ~= 0 then
        local xVel = (self.lat-self.eye):cross(vec3(0,1,0)):normalize() * x
        self.eye = self.eye + xVel
        self.lat = self.lat + xVel
    end
end

function Camera:rotatePoint()
    -- calculate y and z from angV at set distance
    local y = math.cos(math.rad(self.angV))*self.dist
    local O = math.sin(math.rad(self.angV))*self.dist
    -- calculate x and z from angH using O as the set distance
    local x = math.cos(math.rad(self.angH))*O
    local z = math.sin(math.rad(self.angH))*O
    return vec3(x,y,z)
end

function Camera:updateAngles()
    self.angH = math.deg(math.atan2(self.lat.z-self.eye.z,self.lat.x-self.eye.x))+180
    self.angV = -math.deg(math.acos((self.lat.y-self.eye.y)/self.dist))
end

function Camera:lookAt(target)
    self.dist = self.eye:dist(target)
    self.lat = target
    self:updateAngles()
end

function Camera:drawCrosshair(w)
    pushMatrix()pushStyle()
    translate(self.lat.x,self.lat.y, self.lat.z)
    strokeWidth(2)
    line(-w/2,0,w/2,0)
    line(0,-w/2,0,w/2) rotate(90,1,0,0)
    line(0,-w/2,0,w/2)
    popMatrix()popStyle()
end

--# Stick

Stick = class()

function Stick:init(ratio,x,y)
    self.ratio = ratio or 1
    self.i = vec2(x or 120,y or 120)-- initial pos
    self.v = vec2(0,0) -- stick pos relative to initial pos
    self.b = b or 180  -- base diameter
    self.s = s or 100  -- stick diameter
    self.d = d or 50  -- dead zone diameter
    self.a = 0         -- stick angle relative to initial pos
    self.touchId = nil
    self.x,self.y = 0,0
end

function Stick:draw()
    
    if touches[self.touchId] == nil then
        for i,t in pairs(touches) do
            if vec2(t.x,t.y):dist(self.i) < self.b/2 then self.touchId = i end
        end
        self.v = vec2(0,0)
    else
        self.v = vec2(touches[self.touchId].x,touches[self.touchId].y) - self.i
        self.a = math.deg(math.atan2(self.v.y,self.v.x))
    end
    self.t = math.min(self.b/2,self.v:len())
    if self.t >= self.b/2 then
        self.v = vec2(math.cos(math.rad(self.a))*self.b/2,math.sin(math.rad(self.a))*self.b/2)
    end
    
    fill(127, 127, 127, 150)
    ellipse(self.i.x,self.i.y,self.b)
    ellipse(self.i.x+self.v.x,self.i.y+self.v.y,self.s)
    
    self.v = self.v/(self.b/2)*self.ratio
    self.t = self.t/(self.b/2)*self.ratio
    self.x,self.y = self.v.x,self.v.y
    
end

interesting. Thanks.

Nice job. I’ll have a look thru your code to see what you’re doing.

shoot game or basketball game?

Nice routine - small suggestion - I have put a stroke() and colour change before each of the axis lines on the cross hairs, then when you move it you get a better picture of where you are. You could also provide coloured grids in each plane.

This would be really useful to help orientate a model when you need to provide one particular view point, you could have it as a dependency such that a parameter action can switch it on or off during development.

Very neat - you’ve obviously worked on 3D before.

Thanks.

Bri_G

:smiley:

A very good class, I used it in my code, thanks @Jaybob!

A suggestion: If you use a global var named b or s, it maybe change the self.b or self.s, so I add two parameters in the init(), like below:

function Stick:init(ratio,x,y,b,s)
    self.ratio = ratio or 1
    self.i = vec2(x or 120,y or 120)-- initial pos
    self.v = vec2(0,0) -- stick pos relative to initial pos
    self.b = b or 180  -- base diameter
    self.s = s or 100  -- stick diameter
    self.d = d or 50  -- dead zone diameter
    self.a = 0         -- stick angle relative to initial pos
    self.touchId = nil
    self.x,self.y = 0,0
end