JBeam Syntax

Overview

JBeam is the file format that defines the physics skeleton in the BeamNG engine. It is called JBeam as it is based on JSON (with some exceptions) in order to define node/beam constructs.

Just about everything is case sensitive, and it is not at all friendly to syntax errors, so be careful! You create and edit jbeam files using a text processing program (such as Notepad++, VSCode, etc).


Difference to JSON

We modified the JSON parser a bit to make the life of the vehicle authors easier:

  • Comments: C-style, multi-line, and single-line comments are supported: //... and /* ... */
  • Commas: All commas are optional, but it is advised to only omit the commas at the end of lines.

General Structure

{
  "vehicle": {
    "refNodes":[
        ["ref:", "back:", "left:", "up:"]
        ["f3r", "f5r", "f4l", "f8r"]
    ],
    "cameraExternal":{
      "distance": 6.7,
      "distanceMin": 9,
      "offset": {"x": 0.43, "y": 0.11, "z": 0.55},
      "fov": 77,
    },
  }
}

General Concepts

The data in a jbeam file can be categorized into two types of formatting:

Tables

Think of a a spreadsheet table: Lists are always having a header row that defines the column names and then the data follows. For example:

{
  "vehicle": {
    "nodes": [ /* ... */ ],
    "refNodes":[
      ["ref:", "back:", "left:", "up:"],
      ["f3r", "f5r", "f4l", "f8r"],
      ["f2r", "f3r", "f1l", "f1r"]
    ]
  }
}

So in above example, f3r is the first value in its row in the column ref: of the table refNodes that is contained in vehicle. Think of it this way:

Tables are used when you have a lot of the same data, so you save space specifying the key over and over again.

After parsing, the format is transferred into a key-value storage and every row is matched with the coumn headers. For above example turns into this for the engine:

{
  "vehicle": {
    "nodes": [ /* ... */ ],
    "refNodes": [
      {
        "ref:nodes": "f3r",
        "back:nodes": "f5r",
        "left:nodes": "f4l",
        "up:nodes": "f8r",
      },
      {
        "ref:nodes": "f2r",
        "back:nodes": "f3r",
        "left:nodes": "f1l",
        "up:nodes": "f1r",
      }
    ]
  }
}

Section links

The colon (:) in the header row of a table specifies a link to another section of the vehicle. The format is as follows: valueName:targetSectionName. If targetSectionName is omitted, it will be nodes.

For example the table header in row 15:

 1{
 2  "vehicle": {
 3    "nodes": [
 4      ["id", "posX", "posY", "posZ"],
 5      ["f3r", -0.35, -1.56, 0.25],
 6      ["f5r",  0.00, -1.58, 0.24],
 7      ["f4l", -0.37, -0.98, 0.44],
 8      ["f8r", -0.37, -0.98, 0.22],
 9      ["f2r",  0.37, -0.98, 0.22],
10      ["f3r",  0.37, -0.98, 0.44],
11      ["f1l", -0.69, -0.62, 0.39],
12      ["f1r", -0.65, -0.64, 0.22]
13    ],
14    "refNodes":[
15      ["ref:", "back:", "left:", "up:"],
16      ["f3r", "f5r", "f4l", "f8r"],
17      ["f2r", "f3r", "f1l", "f1r"]
18    ]
19  }
20}

In above example, with "ref:nodes": "f3r" the engine would reference to id = f3r in the nodes table.

Dictionaries

These things are key-value data storages. They are used when the data is quite unique and does not repeat itself so much.

{
  "vehicle": {
    "cameraExternal": {
      "distance": 6.7,
      "distanceMin": 9,
      "offset": {"x": 0.43, "y": 0.11, "z": 0.55},
      "fov": 77,
    },
  }
}

There is no post-processing required with dictionaries, but they are seldomly used.

Out-of-bounds modifiers

There are a lot of input variables that the engine looks for, so we added a way to modify certain values.

Scope modifiers

