当用户将鼠标/手指移到球体上时,我试图让 3D 球体旋转。
我可以让它毫无问题地旋转,但是当我尝试使用 Surface SDK 中的 Affine2DInertiaProcessor 为球体添加惯性时,当我快速轻弹球体时,我会遇到跳跃问题,我不知道为什么...
这是我的初始化代码:
private void InitializeManipulationProcessor()
{
manipulationProcessor = new Affine2DManipulationProcessor(
Affine2DManipulations.Rotate |
Affine2DManipulations.TranslateX |
Affine2DManipulations.TranslateY,
_eventSource);
inertiaProcessor = new Affine2DInertiaProcessor();
inertiaProcessor.Affine2DInertiaDelta += Inertia_OnManipulationDelta;
inertiaProcessor.Affine2DInertiaCompleted += InertiaProcessor_Affine2DInertiaCompleted;
manipulationProcessor.Affine2DManipulationStarted += OnManipulationStarted;
manipulationProcessor.Affine2DManipulationDelta += Manipulation_OnManipulationDelta;
manipulationProcessor.Affine2DManipulationCompleted += OnManipulationCompleted;
}
当用户移动手指时,以下是旋转球体的代码:
private void Manipulation_OnManipulationDelta(object sender, Affine2DOperationDeltaEventArgs e)
{
Point currentPosition = e.ManipulationOrigin;
// avoid any zero axis conditions
if (currentPosition == _previousPosition2D)
return;
Track(currentPosition);
_previousPosition2D = currentPosition;
}
当用户停止移动手指时,这会启动 ineria:
private void OnManipulationCompleted(object sender, Affine2DOperationCompletedEventArgs e)
{
inertiaProcessor.InitialOrigin = e.ManipulationOrigin;
inertiaProcessor.InitialVelocity = e.Velocity;
inertiaProcessor.DesiredDeceleration = 0.0001;
inertiaProcessor.Begin();
}
旋转的魔力发生在下面的Track方法中:
private void Track(Point currentPosition)
{
Vector3D currentPosition3D = ProjectToTrackball(currentPosition);
Vector3D axis = Vector3D.CrossProduct(_previousPosition3D, currentPosition3D);
double angle = Vector3D.AngleBetween(_previousPosition3D, currentPosition3D);
// quaterion will throw if this happens - sometimes we can get 3D positions that
// are very similar, so we avoid the throw by doing this check and just ignoring
// the event
if (axis.Length == 0)
return;
Quaternion delta = new Quaternion(axis, -angle);
// Get the current orientantion from the RotateTransform3D
Quaternion q = new Quaternion(_rotation.Axis, _rotation.Angle);
// Compose the delta with the previous orientation
q *= delta;
// Write the new orientation back to the Rotation3D
_rotation.Axis = q.Axis;
_rotation.Angle = q.Angle;
_previousPosition3D = currentPosition3D;
}
_rotation 变量是用于 3d 网格上的 RotateTransform3D 的 AxisAngleRotation3D 类。
我知道这是一个特殊情况,但我有一种感觉,这是一个计算问题,我真的不知道如何调试它。
还有一件事,一件非常有趣的事情是,如果我慢慢地轻弹地球仪,我不会有任何跳跃,而且非常平滑!所以这肯定与大量计算有关,或者只是一些错误......
如果您擅长 3D 旋转并且真正相信您可以提供帮助,那么如果您需要更好的格式来使用,我将很乐意将此项目打包为 ZIP 并将其发送给您
感谢您提供的任何帮助,我非常感谢您的帮助!
标记
最佳答案
我没有明确的答案,但看看你的代码,很多事情对我来说似乎很奇怪。
首先,ProjectToTrackball 到底是做什么的? 由于您使用 2D 惯性,我假设它将 2D 点(在屏幕空间中)投影到球体上并返回 3D 点,对吗?那么当 2D 点位于屏幕上的球体之外时到底会发生什么?返回什么点?当您用手指在球体上开始移动并且 2D 惯性使移动移出球体时会发生什么?
现在介绍一下在 Track 方法中处理旋转的方式。我对四元数了解不多,但我确定的是,如果你想对 3D 旋转进行建模,你需要三个轴和 3 个角度(欧拉角)。在每一步中,您都将仅用一个轴和一个角度覆盖四元数。如果您仅向一个方向移动手指,则此方法有效。如果您在移动过程中改变方向,这将不起作用。
顺便说一句,我不明白为什么你在 delta 四元数中直接使用“-angle”而不是“angle”,但我想如果那里有错误,你会立即注意到它;)
编辑:我下载了你的.rar。
我研究了 ProjectToTrackball 的工作原理(在 SurfaceTrackballDecorator.cs 中),现在对正在发生的事情有了一个大概的了解。
首先,你的球体应该与整个屏幕匹配(这意味着即使你的屏幕不是正方形,它也应该接触屏幕的四个边),否则移动将无法正常运行。如果没有,您应该能够在球体和屏幕边缘之间的空间中旋转球体,我猜这不是所需的效果。
那么当惯性发挥作用时会发生的情况是,运动将在2维中继续,而不是在3D中,就好像你的手指一直在移动(慢慢地减慢速度。)
当运动到达球体边缘时,将 2D 点投影到 3D 球体上的魔力可以使球体旋转得非常快。因此,即使您的手指没有到达球体边缘,惯性 2D 也可以实现这一点。
现在有趣的是,一旦 2D 点不再投影在球体上(当穿过球体边缘时),运动就会残酷地停止。因为所有 2D 点现在都投影到 (z=0) 平面上。
我不知道这是否是你所说的“跳跃球体”的意思:)
现在要解决这个问题,您需要某种 3D 惯性来减慢 3D 旋转,而不是 2D 点移动。
我以前的观点仍然有效。希望这会有所帮助。
编辑:睡不着:)
你能试试这段代码吗?
private Vector3D ProjectToTrackball(Point point)
{
double x = point.X / (ActualWidth / 2); // Scale so bounds map to [0,0] - [2,2]
double y = point.Y / (ActualHeight / 2);
x = x - 1; // Translate 0,0 to the center
y = 1 - y; // Flip so +Y is up instead of down
double alpha = 1.0 / Math.Sqrt(x*x + y*y + 1);
return new Vector3D(x*alpha, y*alpha, alpha);
}
我不保证任何东西,但这应该更加平滑(也许太多)并且球体边缘不再有不连续性......
唯一角度的问题仍然困扰着我......
编辑:新的:
private Vector3D ProjectToTrackball(Point point)
{
// IMPORTANT NOTE: result should always be normalized
double x = point.X / (ActualWidth / 2); // Scale so bounds map to [0,0] - [2,2]
double y = point.Y / (ActualHeight / 2);
x = x - 1; // Translate 0,0 to the center
y = 1 - y; // Flip so +Y is up instead of down
double z2 = 1 - x * x - y * y; // z^2 = 1 - x^2 - y^2
double z = 0;
if(z2 > 0)
z2 = Math.Sqrt(z2); // Ok no need to normalize.
else
{
// I will get rid of the discontinuity with a little trick:
// I construct an imaginary point below the sphere.
double length = Math.Sqrt(x * x + y * y);
x = x / length;
y = y / length;
z = 1 - length;
// Now I normalize:
length = Math.Sqrt(x * x + y * y + z * z);
x = x / length;
y = y / length;
z = z / length;
}
return new Vector3D(x, y, z);
}
现在,当手指在球体内部移动时,它的行为应该像以前一样。穿过球体边缘时不再出现不连续性。移动速度应该很快减慢。
我在第二次编辑中犯了一个错误:跳跃可能是由于当 2D 点不再投影到球体上时,ProjectToTrackball 返回一个非标准化向量。所以在那之后一切都变得疯狂了。
注意:您应该拿起一本 OpenGL 书籍并学习 3D。
2009 年 11 月 18 日的新编辑:
关于唯一角度的问题,我认为这就是导致“它只绕Z轴旋转”问题的原因。
首先将 _rotation 更改为四元数。在将网格与 _rotation 相乘的方法中,必须更改一些代码,但这应该不会太困难。
然后你可以尝试这个新的 Track 方法:
private void Track(Point currentPosition)
{
Vector3D currentPosition3D = ProjectToTrackball(currentPosition);
Vector3D axis = Vector3D.CrossProduct(_previousPosition3D, currentPosition3D);
double angle = Vector3D.AngleBetween(_previousPosition3D, currentPosition3D);
// quaterion will throw if this happens - sometimes we can get 3D positions that
// are very similar, so we avoid the throw by doing this check and just ignoring
// the event
if (axis.Length == 0)
return;
Quaternion delta = new Quaternion(axis, -angle);
// Compose the delta with the previous orientation
_rotation *= delta;
_previousPosition3D = currentPosition3D;
}
对于惯性,我放弃了......你需要某种 3D 旋转惯性。
关于wpf - 3D 地球仪旋转问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1665375/