name: 运动符号系统 description: 设计系统以编码、评分和生成编舞运动,使用Laban符号、计算几何和程序动画原则。 license: MIT
运动符号系统
这个技能提供指导,用于创建系统以编码、分析和生成人类运动,应用于编舞、动画和运动分析。
核心能力
- 运动符号:Labanotation、Benesh、Motif符号
- 计算几何:骨骼表示、关节角度
- 程序动画:基于规则的运动生成
- 努力-形状分析:Laban运动分析(LMA)
- 时间结构:节奏、分句、动态
运动符号基础
运动的挑战
运动本质上是多维的:
- 3D空间路径
- 时间演变
- 身体部位协调
- 定性动态(努力)
- 关系上下文(其他身体、物体、空间)
主要符号系统
| 系统 | 优势 | 应用案例 |
|---|---|---|
| Labanotation | 完整、精确 | 归档、重建 |
| Benesh | 紧凑、视觉 | 芭蕾、治疗 |
| Motif | 抽象、可读 | 教学、分析 |
| 运动捕捉 | 精确坐标 | 动画、研究 |
Laban运动分析框架
身体组件
哪些身体部位在运动:
身体组织:
├── 核心-远端(中心向外)
├── 头-尾(脊柱连接)
├── 上-下(水平分割)
├── 身体-半(左-右)
└── 交叉侧(对角线连接)
身体部位层次:
中心(骨盆)
├── 躯干(脊柱、胸部)
│ ├── 头
│ ├── 肩膀
│ └── 手臂 → 肘部 → 手 → 手指
└── 臀部
└── 腿 → 膝盖 → 脚 → 脚趾
空间组件
身体在哪里运动:
class KinesphereModel:
"""身体周围可及空间"""
DIMENSIONS = {
'vertical': {'up', 'down'},
'horizontal': {'left', 'right'},
'sagittal': {'forward', 'backward'}
}
LEVELS = ['low', 'middle', 'high']
# 动球中的27个方向
DIRECTION_SYMBOLS = {
'place_high': (0, 1, 0),
'place_middle': (0, 0, 0),
'place_low': (0, -1, 0),
'forward_high': (0, 1, 1),
'forward_middle': (0, 0, 1),
'forward_low': (0, -1, 1),
# ... 所有27种组合
}
# 空间尺度
SCALES = {
'near': 0.3, # 靠近身体中心
'mid': 0.6, # 一般可及范围
'far': 1.0 # 完全伸展
}
努力组件
运动如何执行(定性动态):
class EffortFactors:
"""Laban努力品质"""
FACTORS = {
'weight': {
'light': {'sensation': 'buoyant', 'value': -1},
'strong': {'sensation': 'powerful', 'value': 1}
},
'time': {
'sustained': {'sensation': 'leisurely', 'value': -1},
'quick': {'sensation': 'urgent', 'value': 1}
},
'space': {
'indirect': {'sensation': 'flexible', 'value': -1},
'direct': {'sensation': 'focused', 'value': 1}
},
'flow': {
'free': {'sensation': 'fluent', 'value': -1},
'bound': {'sensation': 'controlled', 'value': 1}
}
}
# 基本努力动作(重量、时间、空间的组合)
ACTIONS = {
'punch': {'weight': 'strong', 'time': 'quick', 'space': 'direct'},
'dab': {'weight': 'light', 'time': 'quick', 'space': 'direct'},
'slash': {'weight': 'strong', 'time': 'quick', 'space': 'indirect'},
'flick': {'weight': 'light', 'time': 'quick', 'space': 'indirect'},
'press': {'weight': 'strong', 'time': 'sustained', 'space': 'direct'},
'glide': {'weight': 'light', 'time': 'sustained', 'space': 'direct'},
'wring': {'weight': 'strong', 'time': 'sustained', 'space': 'indirect'},
'float': {'weight': 'light', 'time': 'sustained', 'space': 'indirect'}
}
形状组件
身体如何改变形式:
class ShapeQualities:
"""身体形状变化"""
MODES = {
'shape_flow': {
'description': '内部塑形,自我导向',
'examples': ['呼吸', '生长/收缩']
},
'directional': {
'description': '与环境桥接',
'subtypes': ['辐射状', '弧状']
},
'carving': {
'description': '雕刻3D空间',
'relationship': '与环境互动'
}
}
AFFINITIES = {
'rising': {'effort': 'light', 'direction': 'up'},
'sinking': {'effort': 'strong', 'direction': 'down'},
'spreading': {'effort': 'indirect', 'direction': 'horizontal'},
'enclosing': {'effort': 'direct', 'direction': 'in'},
'advancing': {'effort': 'sustained', 'direction': 'forward'},
'retreating': {'effort': 'quick', 'direction': 'back'}
}
计算运动表示
骨骼数据结构
class Skeleton:
"""分层骨骼表示"""
def __init__(self):
self.joints = {
'pelvis': Joint('pelvis', parent=None),
'spine': Joint('spine', parent='pelvis'),
'chest': Joint('chest', parent='spine'),
'neck': Joint('neck', parent='chest'),
'head': Joint('head', parent='neck'),
'l_shoulder': Joint('l_shoulder', parent='chest'),
'l_elbow': Joint('l_elbow', parent='l_shoulder'),
'l_wrist': Joint('l_wrist', parent='l_elbow'),
'r_shoulder': Joint('r_shoulder', parent='chest'),
# ... 等
}
def get_world_position(self, joint_name):
"""从局部变换计算全局位置"""
joint = self.joints[joint_name]
position = joint.local_position
current = joint
while current.parent:
parent = self.joints[current.parent]
position = parent.rotation.apply(position) + parent.local_position
current = parent
return position
def compute_joint_angles(self):
"""提取关节角度用于分析"""
angles = {}
for name, joint in self.joints.items():
if joint.parent:
angles[name] = joint.rotation.as_euler('xyz')
return angles
class Joint:
"""骨骼层次中的单个关节"""
def __init__(self, name, parent=None):
self.name = name
self.parent = parent
self.local_position = np.array([0, 0, 0])
self.rotation = Rotation.identity()
self.constraints = {} # 关节限制
运动轨迹
class MotionTrajectory:
"""姿势的时间序列"""
def __init__(self, fps=30):
self.fps = fps
self.frames = [] # 骨骼状态列表
self.annotations = [] # 定性标记
def duration(self):
return len(self.frames) / self.fps
def get_velocity(self, joint_name, frame_idx):
"""计算瞬时速度"""
if frame_idx < 1:
return np.zeros(3)
pos_current = self.frames[frame_idx].get_world_position(joint_name)
pos_prev = self.frames[frame_idx - 1].get_world_position(joint_name)
return (pos_current - pos_prev) * self.fps
def extract_effort_features(self, joint_name, window=10):
"""从运动估计Laban努力品质"""
features = {
'weight': self._compute_acceleration_magnitude(joint_name, window),
'time': self._compute_temporal_change_rate(joint_name, window),
'space': self._compute_path_directness(joint_name, window),
'flow': self._compute_flow_continuity(joint_name, window)
}
return features
程序运动生成
基于规则的编舞
class ChoreographyGenerator:
"""从规则生成运动序列"""
def __init__(self):
self.vocabulary = self._load_movement_vocabulary()
self.grammar = self._load_grammar_rules()
def generate_phrase(self, theme, duration_beats=16):
"""生成编舞短语"""
# 从基于主题的动机开始
motif = self._select_motif(theme)
# 通过短语发展
phrase = [motif]
current_beats = motif.duration_beats
while current_beats < duration_beats:
# 应用发展规则
if random.random() < 0.3:
# 带变化的重复
variation = self._vary_motif(phrase[-1])
phrase.append(variation)
elif random.random() < 0.5:
# 对比
contrast = self._generate_contrast(phrase[-1])
phrase.append(contrast)
else:
# 过渡
transition = self._smooth_transition(phrase[-1])
phrase.append(transition)
current_beats += phrase[-1].duration_beats
return phrase
def _vary_motif(self, motif):
"""创建运动变化"""
variations = [
self._change_level, # 相同运动,不同水平
self._mirror, # 左右反转
self._change_size, # 更大或更小
self._change_tempo, # 更快或更慢
self._change_direction, # 面向不同方向
self._fragment, # 运动的部分
self._extend, # 添加到运动上
]
return random.choice(variations)(motif)
努力驱动动画
class EffortAnimator:
"""生成具有指定努力品质的运动"""
def animate_action(self, skeleton, target_position, effort_state):
"""以指定努力移动身体部位"""
# 时间因子影响持续时间
if effort_state['time'] == 'quick':
duration = 0.3
ease_type = 'exponential'
else: # sustained
duration = 1.2
ease_type = 'sine'
# 重量因子影响加速度
if effort_state['weight'] == 'strong':
acceleration_curve = self._strong_curve()
else: # light
acceleration_curve = self._light_curve()
# 空间因子影响路径
if effort_state['space'] == 'direct':
path = self._linear_path(skeleton.current, target_position)
else: # indirect
path = self._curved_path(skeleton.current, target_position)
# 流动因子影响连续性
if effort_state['flow'] == 'bound':
path = self._add_micro_pauses(path)
# 自由流动默认平滑
return self._create_animation(
path=path,
duration=duration,
acceleration=acceleration_curve
)
空间模式生成
class FloorPatternGenerator:
"""生成空间路径"""
def generate_path(self, pattern_type, space_bounds, duration):
"""生成地板图案"""
patterns = {
'circular': self._circular_path,
'spiral': self._spiral_path,
'figure_eight': self._figure_eight,
'diagonal_cross': self._diagonal_cross,
'zigzag': self._zigzag_path,
'random_walk': self._random_walk
}
generator = patterns.get(pattern_type, self._random_walk)
return generator(space_bounds, duration)
def _spiral_path(self, bounds, duration, turns=3):
"""生成螺旋地板图案"""
center = bounds.center
max_radius = min(bounds.width, bounds.height) / 2
points = []
num_points = int(duration * 30) # 每拍30个点
for i in range(num_points):
t = i / num_points
angle = t * turns * 2 * np.pi
radius = max_radius * (1 - t) # 向内螺旋
x = center[0] + radius * np.cos(angle)
y = center[1] + radius * np.sin(angle)
points.append((x, y, t * duration))
return points
符号输出
文本符号格式
class MovementScore:
"""基于文本的运动乐谱"""
def to_notation(self, phrase):
"""将短语转换为可读符号"""
score = []
for movement in phrase:
notation = {
'beat': movement.start_beat,
'duration': movement.duration_beats,
'body': self._notate_body(movement),
'space': self._notate_space(movement),
'effort': self._notate_effort(movement),
'description': movement.description
}
score.append(notation)
return score
def _notate_effort(self, movement):
"""努力符号标记"""
symbols = {
('strong', 'quick', 'direct'): '⚡', # 击打
('light', 'sustained', 'indirect'): '☁️', # 漂浮
('strong', 'sustained', 'indirect'): '🌀', # 扭绞
# ... 等
}
effort_tuple = (
movement.effort['weight'],
movement.effort['time'],
movement.effort['space']
)
return symbols.get(effort_tuple, '○')
视觉乐谱表示
时间 →
| 0 | 1 | 2 | 3 | 4 |
├─────────┼─────────┼─────────┼─────────┼─────────┤
│ ◊ 头 │ │ ○ │ │ │
│ ═ 手臂 │ ═══════│════ │ ═════ │═════ │
│ ║ 躯干 │ ║ │ ║║ │ ║ │ ║║║ │
│ ‖ 腿 │ ‖‖ │ ‖ ‖ │ ‖‖ │ ‖ ‖ │
├─────────┼─────────┼─────────┼─────────┼─────────┤
│ 水平: │ 高 │ 中 │ 低 │ 中 │
│ 努力: │ ⚡ │ ☁️ │ 🌀 │ ⚡ │
└─────────┴─────────┴─────────┴─────────┴─────────┘
最佳实践
编码原则
- 保留意图:捕捉编舞者的意图,不仅仅是位置
- 分层信息:结构 > 形状 > 动态
- 允许解释:除非需要,不要过度指定
- 支持变化:启用受控的即兴创作
分析指南
- 提取定量(位置)和定性(努力)特征
- 考虑文化和风格背景
- 与从业者验证
参考文献
references/labanotation-symbols.md- Labanotation符号参考references/lma-glossary.md- Laban运动分析术语references/motion-capture-formats.md- 常见动作捕捉数据格式