Skip to content

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#

struct FAcresRigid

A rigid transform: rotation Q, then translation T. Composition A * B applies B first (like FQuat).

FAcresRigExpression#

class 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.

bool Compile(const FString& Source, const TArray<FString>& Variables, FString& Error);

FAcresRigExpression::Evaluate#

Evaluates the expression (NaN if it was never compiled).

Argument Description
Variables One value per name given to Compile, same order.
double Evaluate(TArrayView<const double> Variables) const;

FAcresRigMap#

struct 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&lt;FName, FVector> m Named attachment points, armature space, m.
EvaluationOrder TArray&lt;int32> Bone indices in evaluation order: parents and the bones carrying a constraint's pins first.
Variants TMap&lt;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.

static bool Load(const FString& Asset, FAcresRigMap& Out, FString& Error);

FAcresRigMap::Directory#

Content/Simulation/rigs (staged next to the packaged game as loose files).

static FString Directory();

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().
void Evaluate(TArrayView<const double> ControlValues, TArray<FAcresRigid>& OutDeform) const;

FAcresRigMap::RestFrame#

Rest frame of a bone (head, rest orientation) in armature space, m.

FAcresRigid RestFrame(int32 Bone) const ;

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.

bool SelfTest(double& OutMaxPositionErrorM, double& OutMaxAngleErrorDeg) const;

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.

static int32 RunSelfTests();

FAcresRigMap::ToUnrealCm#

Blender armature space (m, FLU) to Unreal component space (cm, X forward, Y right, Z up).

static FVector ToUnrealCm(const FVector& FluM) ;

FAcresRigMap::UnrealBoneName#

Blender bone name -> imported Unreal bone name (UE::Interchange::SanitizeName for joints).

static FName UnrealBoneName(const FString& BlenderName);

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").

static FString SlotKey(const FString& SlotName);

FChannel#

struct 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&lt;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.

double Evaluate(TArrayView<const double> ControlValues) const;

FPin#

struct FPin

A pin carried by a bone: Point (armature space, rest pose, m) moves with Bone's deformation.

FTestPose#

struct FTestPose

Controls and the Blender deformation of every bone for them.

FVariant#

struct FVariant

A finish of a shared mesh (the Polaris): the material instance for every mesh slot.

Name Type Unit Default Description
Materials TMap&lt;FString, FString> SlotKey(slot name) -> material object path.

UAcresRigComponent#

class UAcresRigComponent : public USceneComponent

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.

bool LoadRig(const FString& Asset, FName Variant = NAME_None);

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.

bool SetVariant(FName Variant);

UAcresRigComponent::FindControl#

Index of a control for SetControlByIndex, or INDEX_NONE.

int32 FindControl(FName Control) const ;

UAcresRigComponent::SetControl#

Sets a control (Blender custom-property name and unit). Returns false for an unknown name.

bool SetControl(FName Control, double Value);

UAcresRigComponent::UpdatePose#

Evaluates the rig and writes the bone transforms to the mesh now.

void UpdatePose();

UAcresRigComponent::GetBoneFrameComponent#

A rig bone's current frame (Blender pose matrix) in this component's space, cm, unit scale.

FTransform GetBoneFrameComponent(FName Bone) const;

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.

double MeasurePinErrorCm() const;