VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
  Persistable = 0  'NotPersistable
  DataBindingBehavior = 0  'vbNone
  DataSourceBehavior  = 0  'vbNone
  MTSTransactionMode  = 0  'NotAnMTSObject
END
Attribute VB_Name = "Actor"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = True
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False

'This module handle human bone animation and common movement rules for both players and bots.
'All public vectors had to be divided into their elements because of the limitations of class interfaces in Visual Basic.

Option Explicit

'Modify this from AI or player input
    'Direct control
    Public WalkForward As Single 'How fast should the actor try to walk forward
    Public WalkRight As Single 'How fast should the actor try to walk to the right
    Dim Acceleration As Single 'How fast may it walk or run
    Public Acceleration_Target As Single 'How fast do we want it to walk or run
    Public DirectionAngle As Single
    Public DirectionAngle_Target As Single
    Public Crouching As Single '0 is standing and 1 is maximum crouching
    Public Crouching_Target As Single
    Public Jump As Boolean
    Public Fire As Boolean

'Instance in world space
Public Position_X As Single 'This is the center of object space in world space
Public Position_Y As Single
Public Position_Z As Single
Public SizeMultiplier As Single
Dim AxisSystem As Matrix3 'This must be orthogonal and normalized for a fast inverse using it's transpose
Dim WantedAxisSystem As Matrix3 'Calculated from DirectionAngle_Target instead of DirectionAngle
Dim ScaledAxisSystem As Matrix3 'The not normalized axis system used for graphics

'Internal
Dim Velocity As Vector3
Dim HeadDirection As Vector3
Dim LocalWalkingVelocity As Vector3
Dim WalkingPeriod As Double
Dim SidewayTilt As Single
Dim RotationGForceEffect As Single
Dim RotationVelocity As Single
Dim OnGroundBehaviour_Fast As Single
Dim OnGroundBehaviour_Slow As Single
Dim LeftArmOut As Single
Dim RightArmOut As Single
Dim FearOfHeight As Single

'Generated by interface commands and used by update animation
Dim WantedHeadDirection As Vector3

'Animation
Dim BehaviourSeed
Dim AnimTime As Single

'References to the graphics engine
Dim Model As Long
Dim Instance As Long
Dim BoneFrame As Long

'Skeleton information
Public DefaultHeightAboveGround As Single
Dim FootThickness As Single

'Constants
Const EyeHeight As Single = 0.2 'The height of the eyes from the start of the head bone along the Z axis
Const MaxLookAtDistance As Single = 7
Const TurnBodySpeed As Single = 5
Const TurnHeadSpeed As Single = 5

'For example: BoneIndexByName(BoneName_Head) contains the bone index of "Head" or -1 if the name was not found in the model.
Const BoneName_Head As Long = 0
Const BoneName_LowerTorso As Long = 1
Const BoneName_UpperTorso As Long = 2
Const BoneName_RightUpperArm As Long = 3
Const BoneName_RightLowerArm As Long = 4
Const BoneName_RightHand As Long = 5
Const BoneName_RightUpperLeg As Long = 6
Const BoneName_RightLowerLeg As Long = 7
Const BoneName_RightFoot As Long = 8
Const BoneName_LeftUpperArm As Long = 9
Const BoneName_LeftLowerArm As Long = 10
Const BoneName_LeftHand As Long = 11
Const BoneName_LeftUpperLeg As Long = 12
Const BoneName_LeftLowerLeg As Long = 13
Const BoneName_LeftFoot As Long = 14
Const NumberOfBoneNames As Long = 15
Dim BoneIndexByName(0 To NumberOfBoneNames - 1) As Long
Dim ParentIndexByName(0 To NumberOfBoneNames - 1) As Long

Private Sub Class_Initialize()
    HeadDirection = MakeVector3(0, 0, -1)
    WantedHeadDirection = MakeVector3(0, 0, -1)
    AxisSystem = MakeUnitMatrix3
    ScaledAxisSystem = MakeUnitMatrix3
    Position_X = 0
    Position_Y = 0
    Position_Z = 0
    SizeMultiplier = 1
    BehaviourSeed = Rnd * 10000
    WalkingPeriod = Rnd
    Acceleration = 1
End Sub

Private Sub Class_Terminate()
    If Instance > 0 Then
        frmMain.DGE.Instance_Delete Instance: RE
        Instance = 0
    End If
End Sub