For example:

 1{
 2  "vehicle": {
 3    "nodes": [
 4      ["id", "posX", "posY", "posZ"],
 5      ["f3r", -0.35, -1.56, 0.25],
 6      ["f5r",  0.00, -1.58, 0.24],
 7      ["f4l", -0.37, -0.98, 0.44],
 8      ["f8r", -0.37, -0.98, 0.22],
 9      {"nodeWeight": 3},
10      ["f2r",  0.37, -0.98, 0.22],
11      ["f3r",  0.37, -0.98, 0.44],
12      ["f1l", -0.69, -0.62, 0.39],
13      ["f1r", -0.65, -0.64, 0.22]
14    ],
15  }
16}

The line 9 in above example is certainly not an ordinary table row. (Any rows need to be with [ ]). It will add the key-value {"nodeWeight":3.0} to all following row entries in the same level and below (the same scope).

So above f2r would look like this for the engine:

1{
2  "id": "f2r",
3  "posX":  0.37,
4  "posY": -0.98,
5  "posZ":  0.22,
6  "nodeWeight": 3,
7}
This feature is very powerful and dangerous at the same time, as scope modifiers can leak to other Jbeam files loaded afterwards with weird consequences. Use with care.

Negating the effects of scope modifiers is also possible by setting them to empty values:

 1{
 2  "vehicle": {
 3    "nodes": [
 4      ["id", "posX", "posY", "posZ"],
 5      ["f3r", -0.35, -1.56, 0.25],
 6      ["f5r",  0.00, -1.58, 0.24],
 7      {"group":"body"},
 8      ["f4l", -0.37, -0.98, 0.44],
 9      ["f8r", -0.37, -0.98, 0.22],
10      {"group":""},
11      ["f2r",  0.37, -0.98, 0.22],
12      ["f3r",  0.37, -0.98, 0.44],
13      ["f1l", -0.69, -0.62, 0.39],
14      ["f1r", -0.65, -0.64, 0.22]
15    ],
16  }
17}

In above example the group modifier would only apply to node f4l and f8r.

Table row modifiers

For example:

1{
2  "vehicle": {
3    "nodes": [
4      ["id", "posX", "posY", "posZ"],
5      ["f2r",  0.37, -0.98, 0.22, {"nodeWeight": 3}],
6      ["f3r",  0.37, -0.98, 0.44],
7    ],
8  }
9}

Line 5 in above example contains a row with a dictionary behind the columns. These values are applied for the row only and do not leak anywhere else. The node f2r would have the nodeWeight property, the node f3r would not.

This is preferred way of specifying additional modifiers as they do not leak into other parts below and all.

Scaling process modifiers

There is a special kind of modifiers declared next to the Tables and Dictionaries in the Jbeam syntax. It is used to scale the numeric values of other modifiers.

 1{
 2  "vehicle": {
 3    "slots": [
 4        ["type", "default", "description"],
 5        ["vehicle_body","vehicle_body", "Body"],
 6        ["paint_design","", "Paint Design"],
 7    ],
 8    "scaledragCoef":2.15,
 9    "controller": [
10        ["fileName"],
11        ["vehicleController", {}],
12    ],
13  }
14}

The modifier works by searching for the string scale in keys inside the Jbeam structure next to the tables and dictionaries, and then multiplying all modifiers with the name specified in the rest of the string with the provided value. For example, the one above will multiply all dragCoef modifiers by 2.15. It works globally on the whole vehicle, until a part that negates it is loaded.

It is most often used to finetune the overall drag coefficient of the vehicle, but it’s possible to scale all other modifiers with it too. Keep in mind though that it makes reading the Jbeam file confusing later on as you are no longer working with real values, so it should only be used in specific cases.

Disable modifier

The Disable modifier is a special modifier that will force rows to be skipped by lua and not read. For example the following lines will not be read, but the lines below will:

[
  {"disable": true},

  ["5","7","6"],
  ["5","6","4"],

  ["0","5","4"],

  ["2","6","7"],
  ["2","7","3"],
  {"disable": ""},
]

