#pragma once #include "CoreMinimal.h" /** * Constants of the player's first-person cybernetic arm (Player/Arm/). Centimetres and degrees, Unreal axes * (+X forward = along the arm at rest, +Y right, +Z up). "Arm space" is the space of the viewmodel component: its * origin is the shoulder joint, so every position here is measured from the shoulder. * * Provenance: the arm's own dimensions (30.0 / 29.2 / 19.4 cm) are NOT here, they come from the generated table * Player/Arm/ArmLayout.inl. Everything below is a choice, and every choice that is a guess says so. */ namespace Descent::Tune::Arm { // ---- Mounting on the first-person camera (DESIGN_UE.md 5.1: eye 1.65 m above the feet, FOV 72) ---- /** Where the RIGHT shoulder joint sits relative to the camera: slightly behind, 19 cm to the right, 24 cm below the * eye (a real shoulder is about 20 to 25 cm below the eye line). GUESS from human proportions. */ inline const FVector ShoulderOffsetCm(-5.0, 19.0, -24.0); /** The game's field of view (DESIGN_UE.md 5.1). The first-person primitives are drawn with the SAME field of view as * the world, so the arm looks as it would if it were really in the room. */ constexpr double GameFovDeg = 72.0; /** Camera "first person scale": primitives of the arm are drawn as if they were this fraction of their distance from * the eye (the same place and size on screen, a smaller depth). It is chosen between two limits that the test * Descent.Arm.FirstPersonFit measures over every named pose and every blend between two of them: the deepest point of * the arm (67 cm from the eye) must be drawn short of a wall the body is pressed against (the capsule's 34 cm), and * the nearest point that is on screen (29 cm) must stay beyond the 10 cm near plane. 0.45 gives 30 cm and 13 cm. * DECISION of the arm track; the design only says the arm must exist. */ constexpr double FirstPersonScale = 0.45; /** ...except while the jack-in runs: then the depth is TRUE (1 = off), because the fingers have to go into the port and * behind its rim. Changing the scale moves nothing on screen (it scales about the eye), only what hides what. */ constexpr double JackFirstPersonScale = 1.0; /** Custom depth stencil of the interior ("dry") mask, DESIGN_UE.md 7.4: the first-person arm carries it. */ constexpr int32 DryStencilValue = 1; /** Lighting channels of the arm: sea (0) and carrier interior (1), like the character (DESIGN_UE.md 5.1). While the * camera is in dry air the component drops channel 0 (7.4: the sea's light never falls on anything inside the hull). */ constexpr bool LightChannel0 = true; constexpr bool LightChannel1 = true; // ---- Joint limits (degrees). GUESSES from human ranges, narrowed where the mesh would pass through itself ---- constexpr double ElbowMinFlexDeg = 0.0; constexpr double ElbowMaxFlexDeg = 150.0; /** The shoulder is a ball joint: the upper arm may point anywhere within this angle of straight ahead (so not into * the 30-degree cone straight back, through the body), and may turn freely about its own axis (a rotary actuator; * 180 = no limit). Yaw / pitch / roll are only how a pose WRITES that orientation: limiting each of them separately * (as the first version did) forbade ordinary places such as a hand held low in front of the hip. */ constexpr double ShoulderMaxFromForwardDeg = 150.0; constexpr double ShoulderRollLimitDeg = 180.0; constexpr double WristRollLimitDeg = 100.0; /** Negative = bent back toward the back of the hand. -40, not a human's -65: with the elbow fully bent a hand bent back * further goes into the upper arm (Descent.Arm.Clearance). */ constexpr double WristFlexMinDeg = -40.0; constexpr double WristFlexMaxDeg = 75.0; // positive = bent toward the palm // ---- Fingers ---- /** Absolute curl of each of the three joints of a finger at curl = 1 (a fist), degrees; 0 is a straight finger. * Rows: Index, Middle, Ring, Pinky. GUESS, then taken back by 4 % to where the fingertips' boxes stop short of the * palm (Descent.Arm.Clearance); looked at in the fist shots of arm_showcase. */ constexpr double FingerClosedDeg[4][3] = { { 79.0, 94.0, 60.0 }, { 83.0, 96.0, 61.0 }, { 84.0, 94.0, 60.0 }, { 82.0, 88.0, 40.0 }, // the pinky is short: with a tighter last joint its tip turned up into the palm }; /** The thumb has no "straight" reference (its root has a composite rest orientation), so its angles are measured from * the pose the user modelled, at curl = 0, to these values at curl = 1, about each joint's own hinge axis. */ constexpr double ThumbClosedDeg[3] = { 38.0, 52.0, 58.0 }; /** How far the thumb curls in a fist. Not 1: fully tucked it lies under the palm exactly where the closed fingers' tips * are (Descent.Arm.Clearance measures how far it can go). Used by the showcase's fist and as the ceiling for the thumb * in every pose whose fingers are closed. */ constexpr double FistThumbCurl = 0.30; // ---- The piston rod ---- /** How far the forearm rod slides back into the forearm between a straight elbow and a fully bent one. The rod is * modelled fully extended (that is the straight-arm rest), the housing hides up to about 8 cm of it. */ constexpr double RodStrokeCm = 5.5; // ---- Effector points (where "the hand" is, for the reach solver) ---- /** The palm centre in the hand's frame (hand pivot = wrist flex axis; the palm surface is about 2.1 cm below it). */ inline const FVector PalmPointHandCm(6.0, 0.0, -2.0); // ---- Reach solver ---- /** The elbow folds toward this direction (arm space), the upper arm bulges away from it: up and a little to the * left of the line shoulder-hand, so the right elbow hangs down and slightly outward. The arm has six joints for a * six-number target (position, direction, palm twist), so the elbow has at most two places to be; this picks one. */ inline const FVector ElbowHint(0.0, -0.4, 1.0); /** STATED ACCURACY: a target counts as reached when the effector ends up this close to it, cm (half a millimetre). * The solver is analytic, so the measured error inside the range is far lower (Tests/ArmTests.cpp prints it); * this is the line between "reached" and "out of range". */ constexpr double ReachToleranceCm = 0.05; constexpr int32 ReachIterations = 24; /** Closer than this angle between the hand and the forearm axis, the wrist roll is taken from the palm twist instead * of from the hand direction (which says nothing about roll there), blended smoothly so nothing snaps. */ constexpr double WristSingularityDeg = 10.0; // ---- Pose blending ---- constexpr double PoseBlendSeconds = 0.35; constexpr double HideBlendSeconds = 0.5; // ---- The jack-in sequence (DESIGN_UE.md 6.9: "the 4.8 s wall-plug sequence of v2 (approach 0-0.7 s, feed 1.0-4.1 s, // release at 4.8 s)") ---- /** Phase boundaries in seconds from the start. FROM THE DESIGN: 0.7 (the approach ends: the hand is in front of the * port), 1.0 (the plug is seated, the feed begins), 4.1 (the feed ends), 4.8 (released). GUESSES: the approach is split * into raise (to 0.3) and reach (to 0.7); the plug is pushed straight in between 0.7 and 1.0; the 0.7 s after the feed * is split evenly into a straight withdrawal (to 4.45) and the lowering (to 4.8). */ constexpr double JackRaiseEnd = 0.30; constexpr double JackReachEnd = 0.70; // fingertips at the standoff point on the port's axis constexpr double JackPlugEnd = 1.00; // the plug seats here; the feed begins constexpr double JackFeedEnd = 4.10; constexpr double JackWithdrawEnd = 4.45; constexpr double JackTotal = 4.80; /** Where the fingertips are at the end of the raise, arm space: forward of the shoulder and 15 cm above it, which is * just inside the bottom of the picture (at 6 cm the whole raise happened under it). GUESS. */ inline const FVector JackReadyHandCm(44.0, -4.0, 15.0); /** The plug travels this far along the port's axis, in and out (cm). GUESS. */ constexpr double JackStandoffCm = 14.0; /** Palm twist of the hand while it plugs in: 0 = palm down, +90 = thumb up. GUESS, looked at in the arm_jack shots. */ constexpr double JackPalmTwistDeg = 20.0; // ---- The debug rig (Player/Arm/DescentArmDebugRig: the stand-in for the character in the arm's own scenarios) ---- /** Eye above the floor (DESIGN_UE.md 5.1) and socket above the floor (DESIGN_UE.md, the GALLEY row of the room * table: "nutrient ports ... socket 1.45 m above the floor"). */ constexpr double RigEyeHeightCm = 165.0; constexpr double RigPortHeightCm = 145.0; /** Horizontal distance eye -> wall when the character has walked up to the port. GUESS (the carrier track decides * where the approach ends); at this distance the socket is 70 cm from the shoulder, inside the arm's 79 cm. */ constexpr double RigWallDistanceCm = 55.0; /** The shoulder of the third-person showcase, above the rig's origin. */ constexpr double RigShowcaseShoulderHeightCm = 150.0; // ---- Named poses ---- /** A pose is described the way one poses a real arm (Descent::Arm::PoseFromHandAndElbow): where the HAND PIVOT is (arm * space, cm from the shoulder; the eye is at -ShoulderOffsetCm = (5, -19, 24) from it), where the ELBOW is (turned about the * line shoulder -> hand: 0 = straight below it, + = outward), how the wrist is rolled and bent, and the finger curls. * The hidden pose is given as joints. All GUESSES, tuned on the arm_showcase and arm_character shots. */ struct FPoseRecipe { double HandX, HandY, HandZ; // hand pivot (cm, arm space) double ElbowOutDeg; // 0 = elbow straight below the line shoulder -> hand, + = swung outward (right) double WristRollDeg; // 0 = palm on the inside of the elbow's fold, + = supination (thumb up) double WristFlexDeg; // + = hand bent toward the palm double Curl[5]; // Thumb, Index, Middle, Ring, Pinky }; constexpr FPoseRecipe IdleRecipe = { 40.0, -4.0, 8.0, 35.0, 20.0, 30.0, { 0.30, 0.22, 0.26, 0.30, 0.34 } }; constexpr FPoseRecipe ReachRecipe = { 50.0, -6.0, 13.0, 25.0, 0.0, 30.0, { 0.10, 0.06, 0.05, 0.05, 0.08 } }; /** Carrying a box (DESIGN_UE.md 5.5: the box rides 0.75 m ahead of the eye and 0.35 m below, so mostly under the * picture): the hand at its right side, palm inward. Raised a little so the hand shows; the carry system (M5) puts it * on the real box. */ constexpr FPoseRecipe GripRecipe = { 52.0, 4.0, 3.0, 25.0, 80.0, 10.0, { 0.30, 0.70, 0.72, 0.74, 0.70 } }; constexpr FPoseRecipe PointRecipe = { 44.0, -5.0, 12.0, 30.0, 10.0, 42.0, { 0.55, 0.00, 0.92, 0.94, 0.95 } }; /** The jack-in hand shape: fingers straight and together, thumb tucked against the palm. */ constexpr FPoseRecipe PlugRecipe = { 46.0, -4.0, 8.0, 25.0, 15.0, 20.0, { 0.45, 0.00, 0.00, 0.00, 0.00 } }; constexpr FPoseRecipe HoldRecipe = { 42.0, -5.0, 11.0, 30.0, 70.0, 20.0, { 0.50, 0.56, 0.58, 0.60, 0.60 } }; /** Hidden: the arm hangs down and back, below the view. Joint angles, plus a shift. */ constexpr double HiddenShoulderYawDeg = 8.0; constexpr double HiddenShoulderPitchDeg = -88.0; constexpr double HiddenElbowFlexDeg = 20.0; inline const FVector HiddenShiftCm(-4.0, 6.0, -26.0); constexpr double HiddenCurl = 0.25; }