【问题标题】:Clamp angle with continuous result具有连续结果的夹角
【发布时间】:2017-01-05 14:45:48
【问题描述】:

我正在编写自己的逆运动学,目前正在研究关节约束。大部分都很好,但我想从生成的动画中删除所有不连续性,而约束是最大的问题。

例如,假设您正在看一个围绕您的头旋转的球。它向右旋转,您看起来正确,直到您的颈部扭曲极限。球仍然在移动,当它经过你的背部时,你的头会突然被夹在对面的限制上,向左看。这就是我们通过典型的夹紧得到的结果,它给我带来了帧到帧的不连续性。

我需要的是,你的头会在几帧内从右向左移动。我的第一个想法是用我的 X 轴(在肩膀之间)反映源方向,如果反射方向满足约束限制 - 将其返回。如果它不符合限制 - 剪辑反射方向而不是源方向。

这应该给我带来不错的连续运动,这不是火箭科学,但有很多细节需要注意。我的限制可能像min < max,也可能像max < min,它们可能通过切换角度值(如360->0,或180->-180)或不通过。此外,当我的允许范围大于 180 度时,逻辑应该略有不同。

不同案例的数量增长非常快,所以我想知道我是否可以在某个地方找到它已经完成?

顺便说一句:我使用的是虚幻引擎,如果它有什么不同的话。

编辑:

如果我用我的头部示例误导了你,我很抱歉 - 这只是一个可视化我的需求的类比。我不会使用此代码在最终动画中定位头骨(或任何其他)。我打算将它用作解决 IK 的一个非常基本的操作。它将在每个 IK 链中的每个迭代中,对每个骨骼,多次使用。所以它需要尽可能快。如果需要,所有更高级别的概念,如运动规划、动力学等,都将添加到另一层。

现在我只是要求这样的功能:

float clampAngle( float angle, float min, float max )

这将返回一个用于更改输入的连续值。

我目前正在处理不同的问题,但当我回到这个问题时我会发布我的代码。

【问题讨论】:

  • 单独剪辑是不够的,您需要在几帧的过程中安排从 +clip 到 -clip 的旋转
  • @Alnitak 我不想添加任何计划,因为我不知道未来。我需要它尽可能简单,但要持续输出。我认为我的描述会做到这一点,我只需要对其进行编码:)。
  • 我会选择将头部旋转限制在每次更新的最大角度变化。当球经过头后的中间点时,不要立即将视线从右侧约束转移到左侧约束,而是朝那个方向移动,但不要超过您选择的最大角度。这会更自然,也能反映现实的动态约束。你所描述的内容听起来很奇怪,注视着一个并不真正存在的图像。

标签: c++ 3d geometry unreal-engine4


【解决方案1】:

如果您将此视为模拟,它会更加清晰。您不需要任何计划,如您的评论中所述,因为下一帧仅使用当前帧的可用信息来计算。

如前两段所述,为颈部设置一个关节。随着球的到来,它会在一帧内从左向右翻转。

然后设置第二个相同的关节并编写一个角度弹簧,随着时间的推移将其向第一个关节旋转。通过调整弹簧系数(强度、阻尼等),您可以控制头部的旋转方式。

角弹簧怎么写?

这可能不是最好的方法,但是代码很短很容易,所以这里......

让我们称这 2 个关节为主机和从机。您需要在从关节上存储旋转phi 和角速度omega

phiomegaaxis-angle vectors - 一个 3d 向量,其大小是围绕向量定义的轴旋转的弧度数。它使模拟旋转变得非常容易。无论您的关节旋转是存储为欧拉角、矩阵还是四元数,您都可能有一些非 UE API 的类来帮助提取轴/角度向量。

当主关节发生变化时,将其旋转转换为轴角。让我们称之为phi_m

在开始帧上,Slave 旋转phi 应设置为与 master 相同的值。 phi = phi_m。它没有角速度,所以omega 设置为零向量(0,0,0)

然后你向前移动一帧。帧的长度dt 可能是 1/60 秒或其他。

在模拟时,您首先为phi_m 计算一个新值。那么主从之间的差异(向量phi_m - phi)代表作用在从关节上的扭矩。 The bigger the angle the stronger the torque。假设质量为 1.0,则使用 F=ma,角加速度alpha 等于扭矩。这意味着从属关节在该帧上的角速度变化为alpha*dt。现在我们可以计算出新的角速度:omega = omega + (alpha*dt)。类似地,新旋转是旧旋转加上随时间的旋转变化(角速度)。 phi = phi + (omega * dt)

将它们放在一起并加上弹簧系数strengthdamping,模拟步骤可以是这样的:

damping = 0.01 // between 0 and 1
strength = 0.2 // ..experiment
dt = currentTime-lastTimeEvaluated

phi_m = eulerToAxisAngle(master.rotate)
if (currentTime <= startTime || dt < 0) {
  slave.phi = phi_m
  slave.omega = (0,0,0)  
} else {
  alpha = (phi_m - slave.phi)*strength
  slave.omega = (slave.omega * (1.0 - damping)) + (alpha*dt)
  slave.phi = slave.phi + slave.omega*dt
}
slave.rotate = axisAngleToEuler(slave.phi)
lastTimeEvaluated = currentTime

请注意,阻尼只会抵消一部分存储的速度。如果阻尼非常低,关节会过冲并摆动到位 - 嘭!!

【讨论】:

    【解决方案2】:

    一种方法:

    • 定义不能满足约束的区域(头部后面的圆锥)
    • 衡量您的目标进入该区域的距离(目标与锥体中心之间的角度除以半锥角)
    • 使用这个标量值将您的受约束对象平滑地混合回某个预定义的中性位置(即,当目标移动到头部后面时,将头部平稳地转回中心/静止位置)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-07-21
      • 2021-05-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-04-29
      相关资源
      最近更新 更多