我已经在 SceneKit 中设置了一个场景,并发布了一个命中测试来选择一个项目。但是,我希望能够沿着我场景中的平面移动该项目。我继续收到鼠标拖动事件,但不知道如何将那些 2D 坐标转换为场景中的 3D 坐标。
我的情况很简单。相机位于 0, 0, 50 并指向 0, 0, 0。我只想沿着 z 平面拖动对象,z 值为 0。
命中测试就像一个魅力,但我如何将鼠标点从拖动事件转换到场景中我正在拖动的 3D 对象的新位置?
我已经在 SceneKit 中设置了一个场景,并发布了一个命中测试来选择一个项目。但是,我希望能够沿着我场景中的平面移动该项目。我继续收到鼠标拖动事件,但不知道如何将那些 2D 坐标转换为场景中的 3D 坐标。
我的情况很简单。相机位于 0, 0, 50 并指向 0, 0, 0。我只想沿着 z 平面拖动对象,z 值为 0。
命中测试就像一个魅力,但我如何将鼠标点从拖动事件转换到场景中我正在拖动的 3D 对象的新位置?
你不需要使用不可见的几何体——Scene Kit 可以完成你需要的所有坐标转换,而无需点击测试不可见的对象。基本上,您需要在 2D 绘图应用程序中执行相同的操作来移动对象:找到mouseDown:
位置和对象位置之间的偏移量,然后对于每个mouseMoved:
,将该偏移量添加到新鼠标位置以设置对象的新位置。
这是您可以使用的一种方法...
像您已经在做的那样对初始点击位置进行命中测试。这将为您提供一个SCNHitTestResult
标识您要移动的节点的对象,对吧?
检查该worldCoordinates
命中测试结果的属性。如果您要移动的节点是场景的子节点rootNode
,则这些是您要查找偏移量的向量。(否则,您需要将其转换为您要移动的节点的父节点的坐标系 - 请参阅convertPosition:toNode:
或convertPosition:fromNode:
。)
您将需要此点的参考深度,以便您可以将mouseMoved:
位置与其进行比较。用于projectPoint:
将您在步骤 2 中获得的向量(3D 场景中的一个点)转换回屏幕空间——这将为您提供一个 3D 向量,其 x 和 y 坐标是屏幕空间点,其 z 坐标告诉您该点相对于剪切平面的深度(0.0
在近平面上,1.0
在远平面上)。按住此 z 坐标以便在mouseMoved:
.
从您在步骤 2 中获得的鼠标位置向量中减去position
您要移动的节点的 。这将获得鼠标单击相对于对象位置的偏移量。抓住这个向量——你需要它直到拖动结束。
在mouseMoved:
上,从新鼠标位置的屏幕坐标和在步骤 3 中获得的深度值构造一个新的 3D 矢量。然后,将此矢量转换为场景坐标,使用unprojectPoint:
- 这是场景 3D 空间中的鼠标位置(相当于一个你从命中测试中得到的,但不需要“命中”场景几何体)。
将您在步骤 3 中获得的偏移量添加到您在步骤 5 中获得的新位置 - 这是position
将节点移动到的新位置。(注意:为了使实时拖动看起来正确,您应该确保此位置更改没有动画。默认情况下,当前的持续时间SCNTransaction
为零,因此除非您已经更改它,否则您无需担心这一点。 )
(这有点出乎我的意料,所以你可能应该仔细检查相关的文档和标题。你也许可以通过一些数学来简化这一点。)
作为一项实验,我实施了毕晓普先生的有用答案。由于鼠标单击和 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;
}
我使用了史蒂夫编写的代码,几乎没有修改它对我有用。
在 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;