Public Function TryToLookAtDirection(Dir_X As Single, Dir_Y As Single, Dir_Z As Single) As Boolean
    Static NewWantedHeadDirection As Vector3
    Static I As Long
    NewWantedHeadDirection = MulVecTransposedMat3(MakeVector3(-Dir_X, -Dir_Y, -Dir_Z), AxisSystem)
    If NewWantedHeadDirection.Z > -0.4 Then
        'Try to have a direction close to the wanted direction
        'Since the limit is not absolute, a fast approximation can get a direction near the circle
        NewWantedHeadDirection.Z = -0.4
        NewWantedHeadDirection = NormalVector3(NewWantedHeadDirection)
        WantedHeadDirection = NewWantedHeadDirection
        TryToLookAtDirection = False
    Else
        WantedHeadDirection = NewWantedHeadDirection
        TryToLookAtDirection = True
    End If
End Function

Private Sub UpdateAxisSystemFromDirectionAngle()
    AxisSystem = MakeAxisSystem_Polar(DirectionAngle, 0)
    WantedAxisSystem = MakeAxisSystem_Polar(DirectionAngle_Target, 0)
    ScaledAxisSystem = MulMat3(AxisSystem, SizeMultiplier)
End Sub

Private Sub MapBonesByName()
    Static Bone As Long
    Static Parent As Long
    Static NameIndex As Long
    'In case that a bone was not found, reset all references to -1
    For Bone = 0 To NumberOfBoneNames - 1
        BoneIndexByName(Bone) = -1
    Next Bone
    
    'Get the name of each bone in the model and give it's index to any match in BoneIndexByName
    For Bone = 0 To GetNumberOfBones(Model) - 1
        frmMain.DGE.Model_Bone_GetName_OutSB Model, Bone
        Parent = frmMain.DGE.Model_Bone_GetParentIndex(Model, Bone)
        Select Case GetStringFromEngine
        Case "Head"
            NameIndex = BoneName_Head
        Case "LowerTorso"
            NameIndex = BoneName_LowerTorso
        Case "UpperTorso"
            NameIndex = BoneName_UpperTorso
        Case "RightUpperArm"
            NameIndex = BoneName_RightUpperArm
        Case "RightLowerArm"
            NameIndex = BoneName_RightLowerArm
        Case "RightHand"
            NameIndex = BoneName_RightHand
        Case "RightUpperLeg"
            NameIndex = BoneName_RightUpperLeg
        Case "RightLowerLeg"
            NameIndex = BoneName_RightLowerLeg
        Case "RightFoot"
            NameIndex = BoneName_RightFoot
        Case "LeftUpperArm"
            NameIndex = BoneName_LeftUpperArm
        Case "LeftLowerArm"
            NameIndex = BoneName_LeftLowerArm
        Case "LeftHand"
            NameIndex = BoneName_LeftHand
        Case "LeftUpperLeg"
            NameIndex = BoneName_LeftUpperLeg
        Case "LeftLowerLeg"
            NameIndex = BoneName_LeftLowerLeg
        Case "LeftFoot"
            NameIndex = BoneName_LeftFoot
        Case Else
            NameIndex = -1
        End Select
        If NameIndex > -1 Then
            BoneIndexByName(NameIndex) = Bone
            ParentIndexByName(NameIndex) = Parent
        End If
    Next Bone
End Sub

Public Sub GiveModel(NewModel As Long)
    Static MinimumY As Single
    If NewModel > 0 Then
        If Model = 0 Or Model <> NewModel Then
            'Use the new model
            Model = NewModel
            Instance = frmMain.DGE.Instance_Create(Model): RE
            
            'Make a new bone frame from the model's default pose
            BoneFrame = frmMain.DGE.BoneFrame_CreateFromDefaultPose(Model): RE
        ElseIf Model <> NewModel Then
            'Remove the old bone frame
            frmMain.DGE.BoneFrame_Delete BoneFrame: RE
            
            'Replace the old model
            Model = NewModel
            frmMain.DGE.Instance_ReplaceModel Instance, Model: RE
            
            'Make a new bone frame from the model's default pose that will reuse the memory from the bone frame that we just deleted
            BoneFrame = frmMain.DGE.BoneFrame_CreateFromDefaultPose(Model): RE
        End If
        
        'Get the index of each bone by name
        MapBonesByName
        
        'Get the model's static bounding box minimum
        frmMain.DGE.Model_GetBoundingBoxMinimum_OutV3 Model: MinimumY = frmMain.DGE.GetY1: RE
        DefaultHeightAboveGround = -MinimumY
        
        'Measure foot thickness
        frmMain.DGE.Model_Bone_GetObjectSpacePos_OutV3 Model, BoneIndexByName(BoneName_LeftFoot): RE
        FootThickness = frmMain.DGE.GetY1 - MinimumY: RE
        
        'Assign the bone frame to avoid modifying the model that should be used by multiple actors
        frmMain.DGE.Instance_AssignBoneFrame Instance, BoneFrame: RE '¤¤¤¤ This failed
    End If
End Sub

Private Sub PlaceBone(BoneIndex As Long, Position As Vector3)
    frmMain.DGE.BoneFrame_SetPosition BoneFrame, BoneIndex, Position.X, Position.Y, Position.Z: RE
End Sub

'The Y axis will face Direction and the Z axis is calculated using UpVector
Private Sub DirectYAxis(BoneIndex As Long, Direction As Vector3, UpVector As Vector3)
    Static AxisSystem As Matrix3
    AxisSystem = MakeAxisSystem_Orthogonalized(Direction, UpVector)
    frmMain.DGE.BoneFrame_SetYAxis BoneFrame, BoneIndex, AxisSystem.ZAxis.X, AxisSystem.ZAxis.Y, AxisSystem.ZAxis.Z: RE
    frmMain.DGE.BoneFrame_SetZAxis BoneFrame, BoneIndex, AxisSystem.YAxis.X, AxisSystem.YAxis.Y, AxisSystem.YAxis.Z: RE
End Sub

'The Z axis will face Direction and the Y axis is calculated using UpVector
Private Sub DirectZAxis(BoneIndex As Long, Direction As Vector3, UpVector As Vector3)
    Static AxisSystem As Matrix3
    AxisSystem = MakeAxisSystem_Orthogonalized(Direction, UpVector)
    frmMain.DGE.BoneFrame_SetYAxis BoneFrame, BoneIndex, AxisSystem.YAxis.X, AxisSystem.YAxis.Y, AxisSystem.YAxis.Z: RE
    frmMain.DGE.BoneFrame_SetZAxis BoneFrame, BoneIndex, AxisSystem.ZAxis.X, AxisSystem.ZAxis.Y, AxisSystem.ZAxis.Z: RE
End Sub

Private Function ObjectSpaceToWorldSpace(ObjectSpacePos As Vector3) As Vector3
    ObjectSpaceToWorldSpace = AddVector3(MulVecMat3(ObjectSpacePos, ScaledAxisSystem), MakeVector3(Position_X, Position_Y, Position_Z))
End Function

Private Function WorldSpaceToObjectSpace(WorldSpacePos As Vector3) As Vector3
    Debug.Assert SizeMultiplier > 0
    WorldSpaceToObjectSpace = DivVector3(MulVecTransposedMat3(SubVector3(WorldSpacePos, MakeVector3(Position_X, Position_Y, Position_Z)), AxisSystem), SizeMultiplier)
End Function

Private Sub InheritPosition(ChildNameIndex As Long)
    Static Child As Long
    Static Parent As Long
    Static AxisSystem_Parent As Matrix3
    Static ParentSpacePos As Vector3
    Static ObjectSpacePos As Vector3
    Static ObjectSpacePos_Parent As Vector3
    Child = BoneIndexByName(ChildNameIndex)
    Parent = ParentIndexByName(ChildNameIndex)
    AxisSystem_Parent = GetAxisSystemFromBoneInFrame(BoneFrame, Parent)
    frmMain.DGE.BoneFrame_GetPosition_OutV3 BoneFrame, Parent: ObjectSpacePos_Parent = GetVector3FromMatrixBuffer: RE
    ParentSpacePos = GetBoneStartPos_PS_Model(Model, Child)
    ObjectSpacePos = AddVector3(MulVecMat3(ParentSpacePos, AxisSystem_Parent), ObjectSpacePos_Parent)
    frmMain.DGE.BoneFrame_SetPosition BoneFrame, Child, ObjectSpacePos.X, ObjectSpacePos.Y, ObjectSpacePos.Z: RE
End Sub

Private Sub InheritPositionWithOffset(ChildNameIndex As Long, ParentSpaceOffset As Vector3)
    Static Child As Long
    Static Parent As Long
    Static AxisSystem_Parent As Matrix3
    Static ParentSpacePos As Vector3
    Static ObjectSpacePos As Vector3
    Static ObjectSpacePos_Parent As Vector3
    Child = BoneIndexByName(ChildNameIndex)
    Parent = ParentIndexByName(ChildNameIndex)
    AxisSystem_Parent = GetAxisSystemFromBoneInFrame(BoneFrame, Parent)
    frmMain.DGE.BoneFrame_GetPosition_OutV3 BoneFrame, Parent: ObjectSpacePos_Parent = GetVector3FromMatrixBuffer: RE
    ParentSpacePos = GetBoneStartPos_PS_Model(Model, Child)
    ObjectSpacePos = AddVector3(MulVecMat3(AddVector3(ParentSpacePos, ParentSpaceOffset), AxisSystem_Parent), ObjectSpacePos_Parent)
    frmMain.DGE.BoneFrame_SetPosition BoneFrame, Child, ObjectSpacePos.X, ObjectSpacePos.Y, ObjectSpacePos.Z: RE
End Sub

'Smooth noise from -1 to +1
Private Function Noise(X As Single) As Single
    Noise = (Sin(X * 5.24 + 1.64) * 0.25) + (Sin(X * 4.76 + 2.24) * 0.42) + (Sin(X * 3.16 + 4.62) * 0.33)
End Function

'Smooth noise from 0 to +1
Private Function NoisePos(X As Single) As Single
    NoisePos = (Noise(X) + 1) / 2
End Function

Public Sub Move(TimeStep As Single, Gravity As Single)
    Static TotalForce As Single
    Static Bone As Long
    Static Bone_Parent As Long
    Static ForwardLegOffset As Single
    Static SidewayLegOffset As Single
    Static SplitLegOffset As Single
    Static WalkingSpeed As Single
    Static LeftUpperArmXAngle As Single
    Static LeftLowerArmXAngle As Single
    Static LeftHandXAngle As Single
    Static RightUpperArmXAngle As Single
    Static RightLowerArmXAngle As Single
    Static RightHandXAngle As Single
    Static LeftUpperLegXAngle As Single
    Static LeftLowerLegXAngle As Single
    Static LeftLegRotation As Single
    Static RightUpperLegXAngle As Single
    Static RightLowerLegXAngle As Single
    Static RightLegRotation As Single
    Static PullOutArms As Single
    Static BendLeftLeg As Single
    Static BendRightLeg As Single
    Static SidewayBend As Single
    Static ForwardAcceleration As Single
    Static RightAcceleration As Single
    Static LeftArmOut_Target As Single
    Static RightArmOut_Target As Single
    Static BodyAngleTargetOffset As Single
    Static SidewayTilt_Target As Single
    Static RotationGForceEffect_Target As Single
    Static LiftToes As Single
    
    'Change the acceleration
    MoveCloser_Linear Acceleration, Acceleration_Target, TimeStep * 3
    
    'Enforce control limits
    TotalForce = Sqr(WalkForward ^ 2 + WalkRight ^ 2)
    If TotalForce > 1 Then
        WalkForward = WalkForward / TotalForce
        WalkRight = WalkRight / TotalForce
    End If
    WalkForward = WalkForward * Acceleration
    WalkRight = WalkRight * Acceleration
    
    'Get positions
    Dim LeftFootPos_ObjectSpace As Vector3
    Dim RightFootPos_ObjectSpace As Vector3
    Dim LeftFootPos_WorldSpace As Vector3
    Dim RightFootPos_WorldSpace As Vector3
    frmMain.DGE.BoneFrame_GetPosition_OutV3 BoneFrame, BoneIndexByName(BoneName_LeftFoot): LeftFootPos_ObjectSpace = GetVector3FromMatrixBuffer: RE
    frmMain.DGE.BoneFrame_GetPosition_OutV3 BoneFrame, BoneIndexByName(BoneName_RightFoot): RightFootPos_ObjectSpace = GetVector3FromMatrixBuffer: RE
    
    'Subtract thickness to get the collision points
    LeftFootPos_ObjectSpace.Y = LeftFootPos_ObjectSpace.Y - FootThickness
    RightFootPos_ObjectSpace.Y = RightFootPos_ObjectSpace.Y - FootThickness
    
    'Convert positions to world space
    LeftFootPos_WorldSpace = AddVector3(MulVecMat3(LeftFootPos_ObjectSpace, ScaledAxisSystem), MakeVector3(Position_X, Position_Y, Position_Z))
    RightFootPos_WorldSpace = AddVector3(MulVecMat3(RightFootPos_ObjectSpace, ScaledAxisSystem), MakeVector3(Position_X, Position_Y, Position_Z))
    
    'Walk on the ground
    Dim LeftFootGroundHeight As Single
    Dim RightFootGroundHeight As Single
    Dim LeftFootPressure As Single
    Dim RightFootPressure As Single
    Dim MaxPressure As Single
    LeftFootGroundHeight = GetGroundHeight_4Point(LeftFootPos_WorldSpace, 0.8 * SizeMultiplier, 0.14 * SizeMultiplier)
    RightFootGroundHeight = GetGroundHeight_4Point(RightFootPos_WorldSpace, 0.8 * SizeMultiplier, 0.14 * SizeMultiplier)
    LeftFootPressure = LeftFootGroundHeight - LeftFootPos_WorldSpace.Y
    RightFootPressure = RightFootGroundHeight - RightFootPos_WorldSpace.Y
    MaxPressure = Max_Float(LeftFootPressure, RightFootPressure)
    If MaxPressure > -0.1 And Jump Then
        Velocity.Y = Max_Float(Velocity.Y, 8)
    End If
    If MaxPressure > 0 Then
        Position_Y = Position_Y + MaxPressure
        Velocity.Y = NonNegative(Velocity.Y)
        
        'Destroy ground
        If Fire Then
            If DestroyTile(MiddleVector3(LeftFootPos_WorldSpace, RightFootPos_WorldSpace), 0.2) Then
                Velocity.Y = Max_Float(Velocity.Y, 4)
            End If
        End If
        
        'Accelerate
        ForwardAcceleration = WalkForward * TimeStep * Lerp(25, 10, Crouching)
        RightAcceleration = WalkRight * TimeStep * Lerp(8, 0, Crouching)
        Velocity.X = Velocity.X + (WantedAxisSystem.ZAxis.X * ForwardAcceleration * SizeMultiplier)
        Velocity.Z = Velocity.Z + (WantedAxisSystem.ZAxis.Z * ForwardAcceleration * SizeMultiplier)
        Velocity.X = Velocity.X + (WantedAxisSystem.XAxis.X * RightAcceleration * SizeMultiplier)
        Velocity.Z = Velocity.Z + (WantedAxisSystem.XAxis.Z * RightAcceleration * SizeMultiplier)
        
        'Decrease velocity
        Velocity = MulVector3(Velocity, Lerp(0.2, 0.1, Crouching) ^ TimeStep)
        If Abs(WalkForward) < 0.01 And Abs(WalkRight) < 0.01 Then
            DecreaseLengthVec3_Linear Velocity, TimeStep * 2
        End If
    Else
        'Air acceleration only works below a certain velocity
        If AbsVector2(MakeVector2(Velocity.X, Velocity.Z)) < 5 Then
            ForwardAcceleration = WalkForward * TimeStep * 8
            RightAcceleration = WalkRight * TimeStep * 8
            Velocity.X = Velocity.X + (WantedAxisSystem.ZAxis.X * ForwardAcceleration * SizeMultiplier)
            Velocity.Z = Velocity.Z + (WantedAxisSystem.ZAxis.Z * ForwardAcceleration * SizeMultiplier)
            Velocity.X = Velocity.X + (WantedAxisSystem.XAxis.X * RightAcceleration * SizeMultiplier)
            Velocity.Z = Velocity.Z + (WantedAxisSystem.XAxis.Z * RightAcceleration * SizeMultiplier)
        End If
        
        ForwardAcceleration = 0
        RightAcceleration = 0
    End If
    
    'Collide
    Dim AssForce As Vector3
    AssForce = GetPressureFromSphere(MakeVector3(Position_X, Position_Y - (Lerp(0.8, 0.3, Crouching) * SizeMultiplier), Position_Z), 0.4 * SizeMultiplier) 'Lowering the physical ass prevent getting stuck with the legs
    Velocity = AddVector3(Velocity, MulVector3(AssForce, 1000 * TimeStep))
    'This might have to be adjusted if TimeStep change
    'Too little will make it bouncy and too much will contradict the velocity too much
    Position_X = Position_X + AssForce.X * 0.15
    Position_Y = Position_Y + AssForce.Y * 0.15
    Position_Z = Position_Z + AssForce.Z * 0.15
    
    'Rotate to the wanted direction
    BodyAngleTargetOffset = FixRadianCenter(DirectionAngle_Target - (DirectionAngle + (RotationVelocity * 0.2)))
    MoveCloser_Linear RotationVelocity, BodyAngleTargetOffset, TimeStep * 3
    RotationVelocity = Clamp(-1, RotationVelocity, 1) 'Prevent spinning too fast
    DirectionAngle = DirectionAngle + Clamp(TimeStep * TurnBodySpeed * RotationVelocity, -1, 1)
    DirectionAngle = FixRadianPositive(DirectionAngle)
    UpdateAxisSystemFromDirectionAngle
    
    RotationGForceEffect_Target = Abs(RotationVelocity * 0.6)
    MoveCloser_Exponential RotationGForceEffect, RotationGForceEffect_Target, 0.06, TimeStep
    
    'Update animation time
    AnimTime = Timer + BehaviourSeed
    
    'Calculate how the legs should move
    Dim FlatVelocity As Vector3
    If MaxPressure > -0.05 Then
        'Near the ground
        MoveCloser_Linear OnGroundBehaviour_Fast, 1, TimeStep * 4
        MoveCloser_Linear OnGroundBehaviour_Slow, 1, TimeStep * 3
        
        'Walking
        FlatVelocity = DivVector3(Velocity, 2.5): FlatVelocity.Y = 0
        LocalWalkingVelocity = MulVecTransposedMat3(FlatVelocity, AxisSystem)
        WalkingSpeed = AbsVector3(LocalWalkingVelocity)
        If WalkingSpeed > 1 Then
            LocalWalkingVelocity = DivVector3(LocalWalkingVelocity, WalkingSpeed)
        End If
        SidewayTilt_Target = RotationVelocity * NonNegative(LocalWalkingVelocity.Z) * 0.13 * Acceleration
        MoveCloser_Exponential SidewayTilt, SidewayTilt_Target, 0.05, TimeStep
    Else
        'In air
        MoveCloser_Linear OnGroundBehaviour_Fast, 0, TimeStep * 4
        MoveCloser_Exponential OnGroundBehaviour_Slow, 0, 0.6, TimeStep
        
        'Flying
        FlatVelocity = MakeVector3(0, 0, 0)
        LocalWalkingVelocity = MakeVector3(0, 0, 0)
        WalkingSpeed = 0
        SidewayTilt_Target = 0
        MoveCloser_Linear SidewayTilt, SidewayTilt_Target, 0.5 * TimeStep
    End If
    
    'Approximate fear of height
    Dim GroundHeight As Single
    GroundHeight = GetGroundHeight_4Point(MakeVector3(Position_X, Position_Y, Position_Z), 1000, 0.4)
    MoveCloser_Linear FearOfHeight, Saturate((Position_Y - GroundHeight) * 0.6 - 1.2), 4 * TimeStep
    
    'Generate animations
    SplitLegOffset = Lerp((FearOfHeight ^ 2 + NoisePos(AnimTime + 5.35)) * 0.15, (LocalWalkingVelocity.X ^ 2) * 0.07 + Abs(SidewayTilt * 0.2), OnGroundBehaviour_Slow)
    LeftUpperArmXAngle = (NonPositive(HeadDirection.Y) * -0.15) - (ForwardLegOffset * Acceleration)
    RightUpperArmXAngle = (NonPositive(HeadDirection.Y) * -0.15) + (ForwardLegOffset * Acceleration)
    LeftLowerArmXAngle = Lerp((FearOfHeight ^ 2) * (Sin(AnimTime * 4.75) * 0.2 + 0.3), LeftUpperArmXAngle + Clamp((Cos(WalkingPeriod) + 1) * LocalWalkingVelocity.Z * 0.2 * Acceleration, 0, 2.8), OnGroundBehaviour_Slow)
    RightLowerArmXAngle = Lerp((FearOfHeight ^ 2) * (Sin(AnimTime * 4.36) * 0.2 + 0.3), RightUpperArmXAngle + Clamp(-(Cos(WalkingPeriod) - 1) * LocalWalkingVelocity.Z * 0.2 * Acceleration, 0, 2.8), OnGroundBehaviour_Slow)
    PullOutArms = Lerp((FearOfHeight ^ 2 + NoisePos(AnimTime + 2.74)) * 0.6, Abs((LocalWalkingVelocity.X * 0.5) + (LocalWalkingVelocity.Z * 0.2)) + RotationGForceEffect, OnGroundBehaviour_Slow)
    LeftHandXAngle = LeftLowerArmXAngle + Clamp(0, -0.2, 0.2)
    RightHandXAngle = RightLowerArmXAngle + Clamp(0, -0.2, 0.2)
    SidewayBend = SidewayTilt * (1 + SplitLegOffset)
    WalkingSpeed = Min_Float(WalkingSpeed + Abs(RotationVelocity * 2), Acceleration)
    WalkingPeriod = WalkingPeriod + (TimeStep * WalkingSpeed * 23)
    ForwardLegOffset = HalfFlatSin2(WalkingPeriod) * LocalWalkingVelocity.Z * 0.25
    SidewayLegOffset = FullFlatSin(WalkingPeriod) * LocalWalkingVelocity.X * 0.125
    
    'Move head closer to the goal
    GetCloserDirection_Exponential HeadDirection, WantedHeadDirection, TimeStep * TurnHeadSpeed
    
    'Crouch
    MoveCloser_Linear Crouching, Saturate(Crouching_Target), TimeStep * 5
    
    'Move the arms sideways
    LeftArmOut_Target = PullOutArms + NonNegative(SidewayTilt)
    RightArmOut_Target = PullOutArms - NonPositive(SidewayTilt)
    MoveCloser_Linear LeftArmOut, LeftArmOut_Target, TimeStep * 3.7
    MoveCloser_Linear RightArmOut, RightArmOut_Target, TimeStep * 3.7
    
    BendLeftLeg = Lerp(Crouching + (FearOfHeight ^ 2) * (Noise(AnimTime + 274.76) + 1) * 0.2, Saturate(Crouching - NonPositive(SidewayBend) + ((Cos(WalkingPeriod) + 1) * (Saturate(WalkingSpeed) * 0.25))), OnGroundBehaviour_Fast)
    BendRightLeg = Lerp(Crouching + (FearOfHeight ^ 2) * (Noise(AnimTime + 73.52) + 1) * 0.2, Saturate(Crouching + NonNegative(SidewayBend) - ((Cos(WalkingPeriod) - 1) * (Saturate(WalkingSpeed) * 0.25))), OnGroundBehaviour_Fast)
    
    LeftUpperLegXAngle = Lerp(BendLeftLeg, BendLeftLeg + ForwardLegOffset, OnGroundBehaviour_Fast)
    LeftLowerLegXAngle = Lerp(-LeftUpperLegXAngle, LeftUpperLegXAngle - (BendLeftLeg * 2), OnGroundBehaviour_Fast)
    LeftLegRotation = (-Cos(WalkingPeriod) * (RotationVelocity * 0.6)) * OnGroundBehaviour_Fast
    RightUpperLegXAngle = Lerp(BendRightLeg, BendRightLeg - ForwardLegOffset, OnGroundBehaviour_Fast)
    RightLowerLegXAngle = Lerp(-RightUpperLegXAngle, RightUpperLegXAngle - (BendRightLeg * 2), OnGroundBehaviour_Fast)
    RightLegRotation = (Cos(WalkingPeriod) * (RotationVelocity * 0.6)) * OnGroundBehaviour_Fast
    LiftToes = Lerp(Saturate(-MaxPressure * 2) * -0.8, 0, OnGroundBehaviour_Fast)
    
    'Apply gravity
    Velocity.Y = Velocity.Y - (Gravity * TimeStep)
    
    'Move the position
    Position_X = Position_X + (Velocity.X * TimeStep)
    Position_Y = Position_Y + (Velocity.Y * TimeStep)
    Position_Z = Position_Z + (Velocity.Z * TimeStep)
    
    'Place the body
    frmMain.DGE.Instance_SetPosition Instance, Position_X, Position_Y, Position_Z: RE
    SetInstanceAxisSystem Instance, ScaledAxisSystem
    PlaceBone BoneIndexByName(BoneName_LowerTorso), MakeVector3(SidewayTilt, 0, 0)
    
    'Orient bones and enforce hierarchy
    DirectZAxis BoneIndexByName(BoneName_LowerTorso), MakeVector3(SidewayTilt, 1, (HeadDirection.Y * -0.15) + (Saturate(LocalWalkingVelocity.Z) * Acceleration * 0.1)), MakeVector3(-SidewayTilt, 0, -1)
    DirectZAxis BoneIndexByName(BoneName_UpperTorso), MakeVector3(SidewayTilt, 1, (HeadDirection.Y * 0.3) + (Saturate(LocalWalkingVelocity.Z) * Acceleration * 0.2)), MakeVector3(-SidewayTilt * 2, 0, -1)
    DirectYAxis BoneIndexByName(BoneName_Head), HeadDirection, MakeVector3(SidewayTilt, 1, 0)
    
    DirectZAxis BoneIndexByName(BoneName_LeftUpperArm), MakeVector3(-LeftArmOut, -Cos(LeftUpperArmXAngle), Sin(LeftUpperArmXAngle)), MakeVector3(-1, 1, 0)
    DirectZAxis BoneIndexByName(BoneName_LeftLowerArm), MakeVector3(-LeftArmOut, -Cos(LeftLowerArmXAngle), Sin(LeftLowerArmXAngle)), MakeVector3(-1, 1, 0)
    DirectZAxis BoneIndexByName(BoneName_LeftHand), MakeVector3(-LeftArmOut, -Cos(LeftHandXAngle), Sin(LeftHandXAngle)), MakeVector3(-1, 1, 0)
    
    DirectZAxis BoneIndexByName(BoneName_RightUpperArm), MakeVector3(RightArmOut, -Cos(RightUpperArmXAngle), Sin(RightUpperArmXAngle)), MakeVector3(1, 1, 0)
    DirectZAxis BoneIndexByName(BoneName_RightLowerArm), MakeVector3(RightArmOut, -Cos(RightLowerArmXAngle), Sin(RightLowerArmXAngle)), MakeVector3(1, 1, 0)
    DirectZAxis BoneIndexByName(BoneName_RightHand), MakeVector3(RightArmOut, -Cos(RightHandXAngle), Sin(RightHandXAngle)), MakeVector3(1, 1, 0)
    
    DirectZAxis BoneIndexByName(BoneName_LeftUpperLeg), MakeVector3(SidewayLegOffset - SplitLegOffset - SidewayTilt, -Cos(LeftUpperLegXAngle), Sin(LeftUpperLegXAngle)), MakeVector3(Sin(LeftLegRotation / 2), 0, -1)
    DirectZAxis BoneIndexByName(BoneName_LeftLowerLeg), MakeVector3(SidewayLegOffset - SplitLegOffset - SidewayTilt, -Cos(LeftLowerLegXAngle), Sin(LeftLowerLegXAngle)), MakeVector3(Sin(LeftLegRotation / 2), 0, -1)
    DirectZAxis BoneIndexByName(BoneName_LeftFoot), MakeVector3(Sin(LeftLegRotation), LiftToes, Cos(LeftLegRotation)), MakeVector3(0, 1, 0)
    
    DirectZAxis BoneIndexByName(BoneName_RightUpperLeg), MakeVector3(-SidewayLegOffset + SplitLegOffset - SidewayTilt, -Cos(RightUpperLegXAngle), Sin(RightUpperLegXAngle)), MakeVector3(Sin(RightLegRotation / 2), 0, -1)
    DirectZAxis BoneIndexByName(BoneName_RightLowerLeg), MakeVector3(-SidewayLegOffset + SplitLegOffset - SidewayTilt, -Cos(RightLowerLegXAngle), Sin(RightLowerLegXAngle)), MakeVector3(Sin(RightLegRotation / 2), 0, -1)
    DirectZAxis BoneIndexByName(BoneName_RightFoot), MakeVector3(Sin(RightLegRotation), LiftToes, Cos(RightLegRotation)), MakeVector3(0, 1, 0)
    
    'Enforce hierarchy manually
    InheritPosition BoneName_UpperTorso
    InheritPosition BoneName_Head
    InheritPositionWithOffset BoneName_RightUpperArm, MakeVector3(-0.1, 0, -0.05)
    InheritPositionWithOffset BoneName_LeftUpperArm, MakeVector3(0.1, 0, -0.05)
    InheritPosition BoneName_RightLowerArm
    InheritPosition BoneName_LeftLowerArm
    InheritPosition BoneName_RightHand
    InheritPosition BoneName_LeftHand
    InheritPosition BoneName_RightUpperLeg
    InheritPosition BoneName_LeftUpperLeg
    InheritPosition BoneName_RightLowerLeg
    InheritPosition BoneName_LeftLowerLeg
    InheritPosition BoneName_RightFoot
    InheritPosition BoneName_LeftFoot
End Sub

