Help for GE to vehicle lua, camera vectors

Discussion in 'Programming' started by NickRyge, May 4, 2023.

  1. NickRyge

    NickRyge
    Expand Collapse

    Joined:
    Aug 21, 2013
    Messages:
    20
    Hello - I've created a similar post under mod-support, not realizing this was the correct place to put it. I apologize.

    Anyway - I have been looking into getting a camera position and direction vector, just like in the ge_utils, and other ge files, like:

    core_camera.getQuat() * vec3(0,1,0)
    core_camera.getPosition()
    The issue I am facing is that I'd like these two pieces of information to be in the UpdateGFX-function of some vehicle Lua. I have experimented with getting this through obj:queueGameEngineLua but was disappointed to find out that this call does not return a value, but just a nil.

    I am interested in any way I can get camera information from vehicle lua, and I'd love for it not to require 3 billion function calls, as it is intended to run in the UpdateGFX function and shouldn't impact performance.

    I am at a bit of a loss with the documentation, so if there is an easy way to get the information, I'd be your biggest fan if you'd share it.

    Thanks lads.
     
  2. NickRyge

    NickRyge
    Expand Collapse

    Joined:
    Aug 21, 2013
    Messages:
    20
    I'll just update it in case anyone finds this on Google or whatever..

    The solution I arrived at was just to use the queueGameEngineLua to call a global function in the vehicle lua with the values I want as strings.. The reason it has to be strings is as someone has pointed out in a different thread somewhere, so that they can be escaped. Otherwise it'll just send the function call back to the vehicle lua and you get a nil.

    The code:


    Code:
    --Queue lua in GE      |              Queue vehicle lua from GE                   |     Call function in this class with an escaped string of the desired vectors seperated on ":"
    obj:queueGameEngineLua("be:getObjectByID("..tostring(objectId).."):queueLuaCommand('SetDirVec(\"'..tostring(core_camera.getQuat() * vec3(0,1,0))..\":\"..tostring(core_camera.getPosition())..'\")')")
    

    And then just turn it back to vec3 objects with a dirStr.match and regex:

    Code:
    local x1, y1, z1 = dirStr:match("vec3%((-?%d+%.?%d*),(-?%d+%.?%d*),(-?%d+%.?%d*)%)")
    local dirVec = vec3(tonumber(x1), tonumber(y1), tonumber(z1))
    Whatever, it works.
     
  3. Derpitron

    Derpitron
    Expand Collapse

    Joined:
    Jun 25, 2017
    Messages:
    252
    Hey! The method I currently use in one of my projects (https://github.com/Derpitron/BeamNG-Speed-Camera) is:

    In the GE Lua code:
    PHP:
    local M = {}
    local lpack = require('lpack')

    -- 
    Gets vehicle data from vehicle lua
    local data 
    = {}
    function 
    GetVehicleData(inputData)
        
    data lpack.decode(inputData)
    end
    --Put the rest of your functions after this

    -- Export module functions
    M
    .GetVehicleData GetVehicleData
    --other module functions after this as needed

    return M
    In the Vehicle Lua code:
    PHP:
    local M = {}
    local lpack = require('lpack')
    --
    Get vehicle dataRuns every frame
    local 
    function updateGFX(dt)
        --
    Table containing data of all vehicles
        local data 
    = {}
        --
    ID of individual vehicle
        local vehicleID 
    obj:getID()
     
        --
    Sub-table containing the data for the specific vehicle of ID "vehicleID"
        
    data[vehicleID] = {}
     
        --
    Vehicle velocity
        data
    [vehicleID].velocity vec3(obj:getVelocity())
     
        --
    Vehicle sensors (g-forcedata
        data
    [vehicleID].gforces = {}
        
    data[vehicleID].gforces.sensors.gx2/-9.81
        data
    [vehicleID].gforces.sensors.gy2/-9.81
        data
    [vehicleID].gforces.sensors.gz2/-9.81
     
        
    --Vehicle direction vectors
        data
    [vehicleID].vectors = {}
        
    data[vehicleID].vectors.forward vec3(obj:getDirectionVector  ()):normalized()
        
    data[vehicleID].vectors.up      vec3(obj:getDirectionVectorUp()):normalized()
     
        --Global 
    coordinates of vehicle centre
        data
    [vehicleID].centrePosition vec3(obj:getPosition())
     
        --
    Put any other vehicle data you want here
     
        
    --Send data to GameEngine
        obj
    :queueGameEngineLua(string.format("GetVehicleData(%q)"lpack.encode(data)))
    end

    M
    .updateGFX updateGFX

    return M
    My method jumps from Vlua to GELua (complex, but 1 jump is efficient)
    Your method jumps from GELua to VLua, back to GELua. (simple one-line function call, but 2 jumps is inefficient.

    I have not extensively tested this method for speed or efficiency. It seems to be faster than your method, because my method works from the Vehicle Lua script only, and sends the data to be recieved by the Game Engine Lua once. This is much better at scale: the less expensive jumping between the all the vehicle's Lua VMs and the global GE Lua vm, the more performant it is.
     
  4. NickRyge

    NickRyge
    Expand Collapse

    Joined:
    Aug 21, 2013
    Messages:
    20
    Although, how do you read the data of all vehicles from the vehicle lua?

    The way I see it all you do is send data from the vlua to ge lua, then how do you retrieve it again?

    What I do may be more inefficient, but the lua is only queued whenever it is active, and calling a method in the vlua from the ge lua has the added benefit of being "event driven".
     
  5. Derpitron

    Derpitron
    Expand Collapse

    Joined:
    Jun 25, 2017
    Messages:
    252
    Here's a sample data structure (lua table)of what's returned by this method for one vehicle.

    PHP:
    local data = {
        [
    11088] = { --//11088 is the id of the vehicle in question. more vehicle ids can be slotted in by each vehicle in the data table, which gets returned to GE global scope.
            
    centrePosition vec3(0,0,0), --//centre position of the vehicle in world coordinates
            
    gforces = { --//3 dimensional g forces experienced by the vehicle
                
    0,
                
    0,
                
    1
            
    },
            
    vectors = { --// the directional vectors of the vehicle
                
    forward vec3(0,-1,0),
                
    up vec3(0,0,1)
            },
            
    velocity vec3(0,0,0) --//velocity of the vehicle
        
    }
        [
    11089] = {...} --//represents another vehicle's data with ID 11089
    }
    I'm not sure what you mean by event-driven, but I'll explain how my code works for multiple vehicles.

    the data table itself has multiple vehicles' data, accessible by calling the vehicle's ID index of the data table. each vehicle is responsible for adding and updating their own ID's entry in the data table. when all the vehicles handle their own part of the table and add it to the data table, it then gets pushed to GE Global scope, where it can be accessed and iterated through by whatever implementation you have.

    I recommend you check out the scripts which I've sent in my previous post in my Github repo, to see how it could be used.
     
  6. NickRyge

    NickRyge
    Expand Collapse

    Joined:
    Aug 21, 2013
    Messages:
    20
    Where my confusion lies is in the global scope of the GE. Global scoped GE tables can be read iterated over in vlua? In that case, there is something I have missed. What you do here is what I initially had attempted to do, but I gave it up because I didn't understand how to make a table globally available. I feel like the documentation on this is very lax;

    When I send some data to GE, how do I make it globally accessible? - Or rather, how do I make some variable globally visible. In your code it is a "local data" table as well, I don't understand how that becomes global.
     
  7. Derpitron

    Derpitron
    Expand Collapse

    Joined:
    Jun 25, 2017
    Messages:
    252
    My apologies for the confusion, I had mistyped it.

    The only global object in my code is the function:

    PHP:
    function GetVehicleData(inputData)
        
    data lpack.decode(inputData)
    end
    GetVehicleData() handles all the work for us. It is in the Global scope (no "local" function), meaning that it can be called from anywhere inside the GE Lua. It expects an lpack-encoded table data, and when it's recieved, it decodes it and applies it's value to the script's local scope "data" table.

    My VLua script uses the line:
    PHP:
    obj:queueGameEngineLua(string.format("GetVehicleData(%q)"lpack.encode(data)))
    Which calls the GetVehicleData() global function in GE Lua using queueGameEngineLua, and supplies the lpack-encoded data table (from VLua) into it.

    To explain this with a real life analogy:

    5 houses in a neighborhood put their own mail to be sent in their mailboxes, each marked with their house address/apartment number on it. A metal box surrounds their mailboxes to protect it. Then, the mail truck comes over and picks the metal box up (and inside it the mailboxes), and takes it to the city's central mail processing facility, where it is recieved by the facility's own mail sorting and filtering systems.

    Here, the Mail Truck is like the link between the neighborhood and the city's central mail processing facility. The driver knows where to take the mail to.

    In our case:

    5 cars spawned in the map put their own data into a lua table format, each marked with their Vehicle ID. A table called "data" surrounds their own ID's tables to protect it. Then, the queueGameEngineLua function is called, which picks the "data" table up (and inside it, each vehicle's data marked by ID), and takes it to GE Lua to be recieved by the GE Lua script, which does whatever it wants, using the data for calculations.

    Here, the queueGameEngineLua() function is the link between Vlua and GELua. The code to be queued inside the queueGameEngineLua()'s parameters, i.e GELua's getVehicleData(), tells the data where it should go inside GELua scope.

    Then, we can think of the whole line like this:

    obj:queueGameEngineLua(string.format("GetVehicleData(%q)", lpack.encode(data))) --lpack.encode(data) is sent to GELua using string formatting magic involving the %q thingy

    Red: Vehicle Lua scope
    Blue: GE Lua scope
     
    #7 Derpitron, May 17, 2023
    Last edited: May 17, 2023
  8. NickRyge

    NickRyge
    Expand Collapse

    Joined:
    Aug 21, 2013
    Messages:
    20
    Then I don't understand how your implementation solves my problem. I need the camera vectors in the vehicle lua, I don't need any vehicle data in the GE lua. What you seem to say is that I should just process whatever data I have the GE-lua, which does not solve my problem.

    If I could make a variable globally scoped all I'd have to do was just pass the camera vectors to that globally scoped variable and I'd be able to use it no problem in every vehicle with no overhead. But to my understanding that's not how the VMs work, and in order to get any GE-scoped data in the vehicle lua, I have to do this mess. Doing 2 jumps is inefficient yes, but the only alternative I can think of to have a single jump is having the GE lua call a function in every spawned vehicle, which makes no sense.

    The reason it has to be in the vehicle lua is because I want to use the camera to manipulate the vehicle, which can only be done from the vehicle lua.

    If I have misunderstood something, feel free to explain, but as far as I understand, the 2 jumps is the only way to get any GE scoped data into the VLua.
     
  9. Derpitron

    Derpitron
    Expand Collapse

    Joined:
    Jun 25, 2017
    Messages:
    252
    I honestly don't get why you'd want to send Camera Vectors to VLua.

    This seems like the X-Y Problem https://xyproblem.info/.

    To clear any ambiguity in aims, could you tell me what your final goal is, no matter how simple or "stupid" it may be?

    E.g: all the code I've sent above is for a script which adds dynamic camera effects to the player's camera, depending on the vehicle's speed.
     
  10. NickRyge

    NickRyge
    Expand Collapse

    Joined:
    Aug 21, 2013
    Messages:
    20
    You are right, this might indeed be the XY-problem.

    In the JBEAM of a vehicle, you can set functions to props and flexbodies that make them do things. it can be simpleFunctions in the JBEAM itself, but it can also be set to electrics values for example. While I do not have a definitive goal in mind, I wanted to explore whether I could rotate or move a prop relative to the camera. Lets say it is a police spotlight that centers on the camera, or the "player model head" that always points away from the camera, like you find in GTA, for example, or even a "cornering light" that turns on if you look to the left or the right of the vehicle.

    In the VLUA I could just do electrics["myfunctionname"] = math.deg(Camera vectors), which I have done with the code I have posted above, with no issues.

    Sending data to the GE-lua has no benefits in this case, because it is a per-vehicle, per-camera implementation, the way I see it at least. And even if I could somehow do this from the GE-lua, the way I understand it the GE and VLua VMs need at least one tick to move data back and forth, which means sending it back and forth without starting and processing it in the VLUA would not only mean that the data was delayed - but what if you switch to the next vehicle? Then you just get a call to the previous vehicle with current camera information from a different vehicle. And you could argue that would happen anyway, but then the VLUA could anticipate the function call if it started it itself.


    On a sidenote, when I download mods that do things that could easily be done in the VLUA VM, the authors often STILL do all their processing in GE, either entirely, or with trickery - Which to me is not only counter-intuitive, but also problematic. Looking at the native AI-implementation, it resides pretty much entirely on each seperate AI vehicle, so that each VLUA VM get's its own processor core.
    In other words, I don't entirely understand the fondness of operating within the GE-lua VM.
     
  11. Derpitron

    Derpitron
    Expand Collapse

    Joined:
    Jun 25, 2017
    Messages:
    252
    GE Lua is better for calculations because there may be "MANY" vehicles, but GE Lua VM exists only once, even for servers. GE Lua and VLua have separate libraries and functions exposed to them. E.g GE Lua has the core_camera (all code related to camera management) available only to it.
     
  12. NickRyge

    NickRyge
    Expand Collapse

    Joined:
    Aug 21, 2013
    Messages:
    20
    But this in particular is where I start to wonder whether there is more overhead to a single calculation on the GE or the vehicles, because in my mind it makes far more sense to isolate any silly one-time calculation in the vehicle, instead of holding up the SINGLE instance of GE lua there is. Maybe I am 150% wrong about this, but in my mind there is far more headroom in a vehicle than there is in the GE. if you start to do all your calculations in the GE, aren't you just bottlenecking it? It makes far more sense to put it in a seperate vehicle to reduce the requirements for the core that the GE runs on, since it cannot be scaled, while the vehicle cores effectively just sit there doing nothing.

    But I could be wrong, maybe each vehicle is far more computationally expensive than all of the GE VM combined, which is why they get their own respective cores. That way it makes more sense to put strain on the GE VM since the bottleneck is reached far earlier in a single vehicle, which could potentially increase performance.

    But the way I see it, each vehicle gets it's own processor core. More instances of the same vehicle spread out to different cores is in my mind far more performant than a single GE instance. I really don't understand how it's possible that the GE lua is somehow supposed to be less of a bottleneck to run things on, since it cannot be scaled whatsoever, and as you said - even for servers.

    I understand that it might have some benefits in terms of only running some piece of code once instead of seperately on several vehicles, but I still would assume that - since each vehicle gets its own core - that it would be more performant, even if it is a waste in terms of code-reuse.
     
  13. Derpitron

    Derpitron
    Expand Collapse

    Joined:
    Jun 25, 2017
    Messages:
    252
    I'll be frank: BeamNG lua is a black box to me. Sorely lacking documentation, the only thing I can do is read the code myself, or ask Lua wizards like Zeit who know what they're doing. I have no idea about the specific performance/architectures of GELua and VLua, I can only apply my own general programming knowledge to it.
     
  14. NickRyge

    NickRyge
    Expand Collapse

    Joined:
    Aug 21, 2013
    Messages:
    20
    I appreciate your honesty and input. I am doing the same thing you are, applying my knowledge to what I am working with. I really wish the devs would at least make slightly more of an effort to explains things like this.
     
    • Like Like x 1
  15. Derpitron

    Derpitron
    Expand Collapse

    Joined:
    Jun 25, 2017
    Messages:
    252
    Best of luck to you. If you use VSCode, I recommend you install the "sumneko.lua" extension, and in your json config, add this setting:
    upload_2023-5-18_13-6-6.png

    This adds some nifty autocomplete features for functions and variables.

    https://www.beamng.com/threads/using-git-as-backup-and-sourcecontrol-for-your-mod.92115
    Also, follow the tips in this thread for some tips and instructions on using Git source control and file syncing to supercharge your development.
     
    • Like Like x 2
  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