65.9K
CodeProject 正在变化。 阅读更多。
Home

OSG 中的弹跳球

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.50/5 (4投票s)

2008年8月29日

CPOL

4分钟阅读

viewsIcon

38292

downloadIcon

855

使用离散事件、三元堆和插值在OSG中进行模拟

目录

引言

这个项目演示了一个模拟,其中对象可以连续变化(移动和旋转的球)和瞬时变化(碰撞)。它适用于OSG(至少2.2版)和Visual Studio/Express 2005或2008。

MixedOSG.gif

动机

许多物理模拟通过在固定时间间隔内进行计算来估算物体的位置。这对于使用有限元分析的许多模拟引擎(如ODE)来说效果很好。但是,如果时间间隔太长,可能会导致精度损失——例如,本应发生碰撞的物体反而会互相穿过。我创建这个项目是为了通过计算变化发生的精确时间来模拟一个系统——在这个例子中,是碰撞发生的时间。这样,模拟就可以更快地运行而不损失精度。

在许多OSG示例应用程序中,对象在渲染每一帧后更新,导致运动不平滑。在本例中,对象通过osg::NodeCallback进行更新,以使其平滑移动。

概述

该模拟预测了箱子中弹跳的球的位置和旋转,并使用OSG渲染它们的运动。它根据球当前的轨迹预测球何时发生碰撞,并处理这些碰撞。随着模拟时间的推移,它必须检查是否应该发生任何变化。如果发生,它将找到下一个预测的变化,并在该时间更新球的速度来处理它。

构建

要构建或运行此项目,必须安装OSG(OpenSceneGraph)。最简单的方法是下载与您的Visual Studio版本兼容的预编译二进制文件,可以通过此处的链接获取,在这种情况下,此项目应该在发布模式下构建。

OSG场景

OSG场景由一个透明的盒子(osg::Boxosg::Material)和几个带有纹理表面(osg::Texture2D)的球体(osg::Sphere)组成,这些球体移动和旋转(osg::PositionAttitudeTransformosg::NodeCallback)。

优先级队列

优先级队列是任何允许我们添加项目并删除优先级最高项目的这种数据结构。在这种情况下,项目是碰撞预测导致的变化。一个变化比另一个变化具有更高的优先级,如果它被预测为首先发生。模拟使用优先级队列来查找下一个变化,以便在任何时间点提供准确的情况。STL库包含优先级队列,但我们也希望在碰撞后更新模拟时能够增加或减少项目的优先级。

三元堆

堆是一种树,其中每个项目的父项具有更高的优先级,并且根项目具有最高的优先级。这就是所谓的堆属性。我们可以将堆用作优先级队列。

在三元堆中,每个项目有3个子项(除了最后一个项目可能不足3个,以便为添加或删除项目腾出空间)。项目存储在数组中。这易于维护,并且在数组的特定索引处查找项目的父项和第一个子项也很容易。(三元堆的性能优于二元堆。)

int Parent(int index)
{
return (index - 1) / 3;
}
int Child(int index)
{
return index * 3 + 1;
} 

当堆中项目的优先级发生变化时,这可能会破坏堆属性,但事实证明,我们可以将项目与其父项或子项交换,直到堆属性恢复(向上或向下移动),这允许我们更改、添加或删除堆中的项目。

我们可以通过将项目添加到数组末尾,然后将项目向上移动到堆中来添加项目。我们还可以通过将数组的最后一个项目与第一个项目(堆的根)交换,然后删除最后一个项目,再将新的根项向下移动来删除第一个项目。

连续运动和插值

在OSG中更新对象位置和旋转的推荐方法是使用osg::NodeCallback对象。它用于在渲染的帧的正确时间点更新模拟。然后更新球的位置和旋转。

如果在时间t0时球的位置是向量s0,并且其速度是恒定的向量v0,那么很容易计算(插值)它在任意时间t时的位置。

s = s0 + (t-t0) * v0 

如果其在时间t0的旋转由四元数rot0表示,并且其角速度是a0,那么我们也可以旋转球。(我避免使用osg::Quat::slerp,因为它在两个四元数之间插值,但对于我们的目的来说不够精确)。旋转四元数的计算如下:

osg::Quat Rotation( double time ) const
{
double angular_speed = a0.length();
if( angular_speed < 1.0e-5 )
return rot0;
double t = time - t0;
double half_angle = 0.5 * t * angular_speed;
double cos_half_angle = cos( half_angle );
double sin_half_angle = sin( half_angle );
double factor = sin_half_angle/angular_speed;
osg::Quat rotation;
rotation.x() = a0.x() * factor;
rotation.y() = a0.y() * factor;
rotation.z() = a0.z() * factor;
rotation.w() = cos_half_angle;
return rot0 * rotation;
} 

变化(事件)

当球发生碰撞时,球的速度会发生变化,因此必须重新计算其碰撞预测。

球与墙壁之间的碰撞足够容易预测和处理,因为墙壁与xyz轴对齐。

我发现使用向量更容易计算两个球之间的碰撞。设vs为两个球在时间t0时的相对速度和相对位置。我们想找到相对位置向量大小为r(球半径之和)的时间t。然后我们可以如下预测碰撞:

double s2 = s * s; // dot product
double v2 = v * v; // "
double sv = s * v; // "
double r = b0->radius + b1->radius;
double r2 = r * r;
double sqr = sv*sv - v2 * (s2 - r2 );
if( sqr > 0.00001 )
{
double t = t0 + ( - sqrt( sqr ) - sv ) / v2;
if( t > time )
{
// Collision occurs at time t
ChangeTime( t );
Enable( true );
}
else
{
// Collision occurred in the past
Enable( false );
}
}
else
{
// Collision does not occur
Enable( false );
} 

修订历史

  • 2008年8月29日:原创文章
© . All rights reserved.