def calc_relative_rotation(model: PmxModel, links: BoneLinks, motion: VmdMotion, fno: int, limit_links=None): add_qs = [] for link_idx, link_bone_name in enumerate(links.all()): link_bone = links.get(link_bone_name) if not limit_links or (limit_links and limit_links.get(link_bone_name)): # 上限リンクがある場合、ボーンが存在している場合のみ、モーション内のキー情報を取得 fill_bf = motion.calc_bf(link_bone.name, fno) else: # 上限リンクでボーンがない場合、ボーンは初期値 fill_bf = VmdBoneFrame(fno=fno) fill_bf.set_name(link_bone_name) # 実際の回転量を計算 rot = deform_rotation(model, motion, fill_bf) add_qs.append(rot) return add_qs
def calc_relative_position(model: PmxModel, links: BoneLinks, motion: VmdMotion, fno: int, limit_links=None): trans_vs = [] for link_idx, link_bone_name in enumerate(links.all()): link_bone = links.get(link_bone_name) if not limit_links or (limit_links and limit_links.get(link_bone_name)): # 上限リンクがある倍、ボーンが存在している場合のみ、モーション内のキー情報を取得 fill_bf = motion.calc_bf(link_bone.name, fno) else: # 上限リンクでボーンがない場合、ボーンは初期値 fill_bf = VmdBoneFrame(fno=fno) fill_bf.set_name(link_bone_name) # 位置 if link_idx == 0: # 一番親は、グローバル座標を考慮 trans_vs.append(link_bone.position + fill_bf.position) else: # 位置:自身から親の位置を引いた相対位置 trans_vs.append(link_bone.position + fill_bf.position - links.get(link_bone_name, offset=-1).position) return trans_vs
def prepare_split_stance(self, motion: VmdMotion, target_bone_name: str): fnos = motion.get_bone_fnos(target_bone_name) for fidx, fno in enumerate(fnos): if fidx == 0: continue prev_bf = motion.bones[target_bone_name][fnos[fidx - 1]] bf = motion.bones[target_bone_name][fno] diff_degree = abs(prev_bf.rotation.toDegree() - bf.rotation.toDegree()) if diff_degree >= 150: # 回転量が約150度以上の場合、半分に分割しておく half_fno = prev_bf.fno + round((bf.fno - prev_bf.fno) / 2) if prev_bf.fno < half_fno < bf.fno: # キーが追加できる状態であれば、追加 half_bf = motion.calc_bf(target_bone_name, half_fno) motion.regist_bf(half_bf, target_bone_name, half_fno)
def deform_rotation(model: PmxModel, motion: VmdMotion, bf: VmdBoneFrame): if bf.name not in model.bones: return MQuaternion() bone = model.bones[bf.name] rot = bf.rotation.normalized().copy() if bone.fixed_axis != MVector3D(): # 回転角度を求める if rot != MQuaternion(): # 回転補正 if "右" in bone.name and rot.x() > 0 and bone.fixed_axis.x() <= 0: rot.setX(rot.x() * -1) rot.setScalar(rot.scalar() * -1) elif "左" in bone.name and rot.x() < 0 and bone.fixed_axis.x() >= 0: rot.setX(rot.x() * -1) rot.setScalar(rot.scalar() * -1) # 回転補正(コロン式ミクさん等軸反転パターン) elif "右" in bone.name and rot.x() < 0 and bone.fixed_axis.x() > 0: rot.setX(rot.x() * -1) rot.setScalar(rot.scalar() * -1) elif "左" in bone.name and rot.x() > 0 and bone.fixed_axis.x() < 0: rot.setX(rot.x() * -1) rot.setScalar(rot.scalar() * -1) rot.normalize() # 軸固定の場合、回転を制限する rot = MQuaternion.fromAxisAndAngle(bone.fixed_axis, rot.toDegree()) if bone.getExternalRotationFlag( ) and bone.effect_index in model.bone_indexes: effect_parent_bone = bone effect_bone = model.bones[model.bone_indexes[bone.effect_index]] cnt = 0 while cnt < 100: # 付与親が取得できたら、該当する付与親の回転を取得する effect_bf = motion.calc_bf(effect_bone.name, bf.fno) # 自身の回転量に付与親の回転量を付与率を加味して付与する if effect_parent_bone.effect_factor < 0: # マイナス付与の場合、逆回転 rot = rot * (effect_bf.rotation * abs(effect_parent_bone.effect_factor)).inverted() else: rot = rot * (effect_bf.rotation * effect_parent_bone.effect_factor) if effect_bone.getExternalRotationFlag( ) and effect_bone.effect_index in model.bone_indexes: # 付与親の親として現在のeffectboneを保持 effect_parent_bone = effect_bone # 付与親置き換え effect_bone = model.bones[model.bone_indexes[ effect_bone.effect_index]] else: break cnt += 1 return rot
def calc_IK(model: PmxModel, links: BoneLinks, motion: VmdMotion, fno: int, target_pos: MVector3D, ik_links: BoneLinks, max_count=10): for bone_name in list(ik_links.all().keys())[1:]: # bfをモーションに登録 bf = motion.calc_bf(bone_name, fno) motion.regist_bf(bf, bone_name, fno) local_effector_pos = MVector3D() local_target_pos = MVector3D() for cnt in range(max_count): # 規定回数ループ for ik_idx, joint_name in enumerate(list(ik_links.all().keys())[1:]): # 処理対象IKボーン ik_bone = ik_links.get(joint_name) # 現在のボーングローバル位置と行列を取得 global_3ds_dic, total_mats = calc_global_pos(model, links, motion, fno, return_matrix=True) # エフェクタ(末端) global_effector_pos = global_3ds_dic[ik_links.first_name()] # 注目ノード(実際に動かすボーン) joint_mat = total_mats[joint_name] # ワールド座標系から注目ノードの局所座標系への変換 inv_coord = joint_mat.inverted() # 注目ノードを起点とした、エフェクタのローカル位置 local_effector_pos = inv_coord * global_effector_pos local_target_pos = inv_coord * target_pos # (1) 基準関節→エフェクタ位置への方向ベクトル basis2_effector = local_effector_pos.normalized() # (2) 基準関節→目標位置への方向ベクトル basis2_target = local_target_pos.normalized() # ベクトル (1) を (2) に一致させるための最短回転量(Axis-Angle) # 回転角 rotation_dot = MVector3D.dotProduct(basis2_effector, basis2_target) # 回転角度 rotation_radian = math.acos(max(-1, min(1, rotation_dot))) if abs(rotation_radian) > 0.0001: # 一定角度以上の場合 # 回転軸 rotation_axis = MVector3D.crossProduct( basis2_effector, basis2_target).normalized() # 回転角度 rotation_degree = math.degrees(rotation_radian) # 関節回転量の補正(最大変位量を制限する) correct_qq = MQuaternion.fromAxisAndAngle( rotation_axis, min(rotation_degree, ik_bone.degree_limit)) # ジョイントに補正をかける bf = motion.calc_bf(joint_name, fno) new_ik_qq = correct_qq * bf.rotation # IK軸制限がある場合、上限下限をチェック if ik_bone.ik_limit_min != MVector3D( ) and ik_bone.ik_limit_max != MVector3D(): x_qq, y_qq, z_qq, yz_qq = separate_local_qq( fno, bone_name, new_ik_qq, model.get_local_x_axis(ik_bone.name)) logger.test("new_ik_qq: %s, x_qq: %s, y_qq: %s, z_qq: %s", new_ik_qq.toEulerAngles(), x_qq.toEulerAngles(), y_qq.toEulerAngles(), z_qq.toEulerAngles()) logger.test("new_ik_qq: %s, x_qq: %s, y_qq: %s, z_qq: %s", new_ik_qq.toDegree(), x_qq.toDegree(), y_qq.toDegree(), z_qq.toDegree()) euler_x = min( ik_bone.ik_limit_max.x(), max(ik_bone.ik_limit_min.x(), x_qq.toDegree())) euler_y = min( ik_bone.ik_limit_max.y(), max(ik_bone.ik_limit_min.y(), y_qq.toDegree())) euler_z = min( ik_bone.ik_limit_max.z(), max(ik_bone.ik_limit_min.z(), z_qq.toDegree())) logger.test( "limit_qq: %s -> %s", new_ik_qq.toEulerAngles(), MQuaternion.fromEulerAngles(euler_x, euler_y, euler_z).toEulerAngles()) new_ik_qq = MQuaternion.fromEulerAngles( euler_x, euler_y, euler_z) bf.rotation = new_ik_qq # 位置の差がほとんどない場合、終了 if (local_effector_pos - local_target_pos).lengthSquared() < 0.0001: return return