1

我想在触球结束后获得动力。我在 iOS 上使用带有 Objective-C 和 OpenGL ES 2.0 API 的 GLKit。我正在使用四元数使用 Arcball Rotation 旋转以我的世界原点为中心的对象。当我触摸旋转时,我想继续以递减的速度旋转一段时间,直到没有旋转可做。

我的想法:1.)从四元数获取当前角度 2.)将四元数转换为 4x4 矩阵 3.)使用 GLKit 以从当前四元数减少角度旋转矩阵 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);


}
4

2 回答 2

4

这是一个更简单的方法。不要试图衰减四元数或旋转速度。相反,存储触摸的运动矢量(在 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 次)以self.quat根据momentumVector和衰减进行更新momentumVector

- (void)update {
    [self applyMomentum];

    ... existing update code ...
}

- (void)applyMomentum {
    [self rotateQuaternionWithVector:momentumVector];
    momentumVector.x *= 0.9f;
    momentumVector.y *= 0.9f;
}
于 2013-08-10T19:08:46.883 回答
1

我假设您每次在触摸期间都会计算一个四元数 - 从原始姿势旋转到新姿势的四元数。在这种情况下,我们需要在触摸结束时四元数的时间导数。结果将是对象将进一步旋转的方向。然而,计算导数并不容易。我们可以通过有限差分来近似导数:

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

于 2013-08-10T18:06:11.977 回答