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

'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

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

'Animation
Dim BehaviourSeed
Dim AnimTime As Single
Dim LeftArmOut As Single
Dim RightArmOut 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

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

Public Sub Move(TimeStep 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 RighLegRotation As Single
    Static PullOutArms As Single
    Static LeftArmOut_Target As Single
    Static RightArmOut_Target As Single
    Static BendLeftLeg As Single
    Static BendRightLeg As Single
    Static BodyAngleTargetOffset As Single
    Static SidewayTilt_Target As Single
    Static SidewayBend As Single
    Static RotationGForceEffect_Target As Single
    Static LeftFootHeight As Single
    Static RightFootHeight As Single
    Static ForwardAcceleration As Single
    Static RightAcceleration 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
    
    'Accelerate
    ForwardAcceleration = WalkForward * TimeStep * Lerp(5, 2, Crouching)
    RightAcceleration = WalkRight * TimeStep * Lerp(1.8, 0, Crouching)
    Velocity.X = Velocity.X + (WantedAxisSystem.ZAxis.X * ForwardAcceleration)
    Velocity.Z = Velocity.Z + (WantedAxisSystem.ZAxis.Z * ForwardAcceleration)
    Velocity.X = Velocity.X + (WantedAxisSystem.XAxis.X * RightAcceleration)
    Velocity.Z = Velocity.Z + (WantedAxisSystem.XAxis.Z * RightAcceleration)
    
    'Move
    Position_X = Position_X + (Velocity.X * TimeStep * SizeMultiplier * 2.5)
    Position_Z = Position_Z + (Velocity.Z * TimeStep * SizeMultiplier * 2.5)
    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
    
    'Rotate to the wanted direction
    BodyAngleTargetOffset = FixRadianCenter(DirectionAngle_Target - (DirectionAngle + (RotationVelocity * 0.2)))
    MoveCloser_Linear RotationVelocity, BodyAngleTargetOffset, TimeStep * 3
    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
    
    'Calculate how the legs should move
    LocalWalkingVelocity = MulVecTransposedMat3(Velocity, 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
    SidewayBend = SidewayTilt * (1 + SplitLegOffset)
    
    WalkingSpeed = Min(WalkingSpeed + Abs(RotationVelocity * 2), Acceleration)
    
    WalkingPeriod = WalkingPeriod + (TimeStep * WalkingSpeed * 23)
    ForwardLegOffset = HalfFlatSin2(WalkingPeriod) * LocalWalkingVelocity.Z * 0.25
    SidewayLegOffset = FullFlatSin(WalkingPeriod) * LocalWalkingVelocity.X * 0.125
    SplitLegOffset = (LocalWalkingVelocity.X ^ 2) * 0.07 + Abs(SidewayTilt * 0.2)
    
    'Move head closer to the goal
    GetCloserDirection_Exponential HeadDirection, WantedHeadDirection, TimeStep * TurnHeadSpeed
    
    'Crouch
    MoveCloser_Linear Crouching, Saturate(Crouching_Target), TimeStep * 5
    
    'Update animation time
    AnimTime = Timer + BehaviourSeed
    
    'Calculate angles for the arms
    LeftUpperArmXAngle = (NonPositive(HeadDirection.Y) * -0.15) - (ForwardLegOffset * Acceleration)
    RightUpperArmXAngle = (NonPositive(HeadDirection.Y) * -0.15) + (ForwardLegOffset * Acceleration)
    LeftLowerArmXAngle = LeftUpperArmXAngle + Clamp((Cos(WalkingPeriod) + 1) * LocalWalkingVelocity.Z * 0.2 * Acceleration, 0, 2.8)
    RightLowerArmXAngle = RightUpperArmXAngle + Clamp(-(Cos(WalkingPeriod) - 1) * LocalWalkingVelocity.Z * 0.2 * Acceleration, 0, 2.8)
    LeftHandXAngle = LeftLowerArmXAngle + Clamp(0, -0.2, 0.2)
    RightHandXAngle = RightLowerArmXAngle + Clamp(0, -0.2, 0.2)
    PullOutArms = Abs((LocalWalkingVelocity.X * 0.5) + (LocalWalkingVelocity.Z * 0.2)) + RotationGForceEffect
    
    LeftArmOut_Target = PullOutArms + NonNegative(SidewayTilt)
    RightArmOut_Target = PullOutArms - NonPositive(SidewayTilt)
    
    'Move the arms sideways
    MoveCloser_Exponential LeftArmOut, LeftArmOut_Target, 0.05, TimeStep
    MoveCloser_Exponential RightArmOut, RightArmOut_Target, 0.05, TimeStep
    
    BendLeftLeg = Saturate(Crouching - NonPositive(SidewayBend) + ((Cos(WalkingPeriod) + 1) * (Saturate(WalkingSpeed) * 0.25)))
    BendRightLeg = Saturate(Crouching + NonNegative(SidewayBend) - ((Cos(WalkingPeriod) - 1) * (Saturate(WalkingSpeed) * 0.25)))
    
    LeftUpperLegXAngle = BendLeftLeg + ForwardLegOffset
    LeftLowerLegXAngle = LeftUpperLegXAngle - (BendLeftLeg * 2)
    LeftLegRotation = -Cos(WalkingPeriod) * (RotationVelocity * 0.6)
    RightUpperLegXAngle = BendRightLeg - ForwardLegOffset
    RightLowerLegXAngle = RightUpperLegXAngle - (BendRightLeg * 2)
    RighLegRotation = Cos(WalkingPeriod) * (RotationVelocity * 0.6)
    
    'Place the origin and the instance
    frmMain.DGE.BoneFrame_GetPosition_OutV3 BoneFrame, BoneIndexByName(BoneName_LeftFoot): LeftFootHeight = frmMain.DGE.GetY1: RE
    frmMain.DGE.BoneFrame_GetPosition_OutV3 BoneFrame, BoneIndexByName(BoneName_RightFoot): RightFootHeight = frmMain.DGE.GetY1: RE
    'Setting the position directly is not recomended for a real game because it makes it impossible to use gravity and jumping
    Position_Y = (-Min(LeftFootHeight, RightFootHeight) + FootThickness) * SizeMultiplier
    
    frmMain.DGE.Instance_SetPosition Instance, Position_X, Position_Y, Position_Z
    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), 0, Cos(LeftLegRotation)), MakeVector3(0, 1, 0)
    
    DirectZAxis BoneIndexByName(BoneName_RightUpperLeg), MakeVector3(-SidewayLegOffset + SplitLegOffset - SidewayTilt, -Cos(RightUpperLegXAngle), Sin(RightUpperLegXAngle)), MakeVector3(Sin(RighLegRotation / 2), 0, -1)
    DirectZAxis BoneIndexByName(BoneName_RightLowerLeg), MakeVector3(-SidewayLegOffset + SplitLegOffset - SidewayTilt, -Cos(RightLowerLegXAngle), Sin(RightLowerLegXAngle)), MakeVector3(Sin(RighLegRotation / 2), 0, -1)
    DirectZAxis BoneIndexByName(BoneName_RightFoot), MakeVector3(Sin(RighLegRotation), 0, Cos(RighLegRotation)), 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

