0
$\begingroup$

I’m working on analyzing soccer kicking motion using mocopi BVH data in Blender. My goal is to extract two joint events:

  • MHE (Maximum Hip Extension) → the timing when the hip joint is most extended
  • MKF (Maximum Knee Flexion) → the timing when the knee joint is most flexed

The problem

I want to always detect MHE as the maximum hip extension angle. However, depending on the trial, my calculation sometimes detects it at the minimum angle instead of the maximum.

My question

Why does MHE sometimes come out as a minimum angle instead of the maximum? Is my trunk reference (torso1–6) the wrong choice? Is there a better way to define the hip angle so that MHE consistently corresponds to the maximum?

Any advice on how to fix this would be greatly appreciated!

Here is the code I’m using right now (simplified but runnable):

import bpy
import math
import numpy as np

# ==========================
# User settings
# ==========================
armature_name = ""   # Armature name
frame_start = 
frame_end   = 

# ==========================
# Utility functions
# ==========================
def signed_angle_2d(v1, v2):
    """Signed 2D angle (atan2, deg)"""
    dot = np.dot(v1, v2)
    det = v1[0]*v2[1] - v1[1]*v2[0]
    return math.degrees(math.atan2(det, dot))

def hip_signed_by_body(root, hip, knee, torso_top, torso_bottom):
    """
    Hip flexion/extension angle relative to the trunk
    (extension = positive, flexion = negative)
    Trunk axis is defined by torso_1 → torso_6
    """
    v1 = hip - root   # pelvis→hip
    v2 = knee - hip   # hip→knee

    # trunk forward vector
    forward = torso_top - torso_bottom
    if np.linalg.norm(forward) == 0:
        forward = np.array([0.0, 0.0, 1.0])
    forward = forward / np.linalg.norm(forward)

    # right / up vectors
    up = np.array([0.0, 1.0, 0.0])
    right = np.cross(forward, up)
    if np.linalg.norm(right) == 0:
        right = np.array([1.0, 0.0, 0.0])
    right = right / np.linalg.norm(right)
    up2 = np.cross(right, forward)

    R = np.vstack([right, up2, forward]).T

    # transform to local coordinates
    v1_local = R.T @ v1
    v2_local = R.T @ v2

    # project onto XZ plane
    v1_proj = np.array([v1_local[0], v1_local[2]])
    v2_proj = np.array([v2_local[0], v2_local[2]])

    ang = signed_angle_2d(v1_proj, v2_proj)

    # extension is positive
    return -ang

def knee_3d_angle(hip, knee, foot):
    """3D knee angle (always positive)"""
    v1 = hip - knee
    v2 = foot - knee
    dot = np.dot(v1, v2)
    norm = np.linalg.norm(v1) * np.linalg.norm(v2)
    if norm == 0:
        return 0.0
    return math.degrees(math.acos(np.clip(dot/norm, -1.0, 1.0)))

# ==========================
# Calculate MHE / MKF
# ==========================
def analyze_right_leg():
    hip_bone  = "r_up_leg"
    knee_bone = "r_low_leg"
    foot_bone = "r_foot"
    root_bone = "root"
    torso_top_bone = "torso_1"
    torso_bottom_bone = "torso_6"

    armature = bpy.data.objects[armature_name]

    hip_angles = []
    knee_angles = []

    for f in range(frame_start, frame_end+1):
        bpy.context.scene.frame_set(f)

        root   = np.array(armature.pose.bones[root_bone].head)
        hip    = np.array(armature.pose.bones[hip_bone].head)
        knee   = np.array(armature.pose.bones[knee_bone].head)
        foot   = np.array(armature.pose.bones[foot_bone].head)
        torso1 = np.array(armature.pose.bones[torso_top_bone].head)
        torso6 = np.array(armature.pose.bones[torso_bottom_bone].head)

        hip_angle = hip_signed_by_body(root, hip, knee, torso1, torso6)
        knee_angle = knee_3d_angle(hip, knee, foot)

        hip_angles.append((f, hip_angle))
        knee_angles.append((f, knee_angle))

    # MHE = maximum hip extension (positive = extension)
    mhe_frame, mhe_val = max(hip_angles, key=lambda x: x[1])

    # MKF = minimum knee angle (maximum flexion)
    mkf_frame, mkf_val = min(knee_angles, key=lambda x: x[1])

    return (mhe_frame, mhe_val, mkf_frame, mkf_val)

# ==========================
# Run
# ==========================
mhe_f, mhe_v, mkf_f, mkf_v = analyze_right_leg()

print("=== Right leg kick ===")
print(f"MHE (hip_y max, torso1-6 reference): Frame {mhe_f}, Angle {mhe_v:.2f}°")
print(f"MKF (knee 3D min): Frame {mkf_f}, Angle {mkf_v:.2f}°")
$\endgroup$

0

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.