It is useful when combined with Slot Variables which pass a Boolean value. That allows you to merge multiple variants of a Jbeam part with small changes, which have separate slotTypes, into one part, with the sections that contain changes being surrounded with Disable modifiers which use Boolean slot variables:

[
  {"disable": "$variant1"},

  ["5","7","6"],
  ["5","6","4"],

  ["0","5","4"],

  ["2","6","7"],
  ["2","7","3"],

  {"disable": "$variant2"},

  ["5b","7","6"],
  ["5b","6","4"],

  ["0","5b","4"],

  ["2b","6","7"],
  ["2b","7","3"],

  {"disable": ""},
]

Load a CSV file inside Jbeam

The special includeCSV modifier will load a CSV file provided in a file path relative to the game’s folder. Example usage:

{
"nodes": [
     ["id", "posX", "posY", "posZ"],
     {"nodeWeight":222.222},
     {"group":"barrier"},
     {"nodeMaterial":"|NM_ASPHALT"},
     {"frictionCoef":1.3},
     {"collision":true},
     {"selfCollision":false},
     {"includeCSV": "/vehicles/barrier/barrier_nodes.csv"}
     ["barrier_17", -1.5, 0.16, 1.1],
     {"group":""},
],
}

The CSV file content:

"id", "posX", "posY", "posZ"
"barrier_0", -1.5, -0.4, 0.0
"barrier_1", -1.5, -0.16, 0.6
"barrier_2", -1.5, 0.4, 0.0
"barrier_3", -1.5, 0.16, 0.6
"barrier_4", 1.5, -0.4, 0.0
"barrier_5", 1.5, -0.16, 0.6
"barrier_6", 1.5, 0.4, 0.0
"barrier_7", 1.5, 0.16, 0.6
"barrier_8", 0.0, 0.16, 0.6
"barrier_9", 0.0, 0.4, 0.0
"barrier_10", 0.0, -0.16, 0.6
"barrier_11", 0.0, -0.4, 0.0
"barrier_12", 1.5, -0.16, 1.1
"barrier_13", 1.5, 0.16, 1.1
"barrier_14", 0.0, 0.16, 1.1
"barrier_15", 0.0, -0.16, 1.1
"barrier_16", -1.5, -0.16, 1.1

Functions

Functions are used to dynamically calculate numeric values in Jbeam (for example when a number depends on a variable), and to concatenate string values. They are essentially automatically calculated mathematical and/or logical expressions. Despite having limited use cases in our official content, they have very powerful capabilities.

Since Jbeam files are essentially databases of sandboxed lua expressions, it is possible to construct practically any kind of expression that would be possible in clean lua. The only exception is that Jbeam can’t assign new lua variables, so you can’t reuse the results of calculations you have made in earlier sections of Jbeam.

In order to replace a numeric value with a function, you need to put it in double quotation marks like a string, and start the expression with $=. For example:

{
  "vehicle": {
    "nodes": [
      {"nodeWeight": 4}, //before
      {"nodeWeight": "$=2+2"}, //after
    ],
  }
}

Concatenating a string is similar, the double quotation marks should include the entire function and all of the sub-strings need to be put inside single quotation marks instead:

{
  "vehicle": {
    "nodes": [
      {"group": "my_group"}, //before
      {"group": "$='my_'..'group'"}, //after
    ],
  }
}

Functions are closely tied to variables and often used together with them. A variable used by itself needs to be in double quotation marks, but when used in a function, the function’s own quotation marks will do the job instead.

{
  "vehicle": {
    "nodes": [
      {"nodeWeight": "$weight"}, //before
      {"nodeWeight": "$=$weight*1"}, //after
    ],
  }
}

Operators

Our functions support all standard operators found in lua. They can all be used in function expressions.

