AcresRig.h#
Acres/Source/Acres/AcresRig.h Generated
Data-driven rigs for the farm-equipment and Polaris skeletal meshes (Acres/Raw/farm-equipment).
The Blender source files move their parts from custom-property "controls" on the *_RIG armature (steering_deg, wheel_spin_FL, rear_hitch_lift_deg, boom_deg, ...) through scripted drivers, and keep hydraulic cylinders and the baler's connecting rod on their pins with Copy Location + Damped Track constraints. FBX carries none of that, so Tools/ACRE/Vehicles/export_rig_map.py writes it to Content/Simulation/rigs/<asset>.json and this file evaluates it at runtime, reproducing the coupled mechanisms the drivers encode: Ackermann steering (tractor, Polaris), the tractor's parallel-link hitch with its counter-rotating attachment frame, the baler's slider-crank plunger (0.355 m crank, 1.255 m rod, 0.048 PTO ratio), mirrored boom folds, and every cylinder tracking its two pins.
- FAcresRigExpression: a driver expression (a small Python subset) compiled once to a stack program.
- FAcresRigMap: one rig map: controls, bones (rest frames, driver channels, pin constraints), sockets and the
Blender-evaluated test poses. Evaluate() computes every bone's rigid deformation the way Blender evaluates the
pose (parent deformation, then the driven Euler / location channels in the bone's rest frame, then the pin
constraints). Pure math on plain structs: callable from any thread, e.g. the 120 Hz physics callback.
- UAcresRigComponent: loads a rig map and its skeletal mesh (a UPoseableMeshComponent it owns) and applies
control values to the bones.
Coupling to the physics state (as AAcresVehiclePawn does for the Maxxum, its implement and the Polaris):
Rig = CreateDefaultSubobject<UAcresRigComponent>(TEXT("Rig")); // or NewObject + RegisterComponent
Rig->LoadRig(TEXT("maxxum_150"));
Rig->SetControl(TEXT("steering_deg"), RoadWheelDeg); // one input; the rig splits it by Ackermann
Rig->SetControl(TEXT("wheel_spin_FL"), WheelAngleDeg); // degrees rolled, unbounded (no wrap needed)
Rig->SetControl(TEXT("axle_oscillation_deg"), AxleRollDeg);
Rig->SetControl(TEXT("rear_hitch_lift_deg"), HitchDeg); // lower links, top link, cylinders, frame
const FTransform Mount = Rig->GetBoneFrameWorld(TEXT("rear_attachment")); // where the implement root goes
Controls are named exactly like the Blender custom properties (see each rig map's "controls"): _deg in degrees, _m in metres, spin/roll controls in degrees (tractor, implements) or metres rolled (Polaris wheel_roll_m). SetControl does not clamp: the drivers apply the mechanical limits (as in Blender), so spin angles may grow without bound. For per-frame use, look a control up once with FindControl and call SetControlByIndex.
Frames: a rig map is Blender armature space, metres, +X forward, +Y left, +Z up (FLU), like mechanics.json. Unreal component space is 100 * (x, -y, z) cm, and a rotation by angle a about axis n becomes a rotation by a about (-n.x, n.y, -n.z) (catalog.json "unreal_mapping"); FAcresRigMap::ToUnreal applies that reflection once. The imported skeleton is the Blender bone hierarchy under one extra identity root bone (import_farm_equipment.py), with Blender's names except that Unreal replaced spaces by '-' and '.' by '_' (FAcresRigMap::UnrealBoneName).
Variants: the four Polaris finishes share one mesh and one rig map ("polaris"); a finish is a set of material instances, one per mesh slot, listed in the rig map's variants (LoadRig(TEXT("polaris"), TEXT("desert_tan")) or SetVariant). Slots are matched by FAcresRigMap::SlotKey, since Unreal rewrites the Blender material names.
Why UPoseableMeshComponent rather than a USkeletalMeshComponent with an Animation Blueprint or a native UAnimInstance: the pose is a pure function of a few control values that game code (physics, replay) sets from C++. A poseable mesh takes bone transforms from code directly: no anim graph asset to cook, no animation worker thread (so no one-frame latency behind the physics state), and it behaves the same in the editor, the cooked game, off-screen capture and -nullrhi runs, where the pose is still computed and readable (GetBoneFrameWorld, MeasurePinErrorCm) for sensors or tests.
| Name | Type | Unit | Default | Description |
|---|---|---|---|---|
Unit |
FString |
"deg", "m" or "1". | ||
Min, Max, Default |
double |
Blender custom-property range and default (the drivers clamp; SetControl does not). | ||
UnrealName |
FName |
The name the bone has in the imported Unreal skeleton. | ||
HeadM |
FVector |
m | FVector::ZeroVector |
Rest frame in armature space: head (m) and orientation (columns = bone X, Y, Z axes). |
EulerOrder |
int32 |
Euler rotation order, first-applied axis first (Blender "XYZ" applies X, then Y, then Z). | ||
bTracked |
bool |
false |
Copy Location + Damped Track: head on Location, TrackAxis (bone-local) aimed at Track. |
FAcresRigid#
A rigid transform: rotation Q, then translation T. Composition A * B applies B first (like FQuat).
FAcresRigExpression#
A Blender driver expression compiled to a small stack program.
Grammar: Python arithmetic with + - * / % ** (right-associative, binding tighter than unary minus), parentheses, numbers (".95", "1e-3"), the constants pi and e, the rig's variables and the functions min, max (any number of arguments), abs, sin, cos, tan, asin, acos, atan, atan2, sqrt, exp, log, pow, radians, degrees, floor, ceil. That covers every driver in the package, for example the tractor's Ackermann expression atan(2.6416tan(min(max(v,-38),38)pi/180)/(2.6416+(-1.95)tan(min(max(v,-38),38)*pi/180))).
Example
FAcresRigExpression E;
FString Error;
if (E.Compile(TEXT(".355*cos(v*.048*pi/180)-sqrt(1.255**2-(.355*sin(v*.048*pi/180))**2)+.9"), {TEXT("v")},
Error))
{
const double Values[] = {1234.5};
const double PlungerM = E.Evaluate(Values); // metres along the plunger's axis
}
FAcresRigExpression::Compile#
Parses Source.
| Argument | Description |
|---|---|
Source |
The expression text. |
Variables |
Names the expression may use; Evaluate() takes their values in this order. |
Error |
Set to a readable reason on failure. |
Returns: False on a syntax error, an unknown name or function, or a wrong argument count.
FAcresRigExpression::Evaluate#
Evaluates the expression (NaN if it was never compiled).
| Argument | Description |
|---|---|
Variables |
One value per name given to Compile, same order. |
FAcresRigMap#
One rig map (Content/Simulation/rigs/<asset>.json, written by Tools/ACRE/Vehicles/export_rig_map.py).
Bones are stored parents first. A bone's deformation D maps its rest geometry to its posed geometry in armature space (pose matrix = D * rest frame); the skinned vertices of a rigidly weighted part move by D.
Example
FAcresRigMap Map;
FString Error;
if (FAcresRigMap::Load(TEXT("square_baler"), Map, Error))
{
TArray<double> Values = Map.DefaultControls();
Values[Map.FindControl(TEXT("pto_spin_deg"))] = 7500; // half a crank turn at the 0.048 ratio
TArray<FAcresRigid> Deform;
Map.Evaluate(Values, Deform); // plunger at the end of its 0.71 m stroke
}
| Name | Type | Unit | Default | Description |
|---|---|---|---|---|
SocketsM |
TMap<FName, FVector> |
m | Named attachment points, armature space, m. | |
EvaluationOrder |
TArray<int32> |
Bone indices in evaluation order: parents and the bones carrying a constraint's pins first. | ||
Variants |
TMap<FName, FVariant> |
Material variants by name ("graphite", "forest_green", ...) and the one the imported mesh carries. |
FAcresRigMap::Load#
Reads and compiles Content/Simulation/rigs/<Asset>.json.
| Argument | Description |
|---|---|
Asset |
Asset id, e.g. "maxxum_150", "polaris". |
Out |
Receives the rig; replaced on success. |
Error |
Set to a readable reason on failure. |
Returns: False when the file is missing, has another format version, or an expression or reference is invalid.
FAcresRigMap::Directory#
Content/Simulation/rigs (staged next to the packaged game as loose files).
FAcresRigMap::Evaluate#
Deformation of every bone for the given control values (armature space, m), parents first.
| Argument | Description |
|---|---|
ControlValues |
One value per control, in Controls order. |
OutDeform |
Resized to Bones.Num(). |
FAcresRigMap::RestFrame#
Rest frame of a bone (head, rest orientation) in armature space, m.
FAcresRigMap::SelfTest#
Evaluates every test pose and compares with Blender.
| Argument | Description |
|---|---|
OutMaxPositionErrorM |
Largest distance between Blender's and this evaluation of a bone's head or tail. |
OutMaxAngleErrorDeg |
Largest rotation between the two deformations of a bone. |
Returns: True when both are below 0.1 mm and 0.01 degrees.
FAcresRigMap::RunSelfTests#
Loads every rig map in Directory() and runs SelfTest on it (the game's -RigSelfTest). Logs one line per rig, ACRES_RIG_SELFTEST <asset> PASS|FAIL poses=<n> bones=<n> max_position_error_mm=<x> max_angle_error_deg=<x> (or "<file> FAIL load: <reason>"), then ACRES_RIG_SELFTEST_DONE passed=<n> of <n>.
Returns: The number of rig maps that passed.
FAcresRigMap::ToUnrealCm#
Blender armature space (m, FLU) to Unreal component space (cm, X forward, Y right, Z up).
FAcresRigMap::UnrealBoneName#
Blender bone name -> imported Unreal bone name (UE::Interchange::SanitizeName for joints).
FAcresRigMap::SlotKey#
Material-slot comparison key: lower-case letters and digits only ("Graphite metallic — photo estimate" and Unreal's "Graphite_metallic_photo_estimate" both give "graphitemetallicphotoestimate").
FChannel#
One driven pose channel: rotation_euler[Index] (rad) or location[Index] (m) in the bone's rest frame.
| Name | Type | Unit | Default | Description |
|---|---|---|---|---|
Controls |
TArray<int32> |
Control index for each expression variable. | ||
bLinear |
bool |
false |
Fast path when the driver is exactly Gain * clamp(v, Lo, Hi) + Offset (export_rig_map.py checks it). |
FChannel::Evaluate#
The driver's value, rad or m.
FPin#
A pin carried by a bone: Point (armature space, rest pose, m) moves with Bone's deformation.
FTestPose#
Controls and the Blender deformation of every bone for them.
FVariant#
A finish of a shared mesh (the Polaris): the material instance for every mesh slot.
| Name | Type | Unit | Default | Description |
|---|---|---|---|---|
Materials |
TMap<FString, FString> |
SlotKey(slot name) -> material object path. |
UAcresRigComponent#
A skeletal mesh posed from a rig map's controls.
LoadRig creates the UPoseableMeshComponent (attached to this component, identity relative transform) and loads the mesh named by the rig map. Control changes are applied in UpdatePose(), called by TickComponent when something changed or directly by the owner when it needs the new pose in the same frame.
UAcresRigComponent::LoadRig#
Loads Content/Simulation/rigs/<Asset>.json, its skeletal mesh and maps the rig bones onto the skeleton.
| Argument | Description |
|---|---|
Asset |
Asset id, e.g. "maxxum_150", "polaris". |
Variant |
Material variant to apply (the Polaris finishes: "graphite", "forest_green", "desert_tan", "deep_blue"); NAME_None keeps the materials the mesh was imported with (the rig's default variant). |
Returns: False (and logs ACRES_RIG_ERROR) when the rig map, the mesh or a variant material is missing, or a rig bone is not in the mesh.
UAcresRigComponent::SetVariant#
Puts a variant's material instances on the mesh slots (see LoadRig). False for an unknown variant or when a slot has no material in it.
UAcresRigComponent::FindControl#
Index of a control for SetControlByIndex, or INDEX_NONE.
UAcresRigComponent::SetControl#
Sets a control (Blender custom-property name and unit). Returns false for an unknown name.
UAcresRigComponent::UpdatePose#
Evaluates the rig and writes the bone transforms to the mesh now.
UAcresRigComponent::GetBoneFrameComponent#
A rig bone's current frame (Blender pose matrix) in this component's space, cm, unit scale.
UAcresRigComponent::MeasurePinErrorCm#
Largest distance between a tracked bone's head as the mesh draws it and the pin it must sit on, cm (hydraulic cylinders and connecting rods). A few thousandths of a centimetre means the cylinders stay on their pins.