触摸结束后旋转iOS OpenGL ES 2.0 GLKit GLKMatrix4
在接触结束之后,我想实现动力。 我在Objective-C和OpenGL ES 2.0 API上使用iOS上的GLKit。 我使用四元数来旋转一个以Arcball Rotation为中心关于我的世界的原点的对象。 当我旋转触摸时,我想继续旋转一段时间,直到没有旋转。
我的想法:1)获取从四元2.当前角度)四元数转换到4x4矩阵3)使用GLKit与从目前的四元数4)降低的角度在某一时刻失效计时器旋转矩阵
我试着旋转矩阵后,触摸与X,Y和Z轴结束独立,以及所有一起,但由此产生的旋转是从来没有想过我希望从我的四元数轴。 当我用手指移动物体时,弧球旋转工作得很好 - 只有当手指被释放时,旋转才会继续到一些看似任意的轴。 当我举起手指时,如何使轴与四元数旋转的最后一个状态保持一致? 另外,我从文档中注意到,当对象逆时针旋转时,Quaternion的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);
}
这是一个更简单的方法。 不要试图衰减四元数或旋转速度。 而是存储触摸的运动矢量(在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的arcball代码,我用它来测试这个答案。)
我假设你也有一种每秒调用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;
}
我假设你每次在触摸过程中计算一个四元数 - 一个从原始姿态旋转到一个新姿态的四元数。 在这种情况下,我们需要在触摸结束时四元数的时间导数。 结果将是物体将进一步旋转的方向。 但是,计算导数并不容易。 我们可以用有限差分来近似导数:
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
四元数的公式。
上一篇: iOS OpenGL ES 2.0 GLKit GLKMatrix4 Rotate after Touches Ended