Как найти точку мыши в сцене, используя SceneKit?
Я установил сцену в SceneKit и выпустил хит-тест, чтобы выбрать элемент. Тем не менее, я хочу, чтобы иметь возможность перемещать этот предмет вдоль плоскости в моей сцене. Я продолжаю получать события перетаскивания мыши, но не знаю, как преобразовать эти 2D-координаты в трехмерную координату в сцене.
Мой случай очень прост. Камера расположена в 0, 0, 50 и указана на 0, 0, 0. Я просто хочу перетащить мой объект по плоскости z с z-значением 0.
Тест-поцелуй работает как шарм, но как перевести точку мыши из события перетаскивания в новую позицию в сцене для 3D-объекта, который я перетаскиваю?
Ответы
Ответ 1
Вам не нужно использовать невидимую геометрию - Scene Kit может выполнять все необходимые преобразования координат, не нажимая на тест невидимых объектов. В основном вам нужно сделать то же самое, что в приложении 2D-чертежа для перемещения объекта: найти смещение между местоположением mouseDown:
и объект, затем для каждого mouseMoved:
добавьте это смещение в новое местоположение мыши, чтобы установить новую позицию объекта.
Здесь вы можете использовать подход...
-
Проверяйте начальное местоположение клика, как вы уже делаете. Это дает вам объект SCNHitTestResult
, идентифицирующий node, который вы хотите переместить, правильно?
-
Проверьте свойство worldCoordinates
этого результата теста. Если node, который вы хотите переместить, является дочерним элементом сцены rootNode
, это вектор, который вы хотите найти смещение. (В противном случае вам нужно будет преобразовать его в систему координат родителя node, которую вы хотите переместить - см. convertPosition:toNode:
или convertPosition:fromNode:
.)
-
Вам понадобится эталонная глубина для этого момента, чтобы вы могли сравнить с ней местоположения mouseMoved:
. Используйте projectPoint:
, чтобы преобразовать вектор, полученный на шаге 2 (точка в 3D-сцене), обратно в пространство экрана - это дает вам 3D вектор, координаты x и y которого являются пространством экрана и z-координата которого указывает глубину этой точки относительно плоскостей отсечения (0.0
находится на ближней плоскости, 1.0
находится на дальней плоскости), Удерживайте эту z-координату для использования во время mouseMoved:
.
-
Вычитайте position
node, который вы хотите переместить с вектора мыши, который вы получили на шаге 2. Это дает вам смещение щелчка мыши от позиции объекта. Удерживайте этот вектор - вам понадобится его, пока не перетащите концы.
-
В mouseMoved:
постройте новый 3D-вектор из координаты экрана нового местоположения мыши и значение глубины, которое вы получили на шаге 3. Затем преобразуйте этот вектор в координаты сцены, используя unprojectPoint:
- это местоположение мыши в пространстве 3D сцены (что эквивалентно тому, которое вы получили от теста на удар, но без необходимости "ударять" геометрию сцены).
-
Добавьте смещение, которое вы получили на шаге 3, к новому местоположению, которое вы получили на шаге 5, - это новый position
для перемещения node в. (Примечание: для прямого перетаскивания в реальном времени вы должны убедиться, что это изменение позиции не анимировано. По умолчанию продолжительность текущего SCNTransaction
равно нулю, поэтому вам не нужно беспокоиться об этом, если вы уже не изменили его.)
(Это похоже на верхнюю часть моей головы, поэтому вам, вероятно, следует дважды проверить соответствующие документы и заголовки. И вы можете немного упростить это с помощью некоторой математики.)
Ответ 2
В качестве эксперимента я представил г-ну Бишопу полезный ответ. Перетаскивание не совсем работает (объект - шахматная фигура - прыгает с экрана) из-за различий в координатных величинах между щелчком мыши и трехмерным миром. Я добавил логические выходы здесь и там среди кода.
Я спросил на форумах Apple, если кто-то знал секретный соус, чтобы гомогенизировать координаты, но не получил решающего ответа. Одна вещь, я сделал некоторые экспериментальные изменения к методу г-на Бишопа, и участники форума посоветовали мне вернуться к его технике.
Несмотря на мои ошибки кода, я думал, что кто-то может найти его полезной отправной точкой. Я подозреваю, что с кодом есть только одна или две небольшие проблемы.
Обратите внимание, что журнал мировой матрицы преобразования объекта (шахматная фигура) не является частью процесса, но один из участников форума 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;
}
Ответ 3
Я использовал код, написанный Стивом, и с небольшими изменениями он работал у меня.
В 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;