Operator type
Examples
Notes
Operator type
Arithmetic
Examples
+ - * / % ^
Notes
The - operator works both for substraction, as in ‘$=var1 - var2’, and for negation, as in ‘$= -var1’
Operator type
Relational
Examples
== ~= > < >= <=
Notes
Can be used to check if a variable exists by comparing it to nil
Operator type
Logical
Examples
and or not
Notes
Most often used to construct ternary operators in case you want to perform arithmetics on one of the operands, which would not be allowed if it’s nil
Operator type
String concatenation
Examples
..
Notes
When concatenating a number to a string, it must be put in parentheses, and you should always remember to round or clamp it
Operator type
String length
Examples
#
Notes
When using a variable experssion, it needs to be put in parentheses. Does not allow you to get the length of a Jbeam table

Any operand on any side of an arithmetical, relational or logical operator can be a number, a variable, or one of our built-in functions. The operands of the string concatenation function can be strings, which need to be put in single quotation marks, or numbers, variables or expressions, which need to be put in parentheses. The operands of string length function can be only strings and variables, the latter need to be in parentheses.

Of course, you can combine various operators, numbers, variables and built-in functions to create complex expressions that fit your purpose:

{
  "vehicle": {
    "slots": [
      {"nodeOffset":{"x":"$=case($trackwidth_F == nil, $trackoffset_F+0.26, $trackwidth_F)", "y":-1.295, "z":0.351}}
    ],
    "pressureWheels": [
      {"brakeTorque":"$=$brakebias == nil and $brakestrength*1500 or $brakestrength*5000*(1-$brakebias)"},
    ],
  }
}

Built-in functions

A few lua files present in our game provide complete functions ready to use in your Jbeam files. You don’t have to import any libraries or attach prefixes to use them, they are ready from the get-go. They can be used individually or as parts of your own custom functions, on any numbers, variables or other functions. Although most of them are rarely used in official content, you are welcome to use them in mods if you need to.

Clamping and rounding numbers

The functions defined in lua/common/mathlib.lua are mostly used to round and clamp numbers.

Name
Description
Example
Notes
Name
round(x)
Description
rounds x to an Integer
Example
$=round($var)
Notes
Name
square(x)
Description
squares x
Example
$=square($var)
Notes
Produces the same effect as ‘$=$var^2’
Name
clamp(x, y, z)
Description
clamps x to y (min) and z (max) values
Example
$=clamp($var, 0, 1)
Notes
Name
smoothstep(x)
Description
performs the Smoothstep sigmoid interpolation function
Example
$=smoothstep($var)
Notes
From the Smoothstep interpolation and clamping family, commonly used in game engines and graphics
Name
smootherstep(x)
Description
performs the Smootherstep sigmoid interpolation function
Example
$=smootherstep($var)
Notes
From the Smoothstep interpolation and clamping family, commonly used in game engines and graphics
Name
smootheststep(x)
Description
performs the Smootheststep sigmoid interpolation function
Example
$=smootheststep($var)
Notes
From the Smoothstep interpolation and clamping family, commonly used in game engines and graphics

Debug and compatibility functions

Functions in lua/common/jbeam/expressionParser.lua are used for debug and backwards compatibility reasons.

Name
Description
Example
Notes
Name
case(selector, …)
Description
case selector, input can be int n (returns the nth+1 param it was given) or bool (acts as a ternary if) as the first argument, any number of arguments after that, if n > number of given params returns last param
Example
$=case($var == nil, 0.25, $var)
Notes
Commonly used for backwards compatibility with removed variables as in example. Cannot be used to perform arithmetics within the expression on variables that might have nil values
Name
print(val, label)
Description
prints val in console and passes it on, if label provided it will print ’label = val’, otherwise only val
Example
$=print($var, var)
Notes
Debug only tool, should not be used in final code

Built-in math library

The largest quantity of functions is present in our built-in math library. It is a modified version of a standard Math library for lua, it doesn’t have many high-level differences so you can refer to the official lua documentation for further explanation. These are used the same way as in lua files, except they don’t need the math. prefix.

Be careful with this category of functions! Always make sure that your expression will do what you want it to do, and its result is predictable and cannot possibly produce a nil value. When an expression fails, the value it was modifying will be set to default, which can lead to an instability!

