如何使用SceneKit在场景中找到鼠标点?

时间:2014-04-08 01:13:41

标签: opengl 3d mouse-picking scenekit

我在SceneKit中设置了一个场景,并发出了一个命中测试来选择一个项目。但是,我希望能够在场景中沿着平面移动该项目。我继续接收鼠标拖动事件,但不知道如何将这些2D坐标转换为场景中的3D坐标。

我的情况非常简单。摄像机位于0,0,50并指向0,0,0。我只想沿z平面拖动我的对象,其z值为0.

命中测试就像魅力一样,但是如何将拖动事件中的鼠标点转换为我拖动的3D对象的场景中的新位置?

3 个答案:

答案 0 :(得分:21)

您不需要使用不可见的几何体 - Scene Kit可以执行您需要的所有坐标转换,而无需点击测试不可见的对象。基本上你需要做同样的事情in a 2D drawing app来移动一个对象:找到mouseDown:位置和对象位置之间的偏移量,然后对于每个mouseMoved:,将该偏移量添加到用于设置对象新位置的新鼠标位置。

以下是您可以使用的方法......

  1. 点击测试初始点击位置,就像您现在一样。这会为您提供一个SCNHitTestResult对象,用于标识您要移动的节点,对吗?

  2. 检查该命中测试结果的worldCoordinates属性。如果要移动的节点是场景rootNode的子节点,则这些是您想要查找偏移的矢量。 (否则,您需要将其转换为要移动的节点的父节点的坐标系 - 请参阅convertPosition:toNode:convertPosition:fromNode:。)

  3. 您将需要此点的参考深度,以便您可以将mouseMoved:位置与其进行比较。使用projectPoint:将您在步骤2中获得的矢量(3D场景中的一个点)转换回屏幕空间 - 这将获得一个3D矢量,其x和y坐标是一个屏幕空间点,其z -coordinate告诉你相对于剪裁平面的那个点的深度(0.0在近平面上,1.0在远平面上)。保持此z坐标以在mouseMoved:

  4. 期间使用
  5. 从您在步骤2中获得的鼠标位置向量中减去要移动的节点的position。这将获得鼠标单击对象位置的偏移量。抓住这个向量 - 你需要它直到拖动结束。

  6. mouseMoved:上,根据新鼠标位置的屏幕坐标和步骤3中获得的深度值构建新的3D矢量。然后,使用{{3}将此矢量转换为场景坐标} - 这是场景3D空间中的鼠标位置(相当于您从命中测试中获得的位置,但不需要“点击”场景几何体)。

  7. 将您在步骤3中获得的偏移量添加到您在步骤5中获得的新位置 - 这是将节点移动到的新position。 (注意:对于实时拖动看起来正确,你应该确保这个位置变化没有动画。默认情况下,当前unprojectPoint:的持续时间为零,所以你不必担心这个,除非你'已经改变了它。)

  8. (这有点偏离我的头脑,所以你应该仔细检查相关的文档和标题。你可以通过一些数学来简化这一点。)

答案 1 :(得分:3)

作为一项实验,我实施了毕晓普先生的有益答案。由于鼠标点击和3-D世界之间的坐标幅度不同,拖动不起作用(对象 - 棋子 - 跳出屏幕)。我已在代码中插入日志输出。

我在Apple论坛上询问是否有人知道将坐标均匀化的秘诀,但没有得到决定性的答案。有一件事,我对Bishop先生的方法进行了一些实验性的修改,论坛成员建议我回归他的技术。

尽管我的代码有失败,但我认为有人可能会发现它是一个有用的起点。我怀疑代码只有一两个小问题。

请注意,对象(国际象棋棋子)的世界变换矩阵的日志不是该过程的一部分,但是一位Apple论坛成员告诉我,矩阵通常提供有用的健全性检查' - 确实如此。

- (NSPoint)
viewPointForEvent: (NSEvent *) event_
{
    NSPoint   windowPoint    = [event_ locationInWindow];
    NSPoint   viewPoint        = [self.view convertPoint: windowPoint
                                             fromView: nil];
    return viewPoint;
}

- (SCNHitTestResult *)
hitTestResultForEvent: (NSEvent *) event_
{
    NSPoint      viewPoint        = [self viewPointForEvent: event_];
    CGPoint      cgPoint        = CGPointMake (viewPoint.x, viewPoint.y);
    NSArray * points        = [(SCNView *) self.view hitTest: cgPoint
                                                     options: @{}];
    return points.firstObject;
}

- (void)
mouseDown: (NSEvent *) theEvent
{
    SCNHitTestResult * result = [self hitTestResultForEvent: theEvent];

    SCNVector3 clickWorldCoordinates = result.worldCoordinates;
    log output: clickWorldCoordinates x 208.124578, y -12827.223365, z 3163.659073
    SCNVector3 screenCoordinates = [(SCNView *) self.view projectPoint: clickWorldCoordinates];
    log output: screenCoordinates x 245.128906, y 149.335938, z 0.985565
    // save the z coordinate for use in mouseDragged
    mouseDownClickOnObjectZCoordinate = screenCoordinates.z;

    selectedPiece = result.node;  // save selected piece for use in mouseDragged

    SCNVector3    piecePosition = selectedPiece.position;
    log output: piecePosition x -18.200000, y 6.483060, z 2.350000

    offsetOfMouseClickFromPiece.x = clickWorldCoordinates.x - piecePosition.x;
    offsetOfMouseClickFromPiece.y = clickWorldCoordinates.y - piecePosition.y;
    offsetOfMouseClickFromPiece.z = clickWorldCoordinates.z - piecePosition.z;
    log output: offsetOfMouseClickFromPiece x 226.324578, y -12833.706425, z 3161.309073  
}

- (void)
mouseDragged: (NSEvent *) theEvent;
{
    NSPoint   viewClickPoint        = [self viewPointForEvent: theEvent];

    SCNVector3 clickCoordinates;
    clickCoordinates.x = viewClickPoint.x;
    clickCoordinates.y = viewClickPoint.y;
    clickCoordinates.z = mouseDownClickOnObjectZCoordinate;
    log output:  clickCoordinates x 246.128906, y 0.000000, z 0.985565

    log output:  pieceWorldTransform: 
      m11 = 242.15889219510001, m12 = -0.000045609300002524833, m13 = -0.00000721691076126, m14 = 0, 
      m21 = 0.0000072168760805499971, m22 = -0.000039452697396149999, m23 = 242.15890446329999, m24 = 0, 
      m31 = -0.000045609300002524833, m32 = -242.15889219510001, m33 = -0.000039452676995750002, m34 = 0, 
      m41 = -4268.2349924762348, m42 = -12724.050221935429, m43 = 4852.6652710104272, m44 = 1)

    SCNVector3 newPiecePosition;
    newPiecePosition.x = offsetOfMouseClickFromPiece.x + clickCoordinates.x;
    newPiecePosition.y = offsetOfMouseClickFromPiece.y + clickCoordinates.y;
    newPiecePosition.z = offsetOfMouseClickFromPiece.z + clickCoordinates.z;
    log output: newPiecePosition x 472.453484, y -12833.706425, z 3162.294639

    selectedPiece.position = newPiecePosition;
}

答案 2 :(得分:2)

我使用了Steve编写的代码,并且对我的修改很少。

在mouseDown上,我将clickWorldCoordinates保存在名为startClickWorldCoordinates的属性上。

在mouseDragged上,我用这种方式计算selectedPiece位置:

SCNVector3 worldClickCoordinate = [(SCNView *) self.view unprojectPoint:clickCoordinates.x];

newPiecePosition.x = selectedPiece.position.x + worldClickCoordinate.x - startClickWorldCoordinates.x;
newPiecePosition.y = selectedPiece.position.y + worldClickCoordinate.y - startClickWorldCoordinates.y;
newPiecePosition.z = selectedPiece.position.z + worldClickCoordinate.z - startClickWorldCoordinates.z;

selectedPiece.position = newPiecePosition;

startClickWorldCoordinates = worldClickCoordinate;