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.
Parts and slots
TODO: explain how:
- the slot system and the hierarchy works
- explain how the configs work and how parts are being selected