Name
Description
Example
Notes
Name
abs(x)
Description
returns the absolute of x
Example
$=abs($var)
Notes
Name
acos(x)
Description
returns the inverse cosine of x in radians
Example
$=acos($var)
Notes
Inverse operation to cos(x)
Name
asin(x)
Description
returns the inverse sine of x in radians
Example
$=asin($var)
Notes
Inverse operation to sin(x)
Name
atan2(x)
Description
returns the inverse tangent of x in radians
Example
$=atan($var)
Notes
Inverse operation to tan(x)
Name
atan2(x, y)
Description
returns the inverse tangent of (y/x) in radians
Example
$=atan2($var1,$var2)
Notes
Uses the signs of both arguments to find the quadrant of the result
Name
ceil(x)
Description
returns the integer no greater than x
Example
$=ceil($var)
Notes
Behavior consists for negative values
Name
cos(x)
Description
returns the cosine of x in radians
Example
$=cos($var)
Notes
Name
cosh(x)
Description
returns the hyperbolic cosine of x in radians
Example
$=cosh($var)
Notes
Name
deg(x)
Description
converts from radians to degrees
Example
$=deg($var)
Notes
Inverse operation to rad(x)
Name
exp(x)
Description
returns e (base of natural logarithms) to the power of x
Example
$=deg($var)
Notes
exp(1) returns e
Name
floor(x)
Description
returns the integer no less than x
Example
$=floor($var)
Notes
Behavior consists for negative values
Name
fmod(x,y)
Description
returns the floating-point remainder of x/y
Example
$=fmod($var1,$var2)
Notes
Produces the same result as ‘$=$var1%$var2’
Name
frexp(x)
Description
returns the mantissa and exponent of x
Example
$=frexp($var)
Notes
Only the mantissa is usable in Jbeam and it will be assigned as the value
Name
huge
Description
returns a value larger than any numeric value
Example
$=huge
Notes
Produces the same result as ‘FLT_MAX’
Name
ldexp(x,y)
Description
returns x*2^y
Example
$=ldexp($var1,$var2)
Notes
Inverse operation to frexp(x)
Name
log(x,y)
Description
returns the logarithm of x in the base y
Example
$=log($var1,$var2)
Notes
If y is not given, it defaults to e, and the function becomes the inverse of exp(x)
Name
log10(x)
Description
returns the logarithm of x in the base 10
Example
$=log10($var)
Notes
Name
max(x, …)
Description
returns the largest of provided arguments
Example
$=max($var1, $var2, $var3)
Notes
Name
min(x, …)
Description
returns the smallest of provided arguments
Example
$=min($var1, $var2, $var3)
Notes
Name
modf(x)
Description
returns the integral and fractional of x
Example
$=modf($var)
Notes
Only the integral is usable in Jbeam and it will be assigned as the value
Name
pi
Description
returns 3.1415926535898
Example
$=pi
Notes
Name
pow(x,y)
Description
returns x to the power of y
Example
$=pow($var1,$var2)
Notes
Produces the same result as ‘$=$var1^$var2’
Name
rad(x)
Description
converts from degrees to radians
Example
$=rad($var)
Notes
Name
random(x,y)
Description
Returns a random float between x and y; if y not provided returns a random float between 1 and x; if no arguments provided returns a random float between 0 and 1; all ranges inclusive, uniform distribution; the value will change each time the vehicle is reloaded (ctrl+r by default)
Example
$=random($var1,$var2)
Notes
WARNING!!! USING RANDOM VALUES IN SOFTBODY SIMULATION CAN LEAD TO UNPREDICTABLE AND IRREPRODUCIBLE BEHAVIOR! USE AT YOUR OWN RISK, AND ONLY EVER RANDOMIZE THINGS THAT DO NOT IMPACT THE PHYSICAL SIMULATION, SUCH AS FLEXBODIES OR GLOWMAPS!
Name
randomseed(x)
Description
Sets the seed for the Random function, equal seeds produce equal sequences of numbers
Example
$=randomseed(1234)
Notes
Should be assigned to an unused dummy modifier
Name
sin(x)
Description
returns the sine of x in radians
Example
$=sin($var)
Notes
Name
sinh(x)
Description
returns the hyperbolic sine of x in radians
Example
$=sinh($var)
Notes
Name
sqrt(x)
Description
returns the square root of x
Example
$=sqrt($var)
Notes
Produces the same result as ‘$=$var^0.5’
Name
tan(x)
Description
returns the tangent of x in radians
Example
$=tan($var)
Notes
Name
tanh(x)
Description
returns the hyperbolic tangent of x in radians
Example
$=tanh($var)
Notes

Anatomy of a vehicle jbeam

Vehicles in BeamNG are build together with parts, those parts contain then the so-called sections that contain the actual data.

Every jbeam file is a flat key-value dictionary of available parts this file contains. I.e.

1{
2"autobello_fender_RR": {
3  // ...
4},
5"autobello_fender_RL": {
6  // ...
7},
8}

The key is the name of the part, its value are its sections. The engine constructs a tree of parts during loading time and it needs to know what’s the base or root part. "slotType":"main" is doing that.

Lets try to look at a simple complete jbeam file (to keep it readable we cut it short in some points) (barrier.jbeam):

 1{
 2"barrier_01l": {
 3  "information":{
 4    "name":"Concrete Barrier",
 5    "authors":"BeamNG",
 6  },
 7  "slotType":"main",
 8  "refNodes":[
 9    ["ref:", "back:", "left:", "up:"],
10    ["barrier_11", "barrier_9", "barrier_4", "barrier_15"],
11  ],
12  "cameraExternal":{
13    "distance":5.0,
14    "distanceMin":3,
15    "offset":{"x":0, "y":0.4, "z":0.5},
16    "fov":65,
17  },
18  "flexbodies": [
19    ["mesh", "[group]:"],
20    {"rotation":{"x":0, "y":0, "z":0}, "translation":{"x":0, "y":0, "z":0}},
21    ["barrier_01", ["barrier"]],
22  ],
23  "nodes": [
24    ["id", "posX", "posY", "posZ"],
25    {"nodeWeight":222.222},
26    {"group":"barrier"},
27    {"nodeMaterial":"|NM_ASPHALT"},
28    {"frictionCoef":1.3},
29    {"collision":true},
30    {"selfCollision":false},
31    ["barrier_0", -1.5, -0.4, 0.0],
32    ["barrier_1", -1.5, -0.16, 0.6],
33    ["barrier_2", -1.5, 0.4, 0.0],
34    // ...
35    ["barrier_16", -1.5, -0.16, 1.1],
36    ["barrier_17", -1.5, 0.16, 1.1],
37    {"group":""},
38  ],
39  "beams": [
40    ["id1:", "id2:"],
41    {"beamPrecompression":1, "beamType":"|NORMAL", "beamLongBound":1, "beamShortBound":1},
42    {"beamSpring":10000000,"beamDamp":38000},
43    {"beamDeform":"FLT_MAX","beamStrength":"FLT_MAX"},
44    //main shape
45    ["barrier_5","barrier_15"],
46    ["barrier_2","barrier_3"],
47    // ...
48  ],
49  "triangles": [
50    ["id1:","id2:","id3:"],
51    {"groundModel":"asphalt"},
52    {"group":"barrier"},
53    ["barrier_7","barrier_6","barrier_9"],
54    ["barrier_5","barrier_4","barrier_6"],
55    // ...
56  ],
57},
58}

Lets try to dissect above example:

  • barrier_01l is the name of the part
  • "slotType":"main" defines that this is the root part and can be directly spawned.
  • information, refNodes, cameraExternal, flexbodies, nodes, beams, triangles are sections in the part that contain the data.
Last modified: 16/4/2024 14:55

Any further questions?

Join our discord