14

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

我的情况很简单。相机位于 0, 0, 50 并指向 0, 0, 0。我只想沿着 z 平面拖动对象,z 值为 0。

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

4

3 回答 3

23

你不需要使用不可见的几何体——Scene Kit 可以完成你需要的所有坐标转换,而无需点击测试不可见的对象。基本上,您需要在 2D 绘图应用程序中执行相同的操作来移动对象:找到mouseDown:位置和对象位置之间的偏移量,然后对于每个mouseMoved:,将该偏移量添加到新鼠标位置以设置对象的新位置。

这是您可以使用的一种方法...

  1. 像您已经在做的那样对初始点击位置进行命中测试。这将为您提供一个SCNHitTestResult标识您要移动的节点的对象,对吧?

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

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

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

  5. mouseMoved:上,从新鼠标位置的屏幕坐标和在步骤 3 中获得的深度值构造一个新的 3D 矢量。然后,将此矢量转换为场景坐标,使用unprojectPoint:- 这是场景 3D 空间中的鼠标位置(相当于一个你从命中测试中得到的,但不需要“命中”场景几何体)。

  6. 将您在步骤 3 中获得的偏移量添加到您在步骤 5 中获得的新位置 - 这是position将节点移动到的新位置。(注意:为了使实时拖动看起来正确,您应该确保此位置更改没有动画。默认情况下,当前的持续时间SCNTransaction为零,因此除非您已经更改它,否则您无需担心这一点。 )

(这有点出乎我的意料,所以你可能应该仔细检查相关的文档和标题。你也许可以通过一些数学来简化这一点。)

于 2014-04-08T18:02:01.170 回答
3

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

我在苹果论坛上询问是否有人知道同质化坐标的秘诀,但没有得到决定性的答案。一件事,我对毕晓普先生的方法做了一些实验性的改变,论坛成员建议我回到他的技术。

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

请注意,对象(棋子)的世界变换矩阵的日志不是该过程的一部分,但一位 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;
}
于 2014-05-29T01:55:43.557 回答
2

我使用了史蒂夫编写的代码,几乎没有修改它对我有用。

在 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;
于 2014-08-07T09:42:17.780 回答