Components are generic key-value stores that extend/overwrite Jbeam code. They use the Json dictionary syntax. Their main use case is to provide compatibility between different parts of the vehicle that can be selected independently.
"components": {
"wheels": {
"FL": {
"diameter": 0.56,
"something": 3,
},
},
"cameras": {
"MyDefault":{
"distance":4,
"distanceMin":3,
"offset":{"x":0.0, "y":0.0, "z":0.59},
"fov":65,
}
}
}
Usage
Components are used in Jbeam via a $>>
operator. It points to a data store that will be used for the value.
"cameraExternal":"$>>components.cameras.MyDefault",
Extending and overriding
A part can extend or overwrite component data:
/// the main part
"components": {
"wheels": {
"FL": {
"diameter": 0.56,
"something": 3,
},
},
}
/// the sub part
"components": {
"wheels": {
"RR": {
"diameter": 0.1,
"something": 1,
},
"FL": {
"diameter": 2, // overwrites
"somethingElse": 3,
},
}
}
Scope
Components work not only for table data, but also for modifiers:
"components": {
"collisionEnabled": true,
},
"nodes" :[
["id", "posX", "posY", "posZ"],
{"collision" :"$>>components.collisionEnabled"},
],
They can be used as table rows:
"components": {
["node3", -0.90, -0.93, 0.23],
},
"nodes" :[
["id", "posX", "posY", "posZ"],
["node1", -0.90, -0.93, 0.23],
["node2", -0.33, -0.95, 0.23],
"$>>components.node3",
["node4", 0.90, -0.93, 0.23],
{"group":""},
],
They can also be used inside of another component which is a table row:
"components": {
"posZ": 0.23,
["node3", -0.90, -0.93, "$>>components.posZ"],
},
"nodes" :[
["id", "posX", "posY", "posZ"],
["node1", -0.90, -0.93, 0.23],
["node2", -0.33, -0.95, 0.23],
"$>>components.node3",
["node4", 0.90, -0.93, 0.23],
{"group":""},
],
Expressions
Components work in functions and expressions too. The syntax for their usage there is $components.componentName
:
"components": {
"posZ": 0.03,
},
"nodes" :[
["id", "posX", "posY", "posZ"],
["node1", -0.90, -0.93, 0.23],
["node2", -0.33, -0.95, 0.23],
["node3", -0.90, -0.93, "$= $components.posZ + 0.20"],
["node4", 0.90, -0.93, 0.23],
{"group":""},
],
You can also access nested components with $components.parentComponent.childComponent
:
"components": {
"pos":{
"X":-0.45,
"Y":-0.93,
"Z": 0.03,
},
},
"nodes" :[
["id", "posX", "posY", "posZ"],
["node1", -0.90, -0.93, 0.23],
["node2", -0.33, -0.95, 0.23],
["node3", "$= $components.pos.X * 2", "$= $components.pos.Y", "$= $components.pos.Z + 0.20"],
["node4", 0.90, -0.93, 0.23],
{"group":""},
],
You can use vec3, quat and concat operations on nested components, for example:
"$=concat($components.wheels.F, ',')"
Electrics section
The components section can store the electrics section inside of it, and it will be read as normal. This is useful for deep table merging, such as adding a new custom value or smoother in a different part of the vehicle.
"components": {
"electrics": {
"customValues": [
["electricsName", "electricsFunction"],
["police_flasher_filament","pwm(sign(electrics.lightbar), 0.5, 0.5) * electrics.electricalLoadCoef"],
]
"smoothers": [
["electricsName", "smootherType", "params"],
["police_flasher_filament","temporalNonLinear", [4,4]],
]
}
}
Examples
Some examples of how we use components in vanilla vehicles in the game.
Basic examples
When a part attaches to nodes of a different part which isn’t directly related to it, and the names of the nodes can vary, the node names for each variant of the part get declared as components and as such are referred to by the part in question. For example, semi truck dump upfit attaches to both front and rear halves of the frame which can be changed independently. So for each variant of the front frame we set the names of the frame end nodes it will attach to as components:
"components": {
"frontFrameEndNodes": {
"lastTopRight": "ff5r"
"lastTopLeft": "ff5l"
"lastBottomRight": "ff15r"
"lastBottomLeft": "ff15l"
"2ndLastTopRight": "ff4r"
"2ndLastTopLeft": "ff4l"
"2ndLastBottomRight": "ff14r"
"2ndLastBottomLeft": "ff14l"
},
},
And in the upfit Jbeam file, we attach the upfit by referring to the names stored inside of the components, rather than directly to node names:
"beams": [
["id1:", "id2:"],
["dbph1r", "$=$components.frontFrameEndNodes.lastTopRight"],
["dbph1r", "$=$components.frontFrameEndNodes.lastBottomRight"],
["dbph1l", "$=$components.frontFrameEndNodes.lastTopLeft"],
["dbph1l", "$=$components.frontFrameEndNodes.lastBottomLeft"],
],
Components can also be used to offset the location of a part based on another part. In this example, the position of a semi truck tire depends on the chosen wheel variant, as the different variants have different rim depths.
Components in the wheel:
"components": {
"dualyOffsets_R": {
"offsetInner": 0.075
"offsetOuter": -0.255
},
},
Usage in the tire:
"flexbodies": [
["mesh", "[group]:", "nonFlexMaterials"],
["tire_02d_22.5x9_38.5", ["wheel_R2L","tire_R2L"], [], {"pos":{"x":"$=-1*$components.dualyOffsets_RR.offsetInner+0.814", "y":0.00, "z":0.00}, "rot":{"x":0, "y":0, "z":0}, "scale":{"x":1.00, "y":1.00, "z":1.00}}],
["tire_02d_22.5x9_38.5", ["wheel_R2R","tire_R2R"], [], {"pos":{"x":"$=$components.dualyOffsets_RR.offsetInner-0.814", "y":0.00, "z":0.00}, "rot":{"x":0, "y":0, "z":180}, "scale":{"x":1.00, "y":1.00, "z":1.00}}],
["tire_02d_22.5x9_38.5", ["wheel_R2LL","tire_R2LL"], [], {"pos":{"x":"$=-1*$components.dualyOffsets_RR.offsetOuter+0.814", "y":0.00, "z":0.00}, "rot":{"x":0, "y":0, "z":0}, "scale":{"x":1.00, "y":1.00, "z":1.00}}],
["tire_02d_22.5x9_38.5", ["wheel_R2RR","tire_R2RR"], [], {"pos":{"x":"$=$components.dualyOffsets_RR.offsetOuter-0.814", "y":0.00, "z":0.00}, "rot":{"x":0, "y":0, "z":180}, "scale":{"x":1.00, "y":1.00, "z":1.00}}],
],
The last simple use case for the components is to store the electrics section of Jbeam inside of them.
"components": {
"electrics": {
"customValues": [
["electricsName", "electricsFunction"],
["lowbeam_filament","electrics.lowbeam * electrics.electricalLoadCoef"],
["highbeam_filament","electrics.highbeam * electrics.electricalLoadCoef"],
["lowhighbeam_filament","electrics.lowhighbeam * electrics.electricalLoadCoef"],
["taillight_filament","electrics.lowhighbeam * electrics.electricalLoadCoef"],
["brakelight_filament","electrics.brakelights * electrics.electricalLoadCoef"],
["brakelight_signal_L_filament","electrics.brakelight_signal_L * electrics.electricalLoadCoef"],
["brakelight_signal_R_filament","electrics.brakelight_signal_R * electrics.electricalLoadCoef"],
["lowhighbeam_signal_L_filament","electrics.lowhighbeam_signal_L * electrics.electricalLoadCoef"],
["lowhighbeam_signal_R_filament","electrics.lowhighbeam_signal_R * electrics.electricalLoadCoef"],
["reverselight_filament","electrics.reverse * electrics.electricalLoadCoef"],
["foglight_filament","electrics.fog * electrics.electricalLoadCoef"],
["signal_L_filament","electrics.signal_L * electrics.electricalLoadCoef"],
["signal_R_filament","electrics.signal_R * electrics.electricalLoadCoef"],
["police_flasher_filament","pwm(sign(electrics.lightbar), 0.5, 0.5) * electrics.electricalLoadCoef"],
]
"smoothers": [
["electricsName", "smootherType", "params"],
["lowbeam_filament","temporalNonLinear", [10,10]],
["highbeam_filament","temporalNonLinear", [10,10]],
["lowhighbeam_filament","temporalNonLinear", [10,10]],
["taillight_signal_L_filament","temporalNonLinear", [15,15]],
["taillight_signal_R_filament","temporalNonLinear", [15,15]],
["taillight_filament","temporalNonLinear", [10,10]],
["brakelight_filament","temporalNonLinear", [10,10]],
["brakelight_signal_L_filament","temporalNonLinear", [15,15]],
["brakelight_signal_R_filament","temporalNonLinear", [15,15]],
["lowhighbeam_signal_L_filament","temporalNonLinear", [15,15]],
["lowhighbeam_signal_R_filament","temporalNonLinear", [15,15]],
["reverselight_filament","temporalNonLinear", [10,10]],
["foglight_filament","temporalNonLinear", [10,10]],
["signal_L_filament","temporalNonLinear", [15,15]],
["signal_R_filament","temporalNonLinear", [15,15]],
["police_flasher_filament","temporalNonLinear", [4,4]],
]
}
}
Advanced example
This is the most advanced use case. Components are necessary when the visual mesh that is used for the part depends on several different parts that are used on the vehicle. The best example of this is a semi truck drive shaft. Its length and number of sections depends on the lengths on the front and rear parts of the frame, the position of the frontmost rear drive axle, and the position of the transmission which can be influenced by the engine size used. In this case, the driveshaft part itself defines many different variants, and the Disable Modifier picks out the variants which will not be used based on components defined in the appropriate parts. The names of the meshes themselves can also be variable and stored in components, similarly to the first example.
In game, there are 7 actual front driveshaft variants for the Gavril T-Series, one for each variant of the front frame. Each one has a Props section similar to this (simplified for clarity), where the driveshaft variant is picked depending on the components present in chosen parts:
["driveshaft", "$=$components.driveshaft_R_tridem.slipshaftMesh_convL", "0ax2","dshaftCoL","0ax2r", {"x":89.881, "y":-89.790, "z":28.367}, {"x":0, "y":-1, "z":0} , {"x":0, "y":0, "z":0}, -360, 360, 0, 1, {"disable":"$=$components.driveshaft_R_tridem.enable == nil or $components.driveshaft_R_shortframe.enable == nil"}],
["driveshaft", "$=$components.driveshaft_R_tridem.driveshaftMesh_convL", "dshaftCoL","0ax2","0ax2r", {"x":-90.162, "y":90.261, "z":-62.157}, {"x":0, "y":1, "z":0} , {"x":0, "y":0, "z":0}, -360, 360, 0, 1, {"disable":"$=$components.driveshaft_R_tridem.enable == nil or $components.driveshaft_R_shortframe.enable == nil"}],
["driveshaft", "$=$components.driveshaft_R_tandem.slipshaftMesh_convL", "1ax2","dshaftCoL","1ax2r", {"x":89.881, "y":-89.790, "z":28.367}, {"x":0, "y":-1, "z":0} , {"x":0, "y":0, "z":0}, -360, 360, 0, 1,{"disable":"$=$components.driveshaft_R_tandem.enable == nil or $components.driveshaft_R_shortframe.enable == nil"}],
["driveshaft", "$=$components.driveshaft_R_tandem.driveshaftMesh_convL", "dshaftCoL","1ax2","1ax2r", {"x":-90.162, "y":90.261, "z":-62.157}, {"x":0, "y":1, "z":0} , {"x":0, "y":0, "z":0}, -360, 360, 0, 1,{"disable":"$=$components.driveshaft_R_tandem.enable == nil or $components.driveshaft_R_shortframe.enable == nil"}],
["driveshaft", "$=$components.driveshaft_R_tridem.slipshaftMesh_convL", "0ax2","dshaftCoL","0ax2r", {"x":89.881, "y":-89.790, "z":28.367}, {"x":0, "y":-1, "z":0} , {"x":0, "y":0, "z":0}, -360, 360, 0, 1, {"disable":"$=$components.driveshaft_R_tridem.enable == nil or $components.driveshaft_R_shortframe.enable == true"}],
["driveshaft", "$=$components.driveshaft_R_tridem.driveshaftMesh_convL", "dshaftCoL","0ax2","0ax2r", {"x":-90.162, "y":90.261, "z":-62.157}, {"x":0, "y":1, "z":0} , {"x":0, "y":0, "z":0}, -360, 360, 0, 1, {"disable":"$=$components.driveshaft_R_tridem.enable == nil or $components.driveshaft_R_shortframe.enable == true"}],
["driveshaft", "us_semi_driveshaft_R_1_0", "1ax2","dshaftMframe","1ax2r", {"x":89.881, "y":-89.790, "z":28.367}, {"x":0, "y":-1, "z":0} , {"x":0, "y":0, "z":0}, -360, 360, 0, 1,{"disable":"$=$components.driveshaft_R_tandem.enable == nil or $components.driveshaft_R_shortframe.enable == true"}],
["driveshaft", "us_semi_slipshaft_R_0_9", "dshaftMframe","1ax2","1ax2r", {"x":-90.162, "y":90.261, "z":-62.157}, {"x":0, "y":1, "z":0} , {"x":0, "y":0, "z":0}, -360, 360, 0, 1,{"disable":"$=$components.driveshaft_R_tandem.enable == nil or $components.driveshaft_R_shortframe.enable == true"}],
["driveshaft", "$=$components.driveshaft_R_tandem.driveshaftMesh_convL", "dshaftCoL","dshaftMframe","mf12r", {"x":89.881, "y":-89.790, "z":28.367}, {"x":0, "y":1, "z":0} , {"x":0, "y":0, "z":0}, -360, 360, 0, 1,{"disable":"$=$components.driveshaft_R_tandem.enable == nil or $components.driveshaft_R_shortframe.enable == true"}],
["driveshaft", "$=$components.driveshaft_R_tandem.slipshaftMesh_convL", "dshaftMframe","mf12r","dshaftCoL", {"x":-89.356, "y":-174.514, "z":-69.501}, {"x":0, "y":-1, "z":0}, {"x":0, "y":0, "z":0}, -360, 360, 0, 1,{"disable":"$=$components.driveshaft_R_tandem.enable == nil or $components.driveshaft_R_shortframe.enable == true"}],
["driveshaft", "us_semi_driveshaft_R_1_25", "1ax2","dshaftLframe","1ax2r", {"x":89.881, "y":-89.790, "z":28.367}, {"x":0, "y":-1, "z":0}, {"x":0, "y":0, "z":0}, -360, 360, 0, 1,{"disable":"$=$components.driveshaft_R_tandem.enable == nil or $components.driveshaft_R_shortframe.enable == true"}],
["driveshaft", "us_semi_slipshaft_R_1_5", "dshaftLframe","1ax2","1ax2r", {"x":-90.162, "y":90.261, "z":-62.157}, {"x":0, "y":1, "z":0}, {"x":0, "y":0, "z":0}, -360, 360, 0, 1,{"disable":"$=$components.driveshaft_R_tandem.enable == nil or $components.driveshaft_R_shortframe.enable == true"}],
["driveshaft", "$=$components.driveshaft_R_tandem.driveshaftMesh_conv", "tra1","dshaftLframe","mf12r", {"x":89.881, "y":-89.790, "z":28.367}, {"x":0, "y":1, "z":0}, {"x":0, "y":0, "z":0}, -360, 360, 0, 1,{"disable":"$=$components.driveshaft_R_tandem.enable == nil or $components.driveshaft_R_shortframe.enable == true"}],
["driveshaft", "$=$components.driveshaft_R_tandem.slipshaftMesh_convL", "dshaftLframe","mf12r","tra1", {"x":-89.356, "y":-174.514, "z":-69.501}, {"x":0, "y":-1, "z":0}, {"x":0, "y":0, "z":0}, -360, 360, 0, 1,{"disable":"$=$components.driveshaft_R_tandem.enable == nil or $components.driveshaft_R_shortframe.enable == true"}],
The components used for the driveshaft names are present in a sub-slot of the rear frame section:
"components": {
"driveshaft_R_tridem": {
"slipshaftMesh_convL": "us_semi_slipshaft_R_1_0"
"driveshaftMesh_convL": "us_semi_driveshaft_R_0_9"
"slipshaftMesh_convM": "us_semi_slipshaft_R_1_0"
"driveshaftMesh_convM": "us_semi_driveshaft_R_1_0"
"slipshaftMesh_convS": "us_semi_slipshaft_R_0_55"
"driveshaftMesh_convS": "us_semi_driveshaft_R_0_42"
"slipshaftMesh_cabovL": "us_semi_slipshaft_R_1_0"
"driveshaftMesh_cabovL": "us_semi_driveshaft_R_0_9"
"slipshaftMesh_cabovM": "us_semi_slipshaft_R_1_0"
"driveshaftMesh_cabovM": "us_semi_driveshaft_R_1_0"
"slipshaftMesh_cabovS": "us_semi_slipshaft_R_0_55"
"driveshaftMesh_cabovS": "us_semi_driveshaft_R_0_42"
},
"driveshaft_R_tandem": {
"slipshaftMesh_convL": "us_semi_slipshaft_R_1_0"
"driveshaftMesh_convL": "us_semi_driveshaft_R_2_0"
"slipshaftMesh_convM": "us_semi_slipshaft_R_1_0"
"driveshaftMesh_convM": "us_semi_driveshaft_R_2_5"
"slipshaftMesh_convS": "us_semi_slipshaft_R_1_0"
"driveshaftMesh_convS": "us_semi_driveshaft_R_1_5"
"slipshaftMesh_cabovL": "us_semi_slipshaft_R_1_0"
"driveshaftMesh_cabovL": "us_semi_driveshaft_R_2_0"
"slipshaftMesh_cabovM": "us_semi_slipshaft_R_1_0"
"driveshaftMesh_cabovM": "us_semi_driveshaft_R_2_5"
"slipshaftMesh_cabovS": "us_semi_slipshaft_R_0_42"
"driveshaftMesh_cabovS": "us_semi_driveshaft_R_0_23"
},
},
Then, the various other parts that influence the driveshaft length use components to deactivate the disable modifier for the correct mesh variants:
"components": {
"driveshaft_R_tridem": {
"enable": true
}
},