import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from mpl_toolkits.mplot3d import Axes3D
from scipy.spatial.transform import Rotation as R
# 创建画布和3D坐标轴
fig = plt.figure(figsize=(8, 8))
ax = fig.add_subplot(111, projection='3d')
ax.set_axis_off()
# 人体各部位参数配置(增强版)
body_config = {
'head': {
'type': 'sphere',
'pos': (0, 0, 1.7),
'size': 0.2,
'color': '#FFDAB9' # 肤色
},
'torso': {
'type': 'cylinder',
'start': (0, 0, 1.0),
'end': (0, 0, 0.5),
'radius': 0.3,
'color': '#87CEEB' # 天蓝色
},
'arm_left': {
'type': 'cylinder',
'start': (-0.3, 0, 1.3),
'end': (-0.8, 0, 1.0),
'radius': 0.1,
'color': '#87CEEB'
},
'arm_right': {
'type': 'cylinder',
'start': (0.3, 0, 1.3),
'end': (0.8, 0, 1.0),
'radius': 0.1,
'color': '#87CEEB'
},
'leg_left': {
'type': 'cylinder',
'start': (-0.15, 0, 0.5),
'end': (-0.3, 0, 0.0),
'radius': 0.15,
'color': '#4682B4' # 深蓝色
},
'leg_right': {
'type': 'cylinder',
'start': (0.15, 0, 0.5),
'end': (0.3, 0, 0.0),
'radius': 0.15,
'color': '#4682B4'
}
}
class BodyPart:
"""人体部件基类"""
def __init__(self, config):
self.config = config
def draw(self, ax):
raise NotImplementedError
class Sphere(BodyPart):
"""球体部件(用于头部)"""
def draw(self, ax):
u = np.linspace(0, 2 * np.pi, 30)
v = np.linspace(0, np.pi, 30)
x
= self.
config['pos'][0] + self.
config['size'] * np.
outer(np.
cos(u
), np.
sin(v
)) y
= self.
config['pos'][1] + self.
config['size'] * np.
outer(np.
sin(u
), np.
sin(v
)) z
= self.
config['pos'][2] + self.
config['size'] * np.
outer(np.
ones(np.
size(u
)), np.
cos(v
)) ax.plot_surface(x, y, z, color=self.config['color'], alpha=0.8)
class Cylinder(BodyPart):
"""圆柱体部件(用于四肢和躯干)"""
def draw(self, ax):
start = np.array(self.config['start'])
end = np.array(self.config['end'])
vec = end - start
length = np.linalg.norm(vec)
# 生成圆柱体基础网格
theta = np.linspace(0, 2 * np.pi, 30)
z = np.linspace(0, 1, 30)
theta_grid, z_grid = np.meshgrid(theta, z)
# 计算截面坐标
x_grid
= self.
config['radius'] * np.
cos(theta_grid
) y_grid
= self.
config['radius'] * np.
sin(theta_grid
) z_grid = length * z_grid
# 计算旋转矩阵
target_dir = vec / length
initial_dir = np.array([0, 0, 1])
rotation_axis = np.cross(initial_dir, target_dir)
rotation_angle = np.arccos(np.dot(initial_dir, target_dir))
rotation = R.from_rotvec(rotation_axis * rotation_angle)
# 应用旋转和平移
points = np.vstack([x_grid.ravel(), y_grid.ravel(), z_grid.ravel()])
rotated_points = rotation.apply(points.T).T
translated_points = rotated_points + start.reshape(-1, 1)
# 重塑网格形状
x = translated_points[0].reshape(x_grid.shape)
y = translated_points[1].reshape(y_grid.shape)
z = translated_points[2].reshape(z_grid.shape)
ax.plot_surface(x, y, z, color=self.config['color'], alpha=0.8)
# 创建人体部件
body_parts = []
for part in body_config.values():
if part['type'] == 'sphere':
body_parts.append(Sphere(part))
elif part['type'] == 'cylinder':
body_parts.append(Cylinder(part))
# 绘制初始图形
for part in body_parts:
part.draw(ax)
# 设置视角范围和初始角度
ax.set_xlim(-1, 1)
ax.set_ylim
aW1wb3J0IG51bXB5IGFzIG5wCmltcG9ydCBtYXRwbG90bGliLnB5cGxvdCBhcyBwbHQKZnJvbSBtYXRwbG90bGliLmFuaW1hdGlvbiBpbXBvcnQgRnVuY0FuaW1hdGlvbgpmcm9tIG1wbF90b29sa2l0cy5tcGxvdDNkIGltcG9ydCBBeGVzM0QKZnJvbSBzY2lweS5zcGF0aWFsLnRyYW5zZm9ybSBpbXBvcnQgUm90YXRpb24gYXMgUgoKIyDliJvlu7rnlLvluIPlkowzROWdkOagh+i9tApmaWcgPSBwbHQuZmlndXJlKGZpZ3NpemU9KDgsIDgpKQpheCA9IGZpZy5hZGRfc3VicGxvdCgxMTEsIHByb2plY3Rpb249JzNkJykKYXguc2V0X2F4aXNfb2ZmKCkKCiMg5Lq65L2T5ZCE6YOo5L2N5Y+C5pWw6YWN572u77yI5aKe5by654mI77yJCmJvZHlfY29uZmlnID0gewogICAgJ2hlYWQnOiB7CiAgICAgICAgJ3R5cGUnOiAnc3BoZXJlJywKICAgICAgICAncG9zJzogKDAsIDAsIDEuNyksCiAgICAgICAgJ3NpemUnOiAwLjIsCiAgICAgICAgJ2NvbG9yJzogJyNGRkRBQjknICAjIOiCpOiJsgogICAgfSwKICAgICd0b3Jzbyc6IHsKICAgICAgICAndHlwZSc6ICdjeWxpbmRlcicsCiAgICAgICAgJ3N0YXJ0JzogKDAsIDAsIDEuMCksCiAgICAgICAgJ2VuZCc6ICgwLCAwLCAwLjUpLAogICAgICAgICdyYWRpdXMnOiAwLjMsCiAgICAgICAgJ2NvbG9yJzogJyM4N0NFRUInICAjIOWkqeiTneiJsgogICAgfSwKICAgICdhcm1fbGVmdCc6IHsKICAgICAgICAndHlwZSc6ICdjeWxpbmRlcicsCiAgICAgICAgJ3N0YXJ0JzogKC0wLjMsIDAsIDEuMyksCiAgICAgICAgJ2VuZCc6ICgtMC44LCAwLCAxLjApLAogICAgICAgICdyYWRpdXMnOiAwLjEsCiAgICAgICAgJ2NvbG9yJzogJyM4N0NFRUInCiAgICB9LAogICAgJ2FybV9yaWdodCc6IHsKICAgICAgICAndHlwZSc6ICdjeWxpbmRlcicsCiAgICAgICAgJ3N0YXJ0JzogKDAuMywgMCwgMS4zKSwKICAgICAgICAnZW5kJzogKDAuOCwgMCwgMS4wKSwKICAgICAgICAncmFkaXVzJzogMC4xLAogICAgICAgICdjb2xvcic6ICcjODdDRUVCJwogICAgfSwKICAgICdsZWdfbGVmdCc6IHsKICAgICAgICAndHlwZSc6ICdjeWxpbmRlcicsCiAgICAgICAgJ3N0YXJ0JzogKC0wLjE1LCAwLCAwLjUpLAogICAgICAgICdlbmQnOiAoLTAuMywgMCwgMC4wKSwKICAgICAgICAncmFkaXVzJzogMC4xNSwKICAgICAgICAnY29sb3InOiAnIzQ2ODJCNCcgICMg5rex6JOd6ImyCiAgICB9LAogICAgJ2xlZ19yaWdodCc6IHsKICAgICAgICAndHlwZSc6ICdjeWxpbmRlcicsCiAgICAgICAgJ3N0YXJ0JzogKDAuMTUsIDAsIDAuNSksCiAgICAgICAgJ2VuZCc6ICgwLjMsIDAsIDAuMCksCiAgICAgICAgJ3JhZGl1cyc6IDAuMTUsCiAgICAgICAgJ2NvbG9yJzogJyM0NjgyQjQnCiAgICB9Cn0KCmNsYXNzIEJvZHlQYXJ0OgogICAgIiIi5Lq65L2T6YOo5Lu25Z+657G7IiIiCiAgICBkZWYgX19pbml0X18oc2VsZiwgY29uZmlnKToKICAgICAgICBzZWxmLmNvbmZpZyA9IGNvbmZpZwogICAgCiAgICBkZWYgZHJhdyhzZWxmLCBheCk6CiAgICAgICAgcmFpc2UgTm90SW1wbGVtZW50ZWRFcnJvcgoKY2xhc3MgU3BoZXJlKEJvZHlQYXJ0KToKICAgICIiIueQg+S9k+mDqOS7tu+8iOeUqOS6juWktOmDqO+8iSIiIgogICAgZGVmIGRyYXcoc2VsZiwgYXgpOgogICAgICAgIHUgPSBucC5saW5zcGFjZSgwLCAyICogbnAucGksIDMwKQogICAgICAgIHYgPSBucC5saW5zcGFjZSgwLCBucC5waSwgMzApCiAgICAgICAgeCA9IHNlbGYuY29uZmlnWydwb3MnXVswXSArIHNlbGYuY29uZmlnWydzaXplJ10gKiBucC5vdXRlcihucC5jb3ModSksIG5wLnNpbih2KSkKICAgICAgICB5ID0gc2VsZi5jb25maWdbJ3BvcyddWzFdICsgc2VsZi5jb25maWdbJ3NpemUnXSAqIG5wLm91dGVyKG5wLnNpbih1KSwgbnAuc2luKHYpKQogICAgICAgIHogPSBzZWxmLmNvbmZpZ1sncG9zJ11bMl0gKyBzZWxmLmNvbmZpZ1snc2l6ZSddICogbnAub3V0ZXIobnAub25lcyhucC5zaXplKHUpKSwgbnAuY29zKHYpKQogICAgICAgIGF4LnBsb3Rfc3VyZmFjZSh4LCB5LCB6LCBjb2xvcj1zZWxmLmNvbmZpZ1snY29sb3InXSwgYWxwaGE9MC44KQoKY2xhc3MgQ3lsaW5kZXIoQm9keVBhcnQpOgogICAgIiIi5ZyG5p+x5L2T6YOo5Lu277yI55So5LqO5Zub6IKi5ZKM6Lqv5bmy77yJIiIiCiAgICBkZWYgZHJhdyhzZWxmLCBheCk6CiAgICAgICAgc3RhcnQgPSBucC5hcnJheShzZWxmLmNvbmZpZ1snc3RhcnQnXSkKICAgICAgICBlbmQgPSBucC5hcnJheShzZWxmLmNvbmZpZ1snZW5kJ10pCiAgICAgICAgdmVjID0gZW5kIC0gc3RhcnQKICAgICAgICBsZW5ndGggPSBucC5saW5hbGcubm9ybSh2ZWMpCiAgICAgICAgCiAgICAgICAgIyDnlJ/miJDlnIbmn7HkvZPln7rnoYDnvZHmoLwKICAgICAgICB0aGV0YSA9IG5wLmxpbnNwYWNlKDAsIDIgKiBucC5waSwgMzApCiAgICAgICAgeiA9IG5wLmxpbnNwYWNlKDAsIDEsIDMwKQogICAgICAgIHRoZXRhX2dyaWQsIHpfZ3JpZCA9IG5wLm1lc2hncmlkKHRoZXRhLCB6KQogICAgICAgIAogICAgICAgICMg6K6h566X5oiq6Z2i5Z2Q5qCHCiAgICAgICAgeF9ncmlkID0gc2VsZi5jb25maWdbJ3JhZGl1cyddICogbnAuY29zKHRoZXRhX2dyaWQpCiAgICAgICAgeV9ncmlkID0gc2VsZi5jb25maWdbJ3JhZGl1cyddICogbnAuc2luKHRoZXRhX2dyaWQpCiAgICAgICAgel9ncmlkID0gbGVuZ3RoICogel9ncmlkCiAgICAgICAgCiAgICAgICAgIyDorqHnrpfml4vovaznn6npmLUKICAgICAgICB0YXJnZXRfZGlyID0gdmVjIC8gbGVuZ3RoCiAgICAgICAgaW5pdGlhbF9kaXIgPSBucC5hcnJheShbMCwgMCwgMV0pCiAgICAgICAgcm90YXRpb25fYXhpcyA9IG5wLmNyb3NzKGluaXRpYWxfZGlyLCB0YXJnZXRfZGlyKQogICAgICAgIHJvdGF0aW9uX2FuZ2xlID0gbnAuYXJjY29zKG5wLmRvdChpbml0aWFsX2RpciwgdGFyZ2V0X2RpcikpCiAgICAgICAgcm90YXRpb24gPSBSLmZyb21fcm90dmVjKHJvdGF0aW9uX2F4aXMgKiByb3RhdGlvbl9hbmdsZSkKICAgICAgICAKICAgICAgICAjIOW6lOeUqOaXi+i9rOWSjOW5s+enuwogICAgICAgIHBvaW50cyA9IG5wLnZzdGFjayhbeF9ncmlkLnJhdmVsKCksIHlfZ3JpZC5yYXZlbCgpLCB6X2dyaWQucmF2ZWwoKV0pCiAgICAgICAgcm90YXRlZF9wb2ludHMgPSByb3RhdGlvbi5hcHBseShwb2ludHMuVCkuVAogICAgICAgIHRyYW5zbGF0ZWRfcG9pbnRzID0gcm90YXRlZF9wb2ludHMgKyBzdGFydC5yZXNoYXBlKC0xLCAxKQogICAgICAgIAogICAgICAgICMg6YeN5aGR572R5qC85b2i54q2CiAgICAgICAgeCA9IHRyYW5zbGF0ZWRfcG9pbnRzWzBdLnJlc2hhcGUoeF9ncmlkLnNoYXBlKQogICAgICAgIHkgPSB0cmFuc2xhdGVkX3BvaW50c1sxXS5yZXNoYXBlKHlfZ3JpZC5zaGFwZSkKICAgICAgICB6ID0gdHJhbnNsYXRlZF9wb2ludHNbMl0ucmVzaGFwZSh6X2dyaWQuc2hhcGUpCiAgICAgICAgCiAgICAgICAgYXgucGxvdF9zdXJmYWNlKHgsIHksIHosIGNvbG9yPXNlbGYuY29uZmlnWydjb2xvciddLCBhbHBoYT0wLjgpCgojIOWIm+W7uuS6uuS9k+mDqOS7tgpib2R5X3BhcnRzID0gW10KZm9yIHBhcnQgaW4gYm9keV9jb25maWcudmFsdWVzKCk6CiAgICBpZiBwYXJ0Wyd0eXBlJ10gPT0gJ3NwaGVyZSc6CiAgICAgICAgYm9keV9wYXJ0cy5hcHBlbmQoU3BoZXJlKHBhcnQpKQogICAgZWxpZiBwYXJ0Wyd0eXBlJ10gPT0gJ2N5bGluZGVyJzoKICAgICAgICBib2R5X3BhcnRzLmFwcGVuZChDeWxpbmRlcihwYXJ0KSkKCiMg57uY5Yi25Yid5aeL5Zu+5b2iCmZvciBwYXJ0IGluIGJvZHlfcGFydHM6CiAgICBwYXJ0LmRyYXcoYXgpCgojIOiuvue9ruinhuinkuiMg+WbtOWSjOWIneWni+inkuW6pgpheC5zZXRfeGxpbSgtMSwgMSkKYXguc2V0X3lsaW0K