iOS OpenGL ES 2.0 GLKit GLKMatrix4 触摸结束后旋转
Posted
技术标签:
【中文标题】iOS OpenGL ES 2.0 GLKit GLKMatrix4 触摸结束后旋转【英文标题】:iOS OpenGL ES 2.0 GLKit GLKMatrix4 Rotate after Touches Ended 【发布时间】:2013-08-01 17:12:59 【问题描述】:我想在触球结束后获得动力。我在 ios 上使用带有 Objective-C 和 OpenGL ES 2.0 API 的 GLKit。我正在使用四元数使用 Arcball Rotation 旋转以我的世界原点为中心的对象。当我触摸旋转时,我想继续以递减的速度旋转一段时间,直到没有旋转可做。
我的想法: 1.) 从四元数获取当前角度 2.) 将四元数转换为 4x4 矩阵 3.) 使用 GLKit 以从当前四元数减少的角度旋转 Matrix 4.) 在某些时候使计时器失效
我尝试在以 X、Y 和 Z 轴独立以及一起结束触摸结束后旋转矩阵,但结果旋转永远不会围绕我的四元数所期望的轴。当我用手指移动对象时,轨迹球旋转工作得很好——只有当手指松开时,旋转才会继续围绕某个看似任意的轴。当我抬起手指时,如何使轴与四元数旋转的最后状态一致?另外,我从文档中注意到,当对象逆时针旋转时,四元数的 GetAngle 函数应该返回弧度的负值。即使逆时针旋转,我也总是从这个函数中得到正值。
非常感谢您提供的任何见解!
// Called when touches are ended
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
// The momentum var is initialized to a value
self.momentumVar = 0.05;
// Now since we stopped touching, decay the rotation to simulate momentum
self.momentumTimer = [NSTimer scheduledTimerWithTimeInterval:0.025
target:self
selector:@selector(decayGLKQuaternion)
userInfo:nil
repeats:YES];
// Rotate about the current axis after touches ended but for a greater angle ( radians )
- (void)decayGLKQuaternion
//return;
// What is the current angle for the quaternion?
float currentQuatAngle = GLKQuaternionAngle(self.quat);
// Decay the value each time
self.momentumVar = currentQuatAngle * 0.0055;
NSLog(@"QUAT Angle %f %f",GLKQuaternionAngle(self.quat),GLKMathRadiansToDegrees(GLKQuaternionAngle(self.quat)));
GLKMatrix4 newMat = GLKMatrix4MakeWithQuaternion(self.quat);
float rotate = self.momentumVar * 0.75;
//newMat = GLKMatrix4RotateX(newMat, rotate);
//newMat = GLKMatrix4RotateY(newMat, rotate);
//newMat = GLKMatrix4RotateZ(newMat, rotate);
newMat = GLKMatrix4Rotate(newMat, rotate, 1, 0, 0);
newMat = GLKMatrix4Rotate(newMat, rotate, 0, 1, 0);
newMat = GLKMatrix4Rotate(newMat, rotate, 0, 1, 1);
self.quat = GLKQuaternionMakeWithMatrix4(newMat);
【问题讨论】:
我不是 iOS 方面的专家,但会不会是当你松开手指时,你的输入中出现了一些“跳跃”?当您将四元数保存几个状态然后将其用作参考点时会发生什么? 【参考方案1】:这是一个更简单的方法。不要试图衰减四元数或旋转速度。相反,存储触摸的运动矢量(在 2D 视图坐标中)并衰减该运动矢量。
我假设self.quat
是当前的旋转四元数,并且在touchesMoved:withEvent:
中,您调用一些基于触摸移动向量更新self.quat
的方法。假设该方法称为rotateQuaternionWithVector:
。所以你的touchesMoved:withEvent:
可能看起来像这样:
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView:self.view];
// get touch delta
CGPoint delta = CGPointMake(location.x - iniLocation.x, -(location.y - iniLocation.y));
iniLocation = location;
// rotate
[self rotateQuaternionWithVector:delta];
(该方法来自The Strange Agency's arcball code,我用它来测试这个答案。)
我假设您还有一个方法可以每秒调用 60 次来绘制模拟的每一帧。我假设该方法名为update
。
给自己一些新的实例变量:
@implementation ViewController
... existing instance variables ...
CGPoint pendingMomentumVector; // movement vector as of most recent touchesMoved:withEvent:
NSTimeInterval priorMomentumVectorTime; // time of most recent touchesMoved:withEvent:
CGPoint momentumVector; // momentum vector to apply at each simulation step
当触摸开始时,初始化变量:
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
UITouch *touch = [touches anyObject];
priorMomentumVectorTime = touch.timestamp;
momentumVector = CGPointZero;
pendingMomentumVector = CGPointZero;
... existing touchesBegan:withEvent: code ...
当触摸移动时,更新变量:
- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView:self.view];
CGPoint priorLocation = [touch previousLocationInView:self.view];
pendingMomentumVector = CGPointMake(location.x - priorLocation.x, -(location.y - priorLocation.y));
priorMomentumVectorTime = touch.timestamp;
... existing touchesMoved:withEvent: code ...
当触摸结束时,设置momentumVector
。这需要一些小心,因为touchesEnded:withEvent:
消息有时包含额外的移动,有时在单独的移动事件之后几乎立即出现。所以你需要检查时间戳:
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView:self.view];
if (touch.timestamp - priorMomentumVectorTime >= 1/60.0)
CGPoint priorLocation = [touch previousLocationInView:self.view];
momentumVector = CGPointMake(location.x - priorLocation.x, -(location.y - priorLocation.y));
else
momentumVector = pendingMomentumVector;
最后,修改您的 update
方法(我假设每秒调用 60 次)以根据 momentumVector
更新 self.quat
并衰减 momentumVector
:
- (void)update
[self applyMomentum];
... existing update code ...
- (void)applyMomentum
[self rotateQuaternionWithVector:momentumVector];
momentumVector.x *= 0.9f;
momentumVector.y *= 0.9f;
【讨论】:
您好 - 这个解决方案非常适合在向左或向右滑动时使用 Momentum 旋转。但是当我向上或向下滑动时,动力并不明显——所以当我将手指从屏幕上移开时,任何运动都会停止。我已经在代码中尝试了各种momentumVector 值的组合,但似乎无法让向上/向下动量起作用。您对为什么会发生这种情况有任何想法吗?非常感谢您的宝贵时间。【参考方案2】:我假设您每次在触摸期间都会计算一个四元数 - 一个从原始姿势旋转到新姿势的四元数。在这种情况下,我们需要在触摸结束时四元数的时间导数。结果将是对象将进一步旋转的方向。然而,计算导数并不容易。我们可以通过有限差分来逼近导数:
deriv_q = last_q * GLKQuaternionInvert(previous_q)
其中last_q
是触摸结束时的四元数,previous_q
是“不久前”的四元数。这可能是以前的状态。
如果我们将整体旋转表示为四元数,我们可以通过乘以四元数来继续旋转:
overall := overall * deriv_q
根据API,乘法的顺序可能会颠倒(我不知道iOS)。
如果您在每一帧都执行此操作,您将在最后一个笔划的方向上获得平滑的旋转。但它不会衰减。你想要做的是计算四元数的根:
deriv_q := mth root (deriv_q)
其中m
是衰减量。然而,计算根也并不容易。相反,我们可以将四元数转换为另一种表示形式:轴和角度:
axis = GLKQuaternionAxis(deriv_q)
angle = GLKQuaternionAngle(deriv_q)
现在我们可以衰减角度:
angle := angle * factor
并再次重建四元数:
deriv_q := GLKQuaternionMakeWithAngleAndVector3Axis(angle, axis)
重复此操作,直到绝对角度低于某个阈值(不再旋转)。
如果需要导出矩阵来显示对象,可以在overall
四元数上使用formula from Wikipedia。
【讨论】:
以上是关于iOS OpenGL ES 2.0 GLKit GLKMatrix4 触摸结束后旋转的主要内容,如果未能解决你的问题,请参考以下文章
OpenGL ES 2.0 - iOS - 使用 FBO 渲染到纹理
在 iOS 上使用 OpenGL-ES 2.0 设置横向正交投影的正确方法是啥?