1. Stuck, Having problems with creating your mod? Let us help you: Mod Support

[Tutorial] How to write Camera Mode mods

Discussion in 'Content Creation' started by Funky7Monkey, Aug 8, 2016.

  1. Funky7Monkey

    Funky7Monkey
    Expand Collapse

    Joined:
    Oct 12, 2014
    Messages:
    1,042
    In this tutorial, I will explain the basics of how to write a camera mode mod. This tutorial is not complete. I will update this tutorial as time permits. I will not attempt to teach Lua, geometry, or trigonometry. A basic understanding of at least Lua (or programming in general) and geometry are required to write a camera mode mod, as well as the meanings and uses for necessary and common variables. I will use examples from official camera modes. The thread "Custom camera modes - possible with 0.5.5" explains how to package camera mode mods.

    At the very least, a camera mode needs to define the camera's position, FoV (Field of View), and the direction the camera is facing. It will need to do this every time the camera is updated. The camera's position will need to be a point in a 3D Cartesian coordinate system (using variables x, y, and z). The FoV will need to be an integer. The camera's rotation will need to be a quaternion, which usually gets defined by comparing the camera's position with a target position. I personally haven't learned about quaternions, so I'm going to stick with using a target position in this tutorial.

    Here is an example of a simple and complete camera mode. It is the topDown camera mode that @tdev showed off in the thread "Custom camera modes - possible with 0.5.5."
    -- This Source Code Form is subject to the terms of the bCDDL, v. 1.1.
    -- If a copy of the bCDDL was not distributed with this
    -- file, You can obtain one at http://beamng.com/bCDDL-1.1.txt

    --- This is an example of a custom camera mode :D

    local C = {}
    C.__index = C

    function C:init()
    --- when this is loaded, the config will be in self alread. I suggest to inspect it:
    -- dump(self)
    self.veloSmoother = newExponentialSmoothing(20, 1)
    self.lastDataPos = vec3()
    self.fov = self.fov or 20

    --- this marks it as base mode. It needs to set position, rotation and fov correctly
    self.baseMode = true
    -- add it to the default camera modes?
    self.register = true
    end

    function C:update(data)
    -- get the data
    local ref = vec3(data.veh:getNodePosition(self.refNodes.ref))
    local back = vec3(data.veh:getNodePosition(self.refNodes.back))

    -- we need to manually smooth the velocity as its too spiky otherwise which results in bad camera movement
    local velo = self.veloSmoother:get((self.lastDataPos - data.pos):length())
    self.lastDataPos = data.pos

    -- figure out the way the vehicle is oriented
    local dir = (ref - back):normalized()
    -- find out the target that we should look on
    local targetPos = data.pos + vec3(dir.x, dir.y, 0) * math.min(50, 70 * velo)
    -- and place the camera above it
    local camPos = targetPos + vec3(0, 0, (velo * 80) + 50)
    -- then look from camera position to target :)
    local qdir = quatFromDir((targetPos - camPos):normalized())

    -- set the data, this needs to happen
    data.res.pos = camPos -- required, vec3()
    data.res.rot = qdir -- required, quat()
    data.res.fov = self.fov -- required
    data.res.targetPos = targetPos -- this is optional
    return true
    end

    -- DO NOT CHANGE CLASS IMPLEMENTATION BELOW

    return function(...)
    local o = ... or {}
    setmetatable(o, C)
    o:init()
    return o
    end


    To start off, a camera mode needs an initial function:
    function C:init()
    --- when this is loaded, the config will be in self alread. I suggest to inspect it:
    -- dump(self)
    self.veloSmoother = newExponentialSmoothing(20, 1)
    self.lastDataPos = vec3()
    self.fov = self.fov or 20

    --- this marks it as base mode. It needs to set position, rotation and fov correctly
    self.baseMode = true
    -- add it to the default camera modes?
    self.register = true
    end
    It is the place to define variables that will need to carry over across camera updates, such as the camera's last position, rotation, etc. It is run when the camera mode is loaded.

    Camera modes also need a function that is run on updates:
    function C:update(data)
    -- get the data
    local ref = vec3(data.veh:getNodePosition(self.refNodes.ref))
    local back = vec3(data.veh:getNodePosition(self.refNodes.back))

    -- we need to manually smooth the velocity as its too spiky otherwise which results in bad camera movement
    local velo = self.veloSmoother:get((self.lastDataPos - data.pos):length())
    self.lastDataPos = data.pos

    -- figure out the way the vehicle is oriented
    local dir = (ref - back):normalized()
    -- find out the target that we should look on
    local targetPos = data.pos + vec3(dir.x, dir.y, 0) * math.min(50, 70 * velo)
    -- and place the camera above it
    local camPos = targetPos + vec3(0, 0, (velo * 80) + 50)
    -- then look from camera position to target :)
    local qdir = quatFromDir((targetPos - camPos):normalized())

    -- set the data, this needs to happen
    data.res.pos = camPos -- required, vec3()
    data.res.rot = qdir -- required, quat()
    data.res.fov = self.fov -- required
    data.res.targetPos = targetPos -- this is optional
    return true
    end
    This is where the camera's position, facing, and FoV are calculated, as well as anything else needed to calculate these. This example uses a target position (targetPos) to calculate the camera's facing. It also uses the vehicle's velocity (by subtracting vehicle's the last position (self.lastDataPos) by it's current position (data.pos) to move the target ahead of the vehicle, as well as to lift the camera as speed increases.

    The data that gets sent to the update function is as follows:
    data.veh - The current vehicle
    data.pos - The current position of the vehicle
    data.vid - The ID of the vehicle
    data.dt - a multiplier for camera movement controls

    Other useful functions include reset, reload, and lookback: (example from the default "orbit" camera)
    function C:reset()
    if self.cameraResetted ~= -1 then
    -- if a reload hasn't just happened

    self.camRot = vec3(self.defaultRotation)
    self.cameraResetted = 2
    self.camDist = self.defaultDistance
    self.externalCamBase = nil
    -- for some reason this fixes things sometimes :|
    MoveManager.pitchUpSpeed = 0
    MoveManager.pitchDownSpeed = 0
    MoveManager.yawLeftSpeed = 0
    MoveManager.yawRightSpeed = 0
    else
    self.cameraResetted = 0
    end
    end

    function C:reloaded()
    -- if a reset countdown is NOT already happening
    if self.cameraResetted <= 0 then
    -- signal to reset that a reload has happened
    self.cameraResetted = -1
    end
    -- make sure this gets recalculated by invalidating it
    self.externalCamBase = nil
    -- global fov tuning
    self.fov = self.fov + settings.getValue('cameraFOVTune') or 0
    self.fov = math.min(150, math.max(1, self.fov))
    self.relaxation = settings.getValue('cameraOrbitRelaxation') or 3
    end

    function C:lookback()
    if self.camRot.x > 0 then
    self.camRot.x = 0
    else
    self.camRot.x = 180
    end
    end
    The reset function is run when the camera is reset using "5" on the numpad or clicking in the right stick on a Xinput controller. The reload function is run when the vehicle is reloaded. The lookback function is run when the lookback button is pressed (default: "1" on numpad).
     
    #1 Funky7Monkey, Aug 8, 2016
    Last edited: Aug 9, 2016
    • Like Like x 6
  2. Nadeox1

    Nadeox1
    Expand Collapse
    BeamNG.Support
    Staff Member

    Joined:
    Aug 5, 2012
    Messages:
    11,065
    Stickied! Very useful, thanks :)
     
    • Like Like x 2
  3. Funky7Monkey

    Funky7Monkey
    Expand Collapse

    Joined:
    Oct 12, 2014
    Messages:
    1,042
    I am sometimes available to give assistance if needed, and I'm sure staff will be willing to help as well. I personally will not give very much assistance with learning how to program or the mathematical principles involved. Updates to the tutorial itself will be accompanied by a summarized post to notify people watching the thread that the tutorial has been updated. I plan to explain manual camera movement with the next update to the tutorial.

    At some point, I will get around to writing documentation in the wiki, if a dev doesn't beat me too it.
     
  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice