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}
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.
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.
Advanced
These are some advanced parts of the syntax. They can be useful for an experienced modder, but can be confusing for a beginner.
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.
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.
Debug and compatibility functions
Functions in lua/common/jbeam/expressionParser.lua
are used for debug and backwards compatibility reasons.
1st way: if 1st param is a bool, if true returns 2nd param, if false returns 3rd param
2nd way: if 1st param is an integer ’n’ it returns the ’n’th+1 param (returns last param if ’n’ is > # of params).
if $var = nil: return 0.25
if $var = 0.5: return $var
$=case($a, ‘foo’, ‘bar’, ‘baz’)
if $a = 1: return ‘foo’
if $a = 2: return ‘bar’
if $a = 3: return ‘baz’
Table oprtations
Table operations are usable together with components .
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.
Advanced modifiers
Some advanced out-of-bounds modifiers that can exist in the Jbeam syntax. They are only used in very specific circumstances.
Scaling process modifiers
There is a special kind of modifiers declared next to the Tables and Dictionaries in the main part of Jbeam. 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": ""},
]
Include Modifier
Available in BeamNG version 0.34 and up, Include modifier replaces the unused and deprecated includeCSV modifier. Its purpose is to store contents of a Jbeam table in a CSV file, provided in a file path relative to the game’s folder. It uses an expression syntax similar to variables:
{
"mainEngine":{
"torque": "$=include('vehicles/common/engines/v8_classic/gavril_291_mainTorqueCurve.csv')",
"idleRPM":700,
//...
}
}
The CSV file content:
"rpm","torque"
0,0
500,170
1000,283
1500,342
2000,370
2500,390
3000,402
3500,403
4000,391
4500,360
5000,320
5500,277
6000,235
6500,190
7000,146
7500,98
8000,65
How the game sees the section:
{
"mainEngine":{
"torque":[
["rpm", "torque"],
[0, 0],
[500,170],
[1000,283],
[1500,342],
[2000,370],
[2500,390],
[3000,402],
[3500,403],
[4000,391],
[4500,360],
[5000,320],
[5500,277],
[6000,235],
[6500,190],
[7000,146],
[7500,98],
[8000,65],
],
"idleRPM":700,
//...
}
}
This modifier is useful for storing common Jbeam data, especially powertrain components, shared between vehicles. For example, 2 vehicles might use the same engine, but cannot use the same base Jbeam for it due to slightly different flexbodies adjusted for clearance, intake positions, etc.
Aside from CSV files, it also supports XLSX files. Keep in mind that the XLSX parser is slow and should only be used for prototyping and debug purposes, the final product should be using a CSV file instead. Example usage of XLSX, file available here :
{
"mainEngine":{
"torque": "$=include('vehicles/common/engines/v8_classic/gavril_291_mainTorqueCurve.xlsx')",
"idleRPM":700,
//...
}
}
When the XLSX file contains multiple sheets, you can specify which one you want to take the data from in the second argument of the modifier:
{
"torque": "$=include('vehicles/common/engines/v8_classic/gavril_291_mainTorqueCurve.xlsx', 'Sheet1')",
}
When a sheet is specified, you can reduce the range further to specific cells using the third argument. The library supports a variety of range queries:
- Single Cell: Specify a single cell, e.g., “B2”.
- Entire Column: Specify a column, e.g., “C”.
- Entire Row: Specify a row, e.g., “3”.
- Column Range: Specify a column range, e.g., “B:D”.
- Row Range: Specify a row range, e.g., “2:4”.
- Cell Range: Specify a rectangular range, e.g., “A1:C3”.
- Entire Sheet: Omit the range to get the entire sheet.
Example usage:
{
"torque": "$=include('vehicles/common/engines/v8_classic/gavril_291_mainTorqueCurve.xlsx', 'Sheet1', 'A1:C3')",
}
These arguments are only available for XLSX files, they are not supported for CSV.